vendor: 5,201 bytes, lines 1-136
1/** 2 * Copyright (c) 2014-present, Facebook, Inc. 3 * 4 * This source code is licensed under the MIT license found in the 5 * LICENSE file in the root directory of this source tree. 6 */ 7 8var runtime = (function (exports) { 9 "use strict"; 10 11 var Op = Object.prototype; 12 var hasOwn = Op.hasOwnProperty; 13 var defineProperty = Object.defineProperty || function (obj, key, desc) { obj[key] = desc.value; }; 14 var undefined; // More compressible than void 0. 15 var $Symbol = typeof Symbol === "function" ? Symbol : {}; 16 var iteratorSymbol = $Symbol.iterator || "@@iterator"; 17 var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator"; 18 var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag"; 19 20 function define(obj, key, value) { 21 Object.defineProperty(obj, key, { 22 value: value, 23 enumerable: true, 24 configurable: true, 25 writable: true 26 }); 27 return obj[key]; 28 } 29 try { 30 // IE 8 has a broken Object.defineProperty that only works on DOM objects. 31 define({}, ""); 32 } catch (err) { 33 define = function(obj, key, value) { 34 return obj[key] = value; 35 }; 36 } 37 38 function wrap(innerFn, outerFn, self, tryLocsList) { 39 // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator. 40 var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator; 41 var generator = Object.create(protoGenerator.prototype); 42 var context = new Context(tryLocsList || []); 43 44 // The ._invoke method unifies the implementations of the .next, 45 // .throw, and .return methods. 46 defineProperty(generator, "_invoke", { value: makeInvokeMethod(innerFn, self, context) }); 47 48 return generator; 49 } 50 exports.wrap = wrap; 51 52 // Try/catch helper to minimize deoptimizations. Returns a completion 53 // record like context.tryEntries[i].completion. This interface could 54 // have been (and was previously) designed to take a closure to be 55 // invoked without arguments, but in all the cases we care about we 56 // already have an existing method we want to call, so there's no need 57 // to create a new function object. We can even get away with assuming 58 // the method takes exactly one argument, since that happens to be true 59 // in every case, so we don't have to touch the arguments object. The 60 // only additional allocation required is the completion record, which 61 // has a stable shape and so hopefully should be cheap to allocate. 62 function tryCatch(fn, obj, arg) { 63 try { 64 return { type: "normal", arg: fn.call(obj, arg) }; 65 } catch (err) { 66 return { type: "throw", arg: err }; 67 } 68 } 69 70 var GenStateSuspendedStart = "suspendedStart"; 71 var GenStateSuspendedYield = "suspendedYield"; 72 var GenStateExecuting = "executing"; 73 var GenStateCompleted = "completed"; 74 75 // Returning this object from the innerFn has the same effect as 76 // breaking out of the dispatch switch statement. 77 var ContinueSentinel = {}; 78 79 // Dummy constructor functions that we use as the .constructor and 80 // .constructor.prototype properties for functions that return Generator 81 // objects. For full spec compliance, you may wish to configure your 82 // minifier not to mangle the names of these two functions. 83 function Generator() {} 84 function GeneratorFunction() {} 85 function GeneratorFunctionPrototype() {} 86 87 // This is a polyfill for %IteratorPrototype% for environments that 88 // don't natively support it. 89 var IteratorPrototype = {}; 90 define(IteratorPrototype, iteratorSymbol, function () { 91 return this; 92 }); 93 94 var getProto = Object.getPrototypeOf; 95 var NativeIteratorPrototype = getProto && getProto(getProto(values([]))); 96 if (NativeIteratorPrototype && 97 NativeIteratorPrototype !== Op && 98 hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) { 99 // This environment has a native %IteratorPrototype%; use it instead 100 // of the polyfill. 101 IteratorPrototype = NativeIteratorPrototype; 102 } 103 104 var Gp = GeneratorFunctionPrototype.prototype = 105 Generator.prototype = Object.create(IteratorPrototype); 106 GeneratorFunction.prototype = GeneratorFunctionPrototype; 107 defineProperty(Gp, "constructor", { value: GeneratorFunctionPrototype, configurable: true }); 108 defineProperty( 109 GeneratorFunctionPrototype, 110 "constructor", 111 { value: GeneratorFunction, configurable: true } 112 ); 113 GeneratorFunction.displayName = define( 114 GeneratorFunctionPrototype, 115 toStringTagSymbol, 116 "GeneratorFunction" 117 ); 118 119 // Helper for defining the .next, .throw, and .return methods of the 120 // Iterator interface in terms of a single ._invoke method. 121 function defineIteratorMethods(prototype) { 122 ["next", "throw", "return"].forEach(function(method) { 123 define(prototype, method, function(arg) { 124 return this._invoke(method, arg); 125 }); 126 }); 127 } 128 129 exports.isGeneratorFunction = function(genFun) { 130 var ctor = typeof genFun === "function" && genFun.constructor; 131 return ctor 132 ? ctor === GeneratorFunction || 133 // For the native GeneratorFunction constructor, the best we can 134 // do is to check its .name property. 135 (ctor.displayName || ctor.name) === "GeneratorFunction" 136 : false;
vendor: 20,105 bytes, lines 137-761
137 }; 138 139 exports.mark = function(genFun) { 140 if (Object.setPrototypeOf) { 141 Object.setPrototypeOf(genFun, GeneratorFunctionPrototype); 142 } else { 143 genFun.__proto__ = GeneratorFunctionPrototype; 144 define(genFun, toStringTagSymbol, "GeneratorFunction"); 145 } 146 genFun.prototype = Object.create(Gp); 147 return genFun; 148 }; 149 150 // Within the body of any async function, `await x` is transformed to 151 // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test 152 // `hasOwn.call(value, "__await")` to determine if the yielded value is 153 // meant to be awaited. 154 exports.awrap = function(arg) { 155 return { __await: arg }; 156 }; 157 158 function AsyncIterator(generator, PromiseImpl) { 159 function invoke(method, arg, resolve, reject) { 160 var record = tryCatch(generator[method], generator, arg); 161 if (record.type === "throw") { 162 reject(record.arg); 163 } else { 164 var result = record.arg; 165 var value = result.value; 166 if (value && 167 typeof value === "object" && 168 hasOwn.call(value, "__await")) { 169 return PromiseImpl.resolve(value.__await).then(function(value) { 170 invoke("next", value, resolve, reject); 171 }, function(err) { 172 invoke("throw", err, resolve, reject); 173 }); 174 } 175 176 return PromiseImpl.resolve(value).then(function(unwrapped) { 177 // When a yielded Promise is resolved, its final value becomes 178 // the .value of the Promise<{value,done}> result for the 179 // current iteration. 180 result.value = unwrapped; 181 resolve(result); 182 }, function(error) { 183 // If a rejected Promise was yielded, throw the rejection back 184 // into the async generator function so it can be handled there. 185 return invoke("throw", error, resolve, reject); 186 }); 187 } 188 } 189 190 var previousPromise; 191 192 function enqueue(method, arg) { 193 function callInvokeWithMethodAndArg() { 194 return new PromiseImpl(function(resolve, reject) { 195 invoke(method, arg, resolve, reject); 196 }); 197 } 198 199 return previousPromise = 200 // If enqueue has been called before, then we want to wait until 201 // all previous Promises have been resolved before calling invoke, 202 // so that results are always delivered in the correct order. If 203 // enqueue has not been called before, then it is important to 204 // call invoke immediately, without waiting on a callback to fire, 205 // so that the async generator function has the opportunity to do 206 // any necessary setup in a predictable way. This predictability 207 // is why the Promise constructor synchronously invokes its 208 // executor callback, and why async functions synchronously 209 // execute code before the first await. Since we implement simple 210 // async functions in terms of async generators, it is especially 211 // important to get this right, even though it requires care. 212 previousPromise ? previousPromise.then( 213 callInvokeWithMethodAndArg, 214 // Avoid propagating failures to Promises returned by later 215 // invocations of the iterator. 216 callInvokeWithMethodAndArg 217 ) : callInvokeWithMethodAndArg(); 218 } 219 220 // Define the unified helper method that is used to implement .next, 221 // .throw, and .return (see defineIteratorMethods). 222 defineProperty(this, "_invoke", { value: enqueue }); 223 } 224 225 defineIteratorMethods(AsyncIterator.prototype); 226 define(AsyncIterator.prototype, asyncIteratorSymbol, function () { 227 return this; 228 }); 229 exports.AsyncIterator = AsyncIterator; 230 231 // Note that simple async functions are implemented on top of 232 // AsyncIterator objects; they just return a Promise for the value of 233 // the final result produced by the iterator. 234 exports.async = function(innerFn, outerFn, self, tryLocsList, PromiseImpl) { 235 if (PromiseImpl === void 0) PromiseImpl = Promise; 236 237 var iter = new AsyncIterator( 238 wrap(innerFn, outerFn, self, tryLocsList), 239 PromiseImpl 240 ); 241 242 return exports.isGeneratorFunction(outerFn) 243 ? iter // If outerFn is a generator, return the full iterator. 244 : iter.next().then(function(result) { 245 return result.done ? result.value : iter.next(); 246 }); 247 }; 248 249 function makeInvokeMethod(innerFn, self, context) { 250 var state = GenStateSuspendedStart; 251 252 return function invoke(method, arg) { 253 if (state === GenStateExecuting) { 254 throw new Error("Generator is already running"); 255 } 256 257 if (state === GenStateCompleted) { 258 if (method === "throw") { 259 throw arg; 260 } 261 262 // Be forgiving, per GeneratorResume behavior specified since ES2015: 263 // ES2015 spec, step 3: https://262.ecma-international.org/6.0/#sec-generatorresume 264 // Latest spec, step 2: https://tc39.es/ecma262/#sec-generatorresume 265 return doneResult(); 266 } 267 268 context.method = method; 269 context.arg = arg; 270 271 while (true) { 272 var delegate = context.delegate; 273 if (delegate) { 274 var delegateResult = maybeInvokeDelegate(delegate, context); 275 if (delegateResult) { 276 if (delegateResult === ContinueSentinel) continue; 277 return delegateResult; 278 } 279 } 280 281 if (context.method === "next") { 282 // Setting context._sent for legacy support of Babel's 283 // function.sent implementation. 284 context.sent = context._sent = context.arg; 285 286 } else if (context.method === "throw") { 287 if (state === GenStateSuspendedStart) { 288 state = GenStateCompleted; 289 throw context.arg; 290 } 291 292 context.dispatchException(context.arg); 293 294 } else if (context.method === "return") { 295 context.abrupt("return", context.arg); 296 } 297 298 state = GenStateExecuting; 299 300 var record = tryCatch(innerFn, self, context); 301 if (record.type === "normal") { 302 // If an exception is thrown from innerFn, we leave state === 303 // GenStateExecuting and loop back for another invocation. 304 state = context.done 305 ? GenStateCompleted 306 : GenStateSuspendedYield; 307 308 if (record.arg === ContinueSentinel) { 309 continue; 310 } 311 312 return { 313 value: record.arg, 314 done: context.done 315 }; 316 317 } else if (record.type === "throw") { 318 state = GenStateCompleted; 319 // Dispatch the exception by looping back around to the 320 // context.dispatchException(context.arg) call above. 321 context.method = "throw"; 322 context.arg = record.arg; 323 } 324 } 325 }; 326 } 327 328 // Call delegate.iterator[context.method](context.arg) and handle the 329 // result, either by returning a { value, done } result from the 330 // delegate iterator, or by modifying context.method and context.arg, 331 // setting context.delegate to null, and returning the ContinueSentinel. 332 function maybeInvokeDelegate(delegate, context) { 333 var methodName = context.method; 334 var method = delegate.iterator[methodName]; 335 if (method === undefined) { 336 // A .throw or .return when the delegate iterator has no .throw 337 // method, or a missing .next method, always terminate the 338 // yield* loop. 339 context.delegate = null; 340 341 // Note: ["return"] must be used for ES3 parsing compatibility. 342 if (methodName === "throw" && delegate.iterator["return"]) { 343 // If the delegate iterator has a return method, give it a 344 // chance to clean up. 345 context.method = "return"; 346 context.arg = undefined; 347 maybeInvokeDelegate(delegate, context); 348 349 if (context.method === "throw") { 350 // If maybeInvokeDelegate(context) changed context.method from 351 // "return" to "throw", let that override the TypeError below. 352 return ContinueSentinel; 353 } 354 } 355 if (methodName !== "return") { 356 context.method = "throw"; 357 context.arg = new TypeError( 358 "The iterator does not provide a '" + methodName + "' method"); 359 } 360 361 return ContinueSentinel; 362 } 363 364 var record = tryCatch(method, delegate.iterator, context.arg); 365 366 if (record.type === "throw") { 367 context.method = "throw"; 368 context.arg = record.arg; 369 context.delegate = null; 370 return ContinueSentinel; 371 } 372 373 var info = record.arg; 374 375 if (! info) { 376 context.method = "throw"; 377 context.arg = new TypeError("iterator result is not an object"); 378 context.delegate = null; 379 return ContinueSentinel; 380 } 381 382 if (info.done) { 383 // Assign the result of the finished delegate to the temporary 384 // variable specified by delegate.resultName (see delegateYield). 385 context[delegate.resultName] = info.value; 386 387 // Resume execution at the desired location (see delegateYield). 388 context.next = delegate.nextLoc; 389 390 // If context.method was "throw" but the delegate handled the 391 // exception, let the outer generator proceed normally. If 392 // context.method was "next", forget context.arg since it has been 393 // "consumed" by the delegate iterator. If context.method was 394 // "return", allow the original .return call to continue in the 395 // outer generator. 396 if (context.method !== "return") { 397 context.method = "next"; 398 context.arg = undefined; 399 } 400 401 } else { 402 // Re-yield the result returned by the delegate method. 403 return info; 404 } 405 406 // The delegate iterator is finished, so forget it and continue with 407 // the outer generator. 408 context.delegate = null; 409 return ContinueSentinel; 410 } 411 412 // Define Generator.prototype.{next,throw,return} in terms of the 413 // unified ._invoke helper method. 414 defineIteratorMethods(Gp); 415 416 define(Gp, toStringTagSymbol, "Generator"); 417 418 // A Generator should always return itself as the iterator object when the 419 // @@iterator function is called on it. Some browsers' implementations of the 420 // iterator prototype chain incorrectly implement this, causing the Generator 421 // object to not be returned from this call. This ensures that doesn't happen. 422 // See https://github.com/facebook/regenerator/issues/274 for more details. 423 define(Gp, iteratorSymbol, function() { 424 return this; 425 }); 426 427 define(Gp, "toString", function() { 428 return "[object Generator]"; 429 }); 430 431 function pushTryEntry(locs) { 432 var entry = { tryLoc: locs[0] }; 433 434 if (1 in locs) { 435 entry.catchLoc = locs[1]; 436 } 437 438 if (2 in locs) { 439 entry.finallyLoc = locs[2]; 440 entry.afterLoc = locs[3]; 441 } 442 443 this.tryEntries.push(entry); 444 } 445 446 function resetTryEntry(entry) { 447 var record = entry.completion || {}; 448 record.type = "normal"; 449 delete record.arg; 450 entry.completion = record; 451 } 452 453 function Context(tryLocsList) { 454 // The root entry object (effectively a try statement without a catch 455 // or a finally block) gives us a place to store values thrown from 456 // locations where there is no enclosing try statement. 457 this.tryEntries = [{ tryLoc: "root" }]; 458 tryLocsList.forEach(pushTryEntry, this); 459 this.reset(true); 460 } 461 462 exports.keys = function(val) { 463 var object = Object(val); 464 var keys = []; 465 for (var key in object) { 466 keys.push(key); 467 } 468 keys.reverse(); 469 470 // Rather than returning an object with a next method, we keep 471 // things simple and return the next function itself. 472 return function next() { 473 while (keys.length) { 474 var key = keys.pop(); 475 if (key in object) { 476 next.value = key; 477 next.done = false; 478 return next; 479 } 480 } 481 482 // To avoid creating an additional object, we just hang the .value 483 // and .done properties off the next function object itself. This 484 // also ensures that the minifier will not anonymize the function. 485 next.done = true; 486 return next; 487 }; 488 }; 489 490 function values(iterable) { 491 if (iterable != null) { 492 var iteratorMethod = iterable[iteratorSymbol]; 493 if (iteratorMethod) { 494 return iteratorMethod.call(iterable); 495 } 496 497 if (typeof iterable.next === "function") { 498 return iterable; 499 } 500 501 if (!isNaN(iterable.length)) { 502 var i = -1, next = function next() { 503 while (++i < iterable.length) { 504 if (hasOwn.call(iterable, i)) { 505 next.value = iterable[i]; 506 next.done = false; 507 return next; 508 } 509 } 510 511 next.value = undefined; 512 next.done = true; 513 514 return next; 515 }; 516 517 return next.next = next; 518 } 519 } 520 521 throw new TypeError(typeof iterable + " is not iterable"); 522 } 523 exports.values = values; 524 525 function doneResult() { 526 return { value: undefined, done: true }; 527 } 528 529 Context.prototype = { 530 constructor: Context, 531 532 reset: function(skipTempReset) { 533 this.prev = 0; 534 this.next = 0; 535 // Resetting context._sent for legacy support of Babel's 536 // function.sent implementation. 537 this.sent = this._sent = undefined; 538 this.done = false; 539 this.delegate = null; 540 541 this.method = "next"; 542 this.arg = undefined; 543 544 this.tryEntries.forEach(resetTryEntry); 545 546 if (!skipTempReset) { 547 for (var name in this) { 548 // Not sure about the optimal order of these conditions: 549 if (name.charAt(0) === "t" && 550 hasOwn.call(this, name) && 551 !isNaN(+name.slice(1))) { 552 this[name] = undefined; 553 } 554 } 555 } 556 }, 557 558 stop: function() { 559 this.done = true; 560 561 var rootEntry = this.tryEntries[0]; 562 var rootRecord = rootEntry.completion; 563 if (rootRecord.type === "throw") { 564 throw rootRecord.arg; 565 } 566 567 return this.rval; 568 }, 569 570 dispatchException: function(exception) { 571 if (this.done) { 572 throw exception; 573 } 574 575 var context = this; 576 function handle(loc, caught) { 577 record.type = "throw"; 578 record.arg = exception; 579 context.next = loc; 580 581 if (caught) { 582 // If the dispatched exception was caught by a catch block, 583 // then let that catch block handle the exception normally. 584 context.method = "next"; 585 context.arg = undefined; 586 } 587 588 return !! caught; 589 } 590 591 for (var i = this.tryEntries.length - 1; i >= 0; --i) { 592 var entry = this.tryEntries[i]; 593 var record = entry.completion; 594 595 if (entry.tryLoc === "root") { 596 // Exception thrown outside of any try block that could handle 597 // it, so set the completion value of the entire function to 598 // throw the exception. 599 return handle("end"); 600 } 601 602 if (entry.tryLoc <= this.prev) { 603 var hasCatch = hasOwn.call(entry, "catchLoc"); 604 var hasFinally = hasOwn.call(entry, "finallyLoc"); 605 606 if (hasCatch && hasFinally) { 607 if (this.prev < entry.catchLoc) { 608 return handle(entry.catchLoc, true); 609 } else if (this.prev < entry.finallyLoc) { 610 return handle(entry.finallyLoc); 611 } 612 613 } else if (hasCatch) { 614 if (this.prev < entry.catchLoc) { 615 return handle(entry.catchLoc, true); 616 } 617 618 } else if (hasFinally) { 619 if (this.prev < entry.finallyLoc) { 620 return handle(entry.finallyLoc); 621 } 622 623 } else { 624 throw new Error("try statement without catch or finally"); 625 } 626 } 627 } 628 }, 629 630 abrupt: function(type, arg) { 631 for (var i = this.tryEntries.length - 1; i >= 0; --i) { 632 var entry = this.tryEntries[i]; 633 if (entry.tryLoc <= this.prev && 634 hasOwn.call(entry, "finallyLoc") && 635 this.prev < entry.finallyLoc) { 636 var finallyEntry = entry; 637 break; 638 } 639 } 640 641 if (finallyEntry && 642 (type === "break" || 643 type === "continue") && 644 finallyEntry.tryLoc <= arg && 645 arg <= finallyEntry.finallyLoc) { 646 // Ignore the finally entry if control is not jumping to a 647 // location outside the try/catch block. 648 finallyEntry = null; 649 } 650 651 var record = finallyEntry ? finallyEntry.completion : {}; 652 record.type = type; 653 record.arg = arg; 654 655 if (finallyEntry) { 656 this.method = "next"; 657 this.next = finallyEntry.finallyLoc; 658 return ContinueSentinel; 659 } 660 661 return this.complete(record); 662 }, 663 664 complete: function(record, afterLoc) { 665 if (record.type === "throw") { 666 throw record.arg; 667 } 668 669 if (record.type === "break" || 670 record.type === "continue") { 671 this.next = record.arg; 672 } else if (record.type === "return") { 673 this.rval = this.arg = record.arg; 674 this.method = "return"; 675 this.next = "end"; 676 } else if (record.type === "normal" && afterLoc) { 677 this.next = afterLoc; 678 } 679 680 return ContinueSentinel; 681 }, 682 683 finish: function(finallyLoc) { 684 for (var i = this.tryEntries.length - 1; i >= 0; --i) { 685 var entry = this.tryEntries[i]; 686 if (entry.finallyLoc === finallyLoc) { 687 this.complete(entry.completion, entry.afterLoc); 688 resetTryEntry(entry); 689 return ContinueSentinel; 690 } 691 } 692 }, 693 694 "catch": function(tryLoc) { 695 for (var i = this.tryEntries.length - 1; i >= 0; --i) { 696 var entry = this.tryEntries[i]; 697 if (entry.tryLoc === tryLoc) { 698 var record = entry.completion; 699 if (record.type === "throw") { 700 var thrown = record.arg; 701 resetTryEntry(entry); 702 } 703 return thrown; 704 } 705 } 706 707 // The context.catch method must only be called with a location 708 // argument that corresponds to a known catch block. 709 throw new Error("illegal catch attempt"); 710 }, 711 712 delegateYield: function(iterable, resultName, nextLoc) { 713 this.delegate = { 714 iterator: values(iterable), 715 resultName: resultName, 716 nextLoc: nextLoc 717 }; 718 719 if (this.method === "next") { 720 // Deliberately forget the last sent value so that we don't 721 // accidentally pass it on to the delegate. 722 this.arg = undefined; 723 } 724 725 return ContinueSentinel; 726 } 727 }; 728 729 // Regardless of whether this script is executing as a CommonJS module 730 // or not, return the runtime object so that we can declare the variable 731 // regeneratorRuntime in the outer scope, which allows this module to be 732 // injected easily by `bin/regenerator --include-runtime script.js`. 733 return exports; 734 735}( 736 // If this script is executing as a CommonJS module, use module.exports 737 // as the regeneratorRuntime namespace. Otherwise create a new empty 738 // object. Either way, the resulting object will be used to initialize 739 // the regeneratorRuntime variable at the top of this file. 740 typeof module === "object" ? module.exports : {} 741)); 742 743try { 744 regeneratorRuntime = runtime; 745} catch (accidentalStrictMode) { 746 // This module should not be running in strict mode, so the above 747 // assignment should always work unless something is misconfigured. Just 748 // in case runtime.js accidentally runs in strict mode, in modern engines 749 // we can explicitly access globalThis. In older engines we can escape 750 // strict mode using a global Function call. This could conceivably fail 751 // if a Content Security Policy forbids using Function, but in that case 752 // the proper solution is to fix the accidental strict mode problem. If 753 // you've misconfigured your bundler to force strict mode and applied a 754 // CSP to forbid Function, and you're not willing to fix either of those 755 // problems, please detail your unique predicament in a GitHub issue. 756 if (typeof globalThis === "object") { 757 globalThis.regeneratorRuntime = runtime; 758 } else { 759 Function("r", "regeneratorRuntime = r")(runtime); 760 } 761}
vendor: 2,117 bytes, lines 761-762
761; 762!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.ES6Promise=e()}(this,function(){"use strict";function t(t){var e=typeof t;return null!==t&&("object"===e||"function"===e)}function e(t){return"function"==typeof t}function n(t){B=t}function r(t){G=t}function o(){return function(){return process.nextTick(a)}}function i(){return"undefined"!=typeof z?function(){z(a)}:c()}function s(){var t=0,e=new J(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){return t.port2.postMessage(0)}}function c(){var t=setTimeout;return function(){return t(a,1)}}function a(){for(var t=0;t<W;t+=2){var e=V[t],n=V[t+1];e(n),V[t]=void 0,V[t+1]=void 0}W=0}function f(){try{var t=Function("return this")().require("vertx");return z=t.runOnLoop||t.runOnContext,i()}catch(e){return c()}}function l(t,e){var n=this,r=new this.constructor(p);void 0===r[Z]&&O(r);var o=n._state;if(o){var i=arguments[o-1];G(function(){return P(o,r,i,n._result)})}else E(n,r,t,e);return r}function h(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(p);return g(n,t),n}function p(){}function v(){return new TypeError("You cannot resolve a promise with itself")}function d(){return new TypeError("A promises callback cannot return that same promise.")}function _(t){try{return t.then}catch(e){return nt.error=e,nt}}function y(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function m(t,e,n){G(function(t){var r=!1,o=y(n,e,function(n){r||(r=!0,e!==n?g(t,n):S(t,n))},function(e){r||(r=!0,j(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&o&&(r=!0,j(t,o))},t)}function b(t,e){e._state===tt?S(t,e._result):e._state===et?j(t,e._result):E(e,void 0,function(e){return g(t,e)},function(e){return j(t,e)})}function w(t,n,r){n.constructor===t.constructor&&r===l&&n.constructor.resolve===h?b(t,n):r===nt?(j(t,nt.error),nt.error=null):void 0===r?S(t,n):e(r)?m(t,n,r):S(t,n)}function g(e,n){e===n?j(e,v()):t(n)?w(e,n,_(n)):S(e,n)}
vendor: 1,569 bytes, line 762
762function A(t){t._onerror&&t._onerror(t._result),T(t)}function S(t,e){t._state===$&&(t._result=e,t._state=tt,0!==t._subscribers.length&&G(T,t))}function j(t,e){t._state===$&&(t._state=et,t._result=e,G(A,t))}function E(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+tt]=n,o[i+et]=r,0===i&&t._state&&G(T,t)}function T(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r=void 0,o=void 0,i=t._result,s=0;s<e.length;s+=3)r=e[s],o=e[s+n],r?P(n,r,o,i):o(i);t._subscribers.length=0}}function M(t,e){try{return t(e)}catch(n){return nt.error=n,nt}}function P(t,n,r,o){var i=e(r),s=void 0,u=void 0,c=void 0,a=void 0;if(i){if(s=M(r,o),s===nt?(a=!0,u=s.error,s.error=null):c=!0,n===s)return void j(n,d())}else s=o,c=!0;n._state!==$||(i&&c?g(n,s):a?j(n,u):t===tt?S(n,s):t===et&&j(n,s))}function x(t,e){try{e(function(e){g(t,e)},function(e){j(t,e)})}catch(n){j(t,n)}}function C(){return rt++}function O(t){t[Z]=rt++,t._state=void 0,t._result=void 0,t._subscribers=[]}function k(){return new Error("Array Methods must be provided an Array")}function F(t){return new ot(this,t).promise}function Y(t){var e=this;return new e(U(t)?function(n,r){for(var o=t.length,i=0;i<o;i++)e.resolve(t[i]).then(n,r)}:function(t,e){return e(new TypeError("You must pass an array to race."))})}function q(t){var e=this,n=new e(p);return j(n,t),n}function D(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function K(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object c
762onstructor cannot be called as a function.")}function L(){var t=void 0;if("undefined"!=typeof global)t=global;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;if(n){var r=null;try{r=Object.prototype.toString.call(n.resolve())}catch(e){}if("[object Promise]"===r&&!n.cast)return}t.Promise=it}var N=void 0;N=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var U=N,W=0,z=void 0,B=void 0,G=function(t,e){V[W]=t,V[W+1]=e,W+=2,2===W&&(B?B(a):X())},H="undefined"!=typeof window?window:void 0,I=H||{},J=I.MutationObserver||I.WebKitMutationObserver,Q="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),R="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,V=new Array(1e3),X=void 0;X=Q?o():J?s():R?u():void 0===H&&"function"==typeof require?f():c();var Z=Math.random().toString(36).substring(2),$=void 0,tt=1,et=2,nt={error:null},rt=0,ot=function(){function t(t,e){this._instanceConstructor=t,this.promise=new t(p),this.promise[Z]||O(this.promise),U(e)?(this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?S(this.promise,this._result):(this.length=this.length||0,this._enumerate(e),0===this._remaining&&S(this.promise,this._result))):j(this.promise,k())}return t.prototype._enumerate=function(t){for(var e=0;this._state===$&&e<t.length;e++)this._eachEntry(t[e],e)},t.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===h){var o=_(t);if(o===l&&t._state!==$)this._settledAt(t._state,e,t._result);else if("function"!=typeof o)this._remaining--,this._result[e]=t;else if(n===it){var i=new n(p);w(i,t,o),this._willSettleAt(i,e)}else this._willSettleAt(new n(function(e){return e(t)}),e)}else this._willSettleAt(r(t),e)},t.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===$&&(this._remaining--,t===et?j(r,n):this._result[e]=n),0===this._remaining&&S(r,this._result)},t.prototype._willSettleAt=function(t,e){var n=this;E(t,void 0,function(t){return n._settledAt(tt,e,t)},function(t){return n._settledAt(et,e,t)})},t}(),it=function(){function t(e){this[Z]=C(),this._result=this._state=void 0,this._subscribers=[],p!==e&&("function"!=typeof e&&D(),this instanceof t?x(this,e):K())}return t.prototype["catch"]=function(t){return this.then(null,t)},t.prototype["finally"]=function(t){var e=this,n=e.constructor;return e.then(function(e){return n.resolve(t()).then(function(){return e})},function(e){return n.resolve(t()).then(function(){throw e})})},t}();return it.prototype.then=l,it.all=F,it.race=Y,it.resolve=h,it.reject=q,it._setScheduler=n,it._setAsap=r,it._asap=G,it.polyfill=L,it.Promise=it,it.polyfill(),it});
vendor: 87,657 bytes, lines 763-3990
763/*! 764 * jQuery JavaScript Library v3.7.1 765 * https://jquery.com/ 766 * 767 * Copyright OpenJS Foundation and other contributors 768 * Released under the MIT license 769 * https://jquery.org/license 770 * 771 * Date: 2023-08-28T13:37Z 772 */ 773( function( global, factory ) { 774 775 "use strict"; 776 777 if ( typeof module === "object" && typeof module.exports === "object" ) { 778 779 // For CommonJS and CommonJS-like environments where a proper `window` 780 // is present, execute the factory and get jQuery. 781 // For environments that do not have a `window` with a `document` 782 // (such as Node.js), expose a factory as module.exports. 783 // This accentuates the need for the creation of a real `window`. 784 // e.g. var jQuery = require("jquery")(window); 785 // See ticket trac-14549 for more info. 786 module.exports = global.document ? 787 factory( global, true ) : 788 function( w ) { 789 if ( !w.document ) { 790 throw new Error( "jQuery requires a window with a document" ); 791 } 792 return factory( w ); 793 }; 794 } else { 795 factory( global ); 796 } 797 798// Pass this if window is not defined yet 799} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { 800 801// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 802// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode 803// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common 804// enough that all such attempts are guarded in a try block. 805"use strict"; 806 807var arr = []; 808 809var getProto = Object.getPrototypeOf; 810 811var slice = arr.slice; 812 813var flat = arr.flat ? function( array ) { 814 return arr.flat.call( array ); 815} : function( array ) { 816 return arr.concat.apply( [], array ); 817}; 818 819 820var push = arr.push; 821 822var indexOf = arr.indexOf; 823 824var class2type = {}; 825 826var toString = class2type.toString; 827 828var hasOwn = class2type.hasOwnProperty; 829 830var fnToString = hasOwn.toString; 831 832var ObjectFunctionString = fnToString.call( Object ); 833 834var support = {}; 835 836var isFunction = function isFunction( obj ) { 837 838 // Support: Chrome <=57, Firefox <=52 839 // In some browsers, typeof returns "function" for HTML <object> elements 840 // (i.e., `typeof document.createElement( "object" ) === "function"`). 841 // We don't want to classify *any* DOM node as a function. 842 // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 843 // Plus for old WebKit, typeof returns "function" for HTML collections 844 // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) 845 return typeof obj === "function" && typeof obj.nodeType !== "number" && 846 typeof obj.item !== "function"; 847 }; 848 849 850var isWindow = function isWindow( obj ) { 851 return obj != null && obj === obj.window; 852 }; 853 854 855var document = window.document; 856 857 858 859 var preservedScriptAttributes = { 860 type: true, 861 src: true, 862 nonce: true, 863 noModule: true 864 }; 865 866 function DOMEval( code, node, doc ) { 867 doc = doc || document; 868 869 var i, val, 870 script = doc.createElement( "script" ); 871 872 script.text = code; 873 if ( node ) { 874 for ( i in preservedScriptAttributes ) { 875 876 // Support: Firefox 64+, Edge 18+ 877 // Some browsers don't support the "nonce" property on scripts. 878 // On the other hand, just using `getAttribute` is not enough as 879 // the `nonce` attribute is reset to an empty string whenever it 880 // becomes browsing-context connected. 881 // See https://github.com/whatwg/html/issues/2369 882 // See https://html.spec.whatwg.org/#nonce-attributes 883 // The `node.getAttribute` check was added for the sake of 884 // `jQuery.globalEval` so that it can fake a nonce-containing node 885 // via an object. 886 val = node[ i ] || node.getAttribute && node.getAttribute( i ); 887 if ( val ) { 888 script.setAttribute( i, val ); 889 } 890 } 891 } 892 doc.head.appendChild( script ).parentNode.removeChild( script ); 893 } 894 895 896function toType( obj ) { 897 if ( obj == null ) { 898 return obj + ""; 899 } 900 901 // Support: Android <=2.3 only (functionish RegExp) 902 return typeof obj === "object" || typeof obj === "function" ? 903 class2type[ toString.call( obj ) ] || "object" : 904 typeof obj; 905} 906/* global Symbol */ 907// Defining this global in .eslintrc.json would create a danger of using the global 908// unguarded in another place, it seems safer to define global only for this module 909 910 911 912var version = "3.7.1", 913 914 rhtmlSuffix = /HTML$/i, 915 916 // Define a local copy of jQuery 917 jQuery = function( selector, context ) { 918 919 // The jQuery object is actually just the init constructor 'enhanced' 920 // Need init if jQuery is called (just allow error to be thrown if not included) 921 return new jQuery.fn.init( selector, context ); 922 }; 923 924jQuery.fn = jQuery.prototype = { 925 926 // The current version of jQuery being used 927 jquery: version, 928 929 constructor: jQuery, 930 931 // The default length of a jQuery object is 0 932 length: 0, 933 934 toArray: function() { 935 return slice.call( this ); 936 }, 937 938 // Get the Nth element in the matched element set OR 939 // Get the whole matched element set as a clean array 940 get: function( num ) { 941 942 // Return all the elements in a clean array 943 if ( num == null ) { 944 return slice.call( this ); 945 } 946 947 // Return just the one element from the set 948 return num < 0 ? this[ num + this.length ] : this[ num ]; 949 }, 950 951 // Take an array of elements and push it onto the stack 952 // (returning the new matched element set) 953 pushStack: function( elems ) { 954 955 // Build a new jQuery matched element set 956 var ret = jQuery.merge( this.constructor(), elems ); 957 958 // Add the old object onto the stack (as a reference) 959 ret.prevObject = this; 960 961 // Return the newly-formed element set 962 return ret; 963 }, 964 965 // Execute a callback for every element in the matched set. 966 each: function( callback ) { 967 return jQuery.each( this, callback ); 968 }, 969 970 map: function( callback ) { 971 return this.pushStack( jQuery.map( this, function( elem, i ) { 972 return callback.call( elem, i, elem ); 973 } ) ); 974 }, 975 976 slice: function() { 977 return this.pushStack( slice.apply( this, arguments ) ); 978 }, 979 980 first: function() { 981 return this.eq( 0 ); 982 }, 983 984 last: function() { 985 return this.eq( -1 ); 986 }, 987 988 even: function() { 989 return this.pushStack( jQuery.grep( this, function( _elem, i ) { 990 return ( i + 1 ) % 2; 991 } ) ); 992 }, 993 994 odd: function() { 995 return this.pushStack( jQuery.grep( this, function( _elem, i ) { 996 return i % 2; 997 } ) ); 998 }, 999 1000 eq: function( i ) { 1001 var len = this.length, 1002 j = +i + ( i < 0 ? len : 0 ); 1003 return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); 1004 }, 1005 1006 end: function() { 1007 return this.prevObject || this.constructor(); 1008 }, 1009 1010 // For internal use only. 1011 // Behaves like an Array's method, not like a jQuery method. 1012 push: push, 1013 sort: arr.sort, 1014 splice: arr.splice 1015}; 1016 1017jQuery.extend = jQuery.fn.extend = function() { 1018 var options, name, src, copy, copyIsArray, clone, 1019 target = arguments[ 0 ] || {}, 1020 i = 1, 1021 length = arguments.length, 1022 deep = false; 1023 1024 // Handle a deep copy situation 1025 if ( typeof target === "boolean" ) { 1026 deep = target; 1027 1028 // Skip the boolean and the target 1029 target = arguments[ i ] || {}; 1030 i++; 1031 } 1032 1033 // Handle case when target is a string or something (possible in deep copy) 1034 if ( typeof target !== "object" && !isFunction( target ) ) { 1035 target = {}; 1036 } 1037 1038 // Extend jQuery itself if only one argument is passed 1039 if ( i === length ) { 1040 target = this; 1041 i--; 1042 } 1043 1044 for ( ; i < length; i++ ) { 1045 1046 // Only deal with non-null/undefined values 1047 if ( ( options = arguments[ i ] ) != null ) { 1048 1049 // Extend the base object 1050 for ( name in options ) { 1051 copy = options[ name ]; 1052 1053 // Prevent Object.prototype pollution 1054 // Prevent never-ending loop 1055 if ( name === "__proto__" || target === copy ) { 1056 continue; 1057 } 1058 1059 // Recurse if we're merging plain objects or arrays 1060 if ( deep && copy && ( jQuery.isPlainObject( copy ) || 1061 ( copyIsArray = Array.isArray( copy ) ) ) ) { 1062 src = target[ name ]; 1063 1064 // Ensure proper type for the source value 1065 if ( copyIsArray && !Array.isArray( src ) ) { 1066 clone = []; 1067 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { 1068 clone = {}; 1069 } else { 1070 clone = src; 1071 } 1072 copyIsArray = false; 1073 1074 // Never move original objects, clone them 1075 target[ name ] = jQuery.extend( deep, clone, copy ); 1076 1077 // Don't bring in undefined values 1078 } else if ( copy !== undefined ) { 1079 target[ name ] = copy; 1080 } 1081 } 1082 } 1083 } 1084 1085 // Return the modified object 1086 return target; 1087}; 1088 1089jQuery.extend( { 1090 1091 // Unique for each copy of jQuery on the page 1092 expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), 1093 1094 // Assume jQuery is ready without the ready module 1095 isReady: true, 1096 1097 error: function( msg ) { 1098 throw new Error( msg ); 1099 }, 1100 1101 noop: function() {}, 1102 1103 isPlainObject: function( obj ) { 1104 var proto, Ctor; 1105 1106 // Detect obvious negatives 1107 // Use toString instead of jQuery.type to catch host objects 1108 if ( !obj || toString.call( obj ) !== "[object Object]" ) { 1109 return false; 1110 } 1111 1112 proto = getProto( obj ); 1113 1114 // Objects with no prototype (e.g., `Object.create( null )`) are plain 1115 if ( !proto ) { 1116 return true; 1117 } 1118 1119 // Objects with prototype are plain iff they were constructed by a global Object function 1120 Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; 1121 return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; 1122 }, 1123 1124 isEmptyObject: function( obj ) { 1125 var name; 1126 1127 for ( name in obj ) { 1128 return false; 1129 } 1130 return true; 1131 }, 1132 1133 // Evaluates a script in a provided context; falls back to the global one 1134 // if not specified. 1135 globalEval: function( code, options, doc ) { 1136 DOMEval( code, { nonce: options && options.nonce }, doc ); 1137 }, 1138 1139 each: function( obj, callback ) { 1140 var length, i = 0; 1141 1142 if ( isArrayLike( obj ) ) { 1143 length = obj.length; 1144 for ( ; i < length; i++ ) { 1145 if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { 1146 break; 1147 } 1148 } 1149 } else { 1150 for ( i in obj ) { 1151 if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { 1152 break; 1153 } 1154 } 1155 } 1156 1157 return obj; 1158 }, 1159 1160 1161 // Retrieve the text value of an array of DOM nodes 1162 text: function( elem ) { 1163 var node, 1164 ret = "", 1165 i = 0, 1166 nodeType = elem.nodeType; 1167 1168 if ( !nodeType ) { 1169 1170 // If no nodeType, this is expected to be an array 1171 while ( ( node = elem[ i++ ] ) ) { 1172 1173 // Do not traverse comment nodes 1174 ret += jQuery.text( node ); 1175 } 1176 } 1177 if ( nodeType === 1 || nodeType === 11 ) { 1178 return elem.textContent; 1179 } 1180 if ( nodeType === 9 ) { 1181 return elem.documentElement.textContent; 1182 } 1183 if ( nodeType === 3 || nodeType === 4 ) { 1184 return elem.nodeValue; 1185 } 1186 1187 // Do not include comment or processing instruction nodes 1188 1189 return ret; 1190 }, 1191 1192 // results is for internal usage only 1193 makeArray: function( arr, results ) { 1194 var ret = results || []; 1195 1196 if ( arr != null ) { 1197 if ( isArrayLike( Object( arr ) ) ) { 1198 jQuery.merge( ret, 1199 typeof arr === "string" ? 1200 [ arr ] : arr 1201 ); 1202 } else { 1203 push.call( ret, arr ); 1204 } 1205 } 1206 1207 return ret; 1208 }, 1209 1210 inArray: function( elem, arr, i ) { 1211 return arr == null ? -1 : indexOf.call( arr, elem, i ); 1212 }, 1213 1214 isXMLDoc: function( elem ) { 1215 var namespace = elem && elem.namespaceURI, 1216 docElem = elem && ( elem.ownerDocument || elem ).documentElement; 1217 1218 // Assume HTML when documentElement doesn't yet exist, such as inside 1219 // document fragments. 1220 return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); 1221 }, 1222 1223 // Support: Android <=4.0 only, PhantomJS 1 only 1224 // push.apply(_, arraylike) throws on ancient WebKit 1225 merge: function( first, second ) { 1226 var len = +second.length, 1227 j = 0, 1228 i = first.length; 1229 1230 for ( ; j < len; j++ ) { 1231 first[ i++ ] = second[ j ]; 1232 } 1233 1234 first.length = i; 1235 1236 return first; 1237 }, 1238 1239 grep: function( elems, callback, invert ) { 1240 var callbackInverse, 1241 matches = [], 1242 i = 0, 1243 length = elems.length, 1244 callbackExpect = !invert; 1245 1246 // Go through the array, only saving the items 1247 // that pass the validator function 1248 for ( ; i < length; i++ ) { 1249 callbackInverse = !callback( elems[ i ], i ); 1250 if ( callbackInverse !== callbackExpect ) { 1251 matches.push( elems[ i ] ); 1252 } 1253 } 1254 1255 return matches; 1256 }, 1257 1258 // arg is for internal usage only 1259 map: function( elems, callback, arg ) { 1260 var length, value, 1261 i = 0, 1262 ret = []; 1263 1264 // Go through the array, translating each of the items to their new values 1265 if ( isArrayLike( elems ) ) { 1266 length = elems.length; 1267 for ( ; i < length; i++ ) { 1268 value = callback( elems[ i ], i, arg ); 1269 1270 if ( value != null ) { 1271 ret.push( value ); 1272 } 1273 } 1274 1275 // Go through every key on the object, 1276 } else { 1277 for ( i in elems ) { 1278 value = callback( elems[ i ], i, arg ); 1279 1280 if ( value != null ) { 1281 ret.push( value ); 1282 } 1283 } 1284 } 1285 1286 // Flatten any nested arrays 1287 return flat( ret ); 1288 }, 1289 1290 // A global GUID counter for objects 1291 guid: 1, 1292 1293 // jQuery.support is not used in Core but other projects attach their 1294 // properties to it so it needs to exist. 1295 support: support 1296} ); 1297 1298if ( typeof Symbol === "function" ) { 1299 jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; 1300} 1301 1302// Populate the class2type map 1303jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), 1304 function( _i, name ) { 1305 class2type[ "[object " + name + "]" ] = name.toLowerCase(); 1306 } ); 1307 1308function isArrayLike( obj ) { 1309 1310 // Support: real iOS 8.2 only (not reproducible in simulator) 1311 // `in` check used to prevent JIT error (gh-2145) 1312 // hasOwn isn't used here due to false negatives 1313 // regarding Nodelist length in IE 1314 var length = !!obj && "length" in obj && obj.length, 1315 type = toType( obj ); 1316 1317 if ( isFunction( obj ) || isWindow( obj ) ) { 1318 return false; 1319 } 1320 1321 return type === "array" || length === 0 || 1322 typeof length === "number" && length > 0 && ( length - 1 ) in obj; 1323} 1324 1325 1326function nodeName( elem, name ) { 1327 1328 return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); 1329 1330} 1331var pop = arr.pop; 1332 1333 1334var sort = arr.sort; 1335 1336 1337var splice = arr.splice; 1338 1339 1340var whitespace = "[\\x20\\t\\r\\n\\f]"; 1341 1342 1343var rtrimCSS = new RegExp( 1344 "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", 1345 "g" 1346); 1347 1348 1349 1350 1351// Note: an element does not contain itself 1352jQuery.contains = function( a, b ) { 1353 var bup = b && b.parentNode; 1354 1355 return a === bup || !!( bup && bup.nodeType === 1 && ( 1356 1357 // Support: IE 9 - 11+ 1358 // IE doesn't have `contains` on SVG. 1359 a.contains ? 1360 a.contains( bup ) : 1361 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 1362 ) ); 1363}; 1364 1365 1366 1367 1368// CSS string/identifier serialization 1369// https://drafts.csswg.org/cssom/#common-serializing-idioms 1370var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; 1371 1372function fcssescape( ch, asCodePoint ) { 1373 if ( asCodePoint ) { 1374 1375 // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER 1376 if ( ch === "\0" ) { 1377 return "\uFFFD"; 1378 } 1379 1380 // Control characters and (dependent upon position) numbers get escaped as code points 1381 return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; 1382 } 1383 1384 // Other potentially-special ASCII characters get backslash-escaped 1385 return "\\" + ch; 1386} 1387 1388jQuery.escapeSelector = function( sel ) { 1389 return ( sel + "" ).replace( rcssescape, fcssescape ); 1390}; 1391 1392 1393 1394 1395var preferredDoc = document, 1396 pushNative = push; 1397 1398( function() { 1399 1400var i, 1401 Expr, 1402 outermostContext, 1403 sortInput, 1404 hasDuplicate, 1405 push = pushNative, 1406 1407 // Local document vars 1408 document, 1409 documentElement, 1410 documentIsHTML, 1411 rbuggyQSA, 1412 matches, 1413 1414 // Instance-specific data 1415 expando = jQuery.expando, 1416 dirruns = 0, 1417 done = 0, 1418 classCache = createCache(), 1419 tokenCache = createCache(), 1420 compilerCache = createCache(), 1421 nonnativeSelectorCache = createCache(), 1422 sortOrder = function( a, b ) { 1423 if ( a === b ) { 1424 hasDuplicate = true; 1425 } 1426 return 0; 1427 }, 1428 1429 booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + 1430 "loop|multiple|open|readonly|required|scoped", 1431 1432 // Regular expressions 1433 1434 // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram 1435 identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + 1436 "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", 1437 1438 // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors 1439 attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + 1440 1441 // Operator (capture 2) 1442 "*([*^$|!~]?=)" + whitespace + 1443 1444 // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" 1445 "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + 1446 whitespace + "*\\]", 1447 1448 pseudos = ":(" + identifier + ")(?:\\((" + 1449 1450 // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: 1451 // 1. quoted (capture 3; capture 4 or capture 5) 1452 "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + 1453 1454 // 2. simple (capture 6) 1455 "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + 1456 1457 // 3. anything else (capture 2) 1458 ".*" + 1459 ")\\)|)", 1460 1461 // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter 1462 rwhitespace = new RegExp( whitespace + "+", "g" ), 1463 1464 rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), 1465 rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + 1466 whitespace + "*" ), 1467 rdescend = new RegExp( whitespace + "|>" ), 1468 1469 rpseudo = new RegExp( pseudos ), 1470 ridentifier = new RegExp( "^" + identifier + "$" ), 1471 1472 matchExpr = { 1473 ID: new RegExp( "^#(" + identifier + ")" ), 1474 CLASS: new RegExp( "^\\.(" + identifier + ")" ), 1475 TAG: new RegExp( "^(" + identifier + "|[*])" ), 1476 ATTR: new RegExp( "^" + attributes ), 1477 PSEUDO: new RegExp( "^" + pseudos ), 1478 CHILD: new RegExp( 1479 "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + 1480 whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + 1481 whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), 1482 bool: new RegExp( "^(?:" + booleans + ")$", "i" ), 1483 1484 // For use in libraries implementing .is() 1485 // We use this for POS matching in `select` 1486 needsContext: new RegExp( "^" + whitespace + 1487 "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + 1488 "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) 1489 }, 1490 1491 rinputs = /^(?:input|select|textarea|button)$/i, 1492 rheader = /^h\d$/i, 1493 1494 // Easily-parseable/retrievable ID or TAG or CLASS selectors 1495 rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, 1496 1497 rsibling = /[+~]/, 1498 1499 // CSS escapes 1500 // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters 1501 runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + 1502 "?|\\\\([^\\r\\n\\f])", "g" ), 1503 funescape = function( escape, nonHex ) { 1504 var high = "0x" + escape.slice( 1 ) - 0x10000; 1505 1506 if ( nonHex ) { 1507 1508 // Strip the backslash prefix from a non-hex escape sequence 1509 return nonHex; 1510 } 1511 1512 // Replace a hexadecimal escape sequence with the encoded Unicode code point 1513 // Support: IE <=11+ 1514 // For values outside the Basic Multilingual Plane (BMP), manually construct a 1515 // surrogate pair 1516 return high < 0 ? 1517 String.fromCharCode( high + 0x10000 ) : 1518 String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); 1519 }, 1520 1521 // Used for iframes; see `setDocument`. 1522 // Support: IE 9 - 11+, Edge 12 - 18+ 1523 // Removing the function wrapper causes a "Permission Denied" 1524 // error in IE/Edge. 1525 unloadHandler = function() { 1526 setDocument(); 1527 }, 1528 1529 inDisabledFieldset = addCombinator( 1530 function( elem ) { 1531 return elem.disabled === true && nodeName( elem, "fieldset" ); 1532 }, 1533 { dir: "parentNode", next: "legend" } 1534 ); 1535 1536// Support: IE <=9 only 1537// Accessing document.activeElement can throw unexpectedly 1538// https://bugs.jquery.com/ticket/13393 1539function safeActiveElement() { 1540 try { 1541 return document.activeElement; 1542 } catch ( err ) { } 1543} 1544 1545// Optimize for push.apply( _, NodeList ) 1546try { 1547 push.apply( 1548 ( arr = slice.call( preferredDoc.childNodes ) ), 1549 preferredDoc.childNodes 1550 ); 1551 1552 // Support: Android <=4.0 1553 // Detect silently failing push.apply 1554 // eslint-disable-next-line no-unused-expressions 1555 arr[ preferredDoc.childNodes.length ].nodeType; 1556} catch ( e ) { 1557 push = { 1558 apply: function( target, els ) { 1559 pushNative.apply( target, slice.call( els ) ); 1560 }, 1561 call: function( target ) { 1562 pushNative.apply( target, slice.call( arguments, 1 ) ); 1563 } 1564 }; 1565} 1566 1567function find( selector, context, results, seed ) { 1568 var m, i, elem, nid, match, groups, newSelector, 1569 newContext = context && context.ownerDocument, 1570 1571 // nodeType defaults to 9, since context defaults to document 1572 nodeType = context ? context.nodeType : 9; 1573 1574 results = results || []; 1575 1576 // Return early from calls with invalid selector or context 1577 if ( typeof selector !== "string" || !selector || 1578 nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { 1579 1580 return results; 1581 } 1582 1583 // Try to shortcut find operations (as opposed to filters) in HTML documents 1584 if ( !seed ) { 1585 setDocument( context ); 1586 context = context || document; 1587 1588 if ( documentIsHTML ) { 1589 1590 // If the selector is sufficiently simple, try using a "get*By*" DOM method 1591 // (excepting DocumentFragment context, where the methods don't exist) 1592 if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { 1593 1594 // ID selector 1595 if ( ( m = match[ 1 ] ) ) { 1596 1597 // Document context 1598 if ( nodeType === 9 ) { 1599 if ( ( elem = context.getElementById( m ) ) ) { 1600 1601 // Support: IE 9 only 1602 // getElementById can match elements by name instead of ID 1603 if ( elem.id === m ) { 1604 push.call( results, elem ); 1605 return results; 1606 } 1607 } else { 1608 return results; 1609 } 1610 1611 // Element context 1612 } else { 1613 1614 // Support: IE 9 only 1615 // getElementById can match elements by name instead of ID 1616 if ( newContext && ( elem = newContext.getElementById( m ) ) && 1617 find.contains( context, elem ) && 1618 elem.id === m ) { 1619 1620 push.call( results, elem ); 1621 return results; 1622 } 1623 } 1624 1625 // Type selector 1626 } else if ( match[ 2 ] ) { 1627 push.apply( results, context.getElementsByTagName( selector ) ); 1628 return results; 1629 1630 // Class selector 1631 } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { 1632 push.apply( results, context.getElementsByClassName( m ) ); 1633 return results; 1634 } 1635 } 1636 1637 // Take advantage of querySelectorAll 1638 if ( !nonnativeSelectorCache[ selector + " " ] && 1639 ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { 1640 1641 newSelector = selector; 1642 newContext = context; 1643 1644 // qSA considers elements outside a scoping root when evaluating child or 1645 // descendant combinators, which is not what we want. 1646 // In such cases, we work around the behavior by prefixing every selector in the 1647 // list with an ID selector referencing the scope context. 1648 // The technique has to be used as well when a leading combinator is used 1649 // as such selectors are not recognized by querySelectorAll. 1650 // Thanks to Andrew Dupont for this technique. 1651 if ( nodeType === 1 && 1652 ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { 1653 1654 // Expand context for sibling selectors 1655 newContext = rsibling.test( selector ) && testContext( context.parentNode ) || 1656 context; 1657 1658 // We can use :scope instead of the ID hack if the browser 1659 // supports it & if we're not changing the context. 1660 // Support: IE 11+, Edge 17 - 18+ 1661 // IE/Edge sometimes throw a "Permission denied" error when 1662 // strict-comparing two documents; shallow comparisons work. 1663 // eslint-disable-next-line eqeqeq 1664 if ( newContext != context || !support.scope ) { 1665 1666 // Capture the context ID, setting it first if necessary 1667 if ( ( nid = context.getAttribute( "id" ) ) ) { 1668 nid = jQuery.escapeSelector( nid ); 1669 } else { 1670 context.setAttribute( "id", ( nid = expando ) ); 1671 } 1672 } 1673 1674 // Prefix every selector in the list 1675 groups = tokenize( selector ); 1676 i = groups.length; 1677 while ( i-- ) { 1678 groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + 1679 toSelector( groups[ i ] ); 1680 } 1681 newSelector = groups.join( "," ); 1682 } 1683 1684 try { 1685 push.apply( results, 1686 newContext.querySelectorAll( newSelector ) 1687 ); 1688 return results; 1689 } catch ( qsaError ) { 1690 nonnativeSelectorCache( selector, true ); 1691 } finally { 1692 if ( nid === expando ) { 1693 context.removeAttribute( "id" ); 1694 } 1695 } 1696 } 1697 } 1698 } 1699 1700 // All others 1701 return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); 1702} 1703 1704/** 1705 * Create key-value caches of limited size 1706 * @returns {function(string, object)} Returns the Object data after storing it on itself with 1707 * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) 1708 * deleting the oldest entry 1709 */ 1710function createCache() { 1711 var keys = []; 1712 1713 function cache( key, value ) { 1714 1715 // Use (key + " ") to avoid collision with native prototype properties 1716 // (see https://github.com/jquery/sizzle/issues/157) 1717 if ( keys.push( key + " " ) > Expr.cacheLength ) { 1718 1719 // Only keep the most recent entries 1720 delete cache[ keys.shift() ]; 1721 } 1722 return ( cache[ key + " " ] = value ); 1723 } 1724 return cache; 1725} 1726 1727/** 1728 * Mark a function for special use by jQuery selector module 1729 * @param {Function} fn The function to mark 1730 */ 1731function markFunction( fn ) { 1732 fn[ expando ] = true; 1733 return fn; 1734} 1735 1736/** 1737 * Support testing using an element 1738 * @param {Function} fn Passed the created element and returns a boolean result 1739 */ 1740function assert( fn ) { 1741 var el = document.createElement( "fieldset" ); 1742 1743 try { 1744 return !!fn( el ); 1745 } catch ( e ) { 1746 return false; 1747 } finally { 1748 1749 // Remove from its parent by default 1750 if ( el.parentNode ) { 1751 el.parentNode.removeChild( el ); 1752 } 1753 1754 // release memory in IE 1755 el = null; 1756 } 1757} 1758 1759/** 1760 * Returns a function to use in pseudos for input types 1761 * @param {String} type 1762 */ 1763function createInputPseudo( type ) { 1764 return function( elem ) { 1765 return nodeName( elem, "input" ) && elem.type === type; 1766 }; 1767} 1768 1769/** 1770 * Returns a function to use in pseudos for buttons 1771 * @param {String} type 1772 */ 1773function createButtonPseudo( type ) { 1774 return function( elem ) { 1775 return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && 1776 elem.type === type; 1777 }; 1778} 1779 1780/** 1781 * Returns a function to use in pseudos for :enabled/:disabled 1782 * @param {Boolean} disabled true for :disabled; false for :enabled 1783 */ 1784function createDisabledPseudo( disabled ) { 1785 1786 // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable 1787 return function( elem ) { 1788 1789 // Only certain elements can match :enabled or :disabled 1790 // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled 1791 // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled 1792 if ( "form" in elem ) { 1793 1794 // Check for inherited disabledness on relevant non-disabled elements: 1795 // * listed form-associated elements in a disabled fieldset 1796 // https://html.spec.whatwg.org/multipage/forms.html#category-listed 1797 // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled 1798 // * option elements in a disabled optgroup 1799 // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled 1800 // All such elements have a "form" property. 1801 if ( elem.parentNode && elem.disabled === false ) { 1802 1803 // Option elements defer to a parent optgroup if present 1804 if ( "label" in elem ) { 1805 if ( "label" in elem.parentNode ) { 1806 return elem.parentNode.disabled === disabled; 1807 } else { 1808 return elem.disabled === disabled; 1809 } 1810 } 1811 1812 // Support: IE 6 - 11+ 1813 // Use the isDisabled shortcut property to check for disabled fieldset ancestors 1814 return elem.isDisabled === disabled || 1815 1816 // Where there is no isDisabled, check manually 1817 elem.isDisabled !== !disabled && 1818 inDisabledFieldset( elem ) === disabled; 1819 } 1820 1821 return elem.disabled === disabled; 1822 1823 // Try to winnow out elements that can't be disabled before trusting the disabled property. 1824 // Some victims get caught in our net (label, legend, menu, track), but it shouldn't 1825 // even exist on them, let alone have a boolean value. 1826 } else if ( "label" in elem ) { 1827 return elem.disabled === disabled; 1828 } 1829 1830 // Remaining elements are neither :enabled nor :disabled 1831 return false; 1832 }; 1833} 1834 1835/** 1836 * Returns a function to use in pseudos for positionals 1837 * @param {Function} fn 1838 */ 1839function createPositionalPseudo( fn ) { 1840 return markFunction( function( argument ) { 1841 argument = +argument; 1842 return markFunction( function( seed, matches ) { 1843 var j, 1844 matchIndexes = fn( [], seed.length, argument ), 1845 i = matchIndexes.length; 1846 1847 // Match elements found at the specified indexes 1848 while ( i-- ) { 1849 if ( seed[ ( j = matchIndexes[ i ] ) ] ) { 1850 seed[ j ] = !( matches[ j ] = seed[ j ] ); 1851 } 1852 } 1853 } ); 1854 } ); 1855} 1856 1857/** 1858 * Checks a node for validity as a jQuery selector context 1859 * @param {Element|Object=} context 1860 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value 1861 */ 1862function testContext( context ) { 1863 return context && typeof context.getElementsByTagName !== "undefined" && context; 1864} 1865 1866/** 1867 * Sets document-related variables once based on the current document 1868 * @param {Element|Object} [node] An element or document object to use to set the document 1869 * @returns {Object} Returns the current document 1870 */ 1871function setDocument( node ) { 1872 var subWindow, 1873 doc = node ? node.ownerDocument || node : preferredDoc; 1874 1875 // Return early if doc is invalid or already selected 1876 // Support: IE 11+, Edge 17 - 18+ 1877 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1878 // two documents; shallow comparisons work. 1879 // eslint-disable-next-line eqeqeq 1880 if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { 1881 return document; 1882 } 1883 1884 // Update global variables 1885 document = doc; 1886 documentElement = document.documentElement; 1887 documentIsHTML = !jQuery.isXMLDoc( document ); 1888 1889 // Support: iOS 7 only, IE 9 - 11+ 1890 // Older browsers didn't support unprefixed `matches`. 1891 matches = documentElement.matches || 1892 documentElement.webkitMatchesSelector || 1893 documentElement.msMatchesSelector; 1894 1895 // Support: IE 9 - 11+, Edge 12 - 18+ 1896 // Accessing iframe documents after unload throws "permission denied" errors 1897 // (see trac-13936). 1898 // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, 1899 // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. 1900 if ( documentElement.msMatchesSelector && 1901 1902 // Support: IE 11+, Edge 17 - 18+ 1903 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 1904 // two documents; shallow comparisons work. 1905 // eslint-disable-next-line eqeqeq 1906 preferredDoc != document && 1907 ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { 1908 1909 // Support: IE 9 - 11+, Edge 12 - 18+ 1910 subWindow.addEventListener( "unload", unloadHandler ); 1911 } 1912 1913 // Support: IE <10 1914 // Check if getElementById returns elements by name 1915 // The broken getElementById methods don't pick up programmatically-set names, 1916 // so use a roundabout getElementsByName test 1917 support.getById = assert( function( el ) { 1918 documentElement.appendChild( el ).id = jQuery.expando; 1919 return !document.getElementsByName || 1920 !document.getElementsByName( jQuery.expando ).length; 1921 } ); 1922 1923 // Support: IE 9 only 1924 // Check to see if it's possible to do matchesSelector 1925 // on a disconnected node. 1926 support.disconnectedMatch = assert( function( el ) { 1927 return matches.call( el, "*" ); 1928 } ); 1929 1930 // Support: IE 9 - 11+, Edge 12 - 18+ 1931 // IE/Edge don't support the :scope pseudo-class. 1932 support.scope = assert( function() { 1933 return document.querySelectorAll( ":scope" ); 1934 } ); 1935 1936 // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only 1937 // Make sure the `:has()` argument is parsed unforgivingly. 1938 // We include `*` in the test to detect buggy implementations that are 1939 // _selectively_ forgiving (specifically when the list includes at least 1940 // one valid selector). 1941 // Note that we treat complete lack of support for `:has()` as if it were 1942 // spec-compliant support, which is fine because use of `:has()` in such 1943 // environments will fail in the qSA path and fall back to jQuery traversal 1944 // anyway. 1945 support.cssHas = assert( function() { 1946 try { 1947 document.querySelector( ":has(*,:jqfake)" ); 1948 return false; 1949 } catch ( e ) { 1950 return true; 1951 } 1952 } ); 1953 1954 // ID filter and find 1955 if ( support.getById ) { 1956 Expr.filter.ID = function( id ) { 1957 var attrId = id.replace( runescape, funescape ); 1958 return function( elem ) { 1959 return elem.getAttribute( "id" ) === attrId; 1960 }; 1961 }; 1962 Expr.find.ID = function( id, context ) { 1963 if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 1964 var elem = context.getElementById( id ); 1965 return elem ? [ elem ] : []; 1966 } 1967 }; 1968 } else { 1969 Expr.filter.ID = function( id ) { 1970 var attrId = id.replace( runescape, funescape ); 1971 return function( elem ) { 1972 var node = typeof elem.getAttributeNode !== "undefined" && 1973 elem.getAttributeNode( "id" ); 1974 return node && node.value === attrId; 1975 }; 1976 }; 1977 1978 // Support: IE 6 - 7 only 1979 // getElementById is not reliable as a find shortcut 1980 Expr.find.ID = function( id, context ) { 1981 if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { 1982 var node, i, elems, 1983 elem = context.getElementById( id ); 1984 1985 if ( elem ) { 1986 1987 // Verify the id attribute 1988 node = elem.getAttributeNode( "id" ); 1989 if ( node && node.value === id ) { 1990 return [ elem ]; 1991 } 1992 1993 // Fall back on getElementsByName 1994 elems = context.getElementsByName( id ); 1995 i = 0; 1996 while ( ( elem = elems[ i++ ] ) ) { 1997 node = elem.getAttributeNode( "id" ); 1998 if ( node && node.value === id ) { 1999 return [ elem ]; 2000 } 2001 } 2002 } 2003 2004 return []; 2005 } 2006 }; 2007 } 2008 2009 // Tag 2010 Expr.find.TAG = function( tag, context ) { 2011 if ( typeof context.getElementsByTagName !== "undefined" ) { 2012 return context.getElementsByTagName( tag ); 2013 2014 // DocumentFragment nodes don't have gEBTN 2015 } else { 2016 return context.querySelectorAll( tag ); 2017 } 2018 }; 2019 2020 // Class 2021 Expr.find.CLASS = function( className, context ) { 2022 if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { 2023 return context.getElementsByClassName( className ); 2024 } 2025 }; 2026 2027 /* QSA/matchesSelector 2028 ---------------------------------------------------------------------- */ 2029 2030 // QSA and matchesSelector support 2031 2032 rbuggyQSA = []; 2033 2034 // Build QSA regex 2035 // Regex strategy adopted from Diego Perini 2036 assert( function( el ) { 2037 2038 var input; 2039 2040 documentElement.appendChild( el ).innerHTML = 2041 "<a id='" + expando + "' href='' disabled='disabled'></a>" + 2042 "<select id='" + expando + "-\r\\' disabled='disabled'>" + 2043 "<option selected=''></option></select>"; 2044 2045 // Support: iOS <=7 - 8 only 2046 // Boolean attributes and "value" are not treated correctly in some XML documents 2047 if ( !el.querySelectorAll( "[selected]" ).length ) { 2048 rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); 2049 } 2050 2051 // Support: iOS <=7 - 8 only 2052 if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { 2053 rbuggyQSA.push( "~=" ); 2054 } 2055 2056 // Support: iOS 8 only 2057 // https://bugs.webkit.org/show_bug.cgi?id=136851 2058 // In-page `selector#id sibling-combinator selector` fails 2059 if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { 2060 rbuggyQSA.push( ".#.+[+~]" ); 2061 } 2062 2063 // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ 2064 // In some of the document kinds, these selectors wouldn't work natively. 2065 // This is probably OK but for backwards compatibility we want to maintain 2066 // handling them through jQuery traversal in jQuery 3.x. 2067 if ( !el.querySelectorAll( ":checked" ).length ) { 2068 rbuggyQSA.push( ":checked" ); 2069 } 2070 2071 // Support: Windows 8 Native Apps 2072 // The type and name attributes are restricted during .innerHTML assignment 2073 input = document.createElement( "input" ); 2074 input.setAttribute( "type", "hidden" ); 2075 el.appendChild( input ).setAttribute( "name", "D" ); 2076 2077 // Support: IE 9 - 11+ 2078 // IE's :disabled selector does not pick up the children of disabled fieldsets 2079 // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ 2080 // In some of the document kinds, these selectors wouldn't work natively. 2081 // This is probably OK but for backwards compatibility we want to maintain 2082 // handling them through jQuery traversal in jQuery 3.x. 2083 documentElement.appendChild( el ).disabled = true; 2084 if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { 2085 rbuggyQSA.push( ":enabled", ":disabled" ); 2086 } 2087 2088 // Support: IE 11+, Edge 15 - 18+ 2089 // IE 11/Edge don't find elements on a `[name='']` query in some cases. 2090 // Adding a temporary attribute to the document before the selection works 2091 // around the issue. 2092 // Interestingly, IE 10 & older don't seem to have the issue. 2093 input = document.createElement( "input" ); 2094 input.setAttribute( "name", "" ); 2095 el.appendChild( input ); 2096 if ( !el.querySelectorAll( "[name='']" ).length ) { 2097 rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + 2098 whitespace + "*(?:''|\"\")" ); 2099 } 2100 } ); 2101 2102 if ( !support.cssHas ) { 2103 2104 // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ 2105 // Our regular `try-catch` mechanism fails to detect natively-unsupported 2106 // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) 2107 // in browsers that parse the `:has()` argument as a forgiving selector list. 2108 // https://drafts.csswg.org/selectors/#relational now requires the argument 2109 // to be parsed unforgivingly, but browsers have not yet fully adjusted. 2110 rbuggyQSA.push( ":has" ); 2111 } 2112 2113 rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); 2114 2115 /* Sorting 2116 ---------------------------------------------------------------------- */ 2117 2118 // Document order sorting 2119 sortOrder = function( a, b ) { 2120 2121 // Flag for duplicate removal 2122 if ( a === b ) { 2123 hasDuplicate = true; 2124 return 0; 2125 } 2126 2127 // Sort on method existence if only one input has compareDocumentPosition 2128 var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; 2129 if ( compare ) { 2130 return compare; 2131 } 2132 2133 // Calculate position if both inputs belong to the same document 2134 // Support: IE 11+, Edge 17 - 18+ 2135 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2136 // two documents; shallow comparisons work. 2137 // eslint-disable-next-line eqeqeq 2138 compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? 2139 a.compareDocumentPosition( b ) : 2140 2141 // Otherwise we know they are disconnected 2142 1; 2143 2144 // Disconnected nodes 2145 if ( compare & 1 || 2146 ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { 2147 2148 // Choose the first element that is related to our preferred document 2149 // Support: IE 11+, Edge 17 - 18+ 2150 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2151 // two documents; shallow comparisons work. 2152 // eslint-disable-next-line eqeqeq 2153 if ( a === document || a.ownerDocument == preferredDoc && 2154 find.contains( preferredDoc, a ) ) { 2155 return -1; 2156 } 2157 2158 // Support: IE 11+, Edge 17 - 18+ 2159 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2160 // two documents; shallow comparisons work. 2161 // eslint-disable-next-line eqeqeq 2162 if ( b === document || b.ownerDocument == preferredDoc && 2163 find.contains( preferredDoc, b ) ) { 2164 return 1; 2165 } 2166 2167 // Maintain original order 2168 return sortInput ? 2169 ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 2170 0; 2171 } 2172 2173 return compare & 4 ? -1 : 1; 2174 }; 2175 2176 return document; 2177} 2178 2179find.matches = function( expr, elements ) { 2180 return find( expr, null, null, elements ); 2181}; 2182 2183find.matchesSelector = function( elem, expr ) { 2184 setDocument( elem ); 2185 2186 if ( documentIsHTML && 2187 !nonnativeSelectorCache[ expr + " " ] && 2188 ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { 2189 2190 try { 2191 var ret = matches.call( elem, expr ); 2192 2193 // IE 9's matchesSelector returns false on disconnected nodes 2194 if ( ret || support.disconnectedMatch || 2195 2196 // As well, disconnected nodes are said to be in a document 2197 // fragment in IE 9 2198 elem.document && elem.document.nodeType !== 11 ) { 2199 return ret; 2200 } 2201 } catch ( e ) { 2202 nonnativeSelectorCache( expr, true ); 2203 } 2204 } 2205 2206 return find( expr, document, null, [ elem ] ).length > 0; 2207}; 2208 2209find.contains = function( context, elem ) { 2210 2211 // Set document vars if needed 2212 // Support: IE 11+, Edge 17 - 18+ 2213 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2214 // two documents; shallow comparisons work. 2215 // eslint-disable-next-line eqeqeq 2216 if ( ( context.ownerDocument || context ) != document ) { 2217 setDocument( context ); 2218 } 2219 return jQuery.contains( context, elem ); 2220}; 2221 2222 2223find.attr = function( elem, name ) { 2224 2225 // Set document vars if needed 2226 // Support: IE 11+, Edge 17 - 18+ 2227 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 2228 // two documents; shallow comparisons work. 2229 // eslint-disable-next-line eqeqeq 2230 if ( ( elem.ownerDocument || elem ) != document ) { 2231 setDocument( elem ); 2232 } 2233 2234 var fn = Expr.attrHandle[ name.toLowerCase() ], 2235 2236 // Don't get fooled by Object.prototype properties (see trac-13807) 2237 val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? 2238 fn( elem, name, !documentIsHTML ) : 2239 undefined; 2240 2241 if ( val !== undefined ) { 2242 return val; 2243 } 2244 2245 return elem.getAttribute( name ); 2246}; 2247 2248find.error = function( msg ) { 2249 throw new Error( "Syntax error, unrecognized expression: " + msg ); 2250}; 2251 2252/** 2253 * Document sorting and removing duplicates 2254 * @param {ArrayLike} results 2255 */ 2256jQuery.uniqueSort = function( results ) { 2257 var elem, 2258 duplicates = [], 2259 j = 0, 2260 i = 0; 2261 2262 // Unless we *know* we can detect duplicates, assume their presence 2263 // 2264 // Support: Android <=4.0+ 2265 // Testing for detecting duplicates is unpredictable so instead assume we can't 2266 // depend on duplicate detection in all browsers without a stable sort. 2267 hasDuplicate = !support.sortStable; 2268 sortInput = !support.sortStable && slice.call( results, 0 ); 2269 sort.call( results, sortOrder ); 2270 2271 if ( hasDuplicate ) { 2272 while ( ( elem = results[ i++ ] ) ) { 2273 if ( elem === results[ i ] ) { 2274 j = duplicates.push( i ); 2275 } 2276 } 2277 while ( j-- ) { 2278 splice.call( results, duplicates[ j ], 1 ); 2279 } 2280 } 2281 2282 // Clear input after sorting to release objects 2283 // See https://github.com/jquery/sizzle/pull/225 2284 sortInput = null; 2285 2286 return results; 2287}; 2288 2289jQuery.fn.uniqueSort = function() { 2290 return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); 2291}; 2292 2293Expr = jQuery.expr = { 2294 2295 // Can be adjusted by the user 2296 cacheLength: 50, 2297 2298 createPseudo: markFunction, 2299 2300 match: matchExpr, 2301 2302 attrHandle: {}, 2303 2304 find: {}, 2305 2306 relative: { 2307 ">": { dir: "parentNode", first: true }, 2308 " ": { dir: "parentNode" }, 2309 "+": { dir: "previousSibling", first: true }, 2310 "~": { dir: "previousSibling" } 2311 }, 2312 2313 preFilter: { 2314 ATTR: function( match ) { 2315 match[ 1 ] = match[ 1 ].replace( runescape, funescape ); 2316 2317 // Move the given value to match[3] whether quoted or unquoted 2318 match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) 2319 .replace( runescape, funescape ); 2320 2321 if ( match[ 2 ] === "~=" ) { 2322 match[ 3 ] = " " + match[ 3 ] + " "; 2323 } 2324 2325 return match.slice( 0, 4 ); 2326 }, 2327 2328 CHILD: function( match ) { 2329 2330 /* matches from matchExpr["CHILD"] 2331 1 type (only|nth|...) 2332 2 what (child|of-type) 2333 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 2334 4 xn-component of xn+y argument ([+-]?\d*n|) 2335 5 sign of xn-component 2336 6 x of xn-component 2337 7 sign of y-component 2338 8 y of y-component 2339 */ 2340 match[ 1 ] = match[ 1 ].toLowerCase(); 2341 2342 if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { 2343 2344 // nth-* requires argument 2345 if ( !match[ 3 ] ) { 2346 find.error( match[ 0 ] ); 2347 } 2348 2349 // numeric x and y parameters for Expr.filter.CHILD 2350 // remember that false/true cast respectively to 0/1 2351 match[ 4 ] = +( match[ 4 ] ? 2352 match[ 5 ] + ( match[ 6 ] || 1 ) : 2353 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) 2354 ); 2355 match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); 2356 2357 // other types prohibit arguments 2358 } else if ( match[ 3 ] ) { 2359 find.error( match[ 0 ] ); 2360 } 2361 2362 return match; 2363 }, 2364 2365 PSEUDO: function( match ) { 2366 var excess, 2367 unquoted = !match[ 6 ] && match[ 2 ]; 2368 2369 if ( matchExpr.CHILD.test( match[ 0 ] ) ) { 2370 return null; 2371 } 2372 2373 // Accept quoted arguments as-is 2374 if ( match[ 3 ] ) { 2375 match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; 2376 2377 // Strip excess characters from unquoted arguments 2378 } else if ( unquoted && rpseudo.test( unquoted ) && 2379 2380 // Get excess from tokenize (recursively) 2381 ( excess = tokenize( unquoted, true ) ) && 2382 2383 // advance to the next closing parenthesis 2384 ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { 2385 2386 // excess is a negative index 2387 match[ 0 ] = match[ 0 ].slice( 0, excess ); 2388 match[ 2 ] = unquoted.slice( 0, excess ); 2389 } 2390 2391 // Return only captures needed by the pseudo filter method (type and argument) 2392 return match.slice( 0, 3 ); 2393 } 2394 }, 2395 2396 filter: { 2397 2398 TAG: function( nodeNameSelector ) { 2399 var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); 2400 return nodeNameSelector === "*" ? 2401 function() { 2402 return true; 2403 } : 2404 function( elem ) { 2405 return nodeName( elem, expectedNodeName ); 2406 }; 2407 }, 2408 2409 CLASS: function( className ) { 2410 var pattern = classCache[ className + " " ]; 2411 2412 return pattern || 2413 ( pattern = new RegExp( "(^|" + whitespace + ")" + className + 2414 "(" + whitespace + "|$)" ) ) && 2415 classCache( className, function( elem ) { 2416 return pattern.test( 2417 typeof elem.className === "string" && elem.className || 2418 typeof elem.getAttribute !== "undefined" && 2419 elem.getAttribute( "class" ) || 2420 "" 2421 ); 2422 } ); 2423 }, 2424 2425 ATTR: function( name, operator, check ) { 2426 return function( elem ) { 2427 var result = find.attr( elem, name ); 2428 2429 if ( result == null ) { 2430 return operator === "!="; 2431 } 2432 if ( !operator ) { 2433 return true; 2434 } 2435 2436 result += ""; 2437 2438 if ( operator === "=" ) { 2439 return result === check; 2440 } 2441 if ( operator === "!=" ) { 2442 return result !== check; 2443 } 2444 if ( operator === "^=" ) { 2445 return check && result.indexOf( check ) === 0; 2446 } 2447 if ( operator === "*=" ) { 2448 return check && result.indexOf( check ) > -1; 2449 } 2450 if ( operator === "$=" ) { 2451 return check && result.slice( -check.length ) === check; 2452 } 2453 if ( operator === "~=" ) { 2454 return ( " " + result.replace( rwhitespace, " " ) + " " ) 2455 .indexOf( check ) > -1; 2456 } 2457 if ( operator === "|=" ) { 2458 return result === check || result.slice( 0, check.length + 1 ) === check + "-"; 2459 } 2460 2461 return false; 2462 }; 2463 }, 2464 2465 CHILD: function( type, what, _argument, first, last ) { 2466 var simple = type.slice( 0, 3 ) !== "nth", 2467 forward = type.slice( -4 ) !== "last", 2468 ofType = what === "of-type"; 2469 2470 return first === 1 && last === 0 ? 2471 2472 // Shortcut for :nth-*(n) 2473 function( elem ) { 2474 return !!elem.parentNode; 2475 } : 2476 2477 function( elem, _context, xml ) { 2478 var cache, outerCache, node, nodeIndex, start, 2479 dir = simple !== forward ? "nextSibling" : "previousSibling", 2480 parent = elem.parentNode, 2481 name = ofType && elem.nodeName.toLowerCase(), 2482 useCache = !xml && !ofType, 2483 diff = false; 2484 2485 if ( parent ) { 2486 2487 // :(first|last|only)-(child|of-type) 2488 if ( simple ) { 2489 while ( dir ) { 2490 node = elem; 2491 while ( ( node = node[ dir ] ) ) { 2492 if ( ofType ? 2493 nodeName( node, name ) : 2494 node.nodeType === 1 ) { 2495 2496 return false; 2497 } 2498 } 2499 2500 // Reverse direction for :only-* (if we haven't yet done so) 2501 start = dir = type === "only" && !start && "nextSibling"; 2502 } 2503 return true; 2504 } 2505 2506 start = [ forward ? parent.firstChild : parent.lastChild ]; 2507 2508 // non-xml :nth-child(...) stores cache data on `parent` 2509 if ( forward && useCache ) { 2510 2511 // Seek `elem` from a previously-cached index 2512 outerCache = parent[ expando ] || ( parent[ expando ] = {} ); 2513 cache = outerCache[ type ] || []; 2514 nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; 2515 diff = nodeIndex && cache[ 2 ]; 2516 node = nodeIndex && parent.childNodes[ nodeIndex ]; 2517 2518 while ( ( node = ++nodeIndex && node && node[ dir ] || 2519 2520 // Fallback to seeking `elem` from the start 2521 ( diff = nodeIndex = 0 ) || start.pop() ) ) { 2522 2523 // When found, cache indexes on `parent` and break 2524 if ( node.nodeType === 1 && ++diff && node === elem ) { 2525 outerCache[ type ] = [ dirruns, nodeIndex, diff ]; 2526 break; 2527 } 2528 } 2529 2530 } else { 2531 2532 // Use previously-cached element index if available 2533 if ( useCache ) { 2534 outerCache = elem[ expando ] || ( elem[ expando ] = {} ); 2535 cache = outerCache[ type ] || []; 2536 nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; 2537 diff = nodeIndex; 2538 } 2539 2540 // xml :nth-child(...) 2541 // or :nth-last-child(...) or :nth(-last)?-of-type(...) 2542 if ( diff === false ) { 2543 2544 // Use the same loop as above to seek `elem` from the start 2545 while ( ( node = ++nodeIndex && node && node[ dir ] || 2546 ( diff = nodeIndex = 0 ) || start.pop() ) ) { 2547 2548 if ( ( ofType ? 2549 nodeName( node, name ) : 2550 node.nodeType === 1 ) && 2551 ++diff ) { 2552 2553 // Cache the index of each encountered element 2554 if ( useCache ) { 2555 outerCache = node[ expando ] || 2556 ( node[ expando ] = {} ); 2557 outerCache[ type ] = [ dirruns, diff ]; 2558 } 2559 2560 if ( node === elem ) { 2561 break; 2562 } 2563 } 2564 } 2565 } 2566 } 2567 2568 // Incorporate the offset, then check against cycle size 2569 diff -= last; 2570 return diff === first || ( diff % first === 0 && diff / first >= 0 ); 2571 } 2572 }; 2573 }, 2574 2575 PSEUDO: function( pseudo, argument ) { 2576 2577 // pseudo-class names are case-insensitive 2578 // https://www.w3.org/TR/selectors/#pseudo-classes 2579 // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters 2580 // Remember that setFilters inherits from pseudos 2581 var args, 2582 fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || 2583 find.error( "unsupported pseudo: " + pseudo ); 2584 2585 // The user may use createPseudo to indicate that 2586 // arguments are needed to create the filter function 2587 // just as jQuery does 2588 if ( fn[ expando ] ) { 2589 return fn( argument ); 2590 } 2591 2592 // But maintain support for old signatures 2593 if ( fn.length > 1 ) { 2594 args = [ pseudo, pseudo, "", argument ]; 2595 return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? 2596 markFunction( function( seed, matches ) { 2597 var idx, 2598 matched = fn( seed, argument ), 2599 i = matched.length; 2600 while ( i-- ) { 2601 idx = indexOf.call( seed, matched[ i ] ); 2602 seed[ idx ] = !( matches[ idx ] = matched[ i ] ); 2603 } 2604 } ) : 2605 function( elem ) { 2606 return fn( elem, 0, args ); 2607 }; 2608 } 2609 2610 return fn; 2611 } 2612 }, 2613 2614 pseudos: { 2615 2616 // Potentially complex pseudos 2617 not: markFunction( function( selector ) { 2618 2619 // Trim the selector passed to compile 2620 // to avoid treating leading and trailing 2621 // spaces as combinators 2622 var input = [], 2623 results = [], 2624 matcher = compile( selector.replace( rtrimCSS, "$1" ) ); 2625 2626 return matcher[ expando ] ? 2627 markFunction( function( seed, matches, _context, xml ) { 2628 var elem, 2629 unmatched = matcher( seed, null, xml, [] ), 2630 i = seed.length; 2631 2632 // Match elements unmatched by `matcher` 2633 while ( i-- ) { 2634 if ( ( elem = unmatched[ i ] ) ) { 2635 seed[ i ] = !( matches[ i ] = elem ); 2636 } 2637 } 2638 } ) : 2639 function( elem, _context, xml ) { 2640 input[ 0 ] = elem; 2641 matcher( input, null, xml, results ); 2642 2643 // Don't keep the element 2644 // (see https://github.com/jquery/sizzle/issues/299) 2645 input[ 0 ] = null; 2646 return !results.pop(); 2647 }; 2648 } ), 2649 2650 has: markFunction( function( selector ) { 2651 return function( elem ) { 2652 return find( selector, elem ).length > 0; 2653 }; 2654 } ), 2655 2656 contains: markFunction( function( text ) { 2657 text = text.replace( runescape, funescape ); 2658 return function( elem ) { 2659 return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; 2660 }; 2661 } ), 2662 2663 // "Whether an element is represented by a :lang() selector 2664 // is based solely on the element's language value 2665 // being equal to the identifier C, 2666 // or beginning with the identifier C immediately followed by "-". 2667 // The matching of C against the element's language value is performed case-insensitively. 2668 // The identifier C does not have to be a valid language name." 2669 // https://www.w3.org/TR/selectors/#lang-pseudo 2670 lang: markFunction( function( lang ) { 2671 2672 // lang value must be a valid identifier 2673 if ( !ridentifier.test( lang || "" ) ) { 2674 find.error( "unsupported lang: " + lang ); 2675 } 2676 lang = lang.replace( runescape, funescape ).toLowerCase(); 2677 return function( elem ) { 2678 var elemLang; 2679 do { 2680 if ( ( elemLang = documentIsHTML ? 2681 elem.lang : 2682 elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { 2683 2684 elemLang = elemLang.toLowerCase(); 2685 return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; 2686 } 2687 } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); 2688 return false; 2689 }; 2690 } ), 2691 2692 // Miscellaneous 2693 target: function( elem ) { 2694 var hash = window.location && window.location.hash; 2695 return hash && hash.slice( 1 ) === elem.id; 2696 }, 2697 2698 root: function( elem ) { 2699 return elem === documentElement; 2700 }, 2701 2702 focus: function( elem ) { 2703 return elem === safeActiveElement() && 2704 document.hasFocus() && 2705 !!( elem.type || elem.href || ~elem.tabIndex ); 2706 }, 2707 2708 // Boolean properties 2709 enabled: createDisabledPseudo( false ), 2710 disabled: createDisabledPseudo( true ), 2711 2712 checked: function( elem ) { 2713 2714 // In CSS3, :checked should return both checked and selected elements 2715 // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked 2716 return ( nodeName( elem, "input" ) && !!elem.checked ) || 2717 ( nodeName( elem, "option" ) && !!elem.selected ); 2718 }, 2719 2720 selected: function( elem ) { 2721 2722 // Support: IE <=11+ 2723 // Accessing the selectedIndex property 2724 // forces the browser to treat the default option as 2725 // selected when in an optgroup. 2726 if ( elem.parentNode ) { 2727 // eslint-disable-next-line no-unused-expressions 2728 elem.parentNode.selectedIndex; 2729 } 2730 2731 return elem.selected === true; 2732 }, 2733 2734 // Contents 2735 empty: function( elem ) { 2736 2737 // https://www.w3.org/TR/selectors/#empty-pseudo 2738 // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), 2739 // but not by others (comment: 8; processing instruction: 7; etc.) 2740 // nodeType < 6 works because attributes (2) do not appear as children 2741 for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { 2742 if ( elem.nodeType < 6 ) { 2743 return false; 2744 } 2745 } 2746 return true; 2747 }, 2748 2749 parent: function( elem ) { 2750 return !Expr.pseudos.empty( elem ); 2751 }, 2752 2753 // Element/input types 2754 header: function( elem ) { 2755 return rheader.test( elem.nodeName ); 2756 }, 2757 2758 input: function( elem ) { 2759 return rinputs.test( elem.nodeName ); 2760 }, 2761 2762 button: function( elem ) { 2763 return nodeName( elem, "input" ) && elem.type === "button" || 2764 nodeName( elem, "button" ); 2765 }, 2766 2767 text: function( elem ) { 2768 var attr; 2769 return nodeName( elem, "input" ) && elem.type === "text" && 2770 2771 // Support: IE <10 only 2772 // New HTML5 attribute values (e.g., "search") appear 2773 // with elem.type === "text" 2774 ( ( attr = elem.getAttribute( "type" ) ) == null || 2775 attr.toLowerCase() === "text" ); 2776 }, 2777 2778 // Position-in-collection 2779 first: createPositionalPseudo( function() { 2780 return [ 0 ]; 2781 } ), 2782 2783 last: createPositionalPseudo( function( _matchIndexes, length ) { 2784 return [ length - 1 ]; 2785 } ), 2786 2787 eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { 2788 return [ argument < 0 ? argument + length : argument ]; 2789 } ), 2790 2791 even: createPositionalPseudo( function( matchIndexes, length ) { 2792 var i = 0; 2793 for ( ; i < length; i += 2 ) { 2794 matchIndexes.push( i ); 2795 } 2796 return matchIndexes; 2797 } ), 2798 2799 odd: createPositionalPseudo( function( matchIndexes, length ) { 2800 var i = 1; 2801 for ( ; i < length; i += 2 ) { 2802 matchIndexes.push( i ); 2803 } 2804 return matchIndexes; 2805 } ), 2806 2807 lt: createPositionalPseudo( function( matchIndexes, length, argument ) { 2808 var i; 2809 2810 if ( argument < 0 ) { 2811 i = argument + length; 2812 } else if ( argument > length ) { 2813 i = length; 2814 } else { 2815 i = argument; 2816 } 2817 2818 for ( ; --i >= 0; ) { 2819 matchIndexes.push( i ); 2820 } 2821 return matchIndexes; 2822 } ), 2823 2824 gt: createPositionalPseudo( function( matchIndexes, length, argument ) { 2825 var i = argument < 0 ? argument + length : argument; 2826 for ( ; ++i < length; ) { 2827 matchIndexes.push( i ); 2828 } 2829 return matchIndexes; 2830 } ) 2831 } 2832}; 2833 2834Expr.pseudos.nth = Expr.pseudos.eq; 2835 2836// Add button/input type pseudos 2837for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { 2838 Expr.pseudos[ i ] = createInputPseudo( i ); 2839} 2840for ( i in { submit: true, reset: true } ) { 2841 Expr.pseudos[ i ] = createButtonPseudo( i ); 2842} 2843 2844// Easy API for creating new setFilters 2845function setFilters() {} 2846setFilters.prototype = Expr.filters = Expr.pseudos; 2847Expr.setFilters = new setFilters(); 2848 2849function tokenize( selector, parseOnly ) { 2850 var matched, match, tokens, type, 2851 soFar, groups, preFilters, 2852 cached = tokenCache[ selector + " " ]; 2853 2854 if ( cached ) { 2855 return parseOnly ? 0 : cached.slice( 0 ); 2856 } 2857 2858 soFar = selector; 2859 groups = []; 2860 preFilters = Expr.preFilter; 2861 2862 while ( soFar ) { 2863 2864 // Comma and first run 2865 if ( !matched || ( match = rcomma.exec( soFar ) ) ) { 2866 if ( match ) { 2867 2868 // Don't consume trailing commas as valid 2869 soFar = soFar.slice( match[ 0 ].length ) || soFar; 2870 } 2871 groups.push( ( tokens = [] ) ); 2872 } 2873 2874 matched = false; 2875 2876 // Combinators 2877 if ( ( match = rleadingCombinator.exec( soFar ) ) ) { 2878 matched = match.shift(); 2879 tokens.push( { 2880 value: matched, 2881 2882 // Cast descendant combinators to space 2883 type: match[ 0 ].replace( rtrimCSS, " " ) 2884 } ); 2885 soFar = soFar.slice( matched.length ); 2886 } 2887 2888 // Filters 2889 for ( type in Expr.filter ) { 2890 if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || 2891 ( match = preFilters[ type ]( match ) ) ) ) { 2892 matched = match.shift(); 2893 tokens.push( { 2894 value: matched, 2895 type: type, 2896 matches: match 2897 } ); 2898 soFar = soFar.slice( matched.length ); 2899 } 2900 } 2901 2902 if ( !matched ) { 2903 break; 2904 } 2905 } 2906 2907 // Return the length of the invalid excess 2908 // if we're just parsing 2909 // Otherwise, throw an error or return tokens 2910 if ( parseOnly ) { 2911 return soFar.length; 2912 } 2913 2914 return soFar ? 2915 find.error( selector ) : 2916 2917 // Cache the tokens 2918 tokenCache( selector, groups ).slice( 0 ); 2919} 2920 2921function toSelector( tokens ) { 2922 var i = 0, 2923 len = tokens.length, 2924 selector = ""; 2925 for ( ; i < len; i++ ) { 2926 selector += tokens[ i ].value; 2927 } 2928 return selector; 2929} 2930 2931function addCombinator( matcher, combinator, base ) { 2932 var dir = combinator.dir, 2933 skip = combinator.next, 2934 key = skip || dir, 2935 checkNonElements = base && key === "parentNode", 2936 doneName = done++; 2937 2938 return combinator.first ? 2939 2940 // Check against closest ancestor/preceding element 2941 function( elem, context, xml ) { 2942 while ( ( elem = elem[ dir ] ) ) { 2943 if ( elem.nodeType === 1 || checkNonElements ) { 2944 return matcher( elem, context, xml ); 2945 } 2946 } 2947 return false; 2948 } : 2949 2950 // Check against all ancestor/preceding elements 2951 function( elem, context, xml ) { 2952 var oldCache, outerCache, 2953 newCache = [ dirruns, doneName ]; 2954 2955 // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching 2956 if ( xml ) { 2957 while ( ( elem = elem[ dir ] ) ) { 2958 if ( elem.nodeType === 1 || checkNonElements ) { 2959 if ( matcher( elem, context, xml ) ) { 2960 return true; 2961 } 2962 } 2963 } 2964 } else { 2965 while ( ( elem = elem[ dir ] ) ) { 2966 if ( elem.nodeType === 1 || checkNonElements ) { 2967 outerCache = elem[ expando ] || ( elem[ expando ] = {} ); 2968 2969 if ( skip && nodeName( elem, skip ) ) { 2970 elem = elem[ dir ] || elem; 2971 } else if ( ( oldCache = outerCache[ key ] ) && 2972 oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { 2973 2974 // Assign to newCache so results back-propagate to previous elements 2975 return ( newCache[ 2 ] = oldCache[ 2 ] ); 2976 } else { 2977 2978 // Reuse newcache so results back-propagate to previous elements 2979 outerCache[ key ] = newCache; 2980 2981 // A match means we're done; a fail means we have to keep checking 2982 if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { 2983 return true; 2984 } 2985 } 2986 } 2987 } 2988 } 2989 return false; 2990 }; 2991} 2992 2993function elementMatcher( matchers ) { 2994 return matchers.length > 1 ? 2995 function( elem, context, xml ) { 2996 var i = matchers.length; 2997 while ( i-- ) { 2998 if ( !matchers[ i ]( elem, context, xml ) ) { 2999 return false; 3000 } 3001 } 3002 return true; 3003 } : 3004 matchers[ 0 ]; 3005} 3006 3007function multipleContexts( selector, contexts, results ) { 3008 var i = 0, 3009 len = contexts.length; 3010 for ( ; i < len; i++ ) { 3011 find( selector, contexts[ i ], results ); 3012 } 3013 return results; 3014} 3015 3016function condense( unmatched, map, filter, context, xml ) { 3017 var elem, 3018 newUnmatched = [], 3019 i = 0, 3020 len = unmatched.length, 3021 mapped = map != null; 3022 3023 for ( ; i < len; i++ ) { 3024 if ( ( elem = unmatched[ i ] ) ) { 3025 if ( !filter || filter( elem, context, xml ) ) { 3026 newUnmatched.push( elem ); 3027 if ( mapped ) { 3028 map.push( i ); 3029 } 3030 } 3031 } 3032 } 3033 3034 return newUnmatched; 3035} 3036 3037function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { 3038 if ( postFilter && !postFilter[ expando ] ) { 3039 postFilter = setMatcher( postFilter ); 3040 } 3041 if ( postFinder && !postFinder[ expando ] ) { 3042 postFinder = setMatcher( postFinder, postSelector ); 3043 } 3044 return markFunction( function( seed, results, context, xml ) { 3045 var temp, i, elem, matcherOut, 3046 preMap = [], 3047 postMap = [], 3048 preexisting = results.length, 3049 3050 // Get initial elements from seed or context 3051 elems = seed || 3052 multipleContexts( selector || "*", 3053 context.nodeType ? [ context ] : context, [] ), 3054 3055 // Prefilter to get matcher input, preserving a map for seed-results synchronization 3056 matcherIn = preFilter && ( seed || !selector ) ? 3057 condense( elems, preMap, preFilter, context, xml ) : 3058 elems; 3059 3060 if ( matcher ) { 3061 3062 // If we have a postFinder, or filtered seed, or non-seed postFilter 3063 // or preexisting results, 3064 matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? 3065 3066 // ...intermediate processing is necessary 3067 [] : 3068 3069 // ...otherwise use results directly 3070 results; 3071 3072 // Find primary matches 3073 matcher( matcherIn, matcherOut, context, xml ); 3074 } else { 3075 matcherOut = matcherIn; 3076 } 3077 3078 // Apply postFilter 3079 if ( postFilter ) { 3080 temp = condense( matcherOut, postMap ); 3081 postFilter( temp, [], context, xml ); 3082 3083 // Un-match failing elements by moving them back to matcherIn 3084 i = temp.length; 3085 while ( i-- ) { 3086 if ( ( elem = temp[ i ] ) ) { 3087 matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); 3088 } 3089 } 3090 } 3091 3092 if ( seed ) { 3093 if ( postFinder || preFilter ) { 3094 if ( postFinder ) { 3095 3096 // Get the final matcherOut by condensing this intermediate into postFinder contexts 3097 temp = []; 3098 i = matcherOut.length; 3099 while ( i-- ) { 3100 if ( ( elem = matcherOut[ i ] ) ) { 3101 3102 // Restore matcherIn since elem is not yet a final match 3103 temp.push( ( matcherIn[ i ] = elem ) ); 3104 } 3105 } 3106 postFinder( null, ( matcherOut = [] ), temp, xml ); 3107 } 3108 3109 // Move matched elements from seed to results to keep them synchronized 3110 i = matcherOut.length; 3111 while ( i-- ) { 3112 if ( ( elem = matcherOut[ i ] ) && 3113 ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { 3114 3115 seed[ temp ] = !( results[ temp ] = elem ); 3116 } 3117 } 3118 } 3119 3120 // Add elements to results, through postFinder if defined 3121 } else { 3122 matcherOut = condense( 3123 matcherOut === results ? 3124 matcherOut.splice( preexisting, matcherOut.length ) : 3125 matcherOut 3126 ); 3127 if ( postFinder ) { 3128 postFinder( null, results, matcherOut, xml ); 3129 } else { 3130 push.apply( results, matcherOut ); 3131 } 3132 } 3133 } ); 3134} 3135 3136function matcherFromTokens( tokens ) { 3137 var checkContext, matcher, j, 3138 len = tokens.length, 3139 leadingRelative = Expr.relative[ tokens[ 0 ].type ], 3140 implicitRelative = leadingRelative || Expr.relative[ " " ], 3141 i = leadingRelative ? 1 : 0, 3142 3143 // The foundational matcher ensures that elements are reachable from top-level context(s) 3144 matchContext = addCombinator( function( elem ) { 3145 return elem === checkContext; 3146 }, implicitRelative, true ), 3147 matchAnyContext = addCombinator( function( elem ) { 3148 return indexOf.call( checkContext, elem ) > -1; 3149 }, implicitRelative, true ), 3150 matchers = [ function( elem, context, xml ) { 3151 3152 // Support: IE 11+, Edge 17 - 18+ 3153 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 3154 // two documents; shallow comparisons work. 3155 // eslint-disable-next-line eqeqeq 3156 var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( 3157 ( checkContext = context ).nodeType ? 3158 matchContext( elem, context, xml ) : 3159 matchAnyContext( elem, context, xml ) ); 3160 3161 // Avoid hanging onto element 3162 // (see https://github.com/jquery/sizzle/issues/299) 3163 checkContext = null; 3164 return ret; 3165 } ]; 3166 3167 for ( ; i < len; i++ ) { 3168 if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { 3169 matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; 3170 } else { 3171 matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); 3172 3173 // Return special upon seeing a positional matcher 3174 if ( matcher[ expando ] ) { 3175 3176 // Find the next relative operator (if any) for proper handling 3177 j = ++i; 3178 for ( ; j < len; j++ ) { 3179 if ( Expr.relative[ tokens[ j ].type ] ) { 3180 break; 3181 } 3182 } 3183 return setMatcher( 3184 i > 1 && elementMatcher( matchers ), 3185 i > 1 && toSelector( 3186 3187 // If the preceding token was a descendant combinator, insert an implicit any-element `*` 3188 tokens.slice( 0, i - 1 ) 3189 .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) 3190 ).replace( rtrimCSS, "$1" ), 3191 matcher, 3192 i < j && matcherFromTokens( tokens.slice( i, j ) ), 3193 j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), 3194 j < len && toSelector( tokens ) 3195 ); 3196 } 3197 matchers.push( matcher ); 3198 } 3199 } 3200 3201 return elementMatcher( matchers ); 3202} 3203 3204function matcherFromGroupMatchers( elementMatchers, setMatchers ) { 3205 var bySet = setMatchers.length > 0, 3206 byElement = elementMatchers.length > 0, 3207 superMatcher = function( seed, context, xml, results, outermost ) { 3208 var elem, j, matcher, 3209 matchedCount = 0, 3210 i = "0", 3211 unmatched = seed && [], 3212 setMatched = [], 3213 contextBackup = outermostContext, 3214 3215 // We must always have either seed elements or outermost context 3216 elems = seed || byElement && Expr.find.TAG( "*", outermost ), 3217 3218 // Use integer dirruns iff this is the outermost matcher 3219 dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), 3220 len = elems.length; 3221 3222 if ( outermost ) { 3223 3224 // Support: IE 11+, Edge 17 - 18+ 3225 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 3226 // two documents; shallow comparisons work. 3227 // eslint-disable-next-line eqeqeq 3228 outermostContext = context == document || context || outermost; 3229 } 3230 3231 // Add elements passing elementMatchers directly to results 3232 // Support: iOS <=7 - 9 only 3233 // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching 3234 // elements by id. (see trac-14142) 3235 for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { 3236 if ( byElement && elem ) { 3237 j = 0; 3238 3239 // Support: IE 11+, Edge 17 - 18+ 3240 // IE/Edge sometimes throw a "Permission denied" error when strict-comparing 3241 // two documents; shallow comparisons work. 3242 // eslint-disable-next-line eqeqeq 3243 if ( !context && elem.ownerDocument != document ) { 3244 setDocument( elem ); 3245 xml = !documentIsHTML; 3246 } 3247 while ( ( matcher = elementMatchers[ j++ ] ) ) { 3248 if ( matcher( elem, context || document, xml ) ) { 3249 push.call( results, elem ); 3250 break; 3251 } 3252 } 3253 if ( outermost ) { 3254 dirruns = dirrunsUnique; 3255 } 3256 } 3257 3258 // Track unmatched elements for set filters 3259 if ( bySet ) { 3260 3261 // They will have gone through all possible matchers 3262 if ( ( elem = !matcher && elem ) ) { 3263 matchedCount--; 3264 } 3265 3266 // Lengthen the array for every element, matched or not 3267 if ( seed ) { 3268 unmatched.push( elem ); 3269 } 3270 } 3271 } 3272 3273 // `i` is now the count of elements visited above, and adding it to `matchedCount` 3274 // makes the latter nonnegative. 3275 matchedCount += i; 3276 3277 // Apply set filters to unmatched elements 3278 // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` 3279 // equals `i`), unless we didn't visit _any_ elements in the above loop because we have 3280 // no element matchers and no seed. 3281 // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that 3282 // case, which will result in a "00" `matchedCount` that differs from `i` but is also 3283 // numerically zero. 3284 if ( bySet && i !== matchedCount ) { 3285 j = 0; 3286 while ( ( matcher = setMatchers[ j++ ] ) ) { 3287 matcher( unmatched, setMatched, context, xml ); 3288 } 3289 3290 if ( seed ) { 3291 3292 // Reintegrate element matches to eliminate the need for sorting 3293 if ( matchedCount > 0 ) { 3294 while ( i-- ) { 3295 if ( !( unmatched[ i ] || setMatched[ i ] ) ) { 3296 setMatched[ i ] = pop.call( results ); 3297 } 3298 } 3299 } 3300 3301 // Discard index placeholder values to get only actual matches 3302 setMatched = condense( setMatched ); 3303 } 3304 3305 // Add matches to results 3306 push.apply( results, setMatched ); 3307 3308 // Seedless set matches succeeding multiple successful matchers stipulate sorting 3309 if ( outermost && !seed && setMatched.length > 0 && 3310 ( matchedCount + setMatchers.length ) > 1 ) { 3311 3312 jQuery.uniqueSort( results ); 3313 } 3314 } 3315 3316 // Override manipulation of globals by nested matchers 3317 if ( outermost ) { 3318 dirruns = dirrunsUnique; 3319 outermostContext = contextBackup; 3320 } 3321 3322 return unmatched; 3323 }; 3324 3325 return bySet ? 3326 markFunction( superMatcher ) : 3327 superMatcher; 3328} 3329 3330function compile( selector, match /* Internal Use Only */ ) { 3331 var i, 3332 setMatchers = [], 3333 elementMatchers = [], 3334 cached = compilerCache[ selector + " " ]; 3335 3336 if ( !cached ) { 3337 3338 // Generate a function of recursive functions that can be used to check each element 3339 if ( !match ) { 3340 match = tokenize( selector ); 3341 } 3342 i = match.length; 3343 while ( i-- ) { 3344 cached = matcherFromTokens( match[ i ] ); 3345 if ( cached[ expando ] ) { 3346 setMatchers.push( cached ); 3347 } else { 3348 elementMatchers.push( cached ); 3349 } 3350 } 3351 3352 // Cache the compiled function 3353 cached = compilerCache( selector, 3354 matcherFromGroupMatchers( elementMatchers, setMatchers ) ); 3355 3356 // Save selector and tokenization 3357 cached.selector = selector; 3358 } 3359 return cached; 3360} 3361 3362/** 3363 * A low-level selection function that works with jQuery's compiled 3364 * selector functions 3365 * @param {String|Function} selector A selector or a pre-compiled 3366 * selector function built with jQuery selector compile 3367 * @param {Element} context 3368 * @param {Array} [results] 3369 * @param {Array} [seed] A set of elements to match against 3370 */ 3371function select( selector, context, results, seed ) { 3372 var i, tokens, token, type, find, 3373 compiled = typeof selector === "function" && selector, 3374 match = !seed && tokenize( ( selector = compiled.selector || selector ) ); 3375 3376 results = results || []; 3377 3378 // Try to minimize operations if there is only one selector in the list and no seed 3379 // (the latter of which guarantees us context) 3380 if ( match.length === 1 ) { 3381 3382 // Reduce context if the leading compound selector is an ID 3383 tokens = match[ 0 ] = match[ 0 ].slice( 0 ); 3384 if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && 3385 context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { 3386 3387 context = ( Expr.find.ID( 3388 token.matches[ 0 ].replace( runescape, funescape ), 3389 context 3390 ) || [] )[ 0 ]; 3391 if ( !context ) { 3392 return results; 3393 3394 // Precompiled matchers will still verify ancestry, so step up a level 3395 } else if ( compiled ) { 3396 context = context.parentNode; 3397 } 3398 3399 selector = selector.slice( tokens.shift().value.length ); 3400 } 3401 3402 // Fetch a seed set for right-to-left matching 3403 i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; 3404 while ( i-- ) { 3405 token = tokens[ i ]; 3406 3407 // Abort if we hit a combinator 3408 if ( Expr.relative[ ( type = token.type ) ] ) { 3409 break; 3410 } 3411 if ( ( find = Expr.find[ type ] ) ) { 3412 3413 // Search, expanding context for leading sibling combinators 3414 if ( ( seed = find( 3415 token.matches[ 0 ].replace( runescape, funescape ), 3416 rsibling.test( tokens[ 0 ].type ) && 3417 testContext( context.parentNode ) || context 3418 ) ) ) { 3419 3420 // If seed is empty or no tokens remain, we can return early 3421 tokens.splice( i, 1 ); 3422 selector = seed.length && toSelector( tokens ); 3423 if ( !selector ) { 3424 push.apply( results, seed ); 3425 return results; 3426 } 3427 3428 break; 3429 } 3430 } 3431 } 3432 } 3433 3434 // Compile and execute a filtering function if one is not provided 3435 // Provide `match` to avoid retokenization if we modified the selector above 3436 ( compiled || compile( selector, match ) )( 3437 seed, 3438 context, 3439 !documentIsHTML, 3440 results, 3441 !context || rsibling.test( selector ) && testContext( context.parentNode ) || context 3442 ); 3443 return results; 3444} 3445 3446// One-time assignments 3447 3448// Support: Android <=4.0 - 4.1+ 3449// Sort stability 3450support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; 3451 3452// Initialize against the default document 3453setDocument(); 3454 3455// Support: Android <=4.0 - 4.1+ 3456// Detached nodes confoundingly follow *each other* 3457support.sortDetached = assert( function( el ) { 3458 3459 // Should return 1, but returns 4 (following) 3460 return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; 3461} ); 3462 3463jQuery.find = find; 3464 3465// Deprecated 3466jQuery.expr[ ":" ] = jQuery.expr.pseudos; 3467jQuery.unique = jQuery.uniqueSort; 3468 3469// These have always been private, but they used to be documented as part of 3470// Sizzle so let's maintain them for now for backwards compatibility purposes. 3471find.compile = compile; 3472find.select = select; 3473find.setDocument = setDocument; 3474find.tokenize = tokenize; 3475 3476find.escape = jQuery.escapeSelector; 3477find.getText = jQuery.text; 3478find.isXML = jQuery.isXMLDoc; 3479find.selectors = jQuery.expr; 3480find.support = jQuery.support; 3481find.uniqueSort = jQuery.uniqueSort; 3482 3483 /* eslint-enable */ 3484 3485} )(); 3486 3487 3488var dir = function( elem, dir, until ) { 3489 var matched = [], 3490 truncate = until !== undefined; 3491 3492 while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { 3493 if ( elem.nodeType === 1 ) { 3494 if ( truncate && jQuery( elem ).is( until ) ) { 3495 break; 3496 } 3497 matched.push( elem ); 3498 } 3499 } 3500 return matched; 3501}; 3502 3503 3504var siblings = function( n, elem ) { 3505 var matched = []; 3506 3507 for ( ; n; n = n.nextSibling ) { 3508 if ( n.nodeType === 1 && n !== elem ) { 3509 matched.push( n ); 3510 } 3511 } 3512 3513 return matched; 3514}; 3515 3516 3517var rneedsContext = jQuery.expr.match.needsContext; 3518 3519var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); 3520 3521 3522 3523// Implement the identical functionality for filter and not 3524function winnow( elements, qualifier, not ) { 3525 if ( isFunction( qualifier ) ) { 3526 return jQuery.grep( elements, function( elem, i ) { 3527 return !!qualifier.call( elem, i, elem ) !== not; 3528 } ); 3529 } 3530 3531 // Single element 3532 if ( qualifier.nodeType ) { 3533 return jQuery.grep( elements, function( elem ) { 3534 return ( elem === qualifier ) !== not; 3535 } ); 3536 } 3537 3538 // Arraylike of elements (jQuery, arguments, Array) 3539 if ( typeof qualifier !== "string" ) { 3540 return jQuery.grep( elements, function( elem ) { 3541 return ( indexOf.call( qualifier, elem ) > -1 ) !== not; 3542 } ); 3543 } 3544 3545 // Filtered directly for both simple and complex selectors 3546 return jQuery.filter( qualifier, elements, not ); 3547} 3548 3549jQuery.filter = function( expr, elems, not ) { 3550 var elem = elems[ 0 ]; 3551 3552 if ( not ) { 3553 expr = ":not(" + expr + ")"; 3554 } 3555 3556 if ( elems.length === 1 && elem.nodeType === 1 ) { 3557 return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; 3558 } 3559 3560 return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { 3561 return elem.nodeType === 1; 3562 } ) ); 3563}; 3564 3565jQuery.fn.extend( { 3566 find: function( selector ) { 3567 var i, ret, 3568 len = this.length, 3569 self = this; 3570 3571 if ( typeof selector !== "string" ) { 3572 return this.pushStack( jQuery( selector ).filter( function() { 3573 for ( i = 0; i < len; i++ ) { 3574 if ( jQuery.contains( self[ i ], this ) ) { 3575 return true; 3576 } 3577 } 3578 } ) ); 3579 } 3580 3581 ret = this.pushStack( [] ); 3582 3583 for ( i = 0; i < len; i++ ) { 3584 jQuery.find( selector, self[ i ], ret ); 3585 } 3586 3587 return len > 1 ? jQuery.uniqueSort( ret ) : ret; 3588 }, 3589 filter: function( selector ) { 3590 return this.pushStack( winnow( this, selector || [], false ) ); 3591 }, 3592 not: function( selector ) { 3593 return this.pushStack( winnow( this, selector || [], true ) ); 3594 }, 3595 is: function( selector ) { 3596 return !!winnow( 3597 this, 3598 3599 // If this is a positional/relative selector, check membership in the returned set 3600 // so $("p:first").is("p:last") won't return true for a doc with two "p". 3601 typeof selector === "string" && rneedsContext.test( selector ) ? 3602 jQuery( selector ) : 3603 selector || [], 3604 false 3605 ).length; 3606 } 3607} ); 3608 3609 3610// Initialize a jQuery object 3611 3612 3613// A central reference to the root jQuery(document) 3614var rootjQuery, 3615 3616 // A simple way to check for HTML strings 3617 // Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521) 3618 // Strict HTML recognition (trac-11290: must start with <) 3619 // Shortcut simple #id case for speed 3620 rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, 3621 3622 init = jQuery.fn.init = function( selector, context, root ) { 3623 var match, elem; 3624 3625 // HANDLE: $(""), $(null), $(undefined), $(false) 3626 if ( !selector ) { 3627 return this; 3628 } 3629 3630 // Method init() accepts an alternate rootjQuery 3631 // so migrate can support jQuery.sub (gh-2101) 3632 root = root || rootjQuery; 3633 3634 // Handle HTML strings 3635 if ( typeof selector === "string" ) { 3636 if ( selector[ 0 ] === "<" && 3637 selector[ selector.length - 1 ] === ">" && 3638 selector.length >= 3 ) { 3639 3640 // Assume that strings that start and end with <> are HTML and skip the regex check 3641 match = [ null, selector, null ]; 3642 3643 } else { 3644 match = rquickExpr.exec( selector ); 3645 } 3646 3647 // Match html or make sure no context is specified for #id 3648 if ( match && ( match[ 1 ] || !context ) ) { 3649 3650 // HANDLE: $(html) -> $(array) 3651 if ( match[ 1 ] ) { 3652 context = context instanceof jQuery ? context[ 0 ] : context; 3653 3654 // Option to run scripts is true for back-compat 3655 // Intentionally let the error be thrown if parseHTML is not present 3656 jQuery.merge( this, jQuery.parseHTML( 3657 match[ 1 ], 3658 context && context.nodeType ? context.ownerDocument || context : document, 3659 true 3660 ) ); 3661 3662 // HANDLE: $(html, props) 3663 if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { 3664 for ( match in context ) { 3665 3666 // Properties of context are called as methods if possible 3667 if ( isFunction( this[ match ] ) ) { 3668 this[ match ]( context[ match ] ); 3669 3670 // ...and otherwise set as attributes 3671 } else { 3672 this.attr( match, context[ match ] ); 3673 } 3674 } 3675 } 3676 3677 return this; 3678 3679 // HANDLE: $(#id) 3680 } else { 3681 elem = document.getElementById( match[ 2 ] ); 3682 3683 if ( elem ) { 3684 3685 // Inject the element directly into the jQuery object 3686 this[ 0 ] = elem; 3687 this.length = 1; 3688 } 3689 return this; 3690 } 3691 3692 // HANDLE: $(expr, $(...)) 3693 } else if ( !context || context.jquery ) { 3694 return ( context || root ).find( selector ); 3695 3696 // HANDLE: $(expr, context) 3697 // (which is just equivalent to: $(context).find(expr) 3698 } else { 3699 return this.constructor( context ).find( selector ); 3700 } 3701 3702 // HANDLE: $(DOMElement) 3703 } else if ( selector.nodeType ) { 3704 this[ 0 ] = selector; 3705 this.length = 1; 3706 return this; 3707 3708 // HANDLE: $(function) 3709 // Shortcut for document ready 3710 } else if ( isFunction( selector ) ) { 3711 return root.ready !== undefined ? 3712 root.ready( selector ) : 3713 3714 // Execute immediately if ready is not present 3715 selector( jQuery ); 3716 } 3717 3718 return jQuery.makeArray( selector, this ); 3719 }; 3720 3721// Give the init function the jQuery prototype for later instantiation 3722init.prototype = jQuery.fn; 3723 3724// Initialize central reference 3725rootjQuery = jQuery( document ); 3726 3727 3728var rparentsprev = /^(?:parents|prev(?:Until|All))/, 3729 3730 // Methods guaranteed to produce a unique set when starting from a unique set 3731 guaranteedUnique = { 3732 children: true, 3733 contents: true, 3734 next: true, 3735 prev: true 3736 }; 3737 3738jQuery.fn.extend( { 3739 has: function( target ) { 3740 var targets = jQuery( target, this ), 3741 l = targets.length; 3742 3743 return this.filter( function() { 3744 var i = 0; 3745 for ( ; i < l; i++ ) { 3746 if ( jQuery.contains( this, targets[ i ] ) ) { 3747 return true; 3748 } 3749 } 3750 } ); 3751 }, 3752 3753 closest: function( selectors, context ) { 3754 var cur, 3755 i = 0, 3756 l = this.length, 3757 matched = [], 3758 targets = typeof selectors !== "string" && jQuery( selectors ); 3759 3760 // Positional selectors never match, since there's no _selection_ context 3761 if ( !rneedsContext.test( selectors ) ) { 3762 for ( ; i < l; i++ ) { 3763 for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { 3764 3765 // Always skip document fragments 3766 if ( cur.nodeType < 11 && ( targets ? 3767 targets.index( cur ) > -1 : 3768 3769 // Don't pass non-elements to jQuery#find 3770 cur.nodeType === 1 && 3771 jQuery.find.matchesSelector( cur, selectors ) ) ) { 3772 3773 matched.push( cur ); 3774 break; 3775 } 3776 } 3777 } 3778 } 3779 3780 return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); 3781 }, 3782 3783 // Determine the position of an element within the set 3784 index: function( elem ) { 3785 3786 // No argument, return index in parent 3787 if ( !elem ) { 3788 return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; 3789 } 3790 3791 // Index in selector 3792 if ( typeof elem === "string" ) { 3793 return indexOf.call( jQuery( elem ), this[ 0 ] ); 3794 } 3795 3796 // Locate the position of the desired element 3797 return indexOf.call( this, 3798 3799 // If it receives a jQuery object, the first element is used 3800 elem.jquery ? elem[ 0 ] : elem 3801 ); 3802 }, 3803 3804 add: function( selector, context ) { 3805 return this.pushStack( 3806 jQuery.uniqueSort( 3807 jQuery.merge( this.get(), jQuery( selector, context ) ) 3808 ) 3809 ); 3810 }, 3811 3812 addBack: function( selector ) { 3813 return this.add( selector == null ? 3814 this.prevObject : this.prevObject.filter( selector ) 3815 ); 3816 } 3817} ); 3818 3819function sibling( cur, dir ) { 3820 while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} 3821 return cur; 3822} 3823 3824jQuery.each( { 3825 parent: function( elem ) { 3826 var parent = elem.parentNode; 3827 return parent && parent.nodeType !== 11 ? parent : null; 3828 }, 3829 parents: function( elem ) { 3830 return dir( elem, "parentNode" ); 3831 }, 3832 parentsUntil: function( elem, _i, until ) { 3833 return dir( elem, "parentNode", until ); 3834 }, 3835 next: function( elem ) { 3836 return sibling( elem, "nextSibling" ); 3837 }, 3838 prev: function( elem ) { 3839 return sibling( elem, "previousSibling" ); 3840 }, 3841 nextAll: function( elem ) { 3842 return dir( elem, "nextSibling" ); 3843 }, 3844 prevAll: function( elem ) { 3845 return dir( elem, "previousSibling" ); 3846 }, 3847 nextUntil: function( elem, _i, until ) { 3848 return dir( elem, "nextSibling", until ); 3849 }, 3850 prevUntil: function( elem, _i, until ) { 3851 return dir( elem, "previousSibling", until ); 3852 }, 3853 siblings: function( elem ) { 3854 return siblings( ( elem.parentNode || {} ).firstChild, elem ); 3855 }, 3856 children: function( elem ) { 3857 return siblings( elem.firstChild ); 3858 }, 3859 contents: function( elem ) { 3860 if ( elem.contentDocument != null && 3861 3862 // Support: IE 11+ 3863 // <object> elements with no `data` attribute has an object 3864 // `contentDocument` with a `null` prototype. 3865 getProto( elem.contentDocument ) ) { 3866 3867 return elem.contentDocument; 3868 } 3869 3870 // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only 3871 // Treat the template element as a regular one in browsers that 3872 // don't support it. 3873 if ( nodeName( elem, "template" ) ) { 3874 elem = elem.content || elem; 3875 } 3876 3877 return jQuery.merge( [], elem.childNodes ); 3878 } 3879}, function( name, fn ) { 3880 jQuery.fn[ name ] = function( until, selector ) { 3881 var matched = jQuery.map( this, fn, until ); 3882 3883 if ( name.slice( -5 ) !== "Until" ) { 3884 selector = until; 3885 } 3886 3887 if ( selector && typeof selector === "string" ) { 3888 matched = jQuery.filter( selector, matched ); 3889 } 3890 3891 if ( this.length > 1 ) { 3892 3893 // Remove duplicates 3894 if ( !guaranteedUnique[ name ] ) { 3895 jQuery.uniqueSort( matched ); 3896 } 3897 3898 // Reverse order for parents* and prev-derivatives 3899 if ( rparentsprev.test( name ) ) { 3900 matched.reverse(); 3901 } 3902 } 3903 3904 return this.pushStack( matched ); 3905 }; 3906} ); 3907var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); 3908 3909 3910 3911// Convert String-formatted options into Object-formatted ones 3912function createOptions( options ) { 3913 var object = {}; 3914 jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { 3915 object[ flag ] = true; 3916 } ); 3917 return object; 3918} 3919 3920/* 3921 * Create a callback list using the following parameters: 3922 * 3923 * options: an optional list of space-separated options that will change how 3924 * the callback list behaves or a more traditional option object 3925 * 3926 * By default a callback list will act like an event callback list and can be 3927 * "fired" multiple times. 3928 * 3929 * Possible options: 3930 * 3931 * once: will ensure the callback list can only be fired once (like a Deferred) 3932 * 3933 * memory: will keep track of previous values and will call any callback added 3934 * after the list has been fired right away with the latest "memorized" 3935 * values (like a Deferred) 3936 * 3937 * unique: will ensure a callback can only be added once (no duplicate in the list) 3938 * 3939 * stopOnFalse: interrupt callings when a callback returns false 3940 * 3941 */ 3942jQuery.Callbacks = function( options ) { 3943 3944 // Convert options from String-formatted to Object-formatted if needed 3945 // (we check in cache first) 3946 options = typeof options === "string" ? 3947 createOptions( options ) : 3948 jQuery.extend( {}, options ); 3949 3950 var // Flag to know if list is currently firing 3951 firing, 3952 3953 // Last fire value for non-forgettable lists 3954 memory, 3955 3956 // Flag to know if list was already fired 3957 fired, 3958 3959 // Flag to prevent firing 3960 locked, 3961 3962 // Actual callback list 3963 list = [], 3964 3965 // Queue of execution data for repeatable lists 3966 queue = [], 3967 3968 // Index of currently firing callback (modified by add/remove as needed) 3969 firingIndex = -1, 3970 3971 // Fire callbacks 3972 fire = function() { 3973 3974 // Enforce single-firing 3975 locked = locked || options.once; 3976 3977 // Execute callbacks for all pending executions, 3978 // respecting firingIndex overrides and runtime changes 3979 fired = firing = true; 3980 for ( ; queue.length; firingIndex = -1 ) { 3981 memory = queue.shift(); 3982 while ( ++firingIndex < list.length ) { 3983 3984 // Run callback and check for early termination 3985 if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && 3986 options.stopOnFalse ) { 3987 3988 // Jump to end and forget the data so .add doesn't re-fire 3989 firingIndex = list.length; 3990 memory = false;
vendor: 4,918 bytes, lines 3991-4200
3991 } 3992 } 3993 } 3994 3995 // Forget the data if we're done with it 3996 if ( !options.memory ) { 3997 memory = false; 3998 } 3999 4000 firing = false; 4001 4002 // Clean up if we're done firing for good 4003 if ( locked ) { 4004 4005 // Keep an empty list if we have data for future add calls 4006 if ( memory ) { 4007 list = []; 4008 4009 // Otherwise, this object is spent 4010 } else { 4011 list = ""; 4012 } 4013 } 4014 }, 4015 4016 // Actual Callbacks object 4017 self = { 4018 4019 // Add a callback or a collection of callbacks to the list 4020 add: function() { 4021 if ( list ) { 4022 4023 // If we have memory from a past run, we should fire after adding 4024 if ( memory && !firing ) { 4025 firingIndex = list.length - 1; 4026 queue.push( memory ); 4027 } 4028 4029 ( function add( args ) { 4030 jQuery.each( args, function( _, arg ) { 4031 if ( isFunction( arg ) ) { 4032 if ( !options.unique || !self.has( arg ) ) { 4033 list.push( arg ); 4034 } 4035 } else if ( arg && arg.length && toType( arg ) !== "string" ) { 4036 4037 // Inspect recursively 4038 add( arg ); 4039 } 4040 } ); 4041 } )( arguments ); 4042 4043 if ( memory && !firing ) { 4044 fire(); 4045 } 4046 } 4047 return this; 4048 }, 4049 4050 // Remove a callback from the list 4051 remove: function() { 4052 jQuery.each( arguments, function( _, arg ) { 4053 var index; 4054 while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { 4055 list.splice( index, 1 ); 4056 4057 // Handle firing indexes 4058 if ( index <= firingIndex ) { 4059 firingIndex--; 4060 } 4061 } 4062 } ); 4063 return this; 4064 }, 4065 4066 // Check if a given callback is in the list. 4067 // If no argument is given, return whether or not list has callbacks attached. 4068 has: function( fn ) { 4069 return fn ? 4070 jQuery.inArray( fn, list ) > -1 : 4071 list.length > 0; 4072 }, 4073 4074 // Remove all callbacks from the list 4075 empty: function() { 4076 if ( list ) { 4077 list = []; 4078 } 4079 return this; 4080 }, 4081 4082 // Disable .fire and .add 4083 // Abort any current/pending executions 4084 // Clear all callbacks and values 4085 disable: function() { 4086 locked = queue = []; 4087 list = memory = ""; 4088 return this; 4089 }, 4090 disabled: function() { 4091 return !list; 4092 }, 4093 4094 // Disable .fire 4095 // Also disable .add unless we have memory (since it would have no effect) 4096 // Abort any pending executions 4097 lock: function() { 4098 locked = queue = []; 4099 if ( !memory && !firing ) { 4100 list = memory = ""; 4101 } 4102 return this; 4103 }, 4104 locked: function() { 4105 return !!locked; 4106 }, 4107 4108 // Call all callbacks with the given context and arguments 4109 fireWith: function( context, args ) { 4110 if ( !locked ) { 4111 args = args || []; 4112 args = [ context, args.slice ? args.slice() : args ]; 4113 queue.push( args ); 4114 if ( !firing ) { 4115 fire(); 4116 } 4117 } 4118 return this; 4119 }, 4120 4121 // Call all the callbacks with the given arguments 4122 fire: function() { 4123 self.fireWith( this, arguments ); 4124 return this; 4125 }, 4126 4127 // To know if the callbacks have already been called at least once 4128 fired: function() { 4129 return !!fired; 4130 } 4131 }; 4132 4133 return self; 4134}; 4135 4136 4137function Identity( v ) { 4138 return v; 4139} 4140function Thrower( ex ) { 4141 throw ex; 4142} 4143 4144function adoptValue( value, resolve, reject, noValue ) { 4145 var method; 4146 4147 try { 4148 4149 // Check for promise aspect first to privilege synchronous behavior 4150 if ( value && isFunction( ( method = value.promise ) ) ) { 4151 method.call( value ).done( resolve ).fail( reject ); 4152 4153 // Other thenables 4154 } else if ( value && isFunction( ( method = value.then ) ) ) { 4155 method.call( value, resolve, reject ); 4156 4157 // Other non-thenables 4158 } else { 4159 4160 // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: 4161 // * false: [ value ].slice( 0 ) => resolve( value ) 4162 // * true: [ value ].slice( 1 ) => resolve() 4163 resolve.apply( undefined, [ value ].slice( noValue ) ); 4164 } 4165 4166 // For Promises/A+, convert exceptions into rejections 4167 // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in 4168 // Deferred#then to conditionally suppress rejection. 4169 } catch ( value ) { 4170 4171 // Support: Android 4.0 only 4172 // Strict mode functions invoked without .call/.apply get global-object context 4173 reject.apply( undefined, [ value ] ); 4174 } 4175} 4176 4177jQuery.extend( { 4178 4179 Deferred: function( func ) { 4180 var tuples = [ 4181 4182 // action, add listener, callbacks, 4183 // ... .then handlers, argument index, [final state] 4184 [ "notify", "progress", jQuery.Callbacks( "memory" ), 4185 jQuery.Callbacks( "memory" ), 2 ], 4186 [ "resolve", "done", jQuery.Callbacks( "once memory" ), 4187 jQuery.Callbacks( "once memory" ), 0, "resolved" ], 4188 [ "reject", "fail", jQuery.Callbacks( "once memory" ), 4189 jQuery.Callbacks( "once memory" ), 1, "rejected" ] 4190 ], 4191 state = "pending", 4192 promise = { 4193 state: function() { 4194 return state; 4195 }, 4196 always: function() { 4197 deferred.done( arguments ).fail( arguments ); 4198 return this; 4199 }, 4200 "catch": function( fn ) {
vendor: 70,494 bytes, lines 4201-6812
4201 return promise.then( null, fn ); 4202 }, 4203 4204 // Keep pipe for back-compat 4205 pipe: function( /* fnDone, fnFail, fnProgress */ ) { 4206 var fns = arguments; 4207 4208 return jQuery.Deferred( function( newDefer ) { 4209 jQuery.each( tuples, function( _i, tuple ) { 4210 4211 // Map tuples (progress, done, fail) to arguments (done, fail, progress) 4212 var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; 4213 4214 // deferred.progress(function() { bind to newDefer or newDefer.notify }) 4215 // deferred.done(function() { bind to newDefer or newDefer.resolve }) 4216 // deferred.fail(function() { bind to newDefer or newDefer.reject }) 4217 deferred[ tuple[ 1 ] ]( function() { 4218 var returned = fn && fn.apply( this, arguments ); 4219 if ( returned && isFunction( returned.promise ) ) { 4220 returned.promise() 4221 .progress( newDefer.notify ) 4222 .done( newDefer.resolve ) 4223 .fail( newDefer.reject ); 4224 } else { 4225 newDefer[ tuple[ 0 ] + "With" ]( 4226 this, 4227 fn ? [ returned ] : arguments 4228 ); 4229 } 4230 } ); 4231 } ); 4232 fns = null; 4233 } ).promise(); 4234 }, 4235 then: function( onFulfilled, onRejected, onProgress ) { 4236 var maxDepth = 0; 4237 function resolve( depth, deferred, handler, special ) { 4238 return function() { 4239 var that = this, 4240 args = arguments, 4241 mightThrow = function() { 4242 var returned, then; 4243 4244 // Support: Promises/A+ section 2.3.3.3.3 4245 // https://promisesaplus.com/#point-59 4246 // Ignore double-resolution attempts 4247 if ( depth < maxDepth ) { 4248 return; 4249 } 4250 4251 returned = handler.apply( that, args ); 4252 4253 // Support: Promises/A+ section 2.3.1 4254 // https://promisesaplus.com/#point-48 4255 if ( returned === deferred.promise() ) { 4256 throw new TypeError( "Thenable self-resolution" ); 4257 } 4258 4259 // Support: Promises/A+ sections 2.3.3.1, 3.5 4260 // https://promisesaplus.com/#point-54 4261 // https://promisesaplus.com/#point-75 4262 // Retrieve `then` only once 4263 then = returned && 4264 4265 // Support: Promises/A+ section 2.3.4 4266 // https://promisesaplus.com/#point-64 4267 // Only check objects and functions for thenability 4268 ( typeof returned === "object" || 4269 typeof returned === "function" ) && 4270 returned.then; 4271 4272 // Handle a returned thenable 4273 if ( isFunction( then ) ) { 4274 4275 // Special processors (notify) just wait for resolution 4276 if ( special ) { 4277 then.call( 4278 returned, 4279 resolve( maxDepth, deferred, Identity, special ), 4280 resolve( maxDepth, deferred, Thrower, special ) 4281 ); 4282 4283 // Normal processors (resolve) also hook into progress 4284 } else { 4285 4286 // ...and disregard older resolution values 4287 maxDepth++; 4288 4289 then.call( 4290 returned, 4291 resolve( maxDepth, deferred, Identity, special ), 4292 resolve( maxDepth, deferred, Thrower, special ), 4293 resolve( maxDepth, deferred, Identity, 4294 deferred.notifyWith ) 4295 ); 4296 } 4297 4298 // Handle all other returned values 4299 } else { 4300 4301 // Only substitute handlers pass on context 4302 // and multiple values (non-spec behavior) 4303 if ( handler !== Identity ) { 4304 that = undefined; 4305 args = [ returned ]; 4306 } 4307 4308 // Process the value(s) 4309 // Default process is resolve 4310 ( special || deferred.resolveWith )( that, args ); 4311 } 4312 }, 4313 4314 // Only normal processors (resolve) catch and reject exceptions 4315 process = special ? 4316 mightThrow : 4317 function() { 4318 try { 4319 mightThrow(); 4320 } catch ( e ) { 4321 4322 if ( jQuery.Deferred.exceptionHook ) { 4323 jQuery.Deferred.exceptionHook( e, 4324 process.error ); 4325 } 4326 4327 // Support: Promises/A+ section 2.3.3.3.4.1 4328 // https://promisesaplus.com/#point-61 4329 // Ignore post-resolution exceptions 4330 if ( depth + 1 >= maxDepth ) { 4331 4332 // Only substitute handlers pass on context 4333 // and multiple values (non-spec behavior) 4334 if ( handler !== Thrower ) { 4335 that = undefined; 4336 args = [ e ]; 4337 } 4338 4339 deferred.rejectWith( that, args ); 4340 } 4341 } 4342 }; 4343 4344 // Support: Promises/A+ section 2.3.3.3.1 4345 // https://promisesaplus.com/#point-57 4346 // Re-resolve promises immediately to dodge false rejection from 4347 // subsequent errors 4348 if ( depth ) { 4349 process(); 4350 } else { 4351 4352 // Call an optional hook to record the error, in case of exception 4353 // since it's otherwise lost when execution goes async 4354 if ( jQuery.Deferred.getErrorHook ) { 4355 process.error = jQuery.Deferred.getErrorHook(); 4356 4357 // The deprecated alias of the above. While the name suggests 4358 // returning the stack, not an error instance, jQuery just passes 4359 // it directly to `console.warn` so both will work; an instance 4360 // just better cooperates with source maps. 4361 } else if ( jQuery.Deferred.getStackHook ) { 4362 process.error = jQuery.Deferred.getStackHook(); 4363 } 4364 window.setTimeout( process ); 4365 } 4366 }; 4367 } 4368 4369 return jQuery.Deferred( function( newDefer ) { 4370 4371 // progress_handlers.add( ... ) 4372 tuples[ 0 ][ 3 ].add( 4373 resolve( 4374 0, 4375 newDefer, 4376 isFunction( onProgress ) ? 4377 onProgress : 4378 Identity, 4379 newDefer.notifyWith 4380 ) 4381 ); 4382 4383 // fulfilled_handlers.add( ... ) 4384 tuples[ 1 ][ 3 ].add( 4385 resolve( 4386 0, 4387 newDefer, 4388 isFunction( onFulfilled ) ? 4389 onFulfilled : 4390 Identity 4391 ) 4392 ); 4393 4394 // rejected_handlers.add( ... ) 4395 tuples[ 2 ][ 3 ].add( 4396 resolve( 4397 0, 4398 newDefer, 4399 isFunction( onRejected ) ? 4400 onRejected : 4401 Thrower 4402 ) 4403 ); 4404 } ).promise(); 4405 }, 4406 4407 // Get a promise for this deferred 4408 // If obj is provided, the promise aspect is added to the object 4409 promise: function( obj ) { 4410 return obj != null ? jQuery.extend( obj, promise ) : promise; 4411 } 4412 }, 4413 deferred = {}; 4414 4415 // Add list-specific methods 4416 jQuery.each( tuples, function( i, tuple ) { 4417 var list = tuple[ 2 ], 4418 stateString = tuple[ 5 ]; 4419 4420 // promise.progress = list.add 4421 // promise.done = list.add 4422 // promise.fail = list.add 4423 promise[ tuple[ 1 ] ] = list.add; 4424 4425 // Handle state 4426 if ( stateString ) { 4427 list.add( 4428 function() { 4429 4430 // state = "resolved" (i.e., fulfilled) 4431 // state = "rejected" 4432 state = stateString; 4433 }, 4434 4435 // rejected_callbacks.disable 4436 // fulfilled_callbacks.disable 4437 tuples[ 3 - i ][ 2 ].disable, 4438 4439 // rejected_handlers.disable 4440 // fulfilled_handlers.disable 4441 tuples[ 3 - i ][ 3 ].disable, 4442 4443 // progress_callbacks.lock 4444 tuples[ 0 ][ 2 ].lock, 4445 4446 // progress_handlers.lock 4447 tuples[ 0 ][ 3 ].lock 4448 ); 4449 } 4450 4451 // progress_handlers.fire 4452 // fulfilled_handlers.fire 4453 // rejected_handlers.fire 4454 list.add( tuple[ 3 ].fire ); 4455 4456 // deferred.notify = function() { deferred.notifyWith(...) } 4457 // deferred.resolve = function() { deferred.resolveWith(...) } 4458 // deferred.reject = function() { deferred.rejectWith(...) } 4459 deferred[ tuple[ 0 ] ] = function() { 4460 deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); 4461 return this; 4462 }; 4463 4464 // deferred.notifyWith = list.fireWith 4465 // deferred.resolveWith = list.fireWith 4466 // deferred.rejectWith = list.fireWith 4467 deferred[ tuple[ 0 ] + "With" ] = list.fireWith; 4468 } ); 4469 4470 // Make the deferred a promise 4471 promise.promise( deferred ); 4472 4473 // Call given func if any 4474 if ( func ) { 4475 func.call( deferred, deferred ); 4476 } 4477 4478 // All done! 4479 return deferred; 4480 }, 4481 4482 // Deferred helper 4483 when: function( singleValue ) { 4484 var 4485 4486 // count of uncompleted subordinates 4487 remaining = arguments.length, 4488 4489 // count of unprocessed arguments 4490 i = remaining, 4491 4492 // subordinate fulfillment data 4493 resolveContexts = Array( i ), 4494 resolveValues = slice.call( arguments ), 4495 4496 // the primary Deferred 4497 primary = jQuery.Deferred(), 4498 4499 // subordinate callback factory 4500 updateFunc = function( i ) { 4501 return function( value ) { 4502 resolveContexts[ i ] = this; 4503 resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; 4504 if ( !( --remaining ) ) { 4505 primary.resolveWith( resolveContexts, resolveValues ); 4506 } 4507 }; 4508 }; 4509 4510 // Single- and empty arguments are adopted like Promise.resolve 4511 if ( remaining <= 1 ) { 4512 adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, 4513 !remaining ); 4514 4515 // Use .then() to unwrap secondary thenables (cf. gh-3000) 4516 if ( primary.state() === "pending" || 4517 isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { 4518 4519 return primary.then(); 4520 } 4521 } 4522 4523 // Multiple arguments are aggregated like Promise.all array elements 4524 while ( i-- ) { 4525 adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); 4526 } 4527 4528 return primary.promise(); 4529 } 4530} ); 4531 4532 4533// These usually indicate a programmer mistake during development, 4534// warn about them ASAP rather than swallowing them by default. 4535var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; 4536 4537// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error 4538// captured before the async barrier to get the original error cause 4539// which may otherwise be hidden. 4540jQuery.Deferred.exceptionHook = function( error, asyncError ) { 4541 4542 // Support: IE 8 - 9 only 4543 // Console exists when dev tools are open, which can happen at any time 4544 if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { 4545 window.console.warn( "jQuery.Deferred exception: " + error.message, 4546 error.stack, asyncError ); 4547 } 4548}; 4549 4550 4551 4552 4553jQuery.readyException = function( error ) { 4554 window.setTimeout( function() { 4555 throw error; 4556 } ); 4557}; 4558 4559 4560 4561 4562// The deferred used on DOM ready 4563var readyList = jQuery.Deferred(); 4564 4565jQuery.fn.ready = function( fn ) { 4566 4567 readyList 4568 .then( fn ) 4569 4570 // Wrap jQuery.readyException in a function so that the lookup 4571 // happens at the time of error handling instead of callback 4572 // registration. 4573 .catch( function( error ) { 4574 jQuery.readyException( error ); 4575 } ); 4576 4577 return this; 4578}; 4579 4580jQuery.extend( { 4581 4582 // Is the DOM ready to be used? Set to true once it occurs. 4583 isReady: false, 4584 4585 // A counter to track how many items to wait for before 4586 // the ready event fires. See trac-6781 4587 readyWait: 1, 4588 4589 // Handle when the DOM is ready 4590 ready: function( wait ) { 4591 4592 // Abort if there are pending holds or we're already ready 4593 if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { 4594 return; 4595 } 4596 4597 // Remember that the DOM is ready 4598 jQuery.isReady = true; 4599 4600 // If a normal DOM Ready event fired, decrement, and wait if need be 4601 if ( wait !== true && --jQuery.readyWait > 0 ) { 4602 return; 4603 } 4604 4605 // If there are functions bound, to execute 4606 readyList.resolveWith( document, [ jQuery ] ); 4607 } 4608} ); 4609 4610jQuery.ready.then = readyList.then; 4611 4612// The ready event handler and self cleanup method 4613function completed() { 4614 document.removeEventListener( "DOMContentLoaded", completed ); 4615 window.removeEventListener( "load", completed ); 4616 jQuery.ready(); 4617} 4618 4619// Catch cases where $(document).ready() is called 4620// after the browser event has already occurred. 4621// Support: IE <=9 - 10 only 4622// Older IE sometimes signals "interactive" too soon 4623if ( document.readyState === "complete" || 4624 ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { 4625 4626 // Handle it asynchronously to allow scripts the opportunity to delay ready 4627 window.setTimeout( jQuery.ready ); 4628 4629} else { 4630 4631 // Use the handy event callback 4632 document.addEventListener( "DOMContentLoaded", completed ); 4633 4634 // A fallback to window.onload, that will always work 4635 window.addEventListener( "load", completed ); 4636} 4637 4638 4639 4640 4641// Multifunctional method to get and set values of a collection 4642// The value/s can optionally be executed if it's a function 4643var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { 4644 var i = 0, 4645 len = elems.length, 4646 bulk = key == null; 4647 4648 // Sets many values 4649 if ( toType( key ) === "object" ) { 4650 chainable = true; 4651 for ( i in key ) { 4652 access( elems, fn, i, key[ i ], true, emptyGet, raw ); 4653 } 4654 4655 // Sets one value 4656 } else if ( value !== undefined ) { 4657 chainable = true; 4658 4659 if ( !isFunction( value ) ) { 4660 raw = true; 4661 } 4662 4663 if ( bulk ) { 4664 4665 // Bulk operations run against the entire set 4666 if ( raw ) { 4667 fn.call( elems, value ); 4668 fn = null; 4669 4670 // ...except when executing function values 4671 } else { 4672 bulk = fn; 4673 fn = function( elem, _key, value ) { 4674 return bulk.call( jQuery( elem ), value ); 4675 }; 4676 } 4677 } 4678 4679 if ( fn ) { 4680 for ( ; i < len; i++ ) { 4681 fn( 4682 elems[ i ], key, raw ? 4683 value : 4684 value.call( elems[ i ], i, fn( elems[ i ], key ) ) 4685 ); 4686 } 4687 } 4688 } 4689 4690 if ( chainable ) { 4691 return elems; 4692 } 4693 4694 // Gets 4695 if ( bulk ) { 4696 return fn.call( elems ); 4697 } 4698 4699 return len ? fn( elems[ 0 ], key ) : emptyGet; 4700}; 4701 4702 4703// Matches dashed string for camelizing 4704var rmsPrefix = /^-ms-/, 4705 rdashAlpha = /-([a-z])/g; 4706 4707// Used by camelCase as callback to replace() 4708function fcamelCase( _all, letter ) { 4709 return letter.toUpperCase(); 4710} 4711 4712// Convert dashed to camelCase; used by the css and data modules 4713// Support: IE <=9 - 11, Edge 12 - 15 4714// Microsoft forgot to hump their vendor prefix (trac-9572) 4715function camelCase( string ) { 4716 return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); 4717} 4718var acceptData = function( owner ) { 4719 4720 // Accepts only: 4721 // - Node 4722 // - Node.ELEMENT_NODE 4723 // - Node.DOCUMENT_NODE 4724 // - Object 4725 // - Any 4726 return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); 4727}; 4728 4729 4730 4731 4732function Data() { 4733 this.expando = jQuery.expando + Data.uid++; 4734} 4735 4736Data.uid = 1; 4737 4738Data.prototype = { 4739 4740 cache: function( owner ) { 4741 4742 // Check if the owner object already has a cache 4743 var value = owner[ this.expando ]; 4744 4745 // If not, create one 4746 if ( !value ) { 4747 value = {}; 4748 4749 // We can accept data for non-element nodes in modern browsers, 4750 // but we should not, see trac-8335. 4751 // Always return an empty object. 4752 if ( acceptData( owner ) ) { 4753 4754 // If it is a node unlikely to be stringify-ed or looped over 4755 // use plain assignment 4756 if ( owner.nodeType ) { 4757 owner[ this.expando ] = value; 4758 4759 // Otherwise secure it in a non-enumerable property 4760 // configurable must be true to allow the property to be 4761 // deleted when data is removed 4762 } else { 4763 Object.defineProperty( owner, this.expando, { 4764 value: value, 4765 configurable: true 4766 } ); 4767 } 4768 } 4769 } 4770 4771 return value; 4772 }, 4773 set: function( owner, data, value ) { 4774 var prop, 4775 cache = this.cache( owner ); 4776 4777 // Handle: [ owner, key, value ] args 4778 // Always use camelCase key (gh-2257) 4779 if ( typeof data === "string" ) { 4780 cache[ camelCase( data ) ] = value; 4781 4782 // Handle: [ owner, { properties } ] args 4783 } else { 4784 4785 // Copy the properties one-by-one to the cache object 4786 for ( prop in data ) { 4787 cache[ camelCase( prop ) ] = data[ prop ]; 4788 } 4789 } 4790 return cache; 4791 }, 4792 get: function( owner, key ) { 4793 return key === undefined ? 4794 this.cache( owner ) : 4795 4796 // Always use camelCase key (gh-2257) 4797 owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; 4798 }, 4799 access: function( owner, key, value ) { 4800 4801 // In cases where either: 4802 // 4803 // 1. No key was specified 4804 // 2. A string key was specified, but no value provided 4805 // 4806 // Take the "read" path and allow the get method to determine 4807 // which value to return, respectively either: 4808 // 4809 // 1. The entire cache object 4810 // 2. The data stored at the key 4811 // 4812 if ( key === undefined || 4813 ( ( key && typeof key === "string" ) && value === undefined ) ) { 4814 4815 return this.get( owner, key ); 4816 } 4817 4818 // When the key is not a string, or both a key and value 4819 // are specified, set or extend (existing objects) with either: 4820 // 4821 // 1. An object of properties 4822 // 2. A key and value 4823 // 4824 this.set( owner, key, value ); 4825 4826 // Since the "set" path can have two possible entry points 4827 // return the expected data based on which path was taken[*] 4828 return value !== undefined ? value : key; 4829 }, 4830 remove: function( owner, key ) { 4831 var i, 4832 cache = owner[ this.expando ]; 4833 4834 if ( cache === undefined ) { 4835 return; 4836 } 4837 4838 if ( key !== undefined ) { 4839 4840 // Support array or space separated string of keys 4841 if ( Array.isArray( key ) ) { 4842 4843 // If key is an array of keys... 4844 // We always set camelCase keys, so remove that. 4845 key = key.map( camelCase ); 4846 } else { 4847 key = camelCase( key ); 4848 4849 // If a key with the spaces exists, use it. 4850 // Otherwise, create an array by matching non-whitespace 4851 key = key in cache ? 4852 [ key ] : 4853 ( key.match( rnothtmlwhite ) || [] ); 4854 } 4855 4856 i = key.length; 4857 4858 while ( i-- ) { 4859 delete cache[ key[ i ] ]; 4860 } 4861 } 4862 4863 // Remove the expando if there's no more data 4864 if ( key === undefined || jQuery.isEmptyObject( cache ) ) { 4865 4866 // Support: Chrome <=35 - 45 4867 // Webkit & Blink performance suffers when deleting properties 4868 // from DOM nodes, so set to undefined instead 4869 // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) 4870 if ( owner.nodeType ) { 4871 owner[ this.expando ] = undefined; 4872 } else { 4873 delete owner[ this.expando ]; 4874 } 4875 } 4876 }, 4877 hasData: function( owner ) { 4878 var cache = owner[ this.expando ]; 4879 return cache !== undefined && !jQuery.isEmptyObject( cache ); 4880 } 4881}; 4882var dataPriv = new Data(); 4883 4884var dataUser = new Data(); 4885 4886 4887 4888// Implementation Summary 4889// 4890// 1. Enforce API surface and semantic compatibility with 1.9.x branch 4891// 2. Improve the module's maintainability by reducing the storage 4892// paths to a single mechanism. 4893// 3. Use the same single mechanism to support "private" and "user" data. 4894// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) 4895// 5. Avoid exposing implementation details on user objects (eg. expando properties) 4896// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 4897 4898var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, 4899 rmultiDash = /[A-Z]/g; 4900 4901function getData( data ) { 4902 if ( data === "true" ) { 4903 return true; 4904 } 4905 4906 if ( data === "false" ) { 4907 return false; 4908 } 4909 4910 if ( data === "null" ) { 4911 return null; 4912 } 4913 4914 // Only convert to a number if it doesn't change the string 4915 if ( data === +data + "" ) { 4916 return +data; 4917 } 4918 4919 if ( rbrace.test( data ) ) { 4920 return JSON.parse( data ); 4921 } 4922 4923 return data; 4924} 4925 4926function dataAttr( elem, key, data ) { 4927 var name; 4928 4929 // If nothing was found internally, try to fetch any 4930 // data from the HTML5 data-* attribute 4931 if ( data === undefined && elem.nodeType === 1 ) { 4932 name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); 4933 data = elem.getAttribute( name ); 4934 4935 if ( typeof data === "string" ) { 4936 try { 4937 data = getData( data ); 4938 } catch ( e ) {} 4939 4940 // Make sure we set the data so it isn't changed later 4941 dataUser.set( elem, key, data ); 4942 } else { 4943 data = undefined; 4944 } 4945 } 4946 return data; 4947} 4948 4949jQuery.extend( { 4950 hasData: function( elem ) { 4951 return dataUser.hasData( elem ) || dataPriv.hasData( elem ); 4952 }, 4953 4954 data: function( elem, name, data ) { 4955 return dataUser.access( elem, name, data ); 4956 }, 4957 4958 removeData: function( elem, name ) { 4959 dataUser.remove( elem, name ); 4960 }, 4961 4962 // TODO: Now that all calls to _data and _removeData have been replaced 4963 // with direct calls to dataPriv methods, these can be deprecated. 4964 _data: function( elem, name, data ) { 4965 return dataPriv.access( elem, name, data ); 4966 }, 4967 4968 _removeData: function( elem, name ) { 4969 dataPriv.remove( elem, name ); 4970 } 4971} ); 4972 4973jQuery.fn.extend( { 4974 data: function( key, value ) { 4975 var i, name, data, 4976 elem = this[ 0 ], 4977 attrs = elem && elem.attributes; 4978 4979 // Gets all values 4980 if ( key === undefined ) { 4981 if ( this.length ) { 4982 data = dataUser.get( elem ); 4983 4984 if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { 4985 i = attrs.length; 4986 while ( i-- ) { 4987 4988 // Support: IE 11 only 4989 // The attrs elements can be null (trac-14894) 4990 if ( attrs[ i ] ) { 4991 name = attrs[ i ].name; 4992 if ( name.indexOf( "data-" ) === 0 ) { 4993 name = camelCase( name.slice( 5 ) ); 4994 dataAttr( elem, name, data[ name ] ); 4995 } 4996 } 4997 } 4998 dataPriv.set( elem, "hasDataAttrs", true ); 4999 } 5000 } 5001 5002 return data; 5003 } 5004 5005 // Sets multiple values 5006 if ( typeof key === "object" ) { 5007 return this.each( function() { 5008 dataUser.set( this, key ); 5009 } ); 5010 } 5011 5012 return access( this, function( value ) { 5013 var data; 5014 5015 // The calling jQuery object (element matches) is not empty 5016 // (and therefore has an element appears at this[ 0 ]) and the 5017 // `value` parameter was not undefined. An empty jQuery object 5018 // will result in `undefined` for elem = this[ 0 ] which will 5019 // throw an exception if an attempt to read a data cache is made. 5020 if ( elem && value === undefined ) { 5021 5022 // Attempt to get data from the cache 5023 // The key will always be camelCased in Data 5024 data = dataUser.get( elem, key ); 5025 if ( data !== undefined ) { 5026 return data; 5027 } 5028 5029 // Attempt to "discover" the data in 5030 // HTML5 custom data-* attrs 5031 data = dataAttr( elem, key ); 5032 if ( data !== undefined ) { 5033 return data; 5034 } 5035 5036 // We tried really hard, but the data doesn't exist. 5037 return; 5038 } 5039 5040 // Set the data... 5041 this.each( function() { 5042 5043 // We always store the camelCased key 5044 dataUser.set( this, key, value ); 5045 } ); 5046 }, null, value, arguments.length > 1, null, true ); 5047 }, 5048 5049 removeData: function( key ) { 5050 return this.each( function() { 5051 dataUser.remove( this, key ); 5052 } ); 5053 } 5054} ); 5055 5056 5057jQuery.extend( { 5058 queue: function( elem, type, data ) { 5059 var queue; 5060 5061 if ( elem ) { 5062 type = ( type || "fx" ) + "queue"; 5063 queue = dataPriv.get( elem, type ); 5064 5065 // Speed up dequeue by getting out quickly if this is just a lookup 5066 if ( data ) { 5067 if ( !queue || Array.isArray( data ) ) { 5068 queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); 5069 } else { 5070 queue.push( data ); 5071 } 5072 } 5073 return queue || []; 5074 } 5075 }, 5076 5077 dequeue: function( elem, type ) { 5078 type = type || "fx"; 5079 5080 var queue = jQuery.queue( elem, type ), 5081 startLength = queue.length, 5082 fn = queue.shift(), 5083 hooks = jQuery._queueHooks( elem, type ), 5084 next = function() { 5085 jQuery.dequeue( elem, type ); 5086 }; 5087 5088 // If the fx queue is dequeued, always remove the progress sentinel 5089 if ( fn === "inprogress" ) { 5090 fn = queue.shift(); 5091 startLength--; 5092 } 5093 5094 if ( fn ) { 5095 5096 // Add a progress sentinel to prevent the fx queue from being 5097 // automatically dequeued 5098 if ( type === "fx" ) { 5099 queue.unshift( "inprogress" ); 5100 } 5101 5102 // Clear up the last queue stop function 5103 delete hooks.stop; 5104 fn.call( elem, next, hooks ); 5105 } 5106 5107 if ( !startLength && hooks ) { 5108 hooks.empty.fire(); 5109 } 5110 }, 5111 5112 // Not public - generate a queueHooks object, or return the current one 5113 _queueHooks: function( elem, type ) { 5114 var key = type + "queueHooks"; 5115 return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { 5116 empty: jQuery.Callbacks( "once memory" ).add( function() { 5117 dataPriv.remove( elem, [ type + "queue", key ] ); 5118 } ) 5119 } ); 5120 } 5121} ); 5122 5123jQuery.fn.extend( { 5124 queue: function( type, data ) { 5125 var setter = 2; 5126 5127 if ( typeof type !== "string" ) { 5128 data = type; 5129 type = "fx"; 5130 setter--; 5131 } 5132 5133 if ( arguments.length < setter ) { 5134 return jQuery.queue( this[ 0 ], type ); 5135 } 5136 5137 return data === undefined ? 5138 this : 5139 this.each( function() { 5140 var queue = jQuery.queue( this, type, data ); 5141 5142 // Ensure a hooks for this queue 5143 jQuery._queueHooks( this, type ); 5144 5145 if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { 5146 jQuery.dequeue( this, type ); 5147 } 5148 } ); 5149 }, 5150 dequeue: function( type ) { 5151 return this.each( function() { 5152 jQuery.dequeue( this, type ); 5153 } ); 5154 }, 5155 clearQueue: function( type ) { 5156 return this.queue( type || "fx", [] ); 5157 }, 5158 5159 // Get a promise resolved when queues of a certain type 5160 // are emptied (fx is the type by default) 5161 promise: function( type, obj ) { 5162 var tmp, 5163 count = 1, 5164 defer = jQuery.Deferred(), 5165 elements = this, 5166 i = this.length, 5167 resolve = function() { 5168 if ( !( --count ) ) { 5169 defer.resolveWith( elements, [ elements ] ); 5170 } 5171 }; 5172 5173 if ( typeof type !== "string" ) { 5174 obj = type; 5175 type = undefined; 5176 } 5177 type = type || "fx"; 5178 5179 while ( i-- ) { 5180 tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); 5181 if ( tmp && tmp.empty ) { 5182 count++; 5183 tmp.empty.add( resolve ); 5184 } 5185 } 5186 resolve(); 5187 return defer.promise( obj ); 5188 } 5189} ); 5190var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; 5191 5192var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); 5193 5194 5195var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; 5196 5197var documentElement = document.documentElement; 5198 5199 5200 5201 var isAttached = function( elem ) { 5202 return jQuery.contains( elem.ownerDocument, elem ); 5203 }, 5204 composed = { composed: true }; 5205 5206 // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only 5207 // Check attachment across shadow DOM boundaries when possible (gh-3504) 5208 // Support: iOS 10.0-10.2 only 5209 // Early iOS 10 versions support `attachShadow` but not `getRootNode`, 5210 // leading to errors. We need to check for `getRootNode`. 5211 if ( documentElement.getRootNode ) { 5212 isAttached = function( elem ) { 5213 return jQuery.contains( elem.ownerDocument, elem ) || 5214 elem.getRootNode( composed ) === elem.ownerDocument; 5215 }; 5216 } 5217var isHiddenWithinTree = function( elem, el ) { 5218 5219 // isHiddenWithinTree might be called from jQuery#filter function; 5220 // in that case, element will be second argument 5221 elem = el || elem; 5222 5223 // Inline style trumps all 5224 return elem.style.display === "none" || 5225 elem.style.display === "" && 5226 5227 // Otherwise, check computed style 5228 // Support: Firefox <=43 - 45 5229 // Disconnected elements can have computed display: none, so first confirm that elem is 5230 // in the document. 5231 isAttached( elem ) && 5232 5233 jQuery.css( elem, "display" ) === "none"; 5234 }; 5235 5236 5237 5238function adjustCSS( elem, prop, valueParts, tween ) { 5239 var adjusted, scale, 5240 maxIterations = 20, 5241 currentValue = tween ? 5242 function() { 5243 return tween.cur(); 5244 } : 5245 function() { 5246 return jQuery.css( elem, prop, "" ); 5247 }, 5248 initial = currentValue(), 5249 unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), 5250 5251 // Starting value computation is required for potential unit mismatches 5252 initialInUnit = elem.nodeType && 5253 ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && 5254 rcssNum.exec( jQuery.css( elem, prop ) ); 5255 5256 if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { 5257 5258 // Support: Firefox <=54 5259 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) 5260 initial = initial / 2; 5261 5262 // Trust units reported by jQuery.css 5263 unit = unit || initialInUnit[ 3 ]; 5264 5265 // Iteratively approximate from a nonzero starting point 5266 initialInUnit = +initial || 1; 5267 5268 while ( maxIterations-- ) { 5269 5270 // Evaluate and update our best guess (doubling guesses that zero out). 5271 // Finish if the scale equals or crosses 1 (making the old*new product non-positive). 5272 jQuery.style( elem, prop, initialInUnit + unit ); 5273 if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { 5274 maxIterations = 0; 5275 } 5276 initialInUnit = initialInUnit / scale; 5277 5278 } 5279 5280 initialInUnit = initialInUnit * 2; 5281 jQuery.style( elem, prop, initialInUnit + unit ); 5282 5283 // Make sure we update the tween properties later on 5284 valueParts = valueParts || []; 5285 } 5286 5287 if ( valueParts ) { 5288 initialInUnit = +initialInUnit || +initial || 0; 5289 5290 // Apply relative offset (+=/-=) if specified 5291 adjusted = valueParts[ 1 ] ? 5292 initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : 5293 +valueParts[ 2 ]; 5294 if ( tween ) { 5295 tween.unit = unit; 5296 tween.start = initialInUnit; 5297 tween.end = adjusted; 5298 } 5299 } 5300 return adjusted; 5301} 5302 5303 5304var defaultDisplayMap = {}; 5305 5306function getDefaultDisplay( elem ) { 5307 var temp, 5308 doc = elem.ownerDocument, 5309 nodeName = elem.nodeName, 5310 display = defaultDisplayMap[ nodeName ]; 5311 5312 if ( display ) { 5313 return display; 5314 } 5315 5316 temp = doc.body.appendChild( doc.createElement( nodeName ) ); 5317 display = jQuery.css( temp, "display" ); 5318 5319 temp.parentNode.removeChild( temp ); 5320 5321 if ( display === "none" ) { 5322 display = "block"; 5323 } 5324 defaultDisplayMap[ nodeName ] = display; 5325 5326 return display; 5327} 5328 5329function showHide( elements, show ) { 5330 var display, elem, 5331 values = [], 5332 index = 0, 5333 length = elements.length; 5334 5335 // Determine new display value for elements that need to change 5336 for ( ; index < length; index++ ) { 5337 elem = elements[ index ]; 5338 if ( !elem.style ) { 5339 continue; 5340 } 5341 5342 display = elem.style.display; 5343 if ( show ) { 5344 5345 // Since we force visibility upon cascade-hidden elements, an immediate (and slow) 5346 // check is required in this first loop unless we have a nonempty display value (either 5347 // inline or about-to-be-restored) 5348 if ( display === "none" ) { 5349 values[ index ] = dataPriv.get( elem, "display" ) || null; 5350 if ( !values[ index ] ) { 5351 elem.style.display = ""; 5352 } 5353 } 5354 if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { 5355 values[ index ] = getDefaultDisplay( elem ); 5356 } 5357 } else { 5358 if ( display !== "none" ) { 5359 values[ index ] = "none"; 5360 5361 // Remember what we're overwriting 5362 dataPriv.set( elem, "display", display ); 5363 } 5364 } 5365 } 5366 5367 // Set the display of the elements in a second loop to avoid constant reflow 5368 for ( index = 0; index < length; index++ ) { 5369 if ( values[ index ] != null ) { 5370 elements[ index ].style.display = values[ index ]; 5371 } 5372 } 5373 5374 return elements; 5375} 5376 5377jQuery.fn.extend( { 5378 show: function() { 5379 return showHide( this, true ); 5380 }, 5381 hide: function() { 5382 return showHide( this ); 5383 }, 5384 toggle: function( state ) { 5385 if ( typeof state === "boolean" ) { 5386 return state ? this.show() : this.hide(); 5387 } 5388 5389 return this.each( function() { 5390 if ( isHiddenWithinTree( this ) ) { 5391 jQuery( this ).show(); 5392 } else { 5393 jQuery( this ).hide(); 5394 } 5395 } ); 5396 } 5397} ); 5398var rcheckableType = ( /^(?:checkbox|radio)$/i ); 5399 5400var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); 5401 5402var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); 5403 5404 5405 5406( function() { 5407 var fragment = document.createDocumentFragment(), 5408 div = fragment.appendChild( document.createElement( "div" ) ), 5409 input = document.createElement( "input" ); 5410 5411 // Support: Android 4.0 - 4.3 only 5412 // Check state lost if the name is set (trac-11217) 5413 // Support: Windows Web Apps (WWA) 5414 // `name` and `type` must use .setAttribute for WWA (trac-14901) 5415 input.setAttribute( "type", "radio" ); 5416 input.setAttribute( "checked", "checked" ); 5417 input.setAttribute( "name", "t" ); 5418 5419 div.appendChild( input ); 5420 5421 // Support: Android <=4.1 only 5422 // Older WebKit doesn't clone checked state correctly in fragments 5423 support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; 5424 5425 // Support: IE <=11 only 5426 // Make sure textarea (and checkbox) defaultValue is properly cloned 5427 div.innerHTML = "<textarea>x</textarea>"; 5428 support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; 5429 5430 // Support: IE <=9 only 5431 // IE <=9 replaces <option> tags with their contents when inserted outside of 5432 // the select element. 5433 div.innerHTML = "<option></option>"; 5434 support.option = !!div.lastChild; 5435} )(); 5436 5437 5438// We have to close these tags to support XHTML (trac-13200) 5439var wrapMap = { 5440 5441 // XHTML parsers do not magically insert elements in the 5442 // same way that tag soup parsers do. So we cannot shorten 5443 // this by omitting <tbody> or other required elements. 5444 thead: [ 1, "<table>", "</table>" ], 5445 col: [ 2, "<table><colgroup>", "</colgroup></table>" ], 5446 tr: [ 2, "<table><tbody>", "</tbody></table>" ], 5447 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], 5448 5449 _default: [ 0, "", "" ] 5450}; 5451 5452wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 5453wrapMap.th = wrapMap.td; 5454 5455// Support: IE <=9 only 5456if ( !support.option ) { 5457 wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ]; 5458} 5459 5460 5461function getAll( context, tag ) { 5462 5463 // Support: IE <=9 - 11 only 5464 // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) 5465 var ret; 5466 5467 if ( typeof context.getElementsByTagName !== "undefined" ) { 5468 ret = context.getElementsByTagName( tag || "*" ); 5469 5470 } else if ( typeof context.querySelectorAll !== "undefined" ) { 5471 ret = context.querySelectorAll( tag || "*" ); 5472 5473 } else { 5474 ret = []; 5475 } 5476 5477 if ( tag === undefined || tag && nodeName( context, tag ) ) { 5478 return jQuery.merge( [ context ], ret ); 5479 } 5480 5481 return ret; 5482} 5483 5484 5485// Mark scripts as having already been evaluated 5486function setGlobalEval( elems, refElements ) { 5487 var i = 0, 5488 l = elems.length; 5489 5490 for ( ; i < l; i++ ) { 5491 dataPriv.set( 5492 elems[ i ], 5493 "globalEval", 5494 !refElements || dataPriv.get( refElements[ i ], "globalEval" ) 5495 ); 5496 } 5497} 5498 5499 5500var rhtml = /<|&#?\w+;/; 5501 5502function buildFragment( elems, context, scripts, selection, ignored ) { 5503 var elem, tmp, tag, wrap, attached, j, 5504 fragment = context.createDocumentFragment(), 5505 nodes = [], 5506 i = 0, 5507 l = elems.length; 5508 5509 for ( ; i < l; i++ ) { 5510 elem = elems[ i ]; 5511 5512 if ( elem || elem === 0 ) { 5513 5514 // Add nodes directly 5515 if ( toType( elem ) === "object" ) { 5516 5517 // Support: Android <=4.0 only, PhantomJS 1 only 5518 // push.apply(_, arraylike) throws on ancient WebKit 5519 jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); 5520 5521 // Convert non-html into a text node 5522 } else if ( !rhtml.test( elem ) ) { 5523 nodes.push( context.createTextNode( elem ) ); 5524 5525 // Convert html into DOM nodes 5526 } else { 5527 tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); 5528 5529 // Deserialize a standard representation 5530 tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); 5531 wrap = wrapMap[ tag ] || wrapMap._default; 5532 tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; 5533 5534 // Descend through wrappers to the right content 5535 j = wrap[ 0 ]; 5536 while ( j-- ) { 5537 tmp = tmp.lastChild; 5538 } 5539 5540 // Support: Android <=4.0 only, PhantomJS 1 only 5541 // push.apply(_, arraylike) throws on ancient WebKit 5542 jQuery.merge( nodes, tmp.childNodes ); 5543 5544 // Remember the top-level container 5545 tmp = fragment.firstChild; 5546 5547 // Ensure the created nodes are orphaned (trac-12392) 5548 tmp.textContent = ""; 5549 } 5550 } 5551 } 5552 5553 // Remove wrapper from fragment 5554 fragment.textContent = ""; 5555 5556 i = 0; 5557 while ( ( elem = nodes[ i++ ] ) ) { 5558 5559 // Skip elements already in the context collection (trac-4087) 5560 if ( selection && jQuery.inArray( elem, selection ) > -1 ) { 5561 if ( ignored ) { 5562 ignored.push( elem ); 5563 } 5564 continue; 5565 } 5566 5567 attached = isAttached( elem ); 5568 5569 // Append to fragment 5570 tmp = getAll( fragment.appendChild( elem ), "script" ); 5571 5572 // Preserve script evaluation history 5573 if ( attached ) { 5574 setGlobalEval( tmp ); 5575 } 5576 5577 // Capture executables 5578 if ( scripts ) { 5579 j = 0; 5580 while ( ( elem = tmp[ j++ ] ) ) { 5581 if ( rscriptType.test( elem.type || "" ) ) { 5582 scripts.push( elem ); 5583 } 5584 } 5585 } 5586 } 5587 5588 return fragment; 5589} 5590 5591 5592var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; 5593 5594function returnTrue() { 5595 return true; 5596} 5597 5598function returnFalse() { 5599 return false; 5600} 5601 5602function on( elem, types, selector, data, fn, one ) { 5603 var origFn, type; 5604 5605 // Types can be a map of types/handlers 5606 if ( typeof types === "object" ) { 5607 5608 // ( types-Object, selector, data ) 5609 if ( typeof selector !== "string" ) { 5610 5611 // ( types-Object, data ) 5612 data = data || selector; 5613 selector = undefined; 5614 } 5615 for ( type in types ) { 5616 on( elem, type, selector, data, types[ type ], one ); 5617 } 5618 return elem; 5619 } 5620 5621 if ( data == null && fn == null ) { 5622 5623 // ( types, fn ) 5624 fn = selector; 5625 data = selector = undefined; 5626 } else if ( fn == null ) { 5627 if ( typeof selector === "string" ) { 5628 5629 // ( types, selector, fn ) 5630 fn = data; 5631 data = undefined; 5632 } else { 5633 5634 // ( types, data, fn ) 5635 fn = data; 5636 data = selector; 5637 selector = undefined; 5638 } 5639 } 5640 if ( fn === false ) { 5641 fn = returnFalse; 5642 } else if ( !fn ) { 5643 return elem; 5644 } 5645 5646 if ( one === 1 ) { 5647 origFn = fn; 5648 fn = function( event ) { 5649 5650 // Can use an empty set, since event contains the info 5651 jQuery().off( event ); 5652 return origFn.apply( this, arguments ); 5653 }; 5654 5655 // Use same guid so caller can remove using origFn 5656 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); 5657 } 5658 return elem.each( function() { 5659 jQuery.event.add( this, types, fn, data, selector ); 5660 } ); 5661} 5662 5663/* 5664 * Helper functions for managing events -- not part of the public interface. 5665 * Props to Dean Edwards' addEvent library for many of the ideas. 5666 */ 5667jQuery.event = { 5668 5669 global: {}, 5670 5671 add: function( elem, types, handler, data, selector ) { 5672 5673 var handleObjIn, eventHandle, tmp, 5674 events, t, handleObj, 5675 special, handlers, type, namespaces, origType, 5676 elemData = dataPriv.get( elem ); 5677 5678 // Only attach events to objects that accept data 5679 if ( !acceptData( elem ) ) { 5680 return; 5681 } 5682 5683 // Caller can pass in an object of custom data in lieu of the handler 5684 if ( handler.handler ) { 5685 handleObjIn = handler; 5686 handler = handleObjIn.handler; 5687 selector = handleObjIn.selector; 5688 } 5689 5690 // Ensure that invalid selectors throw exceptions at attach time 5691 // Evaluate against documentElement in case elem is a non-element node (e.g., document) 5692 if ( selector ) { 5693 jQuery.find.matchesSelector( documentElement, selector ); 5694 } 5695 5696 // Make sure that the handler has a unique ID, used to find/remove it later 5697 if ( !handler.guid ) { 5698 handler.guid = jQuery.guid++; 5699 } 5700 5701 // Init the element's event structure and main handler, if this is the first 5702 if ( !( events = elemData.events ) ) { 5703 events = elemData.events = Object.create( null ); 5704 } 5705 if ( !( eventHandle = elemData.handle ) ) { 5706 eventHandle = elemData.handle = function( e ) { 5707 5708 // Discard the second event of a jQuery.event.trigger() and 5709 // when an event is called after a page has unloaded 5710 return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? 5711 jQuery.event.dispatch.apply( elem, arguments ) : undefined; 5712 }; 5713 } 5714 5715 // Handle multiple events separated by a space 5716 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 5717 t = types.length; 5718 while ( t-- ) { 5719 tmp = rtypenamespace.exec( types[ t ] ) || []; 5720 type = origType = tmp[ 1 ]; 5721 namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); 5722 5723 // There *must* be a type, no attaching namespace-only handlers 5724 if ( !type ) { 5725 continue; 5726 } 5727 5728 // If event changes its type, use the special event handlers for the changed type 5729 special = jQuery.event.special[ type ] || {}; 5730 5731 // If selector defined, determine special event api type, otherwise given type 5732 type = ( selector ? special.delegateType : special.bindType ) || type; 5733 5734 // Update special based on newly reset type 5735 special = jQuery.event.special[ type ] || {}; 5736 5737 // handleObj is passed to all event handlers 5738 handleObj = jQuery.extend( { 5739 type: type, 5740 origType: origType, 5741 data: data, 5742 handler: handler, 5743 guid: handler.guid, 5744 selector: selector, 5745 needsContext: selector && jQuery.expr.match.needsContext.test( selector ), 5746 namespace: namespaces.join( "." ) 5747 }, handleObjIn ); 5748 5749 // Init the event handler queue if we're the first 5750 if ( !( handlers = events[ type ] ) ) { 5751 handlers = events[ type ] = []; 5752 handlers.delegateCount = 0; 5753 5754 // Only use addEventListener if the special events handler returns false 5755 if ( !special.setup || 5756 special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 5757 5758 if ( elem.addEventListener ) { 5759 elem.addEventListener( type, eventHandle ); 5760 } 5761 } 5762 } 5763 5764 if ( special.add ) { 5765 special.add.call( elem, handleObj ); 5766 5767 if ( !handleObj.handler.guid ) { 5768 handleObj.handler.guid = handler.guid; 5769 } 5770 } 5771 5772 // Add to the element's handler list, delegates in front 5773 if ( selector ) { 5774 handlers.splice( handlers.delegateCount++, 0, handleObj ); 5775 } else { 5776 handlers.push( handleObj ); 5777 } 5778 5779 // Keep track of which events have ever been used, for event optimization 5780 jQuery.event.global[ type ] = true; 5781 } 5782 5783 }, 5784 5785 // Detach an event or set of events from an element 5786 remove: function( elem, types, handler, selector, mappedTypes ) { 5787 5788 var j, origCount, tmp, 5789 events, t, handleObj, 5790 special, handlers, type, namespaces, origType, 5791 elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); 5792 5793 if ( !elemData || !( events = elemData.events ) ) { 5794 return; 5795 } 5796 5797 // Once for each type.namespace in types; type may be omitted 5798 types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; 5799 t = types.length; 5800 while ( t-- ) { 5801 tmp = rtypenamespace.exec( types[ t ] ) || []; 5802 type = origType = tmp[ 1 ]; 5803 namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); 5804 5805 // Unbind all events (on this namespace, if provided) for the element 5806 if ( !type ) { 5807 for ( type in events ) { 5808 jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); 5809 } 5810 continue; 5811 } 5812 5813 special = jQuery.event.special[ type ] || {}; 5814 type = ( selector ? special.delegateType : special.bindType ) || type; 5815 handlers = events[ type ] || []; 5816 tmp = tmp[ 2 ] && 5817 new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); 5818 5819 // Remove matching events 5820 origCount = j = handlers.length; 5821 while ( j-- ) { 5822 handleObj = handlers[ j ]; 5823 5824 if ( ( mappedTypes || origType === handleObj.origType ) && 5825 ( !handler || handler.guid === handleObj.guid ) && 5826 ( !tmp || tmp.test( handleObj.namespace ) ) && 5827 ( !selector || selector === handleObj.selector || 5828 selector === "**" && handleObj.selector ) ) { 5829 handlers.splice( j, 1 ); 5830 5831 if ( handleObj.selector ) { 5832 handlers.delegateCount--; 5833 } 5834 if ( special.remove ) { 5835 special.remove.call( elem, handleObj ); 5836 } 5837 } 5838 } 5839 5840 // Remove generic event handler if we removed something and no more handlers exist 5841 // (avoids potential for endless recursion during removal of special event handlers) 5842 if ( origCount && !handlers.length ) { 5843 if ( !special.teardown || 5844 special.teardown.call( elem, namespaces, elemData.handle ) === false ) { 5845 5846 jQuery.removeEvent( elem, type, elemData.handle ); 5847 } 5848 5849 delete events[ type ]; 5850 } 5851 } 5852 5853 // Remove data and the expando if it's no longer used 5854 if ( jQuery.isEmptyObject( events ) ) { 5855 dataPriv.remove( elem, "handle events" ); 5856 } 5857 }, 5858 5859 dispatch: function( nativeEvent ) { 5860 5861 var i, j, ret, matched, handleObj, handlerQueue, 5862 args = new Array( arguments.length ), 5863 5864 // Make a writable jQuery.Event from the native event object 5865 event = jQuery.event.fix( nativeEvent ), 5866 5867 handlers = ( 5868 dataPriv.get( this, "events" ) || Object.create( null ) 5869 )[ event.type ] || [], 5870 special = jQuery.event.special[ event.type ] || {}; 5871 5872 // Use the fix-ed jQuery.Event rather than the (read-only) native event 5873 args[ 0 ] = event; 5874 5875 for ( i = 1; i < arguments.length; i++ ) { 5876 args[ i ] = arguments[ i ]; 5877 } 5878 5879 event.delegateTarget = this; 5880 5881 // Call the preDispatch hook for the mapped type, and let it bail if desired 5882 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { 5883 return; 5884 } 5885 5886 // Determine handlers 5887 handlerQueue = jQuery.event.handlers.call( this, event, handlers ); 5888 5889 // Run delegates first; they may want to stop propagation beneath us 5890 i = 0; 5891 while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { 5892 event.currentTarget = matched.elem; 5893 5894 j = 0; 5895 while ( ( handleObj = matched.handlers[ j++ ] ) && 5896 !event.isImmediatePropagationStopped() ) { 5897 5898 // If the event is namespaced, then each handler is only invoked if it is 5899 // specially universal or its namespaces are a superset of the event's. 5900 if ( !event.rnamespace || handleObj.namespace === false || 5901 event.rnamespace.test( handleObj.namespace ) ) { 5902 5903 event.handleObj = handleObj; 5904 event.data = handleObj.data; 5905 5906 ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || 5907 handleObj.handler ).apply( matched.elem, args ); 5908 5909 if ( ret !== undefined ) { 5910 if ( ( event.result = ret ) === false ) { 5911 event.preventDefault(); 5912 event.stopPropagation(); 5913 } 5914 } 5915 } 5916 } 5917 } 5918 5919 // Call the postDispatch hook for the mapped type 5920 if ( special.postDispatch ) { 5921 special.postDispatch.call( this, event ); 5922 } 5923 5924 return event.result; 5925 }, 5926 5927 handlers: function( event, handlers ) { 5928 var i, handleObj, sel, matchedHandlers, matchedSelectors, 5929 handlerQueue = [], 5930 delegateCount = handlers.delegateCount, 5931 cur = event.target; 5932 5933 // Find delegate handlers 5934 if ( delegateCount && 5935 5936 // Support: IE <=9 5937 // Black-hole SVG <use> instance trees (trac-13180) 5938 cur.nodeType && 5939 5940 // Support: Firefox <=42 5941 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) 5942 // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click 5943 // Support: IE 11 only 5944 // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) 5945 !( event.type === "click" && event.button >= 1 ) ) { 5946 5947 for ( ; cur !== this; cur = cur.parentNode || this ) { 5948 5949 // Don't check non-elements (trac-13208) 5950 // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) 5951 if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { 5952 matchedHandlers = []; 5953 matchedSelectors = {}; 5954 for ( i = 0; i < delegateCount; i++ ) { 5955 handleObj = handlers[ i ]; 5956 5957 // Don't conflict with Object.prototype properties (trac-13203) 5958 sel = handleObj.selector + " "; 5959 5960 if ( matchedSelectors[ sel ] === undefined ) { 5961 matchedSelectors[ sel ] = handleObj.needsContext ? 5962 jQuery( sel, this ).index( cur ) > -1 : 5963 jQuery.find( sel, this, null, [ cur ] ).length; 5964 } 5965 if ( matchedSelectors[ sel ] ) { 5966 matchedHandlers.push( handleObj ); 5967 } 5968 } 5969 if ( matchedHandlers.length ) { 5970 handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); 5971 } 5972 } 5973 } 5974 } 5975 5976 // Add the remaining (directly-bound) handlers 5977 cur = this; 5978 if ( delegateCount < handlers.length ) { 5979 handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); 5980 } 5981 5982 return handlerQueue; 5983 }, 5984 5985 addProp: function( name, hook ) { 5986 Object.defineProperty( jQuery.Event.prototype, name, { 5987 enumerable: true, 5988 configurable: true, 5989 5990 get: isFunction( hook ) ? 5991 function() { 5992 if ( this.originalEvent ) { 5993 return hook( this.originalEvent ); 5994 } 5995 } : 5996 function() { 5997 if ( this.originalEvent ) { 5998 return this.originalEvent[ name ]; 5999 } 6000 }, 6001 6002 set: function( value ) { 6003 Object.defineProperty( this, name, { 6004 enumerable: true, 6005 configurable: true, 6006 writable: true, 6007 value: value 6008 } ); 6009 } 6010 } ); 6011 }, 6012 6013 fix: function( originalEvent ) { 6014 return originalEvent[ jQuery.expando ] ? 6015 originalEvent : 6016 new jQuery.Event( originalEvent ); 6017 }, 6018 6019 special: { 6020 load: { 6021 6022 // Prevent triggered image.load events from bubbling to window.load 6023 noBubble: true 6024 }, 6025 click: { 6026 6027 // Utilize native event to ensure correct state for checkable inputs 6028 setup: function( data ) { 6029 6030 // For mutual compressibility with _default, replace `this` access with a local var. 6031 // `|| data` is dead code meant only to preserve the variable through minification. 6032 var el = this || data; 6033 6034 // Claim the first handler 6035 if ( rcheckableType.test( el.type ) && 6036 el.click && nodeName( el, "input" ) ) { 6037 6038 // dataPriv.set( el, "click", ... ) 6039 leverageNative( el, "click", true ); 6040 } 6041 6042 // Return false to allow normal processing in the caller 6043 return false; 6044 }, 6045 trigger: function( data ) { 6046 6047 // For mutual compressibility with _default, replace `this` access with a local var. 6048 // `|| data` is dead code meant only to preserve the variable through minification. 6049 var el = this || data; 6050 6051 // Force setup before triggering a click 6052 if ( rcheckableType.test( el.type ) && 6053 el.click && nodeName( el, "input" ) ) { 6054 6055 leverageNative( el, "click" ); 6056 } 6057 6058 // Return non-false to allow normal event-path propagation 6059 return true; 6060 }, 6061 6062 // For cross-browser consistency, suppress native .click() on links 6063 // Also prevent it if we're currently inside a leveraged native-event stack 6064 _default: function( event ) { 6065 var target = event.target; 6066 return rcheckableType.test( target.type ) && 6067 target.click && nodeName( target, "input" ) && 6068 dataPriv.get( target, "click" ) || 6069 nodeName( target, "a" ); 6070 } 6071 }, 6072 6073 beforeunload: { 6074 postDispatch: function( event ) { 6075 6076 // Support: Firefox 20+ 6077 // Firefox doesn't alert if the returnValue field is not set. 6078 if ( event.result !== undefined && event.originalEvent ) { 6079 event.originalEvent.returnValue = event.result; 6080 } 6081 } 6082 } 6083 } 6084}; 6085 6086// Ensure the presence of an event listener that handles manually-triggered 6087// synthetic events by interrupting progress until reinvoked in response to 6088// *native* events that it fires directly, ensuring that state changes have 6089// already occurred before other listeners are invoked. 6090function leverageNative( el, type, isSetup ) { 6091 6092 // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add 6093 if ( !isSetup ) { 6094 if ( dataPriv.get( el, type ) === undefined ) { 6095 jQuery.event.add( el, type, returnTrue ); 6096 } 6097 return; 6098 } 6099 6100 // Register the controller as a special universal handler for all event namespaces 6101 dataPriv.set( el, type, false ); 6102 jQuery.event.add( el, type, { 6103 namespace: false, 6104 handler: function( event ) { 6105 var result, 6106 saved = dataPriv.get( this, type ); 6107 6108 if ( ( event.isTrigger & 1 ) && this[ type ] ) { 6109 6110 // Interrupt processing of the outer synthetic .trigger()ed event 6111 if ( !saved ) { 6112 6113 // Store arguments for use when handling the inner native event 6114 // There will always be at least one argument (an event object), so this array 6115 // will not be confused with a leftover capture object. 6116 saved = slice.call( arguments ); 6117 dataPriv.set( this, type, saved ); 6118 6119 // Trigger the native event and capture its result 6120 this[ type ](); 6121 result = dataPriv.get( this, type ); 6122 dataPriv.set( this, type, false ); 6123 6124 if ( saved !== result ) { 6125 6126 // Cancel the outer synthetic event 6127 event.stopImmediatePropagation(); 6128 event.preventDefault(); 6129 6130 return result; 6131 } 6132 6133 // If this is an inner synthetic event for an event with a bubbling surrogate 6134 // (focus or blur), assume that the surrogate already propagated from triggering 6135 // the native event and prevent that from happening again here. 6136 // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the 6137 // bubbling surrogate propagates *after* the non-bubbling base), but that seems 6138 // less bad than duplication. 6139 } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { 6140 event.stopPropagation(); 6141 } 6142 6143 // If this is a native event triggered above, everything is now in order 6144 // Fire an inner synthetic event with the original arguments 6145 } else if ( saved ) { 6146 6147 // ...and capture the result 6148 dataPriv.set( this, type, jQuery.event.trigger( 6149 saved[ 0 ], 6150 saved.slice( 1 ), 6151 this 6152 ) ); 6153 6154 // Abort handling of the native event by all jQuery handlers while allowing 6155 // native handlers on the same element to run. On target, this is achieved 6156 // by stopping immediate propagation just on the jQuery event. However, 6157 // the native event is re-wrapped by a jQuery one on each level of the 6158 // propagation so the only way to stop it for jQuery is to stop it for 6159 // everyone via native `stopPropagation()`. This is not a problem for 6160 // focus/blur which don't bubble, but it does also stop click on checkboxes 6161 // and radios. We accept this limitation. 6162 event.stopPropagation(); 6163 event.isImmediatePropagationStopped = returnTrue; 6164 } 6165 } 6166 } ); 6167} 6168 6169jQuery.removeEvent = function( elem, type, handle ) { 6170 6171 // This "if" is needed for plain objects 6172 if ( elem.removeEventListener ) { 6173 elem.removeEventListener( type, handle ); 6174 } 6175}; 6176 6177jQuery.Event = function( src, props ) { 6178 6179 // Allow instantiation without the 'new' keyword 6180 if ( !( this instanceof jQuery.Event ) ) { 6181 return new jQuery.Event( src, props ); 6182 } 6183 6184 // Event object 6185 if ( src && src.type ) { 6186 this.originalEvent = src; 6187 this.type = src.type; 6188 6189 // Events bubbling up the document may have been marked as prevented 6190 // by a handler lower down the tree; reflect the correct value. 6191 this.isDefaultPrevented = src.defaultPrevented || 6192 src.defaultPrevented === undefined && 6193 6194 // Support: Android <=2.3 only 6195 src.returnValue === false ? 6196 returnTrue : 6197 returnFalse; 6198 6199 // Create target properties 6200 // Support: Safari <=6 - 7 only 6201 // Target should not be a text node (trac-504, trac-13143) 6202 this.target = ( src.target && src.target.nodeType === 3 ) ? 6203 src.target.parentNode : 6204 src.target; 6205 6206 this.currentTarget = src.currentTarget; 6207 this.relatedTarget = src.relatedTarget; 6208 6209 // Event type 6210 } else { 6211 this.type = src; 6212 } 6213 6214 // Put explicitly provided properties onto the event object 6215 if ( props ) { 6216 jQuery.extend( this, props ); 6217 } 6218 6219 // Create a timestamp if incoming event doesn't have one 6220 this.timeStamp = src && src.timeStamp || Date.now(); 6221 6222 // Mark it as fixed 6223 this[ jQuery.expando ] = true; 6224}; 6225 6226// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding 6227// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 6228jQuery.Event.prototype = { 6229 constructor: jQuery.Event, 6230 isDefaultPrevented: returnFalse, 6231 isPropagationStopped: returnFalse, 6232 isImmediatePropagationStopped: returnFalse, 6233 isSimulated: false, 6234 6235 preventDefault: function() { 6236 var e = this.originalEvent; 6237 6238 this.isDefaultPrevented = returnTrue; 6239 6240 if ( e && !this.isSimulated ) { 6241 e.preventDefault(); 6242 } 6243 }, 6244 stopPropagation: function() { 6245 var e = this.originalEvent; 6246 6247 this.isPropagationStopped = returnTrue; 6248 6249 if ( e && !this.isSimulated ) { 6250 e.stopPropagation(); 6251 } 6252 }, 6253 stopImmediatePropagation: function() { 6254 var e = this.originalEvent; 6255 6256 this.isImmediatePropagationStopped = returnTrue; 6257 6258 if ( e && !this.isSimulated ) { 6259 e.stopImmediatePropagation(); 6260 } 6261 6262 this.stopPropagation(); 6263 } 6264}; 6265 6266// Includes all common event props including KeyEvent and MouseEvent specific props 6267jQuery.each( { 6268 altKey: true, 6269 bubbles: true, 6270 cancelable: true, 6271 changedTouches: true, 6272 ctrlKey: true, 6273 detail: true, 6274 eventPhase: true, 6275 metaKey: true, 6276 pageX: true, 6277 pageY: true, 6278 shiftKey: true, 6279 view: true, 6280 "char": true, 6281 code: true, 6282 charCode: true, 6283 key: true, 6284 keyCode: true, 6285 button: true, 6286 buttons: true, 6287 clientX: true, 6288 clientY: true, 6289 offsetX: true, 6290 offsetY: true, 6291 pointerId: true, 6292 pointerType: true, 6293 screenX: true, 6294 screenY: true, 6295 targetTouches: true, 6296 toElement: true, 6297 touches: true, 6298 which: true 6299}, jQuery.event.addProp ); 6300 6301jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { 6302 6303 function focusMappedHandler( nativeEvent ) { 6304 if ( document.documentMode ) { 6305 6306 // Support: IE 11+ 6307 // Attach a single focusin/focusout handler on the document while someone wants 6308 // focus/blur. This is because the former are synchronous in IE while the latter 6309 // are async. In other browsers, all those handlers are invoked synchronously. 6310 6311 // `handle` from private data would already wrap the event, but we need 6312 // to change the `type` here. 6313 var handle = dataPriv.get( this, "handle" ), 6314 event = jQuery.event.fix( nativeEvent ); 6315 event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; 6316 event.isSimulated = true; 6317 6318 // First, handle focusin/focusout 6319 handle( nativeEvent ); 6320 6321 // ...then, handle focus/blur 6322 // 6323 // focus/blur don't bubble while focusin/focusout do; simulate the former by only 6324 // invoking the handler at the lower level. 6325 if ( event.target === event.currentTarget ) { 6326 6327 // The setup part calls `leverageNative`, which, in turn, calls 6328 // `jQuery.event.add`, so event handle will already have been set 6329 // by this point. 6330 handle( event ); 6331 } 6332 } else { 6333 6334 // For non-IE browsers, attach a single capturing handler on the document 6335 // while someone wants focusin/focusout. 6336 jQuery.event.simulate( delegateType, nativeEvent.target, 6337 jQuery.event.fix( nativeEvent ) ); 6338 } 6339 } 6340 6341 jQuery.event.special[ type ] = { 6342 6343 // Utilize native event if possible so blur/focus sequence is correct 6344 setup: function() { 6345 6346 var attaches; 6347 6348 // Claim the first handler 6349 // dataPriv.set( this, "focus", ... ) 6350 // dataPriv.set( this, "blur", ... ) 6351 leverageNative( this, type, true ); 6352 6353 if ( document.documentMode ) { 6354 6355 // Support: IE 9 - 11+ 6356 // We use the same native handler for focusin & focus (and focusout & blur) 6357 // so we need to coordinate setup & teardown parts between those events. 6358 // Use `delegateType` as the key as `type` is already used by `leverageNative`. 6359 attaches = dataPriv.get( this, delegateType ); 6360 if ( !attaches ) { 6361 this.addEventListener( delegateType, focusMappedHandler ); 6362 } 6363 dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); 6364 } else { 6365 6366 // Return false to allow normal processing in the caller 6367 return false; 6368 } 6369 }, 6370 trigger: function() { 6371 6372 // Force setup before trigger 6373 leverageNative( this, type ); 6374 6375 // Return non-false to allow normal event-path propagation 6376 return true; 6377 }, 6378 6379 teardown: function() { 6380 var attaches; 6381 6382 if ( document.documentMode ) { 6383 attaches = dataPriv.get( this, delegateType ) - 1; 6384 if ( !attaches ) { 6385 this.removeEventListener( delegateType, focusMappedHandler ); 6386 dataPriv.remove( this, delegateType ); 6387 } else { 6388 dataPriv.set( this, delegateType, attaches ); 6389 } 6390 } else { 6391 6392 // Return false to indicate standard teardown should be applied 6393 return false; 6394 } 6395 }, 6396 6397 // Suppress native focus or blur if we're currently inside 6398 // a leveraged native-event stack 6399 _default: function( event ) { 6400 return dataPriv.get( event.target, type ); 6401 }, 6402 6403 delegateType: delegateType 6404 }; 6405 6406 // Support: Firefox <=44 6407 // Firefox doesn't have focus(in | out) events 6408 // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 6409 // 6410 // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 6411 // focus(in | out) events fire after focus & blur events, 6412 // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order 6413 // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 6414 // 6415 // Support: IE 9 - 11+ 6416 // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, 6417 // attach a single handler for both events in IE. 6418 jQuery.event.special[ delegateType ] = { 6419 setup: function() { 6420 6421 // Handle: regular nodes (via `this.ownerDocument`), window 6422 // (via `this.document`) & document (via `this`). 6423 var doc = this.ownerDocument || this.document || this, 6424 dataHolder = document.documentMode ? this : doc, 6425 attaches = dataPriv.get( dataHolder, delegateType ); 6426 6427 // Support: IE 9 - 11+ 6428 // We use the same native handler for focusin & focus (and focusout & blur) 6429 // so we need to coordinate setup & teardown parts between those events. 6430 // Use `delegateType` as the key as `type` is already used by `leverageNative`. 6431 if ( !attaches ) { 6432 if ( document.documentMode ) { 6433 this.addEventListener( delegateType, focusMappedHandler ); 6434 } else { 6435 doc.addEventListener( type, focusMappedHandler, true ); 6436 } 6437 } 6438 dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); 6439 }, 6440 teardown: function() { 6441 var doc = this.ownerDocument || this.document || this, 6442 dataHolder = document.documentMode ? this : doc, 6443 attaches = dataPriv.get( dataHolder, delegateType ) - 1; 6444 6445 if ( !attaches ) { 6446 if ( document.documentMode ) { 6447 this.removeEventListener( delegateType, focusMappedHandler ); 6448 } else { 6449 doc.removeEventListener( type, focusMappedHandler, true ); 6450 } 6451 dataPriv.remove( dataHolder, delegateType ); 6452 } else { 6453 dataPriv.set( dataHolder, delegateType, attaches ); 6454 } 6455 } 6456 }; 6457} ); 6458 6459// Create mouseenter/leave events using mouseover/out and event-time checks 6460// so that event delegation works in jQuery. 6461// Do the same for pointerenter/pointerleave and pointerover/pointerout 6462// 6463// Support: Safari 7 only 6464// Safari sends mouseenter too often; see: 6465// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 6466// for the description of the bug (it existed in older Chrome versions as well). 6467jQuery.each( { 6468 mouseenter: "mouseover", 6469 mouseleave: "mouseout", 6470 pointerenter: "pointerover", 6471 pointerleave: "pointerout" 6472}, function( orig, fix ) { 6473 jQuery.event.special[ orig ] = { 6474 delegateType: fix, 6475 bindType: fix, 6476 6477 handle: function( event ) { 6478 var ret, 6479 target = this, 6480 related = event.relatedTarget, 6481 handleObj = event.handleObj; 6482 6483 // For mouseenter/leave call the handler if related is outside the target. 6484 // NB: No relatedTarget if the mouse left/entered the browser window 6485 if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { 6486 event.type = handleObj.origType; 6487 ret = handleObj.handler.apply( this, arguments ); 6488 event.type = fix; 6489 } 6490 return ret; 6491 } 6492 }; 6493} ); 6494 6495jQuery.fn.extend( { 6496 6497 on: function( types, selector, data, fn ) { 6498 return on( this, types, selector, data, fn ); 6499 }, 6500 one: function( types, selector, data, fn ) { 6501 return on( this, types, selector, data, fn, 1 ); 6502 }, 6503 off: function( types, selector, fn ) { 6504 var handleObj, type; 6505 if ( types && types.preventDefault && types.handleObj ) { 6506 6507 // ( event ) dispatched jQuery.Event 6508 handleObj = types.handleObj; 6509 jQuery( types.delegateTarget ).off( 6510 handleObj.namespace ? 6511 handleObj.origType + "." + handleObj.namespace : 6512 handleObj.origType, 6513 handleObj.selector, 6514 handleObj.handler 6515 ); 6516 return this; 6517 } 6518 if ( typeof types === "object" ) { 6519 6520 // ( types-object [, selector] ) 6521 for ( type in types ) { 6522 this.off( type, selector, types[ type ] ); 6523 } 6524 return this; 6525 } 6526 if ( selector === false || typeof selector === "function" ) { 6527 6528 // ( types [, fn] ) 6529 fn = selector; 6530 selector = undefined; 6531 } 6532 if ( fn === false ) { 6533 fn = returnFalse; 6534 } 6535 return this.each( function() { 6536 jQuery.event.remove( this, types, fn, selector ); 6537 } ); 6538 } 6539} ); 6540 6541 6542var 6543 6544 // Support: IE <=10 - 11, Edge 12 - 13 only 6545 // In IE/Edge using regex groups here causes severe slowdowns. 6546 // See https://connect.microsoft.com/IE/feedback/details/1736512/ 6547 rnoInnerhtml = /<script|<style|<link/i, 6548 6549 // checked="checked" or checked 6550 rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, 6551 6552 rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g; 6553 6554// Prefer a tbody over its parent table for containing new rows 6555function manipulationTarget( elem, content ) { 6556 if ( nodeName( elem, "table" ) && 6557 nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { 6558 6559 return jQuery( elem ).children( "tbody" )[ 0 ] || elem; 6560 } 6561 6562 return elem; 6563} 6564 6565// Replace/restore the type attribute of script elements for safe DOM manipulation 6566function disableScript( elem ) { 6567 elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; 6568 return elem; 6569} 6570function restoreScript( elem ) { 6571 if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { 6572 elem.type = elem.type.slice( 5 ); 6573 } else { 6574 elem.removeAttribute( "type" ); 6575 } 6576 6577 return elem; 6578} 6579 6580function cloneCopyEvent( src, dest ) { 6581 var i, l, type, pdataOld, udataOld, udataCur, events; 6582 6583 if ( dest.nodeType !== 1 ) { 6584 return; 6585 } 6586 6587 // 1. Copy private data: events, handlers, etc. 6588 if ( dataPriv.hasData( src ) ) { 6589 pdataOld = dataPriv.get( src ); 6590 events = pdataOld.events; 6591 6592 if ( events ) { 6593 dataPriv.remove( dest, "handle events" ); 6594 6595 for ( type in events ) { 6596 for ( i = 0, l = events[ type ].length; i < l; i++ ) { 6597 jQuery.event.add( dest, type, events[ type ][ i ] ); 6598 } 6599 } 6600 } 6601 } 6602 6603 // 2. Copy user data 6604 if ( dataUser.hasData( src ) ) { 6605 udataOld = dataUser.access( src ); 6606 udataCur = jQuery.extend( {}, udataOld ); 6607 6608 dataUser.set( dest, udataCur ); 6609 } 6610} 6611 6612// Fix IE bugs, see support tests 6613function fixInput( src, dest ) { 6614 var nodeName = dest.nodeName.toLowerCase(); 6615 6616 // Fails to persist the checked state of a cloned checkbox or radio button. 6617 if ( nodeName === "input" && rcheckableType.test( src.type ) ) { 6618 dest.checked = src.checked; 6619 6620 // Fails to return the selected option to the default selected state when cloning options 6621 } else if ( nodeName === "input" || nodeName === "textarea" ) { 6622 dest.defaultValue = src.defaultValue; 6623 } 6624} 6625 6626function domManip( collection, args, callback, ignored ) { 6627 6628 // Flatten any nested arrays 6629 args = flat( args ); 6630 6631 var fragment, first, scripts, hasScripts, node, doc, 6632 i = 0, 6633 l = collection.length, 6634 iNoClone = l - 1, 6635 value = args[ 0 ], 6636 valueIsFunction = isFunction( value ); 6637 6638 // We can't cloneNode fragments that contain checked, in WebKit 6639 if ( valueIsFunction || 6640 ( l > 1 && typeof value === "string" && 6641 !support.checkClone && rchecked.test( value ) ) ) { 6642 return collection.each( function( index ) { 6643 var self = collection.eq( index ); 6644 if ( valueIsFunction ) { 6645 args[ 0 ] = value.call( this, index, self.html() ); 6646 } 6647 domManip( self, args, callback, ignored ); 6648 } ); 6649 } 6650 6651 if ( l ) { 6652 fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); 6653 first = fragment.firstChild; 6654 6655 if ( fragment.childNodes.length === 1 ) { 6656 fragment = first; 6657 } 6658 6659 // Require either new content or an interest in ignored elements to invoke the callback 6660 if ( first || ignored ) { 6661 scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); 6662 hasScripts = scripts.length; 6663 6664 // Use the original fragment for the last item 6665 // instead of the first because it can end up 6666 // being emptied incorrectly in certain situations (trac-8070). 6667 for ( ; i < l; i++ ) { 6668 node = fragment; 6669 6670 if ( i !== iNoClone ) { 6671 node = jQuery.clone( node, true, true ); 6672 6673 // Keep references to cloned scripts for later restoration 6674 if ( hasScripts ) { 6675 6676 // Support: Android <=4.0 only, PhantomJS 1 only 6677 // push.apply(_, arraylike) throws on ancient WebKit 6678 jQuery.merge( scripts, getAll( node, "script" ) ); 6679 } 6680 } 6681 6682 callback.call( collection[ i ], node, i ); 6683 } 6684 6685 if ( hasScripts ) { 6686 doc = scripts[ scripts.length - 1 ].ownerDocument; 6687 6688 // Re-enable scripts 6689 jQuery.map( scripts, restoreScript ); 6690 6691 // Evaluate executable scripts on first document insertion 6692 for ( i = 0; i < hasScripts; i++ ) { 6693 node = scripts[ i ]; 6694 if ( rscriptType.test( node.type || "" ) && 6695 !dataPriv.access( node, "globalEval" ) && 6696 jQuery.contains( doc, node ) ) { 6697 6698 if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { 6699 6700 // Optional AJAX dependency, but won't run scripts if not present 6701 if ( jQuery._evalUrl && !node.noModule ) { 6702 jQuery._evalUrl( node.src, { 6703 nonce: node.nonce || node.getAttribute( "nonce" ) 6704 }, doc ); 6705 } 6706 } else { 6707 6708 // Unwrap a CDATA section containing script contents. This shouldn't be 6709 // needed as in XML documents they're already not visible when 6710 // inspecting element contents and in HTML documents they have no 6711 // meaning but we're preserving that logic for backwards compatibility. 6712 // This will be removed completely in 4.0. See gh-4904. 6713 DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); 6714 } 6715 } 6716 } 6717 } 6718 } 6719 } 6720 6721 return collection; 6722} 6723 6724function remove( elem, selector, keepData ) { 6725 var node, 6726 nodes = selector ? jQuery.filter( selector, elem ) : elem, 6727 i = 0; 6728 6729 for ( ; ( node = nodes[ i ] ) != null; i++ ) { 6730 if ( !keepData && node.nodeType === 1 ) { 6731 jQuery.cleanData( getAll( node ) ); 6732 } 6733 6734 if ( node.parentNode ) { 6735 if ( keepData && isAttached( node ) ) { 6736 setGlobalEval( getAll( node, "script" ) ); 6737 } 6738 node.parentNode.removeChild( node ); 6739 } 6740 } 6741 6742 return elem; 6743} 6744 6745jQuery.extend( { 6746 htmlPrefilter: function( html ) { 6747 return html; 6748 }, 6749 6750 clone: function( elem, dataAndEvents, deepDataAndEvents ) { 6751 var i, l, srcElements, destElements, 6752 clone = elem.cloneNode( true ), 6753 inPage = isAttached( elem ); 6754 6755 // Fix IE cloning issues 6756 if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && 6757 !jQuery.isXMLDoc( elem ) ) { 6758 6759 // We eschew jQuery#find here for performance reasons: 6760 // https://jsperf.com/getall-vs-sizzle/2 6761 destElements = getAll( clone ); 6762 srcElements = getAll( elem ); 6763 6764 for ( i = 0, l = srcElements.length; i < l; i++ ) { 6765 fixInput( srcElements[ i ], destElements[ i ] ); 6766 } 6767 } 6768 6769 // Copy the events from the original to the clone 6770 if ( dataAndEvents ) { 6771 if ( deepDataAndEvents ) { 6772 srcElements = srcElements || getAll( elem ); 6773 destElements = destElements || getAll( clone ); 6774 6775 for ( i = 0, l = srcElements.length; i < l; i++ ) { 6776 cloneCopyEvent( srcElements[ i ], destElements[ i ] ); 6777 } 6778 } else { 6779 cloneCopyEvent( elem, clone ); 6780 } 6781 } 6782 6783 // Preserve script evaluation history 6784 destElements = getAll( clone, "script" ); 6785 if ( destElements.length > 0 ) { 6786 setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); 6787 } 6788 6789 // Return the cloned set 6790 return clone; 6791 }, 6792 6793 cleanData: function( elems ) { 6794 var data, elem, type, 6795 special = jQuery.event.special, 6796 i = 0; 6797 6798 for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { 6799 if ( acceptData( elem ) ) { 6800 if ( ( data = elem[ dataPriv.expando ] ) ) { 6801 if ( data.events ) { 6802 for ( type in data.events ) { 6803 if ( special[ type ] ) { 6804 jQuery.event.remove( elem, type ); 6805 6806 // This is a shortcut to avoid jQuery.event.remove's overhead 6807 } else { 6808 jQuery.removeEvent( elem, type, data.handle ); 6809 } 6810 } 6811 } 6812
vendor: 1,119 bytes, lines 6813-6857
6813 // Support: Chrome <=35 - 45+ 6814 // Assign undefined instead of using delete, see Data#remove 6815 elem[ dataPriv.expando ] = undefined; 6816 } 6817 if ( elem[ dataUser.expando ] ) { 6818 6819 // Support: Chrome <=35 - 45+ 6820 // Assign undefined instead of using delete, see Data#remove 6821 elem[ dataUser.expando ] = undefined; 6822 } 6823 } 6824 } 6825 } 6826} ); 6827 6828jQuery.fn.extend( { 6829 detach: function( selector ) { 6830 return remove( this, selector, true ); 6831 }, 6832 6833 remove: function( selector ) { 6834 return remove( this, selector ); 6835 }, 6836 6837 text: function( value ) { 6838 return access( this, function( value ) { 6839 return value === undefined ? 6840 jQuery.text( this ) : 6841 this.empty().each( function() { 6842 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6843 this.textContent = value; 6844 } 6845 } ); 6846 }, null, value, arguments.length ); 6847 }, 6848 6849 append: function() { 6850 return domManip( this, arguments, function( elem ) { 6851 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6852 var target = manipulationTarget( this, elem ); 6853 target.appendChild( elem ); 6854 } 6855 } ); 6856 }, 6857
vendor: 58,446 bytes, lines 6858-9072
6858 prepend: function() { 6859 return domManip( this, arguments, function( elem ) { 6860 if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { 6861 var target = manipulationTarget( this, elem ); 6862 target.insertBefore( elem, target.firstChild ); 6863 } 6864 } ); 6865 }, 6866 6867 before: function() { 6868 return domManip( this, arguments, function( elem ) { 6869 if ( this.parentNode ) { 6870 this.parentNode.insertBefore( elem, this ); 6871 } 6872 } ); 6873 }, 6874 6875 after: function() { 6876 return domManip( this, arguments, function( elem ) { 6877 if ( this.parentNode ) { 6878 this.parentNode.insertBefore( elem, this.nextSibling ); 6879 } 6880 } ); 6881 }, 6882 6883 empty: function() { 6884 var elem, 6885 i = 0; 6886 6887 for ( ; ( elem = this[ i ] ) != null; i++ ) { 6888 if ( elem.nodeType === 1 ) { 6889 6890 // Prevent memory leaks 6891 jQuery.cleanData( getAll( elem, false ) ); 6892 6893 // Remove any remaining nodes 6894 elem.textContent = ""; 6895 } 6896 } 6897 6898 return this; 6899 }, 6900 6901 clone: function( dataAndEvents, deepDataAndEvents ) { 6902 dataAndEvents = dataAndEvents == null ? false : dataAndEvents; 6903 deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; 6904 6905 return this.map( function() { 6906 return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); 6907 } ); 6908 }, 6909 6910 html: function( value ) { 6911 return access( this, function( value ) { 6912 var elem = this[ 0 ] || {}, 6913 i = 0, 6914 l = this.length; 6915 6916 if ( value === undefined && elem.nodeType === 1 ) { 6917 return elem.innerHTML; 6918 } 6919 6920 // See if we can take a shortcut and just use innerHTML 6921 if ( typeof value === "string" && !rnoInnerhtml.test( value ) && 6922 !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { 6923 6924 value = jQuery.htmlPrefilter( value ); 6925 6926 try { 6927 for ( ; i < l; i++ ) { 6928 elem = this[ i ] || {}; 6929 6930 // Remove element nodes and prevent memory leaks 6931 if ( elem.nodeType === 1 ) { 6932 jQuery.cleanData( getAll( elem, false ) ); 6933 elem.innerHTML = value; 6934 } 6935 } 6936 6937 elem = 0; 6938 6939 // If using innerHTML throws an exception, use the fallback method 6940 } catch ( e ) {} 6941 } 6942 6943 if ( elem ) { 6944 this.empty().append( value ); 6945 } 6946 }, null, value, arguments.length ); 6947 }, 6948 6949 replaceWith: function() { 6950 var ignored = []; 6951 6952 // Make the changes, replacing each non-ignored context element with the new content 6953 return domManip( this, arguments, function( elem ) { 6954 var parent = this.parentNode; 6955 6956 if ( jQuery.inArray( this, ignored ) < 0 ) { 6957 jQuery.cleanData( getAll( this ) ); 6958 if ( parent ) { 6959 parent.replaceChild( elem, this ); 6960 } 6961 } 6962 6963 // Force callback invocation 6964 }, ignored ); 6965 } 6966} ); 6967 6968jQuery.each( { 6969 appendTo: "append", 6970 prependTo: "prepend", 6971 insertBefore: "before", 6972 insertAfter: "after", 6973 replaceAll: "replaceWith" 6974}, function( name, original ) { 6975 jQuery.fn[ name ] = function( selector ) { 6976 var elems, 6977 ret = [], 6978 insert = jQuery( selector ), 6979 last = insert.length - 1, 6980 i = 0; 6981 6982 for ( ; i <= last; i++ ) { 6983 elems = i === last ? this : this.clone( true ); 6984 jQuery( insert[ i ] )[ original ]( elems ); 6985 6986 // Support: Android <=4.0 only, PhantomJS 1 only 6987 // .get() because push.apply(_, arraylike) throws on ancient WebKit 6988 push.apply( ret, elems.get() ); 6989 } 6990 6991 return this.pushStack( ret ); 6992 }; 6993} ); 6994var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); 6995 6996var rcustomProp = /^--/; 6997 6998 6999var getStyles = function( elem ) { 7000 7001 // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) 7002 // IE throws on elements created in popups 7003 // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" 7004 var view = elem.ownerDocument.defaultView; 7005 7006 if ( !view || !view.opener ) { 7007 view = window; 7008 } 7009 7010 return view.getComputedStyle( elem ); 7011 }; 7012 7013var swap = function( elem, options, callback ) { 7014 var ret, name, 7015 old = {}; 7016 7017 // Remember the old values, and insert the new ones 7018 for ( name in options ) { 7019 old[ name ] = elem.style[ name ]; 7020 elem.style[ name ] = options[ name ]; 7021 } 7022 7023 ret = callback.call( elem ); 7024 7025 // Revert the old values 7026 for ( name in options ) { 7027 elem.style[ name ] = old[ name ]; 7028 } 7029 7030 return ret; 7031}; 7032 7033 7034var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); 7035 7036 7037 7038( function() { 7039 7040 // Executing both pixelPosition & boxSizingReliable tests require only one layout 7041 // so they're executed at the same time to save the second computation. 7042 function computeStyleTests() { 7043 7044 // This is a singleton, we need to execute it only once 7045 if ( !div ) { 7046 return; 7047 } 7048 7049 container.style.cssText = "position:absolute;left:-11111px;width:60px;" + 7050 "margin-top:1px;padding:0;border:0"; 7051 div.style.cssText = 7052 "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + 7053 "margin:auto;border:1px;padding:1px;" + 7054 "width:60%;top:1%"; 7055 documentElement.appendChild( container ).appendChild( div ); 7056 7057 var divStyle = window.getComputedStyle( div ); 7058 pixelPositionVal = divStyle.top !== "1%"; 7059 7060 // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 7061 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; 7062 7063 // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 7064 // Some styles come back with percentage values, even though they shouldn't 7065 div.style.right = "60%"; 7066 pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; 7067 7068 // Support: IE 9 - 11 only 7069 // Detect misreporting of content dimensions for box-sizing:border-box elements 7070 boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; 7071 7072 // Support: IE 9 only 7073 // Detect overflow:scroll screwiness (gh-3699) 7074 // Support: Chrome <=64 7075 // Don't get tricked when zoom affects offsetWidth (gh-4029) 7076 div.style.position = "absolute"; 7077 scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; 7078 7079 documentElement.removeChild( container ); 7080 7081 // Nullify the div so it wouldn't be stored in the memory and 7082 // it will also be a sign that checks already performed 7083 div = null; 7084 } 7085 7086 function roundPixelMeasures( measure ) { 7087 return Math.round( parseFloat( measure ) ); 7088 } 7089 7090 var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, 7091 reliableTrDimensionsVal, reliableMarginLeftVal, 7092 container = document.createElement( "div" ), 7093 div = document.createElement( "div" ); 7094 7095 // Finish early in limited (non-browser) environments 7096 if ( !div.style ) { 7097 return; 7098 } 7099 7100 // Support: IE <=9 - 11 only 7101 // Style of cloned element affects source element cloned (trac-8908) 7102 div.style.backgroundClip = "content-box"; 7103 div.cloneNode( true ).style.backgroundClip = ""; 7104 support.clearCloneStyle = div.style.backgroundClip === "content-box"; 7105 7106 jQuery.extend( support, { 7107 boxSizingReliable: function() { 7108 computeStyleTests(); 7109 return boxSizingReliableVal; 7110 }, 7111 pixelBoxStyles: function() { 7112 computeStyleTests(); 7113 return pixelBoxStylesVal; 7114 }, 7115 pixelPosition: function() { 7116 computeStyleTests(); 7117 return pixelPositionVal; 7118 }, 7119 reliableMarginLeft: function() { 7120 computeStyleTests(); 7121 return reliableMarginLeftVal; 7122 }, 7123 scrollboxSize: function() { 7124 computeStyleTests(); 7125 return scrollboxSizeVal; 7126 }, 7127 7128 // Support: IE 9 - 11+, Edge 15 - 18+ 7129 // IE/Edge misreport `getComputedStyle` of table rows with width/height 7130 // set in CSS while `offset*` properties report correct values. 7131 // Behavior in IE 9 is more subtle than in newer versions & it passes 7132 // some versions of this test; make sure not to make it pass there! 7133 // 7134 // Support: Firefox 70+ 7135 // Only Firefox includes border widths 7136 // in computed dimensions. (gh-4529) 7137 reliableTrDimensions: function() { 7138 var table, tr, trChild, trStyle; 7139 if ( reliableTrDimensionsVal == null ) { 7140 table = document.createElement( "table" ); 7141 tr = document.createElement( "tr" ); 7142 trChild = document.createElement( "div" ); 7143 7144 table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; 7145 tr.style.cssText = "box-sizing:content-box;border:1px solid"; 7146 7147 // Support: Chrome 86+ 7148 // Height set through cssText does not get applied. 7149 // Computed height then comes back as 0. 7150 tr.style.height = "1px"; 7151 trChild.style.height = "9px"; 7152 7153 // Support: Android 8 Chrome 86+ 7154 // In our bodyBackground.html iframe, 7155 // display for all div elements is set to "inline", 7156 // which causes a problem only in Android 8 Chrome 86. 7157 // Ensuring the div is `display: block` 7158 // gets around this issue. 7159 trChild.style.display = "block"; 7160 7161 documentElement 7162 .appendChild( table ) 7163 .appendChild( tr ) 7164 .appendChild( trChild ); 7165 7166 trStyle = window.getComputedStyle( tr ); 7167 reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + 7168 parseInt( trStyle.borderTopWidth, 10 ) + 7169 parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; 7170 7171 documentElement.removeChild( table ); 7172 } 7173 return reliableTrDimensionsVal; 7174 } 7175 } ); 7176} )(); 7177 7178 7179function curCSS( elem, name, computed ) { 7180 var width, minWidth, maxWidth, ret, 7181 isCustomProp = rcustomProp.test( name ), 7182 7183 // Support: Firefox 51+ 7184 // Retrieving style before computed somehow 7185 // fixes an issue with getting wrong values 7186 // on detached elements 7187 style = elem.style; 7188 7189 computed = computed || getStyles( elem ); 7190 7191 // getPropertyValue is needed for: 7192 // .css('filter') (IE 9 only, trac-12537) 7193 // .css('--customProperty) (gh-3144) 7194 if ( computed ) { 7195 7196 // Support: IE <=9 - 11+ 7197 // IE only supports `"float"` in `getPropertyValue`; in computed styles 7198 // it's only available as `"cssFloat"`. We no longer modify properties 7199 // sent to `.css()` apart from camelCasing, so we need to check both. 7200 // Normally, this would create difference in behavior: if 7201 // `getPropertyValue` returns an empty string, the value returned 7202 // by `.css()` would be `undefined`. This is usually the case for 7203 // disconnected elements. However, in IE even disconnected elements 7204 // with no styles return `"none"` for `getPropertyValue( "float" )` 7205 ret = computed.getPropertyValue( name ) || computed[ name ]; 7206 7207 if ( isCustomProp && ret ) { 7208 7209 // Support: Firefox 105+, Chrome <=105+ 7210 // Spec requires trimming whitespace for custom properties (gh-4926). 7211 // Firefox only trims leading whitespace. Chrome just collapses 7212 // both leading & trailing whitespace to a single space. 7213 // 7214 // Fall back to `undefined` if empty string returned. 7215 // This collapses a missing definition with property defined 7216 // and set to an empty string but there's no standard API 7217 // allowing us to differentiate them without a performance penalty 7218 // and returning `undefined` aligns with older jQuery. 7219 // 7220 // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED 7221 // as whitespace while CSS does not, but this is not a problem 7222 // because CSS preprocessing replaces them with U+000A LINE FEED 7223 // (which *is* CSS whitespace) 7224 // https://www.w3.org/TR/css-syntax-3/#input-preprocessing 7225 ret = ret.replace( rtrimCSS, "$1" ) || undefined; 7226 } 7227 7228 if ( ret === "" && !isAttached( elem ) ) { 7229 ret = jQuery.style( elem, name ); 7230 } 7231 7232 // A tribute to the "awesome hack by Dean Edwards" 7233 // Android Browser returns percentage for some values, 7234 // but width seems to be reliably pixels. 7235 // This is against the CSSOM draft spec: 7236 // https://drafts.csswg.org/cssom/#resolved-values 7237 if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { 7238 7239 // Remember the original values 7240 width = style.width; 7241 minWidth = style.minWidth; 7242 maxWidth = style.maxWidth; 7243 7244 // Put in the new values to get a computed value out 7245 style.minWidth = style.maxWidth = style.width = ret; 7246 ret = computed.width; 7247 7248 // Revert the changed values 7249 style.width = width; 7250 style.minWidth = minWidth; 7251 style.maxWidth = maxWidth; 7252 } 7253 } 7254 7255 return ret !== undefined ? 7256 7257 // Support: IE <=9 - 11 only 7258 // IE returns zIndex value as an integer. 7259 ret + "" : 7260 ret; 7261} 7262 7263 7264function addGetHookIf( conditionFn, hookFn ) { 7265 7266 // Define the hook, we'll check on the first run if it's really needed. 7267 return { 7268 get: function() { 7269 if ( conditionFn() ) { 7270 7271 // Hook not needed (or it's not possible to use it due 7272 // to missing dependency), remove it. 7273 delete this.get; 7274 return; 7275 } 7276 7277 // Hook needed; redefine it so that the support test is not executed again. 7278 return ( this.get = hookFn ).apply( this, arguments ); 7279 } 7280 }; 7281} 7282 7283 7284var cssPrefixes = [ "Webkit", "Moz", "ms" ], 7285 emptyStyle = document.createElement( "div" ).style, 7286 vendorProps = {}; 7287 7288// Return a vendor-prefixed property or undefined 7289function vendorPropName( name ) { 7290 7291 // Check for vendor prefixed names 7292 var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), 7293 i = cssPrefixes.length; 7294 7295 while ( i-- ) { 7296 name = cssPrefixes[ i ] + capName; 7297 if ( name in emptyStyle ) { 7298 return name; 7299 } 7300 } 7301} 7302 7303// Return a potentially-mapped jQuery.cssProps or vendor prefixed property 7304function finalPropName( name ) { 7305 var final = jQuery.cssProps[ name ] || vendorProps[ name ]; 7306 7307 if ( final ) { 7308 return final; 7309 } 7310 if ( name in emptyStyle ) { 7311 return name; 7312 } 7313 return vendorProps[ name ] = vendorPropName( name ) || name; 7314} 7315 7316 7317var 7318 7319 // Swappable if display is none or starts with table 7320 // except "table", "table-cell", or "table-caption" 7321 // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display 7322 rdisplayswap = /^(none|table(?!-c[ea]).+)/, 7323 cssShow = { position: "absolute", visibility: "hidden", display: "block" }, 7324 cssNormalTransform = { 7325 letterSpacing: "0", 7326 fontWeight: "400" 7327 }; 7328 7329function setPositiveNumber( _elem, value, subtract ) { 7330 7331 // Any relative (+/-) values have already been 7332 // normalized at this point 7333 var matches = rcssNum.exec( value ); 7334 return matches ? 7335 7336 // Guard against undefined "subtract", e.g., when used as in cssHooks 7337 Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : 7338 value; 7339} 7340 7341function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { 7342 var i = dimension === "width" ? 1 : 0, 7343 extra = 0, 7344 delta = 0, 7345 marginDelta = 0; 7346 7347 // Adjustment may not be necessary 7348 if ( box === ( isBorderBox ? "border" : "content" ) ) { 7349 return 0; 7350 } 7351 7352 for ( ; i < 4; i += 2 ) { 7353 7354 // Both box models exclude margin 7355 // Count margin delta separately to only add it after scroll gutter adjustment. 7356 // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). 7357 if ( box === "margin" ) { 7358 marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); 7359 } 7360 7361 // If we get here with a content-box, we're seeking "padding" or "border" or "margin" 7362 if ( !isBorderBox ) { 7363 7364 // Add padding 7365 delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); 7366 7367 // For "border" or "margin", add border 7368 if ( box !== "padding" ) { 7369 delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 7370 7371 // But still keep track of it otherwise 7372 } else { 7373 extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 7374 } 7375 7376 // If we get here with a border-box (content + padding + border), we're seeking "content" or 7377 // "padding" or "margin" 7378 } else { 7379 7380 // For "content", subtract padding 7381 if ( box === "content" ) { 7382 delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); 7383 } 7384 7385 // For "content" or "padding", subtract border 7386 if ( box !== "margin" ) { 7387 delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); 7388 } 7389 } 7390 } 7391 7392 // Account for positive content-box scroll gutter when requested by providing computedVal 7393 if ( !isBorderBox && computedVal >= 0 ) { 7394 7395 // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border 7396 // Assuming integer scroll gutter, subtract the rest and round down 7397 delta += Math.max( 0, Math.ceil( 7398 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - 7399 computedVal - 7400 delta - 7401 extra - 7402 0.5 7403 7404 // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter 7405 // Use an explicit zero to avoid NaN (gh-3964) 7406 ) ) || 0; 7407 } 7408 7409 return delta + marginDelta; 7410} 7411 7412function getWidthOrHeight( elem, dimension, extra ) { 7413 7414 // Start with computed style 7415 var styles = getStyles( elem ), 7416 7417 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). 7418 // Fake content-box until we know it's needed to know the true value. 7419 boxSizingNeeded = !support.boxSizingReliable() || extra, 7420 isBorderBox = boxSizingNeeded && 7421 jQuery.css( elem, "boxSizing", false, styles ) === "border-box", 7422 valueIsBorderBox = isBorderBox, 7423 7424 val = curCSS( elem, dimension, styles ), 7425 offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); 7426 7427 // Support: Firefox <=54 7428 // Return a confounding non-pixel value or feign ignorance, as appropriate. 7429 if ( rnumnonpx.test( val ) ) { 7430 if ( !extra ) { 7431 return val; 7432 } 7433 val = "auto"; 7434 } 7435 7436 7437 // Support: IE 9 - 11 only 7438 // Use offsetWidth/offsetHeight for when box sizing is unreliable. 7439 // In those cases, the computed value can be trusted to be border-box. 7440 if ( ( !support.boxSizingReliable() && isBorderBox || 7441 7442 // Support: IE 10 - 11+, Edge 15 - 18+ 7443 // IE/Edge misreport `getComputedStyle` of table rows with width/height 7444 // set in CSS while `offset*` properties report correct values. 7445 // Interestingly, in some cases IE 9 doesn't suffer from this issue. 7446 !support.reliableTrDimensions() && nodeName( elem, "tr" ) || 7447 7448 // Fall back to offsetWidth/offsetHeight when value is "auto" 7449 // This happens for inline elements with no explicit setting (gh-3571) 7450 val === "auto" || 7451 7452 // Support: Android <=4.1 - 4.3 only 7453 // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) 7454 !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && 7455 7456 // Make sure the element is visible & connected 7457 elem.getClientRects().length ) { 7458 7459 isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; 7460 7461 // Where available, offsetWidth/offsetHeight approximate border box dimensions. 7462 // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the 7463 // retrieved value as a content box dimension. 7464 valueIsBorderBox = offsetProp in elem; 7465 if ( valueIsBorderBox ) { 7466 val = elem[ offsetProp ]; 7467 } 7468 } 7469 7470 // Normalize "" and auto 7471 val = parseFloat( val ) || 0; 7472 7473 // Adjust for the element's box model 7474 return ( val + 7475 boxModelAdjustment( 7476 elem, 7477 dimension, 7478 extra || ( isBorderBox ? "border" : "content" ), 7479 valueIsBorderBox, 7480 styles, 7481 7482 // Provide the current computed size to request scroll gutter calculation (gh-3589) 7483 val 7484 ) 7485 ) + "px"; 7486} 7487 7488jQuery.extend( { 7489 7490 // Add in style property hooks for overriding the default 7491 // behavior of getting and setting a style property 7492 cssHooks: { 7493 opacity: { 7494 get: function( elem, computed ) { 7495 if ( computed ) { 7496 7497 // We should always get a number back from opacity 7498 var ret = curCSS( elem, "opacity" ); 7499 return ret === "" ? "1" : ret; 7500 } 7501 } 7502 } 7503 }, 7504 7505 // Don't automatically add "px" to these possibly-unitless properties 7506 cssNumber: { 7507 animationIterationCount: true, 7508 aspectRatio: true, 7509 borderImageSlice: true, 7510 columnCount: true, 7511 flexGrow: true, 7512 flexShrink: true, 7513 fontWeight: true, 7514 gridArea: true, 7515 gridColumn: true, 7516 gridColumnEnd: true, 7517 gridColumnStart: true, 7518 gridRow: true, 7519 gridRowEnd: true, 7520 gridRowStart: true, 7521 lineHeight: true, 7522 opacity: true, 7523 order: true, 7524 orphans: true, 7525 scale: true, 7526 widows: true, 7527 zIndex: true, 7528 zoom: true, 7529 7530 // SVG-related 7531 fillOpacity: true, 7532 floodOpacity: true, 7533 stopOpacity: true, 7534 strokeMiterlimit: true, 7535 strokeOpacity: true 7536 }, 7537 7538 // Add in properties whose names you wish to fix before 7539 // setting or getting the value 7540 cssProps: {}, 7541 7542 // Get and set the style property on a DOM Node 7543 style: function( elem, name, value, extra ) { 7544 7545 // Don't set styles on text and comment nodes 7546 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { 7547 return; 7548 } 7549 7550 // Make sure that we're working with the right name 7551 var ret, type, hooks, 7552 origName = camelCase( name ), 7553 isCustomProp = rcustomProp.test( name ), 7554 style = elem.style; 7555 7556 // Make sure that we're working with the right name. We don't 7557 // want to query the value if it is a CSS custom property 7558 // since they are user-defined. 7559 if ( !isCustomProp ) { 7560 name = finalPropName( origName ); 7561 } 7562 7563 // Gets hook for the prefixed version, then unprefixed version 7564 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; 7565 7566 // Check if we're setting a value 7567 if ( value !== undefined ) { 7568 type = typeof value; 7569 7570 // Convert "+=" or "-=" to relative numbers (trac-7345) 7571 if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { 7572 value = adjustCSS( elem, name, ret ); 7573 7574 // Fixes bug trac-9237 7575 type = "number"; 7576 } 7577 7578 // Make sure that null and NaN values aren't set (trac-7116) 7579 if ( value == null || value !== value ) { 7580 return; 7581 } 7582 7583 // If a number was passed in, add the unit (except for certain CSS properties) 7584 // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append 7585 // "px" to a few hardcoded values. 7586 if ( type === "number" && !isCustomProp ) { 7587 value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); 7588 } 7589 7590 // background-* props affect original clone's values 7591 if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { 7592 style[ name ] = "inherit"; 7593 } 7594 7595 // If a hook was provided, use that value, otherwise just set the specified value 7596 if ( !hooks || !( "set" in hooks ) || 7597 ( value = hooks.set( elem, value, extra ) ) !== undefined ) { 7598 7599 if ( isCustomProp ) { 7600 style.setProperty( name, value ); 7601 } else { 7602 style[ name ] = value; 7603 } 7604 } 7605 7606 } else { 7607 7608 // If a hook was provided get the non-computed value from there 7609 if ( hooks && "get" in hooks && 7610 ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { 7611 7612 return ret; 7613 } 7614 7615 // Otherwise just get the value from the style object 7616 return style[ name ]; 7617 } 7618 }, 7619 7620 css: function( elem, name, extra, styles ) { 7621 var val, num, hooks, 7622 origName = camelCase( name ), 7623 isCustomProp = rcustomProp.test( name ); 7624 7625 // Make sure that we're working with the right name. We don't 7626 // want to modify the value if it is a CSS custom property 7627 // since they are user-defined. 7628 if ( !isCustomProp ) { 7629 name = finalPropName( origName ); 7630 } 7631 7632 // Try prefixed name followed by the unprefixed name 7633 hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; 7634 7635 // If a hook was provided get the computed value from there 7636 if ( hooks && "get" in hooks ) { 7637 val = hooks.get( elem, true, extra ); 7638 } 7639 7640 // Otherwise, if a way to get the computed value exists, use that 7641 if ( val === undefined ) { 7642 val = curCSS( elem, name, styles ); 7643 } 7644 7645 // Convert "normal" to computed value 7646 if ( val === "normal" && name in cssNormalTransform ) { 7647 val = cssNormalTransform[ name ]; 7648 } 7649 7650 // Make numeric if forced or a qualifier was provided and val looks numeric 7651 if ( extra === "" || extra ) { 7652 num = parseFloat( val ); 7653 return extra === true || isFinite( num ) ? num || 0 : val; 7654 } 7655 7656 return val; 7657 } 7658} ); 7659 7660jQuery.each( [ "height", "width" ], function( _i, dimension ) { 7661 jQuery.cssHooks[ dimension ] = { 7662 get: function( elem, computed, extra ) { 7663 if ( computed ) { 7664 7665 // Certain elements can have dimension info if we invisibly show them 7666 // but it must have a current display style that would benefit 7667 return rdisplayswap.test( jQuery.css( elem, "display" ) ) && 7668 7669 // Support: Safari 8+ 7670 // Table columns in Safari have non-zero offsetWidth & zero 7671 // getBoundingClientRect().width unless display is changed. 7672 // Support: IE <=11 only 7673 // Running getBoundingClientRect on a disconnected node 7674 // in IE throws an error. 7675 ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? 7676 swap( elem, cssShow, function() { 7677 return getWidthOrHeight( elem, dimension, extra ); 7678 } ) : 7679 getWidthOrHeight( elem, dimension, extra ); 7680 } 7681 }, 7682 7683 set: function( elem, value, extra ) { 7684 var matches, 7685 styles = getStyles( elem ), 7686 7687 // Only read styles.position if the test has a chance to fail 7688 // to avoid forcing a reflow. 7689 scrollboxSizeBuggy = !support.scrollboxSize() && 7690 styles.position === "absolute", 7691 7692 // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) 7693 boxSizingNeeded = scrollboxSizeBuggy || extra, 7694 isBorderBox = boxSizingNeeded && 7695 jQuery.css( elem, "boxSizing", false, styles ) === "border-box", 7696 subtract = extra ? 7697 boxModelAdjustment( 7698 elem, 7699 dimension, 7700 extra, 7701 isBorderBox, 7702 styles 7703 ) : 7704 0; 7705 7706 // Account for unreliable border-box dimensions by comparing offset* to computed and 7707 // faking a content-box to get border and padding (gh-3699) 7708 if ( isBorderBox && scrollboxSizeBuggy ) { 7709 subtract -= Math.ceil( 7710 elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - 7711 parseFloat( styles[ dimension ] ) - 7712 boxModelAdjustment( elem, dimension, "border", false, styles ) - 7713 0.5 7714 ); 7715 } 7716 7717 // Convert to pixels if value adjustment is needed 7718 if ( subtract && ( matches = rcssNum.exec( value ) ) && 7719 ( matches[ 3 ] || "px" ) !== "px" ) { 7720 7721 elem.style[ dimension ] = value; 7722 value = jQuery.css( elem, dimension ); 7723 } 7724 7725 return setPositiveNumber( elem, value, subtract ); 7726 } 7727 }; 7728} ); 7729 7730jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, 7731 function( elem, computed ) { 7732 if ( computed ) { 7733 return ( parseFloat( curCSS( elem, "marginLeft" ) ) || 7734 elem.getBoundingClientRect().left - 7735 swap( elem, { marginLeft: 0 }, function() { 7736 return elem.getBoundingClientRect().left; 7737 } ) 7738 ) + "px"; 7739 } 7740 } 7741); 7742 7743// These hooks are used by animate to expand properties 7744jQuery.each( { 7745 margin: "", 7746 padding: "", 7747 border: "Width" 7748}, function( prefix, suffix ) { 7749 jQuery.cssHooks[ prefix + suffix ] = { 7750 expand: function( value ) { 7751 var i = 0, 7752 expanded = {}, 7753 7754 // Assumes a single number if not a string 7755 parts = typeof value === "string" ? value.split( " " ) : [ value ]; 7756 7757 for ( ; i < 4; i++ ) { 7758 expanded[ prefix + cssExpand[ i ] + suffix ] = 7759 parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; 7760 } 7761 7762 return expanded; 7763 } 7764 }; 7765 7766 if ( prefix !== "margin" ) { 7767 jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; 7768 } 7769} ); 7770 7771jQuery.fn.extend( { 7772 css: function( name, value ) { 7773 return access( this, function( elem, name, value ) { 7774 var styles, len, 7775 map = {}, 7776 i = 0; 7777 7778 if ( Array.isArray( name ) ) { 7779 styles = getStyles( elem ); 7780 len = name.length; 7781 7782 for ( ; i < len; i++ ) { 7783 map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); 7784 } 7785 7786 return map; 7787 } 7788 7789 return value !== undefined ? 7790 jQuery.style( elem, name, value ) : 7791 jQuery.css( elem, name ); 7792 }, name, value, arguments.length > 1 ); 7793 } 7794} ); 7795 7796 7797function Tween( elem, options, prop, end, easing ) { 7798 return new Tween.prototype.init( elem, options, prop, end, easing ); 7799} 7800jQuery.Tween = Tween; 7801 7802Tween.prototype = { 7803 constructor: Tween, 7804 init: function( elem, options, prop, end, easing, unit ) { 7805 this.elem = elem; 7806 this.prop = prop; 7807 this.easing = easing || jQuery.easing._default; 7808 this.options = options; 7809 this.start = this.now = this.cur(); 7810 this.end = end; 7811 this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); 7812 }, 7813 cur: function() { 7814 var hooks = Tween.propHooks[ this.prop ]; 7815 7816 return hooks && hooks.get ? 7817 hooks.get( this ) : 7818 Tween.propHooks._default.get( this ); 7819 }, 7820 run: function( percent ) { 7821 var eased, 7822 hooks = Tween.propHooks[ this.prop ]; 7823 7824 if ( this.options.duration ) { 7825 this.pos = eased = jQuery.easing[ this.easing ]( 7826 percent, this.options.duration * percent, 0, 1, this.options.duration 7827 ); 7828 } else { 7829 this.pos = eased = percent; 7830 } 7831 this.now = ( this.end - this.start ) * eased + this.start; 7832 7833 if ( this.options.step ) { 7834 this.options.step.call( this.elem, this.now, this ); 7835 } 7836 7837 if ( hooks && hooks.set ) { 7838 hooks.set( this ); 7839 } else { 7840 Tween.propHooks._default.set( this ); 7841 } 7842 return this; 7843 } 7844}; 7845 7846Tween.prototype.init.prototype = Tween.prototype; 7847 7848Tween.propHooks = { 7849 _default: { 7850 get: function( tween ) { 7851 var result; 7852 7853 // Use a property on the element directly when it is not a DOM element, 7854 // or when there is no matching style property that exists. 7855 if ( tween.elem.nodeType !== 1 || 7856 tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { 7857 return tween.elem[ tween.prop ]; 7858 } 7859 7860 // Passing an empty string as a 3rd parameter to .css will automatically 7861 // attempt a parseFloat and fallback to a string if the parse fails. 7862 // Simple values such as "10px" are parsed to Float; 7863 // complex values such as "rotate(1rad)" are returned as-is. 7864 result = jQuery.css( tween.elem, tween.prop, "" ); 7865 7866 // Empty strings, null, undefined and "auto" are converted to 0. 7867 return !result || result === "auto" ? 0 : result; 7868 }, 7869 set: function( tween ) { 7870 7871 // Use step hook for back compat. 7872 // Use cssHook if its there. 7873 // Use .style if available and use plain properties where available. 7874 if ( jQuery.fx.step[ tween.prop ] ) { 7875 jQuery.fx.step[ tween.prop ]( tween ); 7876 } else if ( tween.elem.nodeType === 1 && ( 7877 jQuery.cssHooks[ tween.prop ] || 7878 tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { 7879 jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); 7880 } else { 7881 tween.elem[ tween.prop ] = tween.now; 7882 } 7883 } 7884 } 7885}; 7886 7887// Support: IE <=9 only 7888// Panic based approach to setting things on disconnected nodes 7889Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { 7890 set: function( tween ) { 7891 if ( tween.elem.nodeType && tween.elem.parentNode ) { 7892 tween.elem[ tween.prop ] = tween.now; 7893 } 7894 } 7895}; 7896 7897jQuery.easing = { 7898 linear: function( p ) { 7899 return p; 7900 }, 7901 swing: function( p ) { 7902 return 0.5 - Math.cos( p * Math.PI ) / 2; 7903 }, 7904 _default: "swing" 7905}; 7906 7907jQuery.fx = Tween.prototype.init; 7908 7909// Back compat <1.8 extension point 7910jQuery.fx.step = {}; 7911 7912 7913 7914 7915var 7916 fxNow, inProgress, 7917 rfxtypes = /^(?:toggle|show|hide)$/, 7918 rrun = /queueHooks$/; 7919 7920function schedule() { 7921 if ( inProgress ) { 7922 if ( document.hidden === false && window.requestAnimationFrame ) { 7923 window.requestAnimationFrame( schedule ); 7924 } else { 7925 window.setTimeout( schedule, jQuery.fx.interval ); 7926 } 7927 7928 jQuery.fx.tick(); 7929 } 7930} 7931 7932// Animations created synchronously will run synchronously 7933function createFxNow() { 7934 window.setTimeout( function() { 7935 fxNow = undefined; 7936 } ); 7937 return ( fxNow = Date.now() ); 7938} 7939 7940// Generate parameters to create a standard animation 7941function genFx( type, includeWidth ) { 7942 var which, 7943 i = 0, 7944 attrs = { height: type }; 7945 7946 // If we include width, step value is 1 to do all cssExpand values, 7947 // otherwise step value is 2 to skip over Left and Right 7948 includeWidth = includeWidth ? 1 : 0; 7949 for ( ; i < 4; i += 2 - includeWidth ) { 7950 which = cssExpand[ i ]; 7951 attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; 7952 } 7953 7954 if ( includeWidth ) { 7955 attrs.opacity = attrs.width = type; 7956 } 7957 7958 return attrs; 7959} 7960 7961function createTween( value, prop, animation ) { 7962 var tween, 7963 collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), 7964 index = 0, 7965 length = collection.length; 7966 for ( ; index < length; index++ ) { 7967 if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { 7968 7969 // We're done with this property 7970 return tween; 7971 } 7972 } 7973} 7974 7975function defaultPrefilter( elem, props, opts ) { 7976 var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, 7977 isBox = "width" in props || "height" in props, 7978 anim = this, 7979 orig = {}, 7980 style = elem.style, 7981 hidden = elem.nodeType && isHiddenWithinTree( elem ), 7982 dataShow = dataPriv.get( elem, "fxshow" ); 7983 7984 // Queue-skipping animations hijack the fx hooks 7985 if ( !opts.queue ) { 7986 hooks = jQuery._queueHooks( elem, "fx" ); 7987 if ( hooks.unqueued == null ) { 7988 hooks.unqueued = 0; 7989 oldfire = hooks.empty.fire; 7990 hooks.empty.fire = function() { 7991 if ( !hooks.unqueued ) { 7992 oldfire(); 7993 } 7994 }; 7995 } 7996 hooks.unqueued++; 7997 7998 anim.always( function() { 7999 8000 // Ensure the complete handler is called before this completes 8001 anim.always( function() { 8002 hooks.unqueued--; 8003 if ( !jQuery.queue( elem, "fx" ).length ) { 8004 hooks.empty.fire(); 8005 } 8006 } ); 8007 } ); 8008 } 8009 8010 // Detect show/hide animations 8011 for ( prop in props ) { 8012 value = props[ prop ]; 8013 if ( rfxtypes.test( value ) ) { 8014 delete props[ prop ]; 8015 toggle = toggle || value === "toggle"; 8016 if ( value === ( hidden ? "hide" : "show" ) ) { 8017 8018 // Pretend to be hidden if this is a "show" and 8019 // there is still data from a stopped show/hide 8020 if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { 8021 hidden = true; 8022 8023 // Ignore all other no-op show/hide data 8024 } else { 8025 continue; 8026 } 8027 } 8028 orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); 8029 } 8030 } 8031 8032 // Bail out if this is a no-op like .hide().hide() 8033 propTween = !jQuery.isEmptyObject( props ); 8034 if ( !propTween && jQuery.isEmptyObject( orig ) ) { 8035 return; 8036 } 8037 8038 // Restrict "overflow" and "display" styles during box animations 8039 if ( isBox && elem.nodeType === 1 ) { 8040 8041 // Support: IE <=9 - 11, Edge 12 - 15 8042 // Record all 3 overflow attributes because IE does not infer the shorthand 8043 // from identically-valued overflowX and overflowY and Edge just mirrors 8044 // the overflowX value there. 8045 opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; 8046 8047 // Identify a display type, preferring old show/hide data over the CSS cascade 8048 restoreDisplay = dataShow && dataShow.display; 8049 if ( restoreDisplay == null ) { 8050 restoreDisplay = dataPriv.get( elem, "display" ); 8051 } 8052 display = jQuery.css( elem, "display" ); 8053 if ( display === "none" ) { 8054 if ( restoreDisplay ) { 8055 display = restoreDisplay; 8056 } else { 8057 8058 // Get nonempty value(s) by temporarily forcing visibility 8059 showHide( [ elem ], true ); 8060 restoreDisplay = elem.style.display || restoreDisplay; 8061 display = jQuery.css( elem, "display" ); 8062 showHide( [ elem ] ); 8063 } 8064 } 8065 8066 // Animate inline elements as inline-block 8067 if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { 8068 if ( jQuery.css( elem, "float" ) === "none" ) { 8069 8070 // Restore the original display value at the end of pure show/hide animations 8071 if ( !propTween ) { 8072 anim.done( function() { 8073 style.display = restoreDisplay; 8074 } ); 8075 if ( restoreDisplay == null ) { 8076 display = style.display; 8077 restoreDisplay = display === "none" ? "" : display; 8078 } 8079 } 8080 style.display = "inline-block"; 8081 } 8082 } 8083 } 8084 8085 if ( opts.overflow ) { 8086 style.overflow = "hidden"; 8087 anim.always( function() { 8088 style.overflow = opts.overflow[ 0 ]; 8089 style.overflowX = opts.overflow[ 1 ]; 8090 style.overflowY = opts.overflow[ 2 ]; 8091 } ); 8092 } 8093 8094 // Implement show/hide animations 8095 propTween = false; 8096 for ( prop in orig ) { 8097 8098 // General show/hide setup for this element animation 8099 if ( !propTween ) { 8100 if ( dataShow ) { 8101 if ( "hidden" in dataShow ) { 8102 hidden = dataShow.hidden; 8103 } 8104 } else { 8105 dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); 8106 } 8107 8108 // Store hidden/visible for toggle so `.stop().toggle()` "reverses" 8109 if ( toggle ) { 8110 dataShow.hidden = !hidden; 8111 } 8112 8113 // Show elements before animating them 8114 if ( hidden ) { 8115 showHide( [ elem ], true ); 8116 } 8117 8118 /* eslint-disable no-loop-func */ 8119 8120 anim.done( function() { 8121 8122 /* eslint-enable no-loop-func */ 8123 8124 // The final step of a "hide" animation is actually hiding the element 8125 if ( !hidden ) { 8126 showHide( [ elem ] ); 8127 } 8128 dataPriv.remove( elem, "fxshow" ); 8129 for ( prop in orig ) { 8130 jQuery.style( elem, prop, orig[ prop ] ); 8131 } 8132 } ); 8133 } 8134 8135 // Per-property setup 8136 propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); 8137 if ( !( prop in dataShow ) ) { 8138 dataShow[ prop ] = propTween.start; 8139 if ( hidden ) { 8140 propTween.end = propTween.start; 8141 propTween.start = 0; 8142 } 8143 } 8144 } 8145} 8146 8147function propFilter( props, specialEasing ) { 8148 var index, name, easing, value, hooks; 8149 8150 // camelCase, specialEasing and expand cssHook pass 8151 for ( index in props ) { 8152 name = camelCase( index ); 8153 easing = specialEasing[ name ]; 8154 value = props[ index ]; 8155 if ( Array.isArray( value ) ) { 8156 easing = value[ 1 ]; 8157 value = props[ index ] = value[ 0 ]; 8158 } 8159 8160 if ( index !== name ) { 8161 props[ name ] = value; 8162 delete props[ index ]; 8163 } 8164 8165 hooks = jQuery.cssHooks[ name ]; 8166 if ( hooks && "expand" in hooks ) { 8167 value = hooks.expand( value ); 8168 delete props[ name ]; 8169 8170 // Not quite $.extend, this won't overwrite existing keys. 8171 // Reusing 'index' because we have the correct "name" 8172 for ( index in value ) { 8173 if ( !( index in props ) ) { 8174 props[ index ] = value[ index ]; 8175 specialEasing[ index ] = easing; 8176 } 8177 } 8178 } else { 8179 specialEasing[ name ] = easing; 8180 } 8181 } 8182} 8183 8184function Animation( elem, properties, options ) { 8185 var result, 8186 stopped, 8187 index = 0, 8188 length = Animation.prefilters.length, 8189 deferred = jQuery.Deferred().always( function() { 8190 8191 // Don't match elem in the :animated selector 8192 delete tick.elem; 8193 } ), 8194 tick = function() { 8195 if ( stopped ) { 8196 return false; 8197 } 8198 var currentTime = fxNow || createFxNow(), 8199 remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), 8200 8201 // Support: Android 2.3 only 8202 // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497) 8203 temp = remaining / animation.duration || 0, 8204 percent = 1 - temp, 8205 index = 0, 8206 length = animation.tweens.length; 8207 8208 for ( ; index < length; index++ ) { 8209 animation.tweens[ index ].run( percent ); 8210 } 8211 8212 deferred.notifyWith( elem, [ animation, percent, remaining ] ); 8213 8214 // If there's more to do, yield 8215 if ( percent < 1 && length ) { 8216 return remaining; 8217 } 8218 8219 // If this was an empty animation, synthesize a final progress notification 8220 if ( !length ) { 8221 deferred.notifyWith( elem, [ animation, 1, 0 ] ); 8222 } 8223 8224 // Resolve the animation and report its conclusion 8225 deferred.resolveWith( elem, [ animation ] ); 8226 return false; 8227 }, 8228 animation = deferred.promise( { 8229 elem: elem, 8230 props: jQuery.extend( {}, properties ), 8231 opts: jQuery.extend( true, { 8232 specialEasing: {}, 8233 easing: jQuery.easing._default 8234 }, options ), 8235 originalProperties: properties, 8236 originalOptions: options, 8237 startTime: fxNow || createFxNow(), 8238 duration: options.duration, 8239 tweens: [], 8240 createTween: function( prop, end ) { 8241 var tween = jQuery.Tween( elem, animation.opts, prop, end, 8242 animation.opts.specialEasing[ prop ] || animation.opts.easing ); 8243 animation.tweens.push( tween ); 8244 return tween; 8245 }, 8246 stop: function( gotoEnd ) { 8247 var index = 0, 8248 8249 // If we are going to the end, we want to run all the tweens 8250 // otherwise we skip this part 8251 length = gotoEnd ? animation.tweens.length : 0; 8252 if ( stopped ) { 8253 return this; 8254 } 8255 stopped = true; 8256 for ( ; index < length; index++ ) { 8257 animation.tweens[ index ].run( 1 ); 8258 } 8259 8260 // Resolve when we played the last frame; otherwise, reject 8261 if ( gotoEnd ) { 8262 deferred.notifyWith( elem, [ animation, 1, 0 ] ); 8263 deferred.resolveWith( elem, [ animation, gotoEnd ] ); 8264 } else { 8265 deferred.rejectWith( elem, [ animation, gotoEnd ] ); 8266 } 8267 return this; 8268 } 8269 } ), 8270 props = animation.props; 8271 8272 propFilter( props, animation.opts.specialEasing ); 8273 8274 for ( ; index < length; index++ ) { 8275 result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); 8276 if ( result ) { 8277 if ( isFunction( result.stop ) ) { 8278 jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = 8279 result.stop.bind( result ); 8280 } 8281 return result; 8282 } 8283 } 8284 8285 jQuery.map( props, createTween, animation ); 8286 8287 if ( isFunction( animation.opts.start ) ) { 8288 animation.opts.start.call( elem, animation ); 8289 } 8290 8291 // Attach callbacks from options 8292 animation 8293 .progress( animation.opts.progress ) 8294 .done( animation.opts.done, animation.opts.complete ) 8295 .fail( animation.opts.fail ) 8296 .always( animation.opts.always ); 8297 8298 jQuery.fx.timer( 8299 jQuery.extend( tick, { 8300 elem: elem, 8301 anim: animation, 8302 queue: animation.opts.queue 8303 } ) 8304 ); 8305 8306 return animation; 8307} 8308 8309jQuery.Animation = jQuery.extend( Animation, { 8310 8311 tweeners: { 8312 "*": [ function( prop, value ) { 8313 var tween = this.createTween( prop, value ); 8314 adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); 8315 return tween; 8316 } ] 8317 }, 8318 8319 tweener: function( props, callback ) { 8320 if ( isFunction( props ) ) { 8321 callback = props; 8322 props = [ "*" ]; 8323 } else { 8324 props = props.match( rnothtmlwhite ); 8325 } 8326 8327 var prop, 8328 index = 0, 8329 length = props.length; 8330 8331 for ( ; index < length; index++ ) { 8332 prop = props[ index ]; 8333 Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; 8334 Animation.tweeners[ prop ].unshift( callback ); 8335 } 8336 }, 8337 8338 prefilters: [ defaultPrefilter ], 8339 8340 prefilter: function( callback, prepend ) { 8341 if ( prepend ) { 8342 Animation.prefilters.unshift( callback ); 8343 } else { 8344 Animation.prefilters.push( callback ); 8345 } 8346 } 8347} ); 8348 8349jQuery.speed = function( speed, easing, fn ) { 8350 var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { 8351 complete: fn || !fn && easing || 8352 isFunction( speed ) && speed, 8353 duration: speed, 8354 easing: fn && easing || easing && !isFunction( easing ) && easing 8355 }; 8356 8357 // Go to the end state if fx are off 8358 if ( jQuery.fx.off ) { 8359 opt.duration = 0; 8360 8361 } else { 8362 if ( typeof opt.duration !== "number" ) { 8363 if ( opt.duration in jQuery.fx.speeds ) { 8364 opt.duration = jQuery.fx.speeds[ opt.duration ]; 8365 8366 } else { 8367 opt.duration = jQuery.fx.speeds._default; 8368 } 8369 } 8370 } 8371 8372 // Normalize opt.queue - true/undefined/null -> "fx" 8373 if ( opt.queue == null || opt.queue === true ) { 8374 opt.queue = "fx"; 8375 } 8376 8377 // Queueing 8378 opt.old = opt.complete; 8379 8380 opt.complete = function() { 8381 if ( isFunction( opt.old ) ) { 8382 opt.old.call( this ); 8383 } 8384 8385 if ( opt.queue ) { 8386 jQuery.dequeue( this, opt.queue ); 8387 } 8388 }; 8389 8390 return opt; 8391}; 8392 8393jQuery.fn.extend( { 8394 fadeTo: function( speed, to, easing, callback ) { 8395 8396 // Show any hidden elements after setting opacity to 0 8397 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() 8398 8399 // Animate to the value specified 8400 .end().animate( { opacity: to }, speed, easing, callback ); 8401 }, 8402 animate: function( prop, speed, easing, callback ) { 8403 var empty = jQuery.isEmptyObject( prop ), 8404 optall = jQuery.speed( speed, easing, callback ), 8405 doAnimation = function() { 8406 8407 // Operate on a copy of prop so per-property easing won't be lost 8408 var anim = Animation( this, jQuery.extend( {}, prop ), optall ); 8409 8410 // Empty animations, or finishing resolves immediately 8411 if ( empty || dataPriv.get( this, "finish" ) ) { 8412 anim.stop( true ); 8413 } 8414 }; 8415 8416 doAnimation.finish = doAnimation; 8417 8418 return empty || optall.queue === false ? 8419 this.each( doAnimation ) : 8420 this.queue( optall.queue, doAnimation ); 8421 }, 8422 stop: function( type, clearQueue, gotoEnd ) { 8423 var stopQueue = function( hooks ) { 8424 var stop = hooks.stop; 8425 delete hooks.stop; 8426 stop( gotoEnd ); 8427 }; 8428 8429 if ( typeof type !== "string" ) { 8430 gotoEnd = clearQueue; 8431 clearQueue = type; 8432 type = undefined; 8433 } 8434 if ( clearQueue ) { 8435 this.queue( type || "fx", [] ); 8436 } 8437 8438 return this.each( function() { 8439 var dequeue = true, 8440 index = type != null && type + "queueHooks", 8441 timers = jQuery.timers, 8442 data = dataPriv.get( this ); 8443 8444 if ( index ) { 8445 if ( data[ index ] && data[ index ].stop ) { 8446 stopQueue( data[ index ] ); 8447 } 8448 } else { 8449 for ( index in data ) { 8450 if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { 8451 stopQueue( data[ index ] ); 8452 } 8453 } 8454 } 8455 8456 for ( index = timers.length; index--; ) { 8457 if ( timers[ index ].elem === this && 8458 ( type == null || timers[ index ].queue === type ) ) { 8459 8460 timers[ index ].anim.stop( gotoEnd ); 8461 dequeue = false; 8462 timers.splice( index, 1 ); 8463 } 8464 } 8465 8466 // Start the next in the queue if the last step wasn't forced. 8467 // Timers currently will call their complete callbacks, which 8468 // will dequeue but only if they were gotoEnd. 8469 if ( dequeue || !gotoEnd ) { 8470 jQuery.dequeue( this, type ); 8471 } 8472 } ); 8473 }, 8474 finish: function( type ) { 8475 if ( type !== false ) { 8476 type = type || "fx"; 8477 } 8478 return this.each( function() { 8479 var index, 8480 data = dataPriv.get( this ), 8481 queue = data[ type + "queue" ], 8482 hooks = data[ type + "queueHooks" ], 8483 timers = jQuery.timers, 8484 length = queue ? queue.length : 0; 8485 8486 // Enable finishing flag on private data 8487 data.finish = true; 8488 8489 // Empty the queue first 8490 jQuery.queue( this, type, [] ); 8491 8492 if ( hooks && hooks.stop ) { 8493 hooks.stop.call( this, true ); 8494 } 8495 8496 // Look for any active animations, and finish them 8497 for ( index = timers.length; index--; ) { 8498 if ( timers[ index ].elem === this && timers[ index ].queue === type ) { 8499 timers[ index ].anim.stop( true ); 8500 timers.splice( index, 1 ); 8501 } 8502 } 8503 8504 // Look for any animations in the old queue and finish them 8505 for ( index = 0; index < length; index++ ) { 8506 if ( queue[ index ] && queue[ index ].finish ) { 8507 queue[ index ].finish.call( this ); 8508 } 8509 } 8510 8511 // Turn off finishing flag 8512 delete data.finish; 8513 } ); 8514 } 8515} ); 8516 8517jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { 8518 var cssFn = jQuery.fn[ name ]; 8519 jQuery.fn[ name ] = function( speed, easing, callback ) { 8520 return speed == null || typeof speed === "boolean" ? 8521 cssFn.apply( this, arguments ) : 8522 this.animate( genFx( name, true ), speed, easing, callback ); 8523 }; 8524} ); 8525 8526// Generate shortcuts for custom animations 8527jQuery.each( { 8528 slideDown: genFx( "show" ), 8529 slideUp: genFx( "hide" ), 8530 slideToggle: genFx( "toggle" ), 8531 fadeIn: { opacity: "show" }, 8532 fadeOut: { opacity: "hide" }, 8533 fadeToggle: { opacity: "toggle" } 8534}, function( name, props ) { 8535 jQuery.fn[ name ] = function( speed, easing, callback ) { 8536 return this.animate( props, speed, easing, callback ); 8537 }; 8538} ); 8539 8540jQuery.timers = []; 8541jQuery.fx.tick = function() { 8542 var timer, 8543 i = 0, 8544 timers = jQuery.timers; 8545 8546 fxNow = Date.now(); 8547 8548 for ( ; i < timers.length; i++ ) { 8549 timer = timers[ i ]; 8550 8551 // Run the timer and safely remove it when done (allowing for external removal) 8552 if ( !timer() && timers[ i ] === timer ) { 8553 timers.splice( i--, 1 ); 8554 } 8555 } 8556 8557 if ( !timers.length ) { 8558 jQuery.fx.stop(); 8559 } 8560 fxNow = undefined; 8561}; 8562 8563jQuery.fx.timer = function( timer ) { 8564 jQuery.timers.push( timer ); 8565 jQuery.fx.start(); 8566}; 8567 8568jQuery.fx.interval = 13; 8569jQuery.fx.start = function() { 8570 if ( inProgress ) { 8571 return; 8572 } 8573 8574 inProgress = true; 8575 schedule(); 8576}; 8577 8578jQuery.fx.stop = function() { 8579 inProgress = null; 8580}; 8581 8582jQuery.fx.speeds = { 8583 slow: 600, 8584 fast: 200, 8585 8586 // Default speed 8587 _default: 400 8588}; 8589 8590 8591// Based off of the plugin by Clint Helfers, with permission. 8592jQuery.fn.delay = function( time, type ) { 8593 time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; 8594 type = type || "fx"; 8595 8596 return this.queue( type, function( next, hooks ) { 8597 var timeout = window.setTimeout( next, time ); 8598 hooks.stop = function() { 8599 window.clearTimeout( timeout ); 8600 }; 8601 } ); 8602}; 8603 8604 8605( function() { 8606 var input = document.createElement( "input" ), 8607 select = document.createElement( "select" ), 8608 opt = select.appendChild( document.createElement( "option" ) ); 8609 8610 input.type = "checkbox"; 8611 8612 // Support: Android <=4.3 only 8613 // Default value for a checkbox should be "on" 8614 support.checkOn = input.value !== ""; 8615 8616 // Support: IE <=11 only 8617 // Must access selectedIndex to make default options select 8618 support.optSelected = opt.selected; 8619 8620 // Support: IE <=11 only 8621 // An input loses its value after becoming a radio 8622 input = document.createElement( "input" ); 8623 input.value = "t"; 8624 input.type = "radio"; 8625 support.radioValue = input.value === "t"; 8626} )(); 8627 8628 8629var boolHook, 8630 attrHandle = jQuery.expr.attrHandle; 8631 8632jQuery.fn.extend( { 8633 attr: function( name, value ) { 8634 return access( this, jQuery.attr, name, value, arguments.length > 1 ); 8635 }, 8636 8637 removeAttr: function( name ) { 8638 return this.each( function() { 8639 jQuery.removeAttr( this, name ); 8640 } ); 8641 } 8642} ); 8643 8644jQuery.extend( { 8645 attr: function( elem, name, value ) { 8646 var ret, hooks, 8647 nType = elem.nodeType; 8648 8649 // Don't get/set attributes on text, comment and attribute nodes 8650 if ( nType === 3 || nType === 8 || nType === 2 ) { 8651 return; 8652 } 8653 8654 // Fallback to prop when attributes are not supported 8655 if ( typeof elem.getAttribute === "undefined" ) { 8656 return jQuery.prop( elem, name, value ); 8657 } 8658 8659 // Attribute hooks are determined by the lowercase version 8660 // Grab necessary hook if one is defined 8661 if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { 8662 hooks = jQuery.attrHooks[ name.toLowerCase() ] || 8663 ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); 8664 } 8665 8666 if ( value !== undefined ) { 8667 if ( value === null ) { 8668 jQuery.removeAttr( elem, name ); 8669 return; 8670 } 8671 8672 if ( hooks && "set" in hooks && 8673 ( ret = hooks.set( elem, value, name ) ) !== undefined ) { 8674 return ret; 8675 } 8676 8677 elem.setAttribute( name, value + "" ); 8678 return value; 8679 } 8680 8681 if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { 8682 return ret; 8683 } 8684 8685 ret = jQuery.find.attr( elem, name ); 8686 8687 // Non-existent attributes return null, we normalize to undefined 8688 return ret == null ? undefined : ret; 8689 }, 8690 8691 attrHooks: { 8692 type: { 8693 set: function( elem, value ) { 8694 if ( !support.radioValue && value === "radio" && 8695 nodeName( elem, "input" ) ) { 8696 var val = elem.value; 8697 elem.setAttribute( "type", value ); 8698 if ( val ) { 8699 elem.value = val; 8700 } 8701 return value; 8702 } 8703 } 8704 } 8705 }, 8706 8707 removeAttr: function( elem, value ) { 8708 var name, 8709 i = 0, 8710 8711 // Attribute names can contain non-HTML whitespace characters 8712 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 8713 attrNames = value && value.match( rnothtmlwhite ); 8714 8715 if ( attrNames && elem.nodeType === 1 ) { 8716 while ( ( name = attrNames[ i++ ] ) ) { 8717 elem.removeAttribute( name ); 8718 } 8719 } 8720 } 8721} ); 8722 8723// Hooks for boolean attributes 8724boolHook = { 8725 set: function( elem, value, name ) { 8726 if ( value === false ) { 8727 8728 // Remove boolean attributes when set to false 8729 jQuery.removeAttr( elem, name ); 8730 } else { 8731 elem.setAttribute( name, name ); 8732 } 8733 return name; 8734 } 8735}; 8736 8737jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { 8738 var getter = attrHandle[ name ] || jQuery.find.attr; 8739 8740 attrHandle[ name ] = function( elem, name, isXML ) { 8741 var ret, handle, 8742 lowercaseName = name.toLowerCase(); 8743 8744 if ( !isXML ) { 8745 8746 // Avoid an infinite loop by temporarily removing this function from the getter 8747 handle = attrHandle[ lowercaseName ]; 8748 attrHandle[ lowercaseName ] = ret; 8749 ret = getter( elem, name, isXML ) != null ? 8750 lowercaseName : 8751 null; 8752 attrHandle[ lowercaseName ] = handle; 8753 } 8754 return ret; 8755 }; 8756} ); 8757 8758 8759 8760 8761var rfocusable = /^(?:input|select|textarea|button)$/i, 8762 rclickable = /^(?:a|area)$/i; 8763 8764jQuery.fn.extend( { 8765 prop: function( name, value ) { 8766 return access( this, jQuery.prop, name, value, arguments.length > 1 ); 8767 }, 8768 8769 removeProp: function( name ) { 8770 return this.each( function() { 8771 delete this[ jQuery.propFix[ name ] || name ]; 8772 } ); 8773 } 8774} ); 8775 8776jQuery.extend( { 8777 prop: function( elem, name, value ) { 8778 var ret, hooks, 8779 nType = elem.nodeType; 8780 8781 // Don't get/set properties on text, comment and attribute nodes 8782 if ( nType === 3 || nType === 8 || nType === 2 ) { 8783 return; 8784 } 8785 8786 if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { 8787 8788 // Fix name and attach hooks 8789 name = jQuery.propFix[ name ] || name; 8790 hooks = jQuery.propHooks[ name ]; 8791 } 8792 8793 if ( value !== undefined ) { 8794 if ( hooks && "set" in hooks && 8795 ( ret = hooks.set( elem, value, name ) ) !== undefined ) { 8796 return ret; 8797 } 8798 8799 return ( elem[ name ] = value ); 8800 } 8801 8802 if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { 8803 return ret; 8804 } 8805 8806 return elem[ name ]; 8807 }, 8808 8809 propHooks: { 8810 tabIndex: { 8811 get: function( elem ) { 8812 8813 // Support: IE <=9 - 11 only 8814 // elem.tabIndex doesn't always return the 8815 // correct value when it hasn't been explicitly set 8816 // Use proper attribute retrieval (trac-12072) 8817 var tabindex = jQuery.find.attr( elem, "tabindex" ); 8818 8819 if ( tabindex ) { 8820 return parseInt( tabindex, 10 ); 8821 } 8822 8823 if ( 8824 rfocusable.test( elem.nodeName ) || 8825 rclickable.test( elem.nodeName ) && 8826 elem.href 8827 ) { 8828 return 0; 8829 } 8830 8831 return -1; 8832 } 8833 } 8834 }, 8835 8836 propFix: { 8837 "for": "htmlFor", 8838 "class": "className" 8839 } 8840} ); 8841 8842// Support: IE <=11 only 8843// Accessing the selectedIndex property 8844// forces the browser to respect setting selected 8845// on the option 8846// The getter ensures a default option is selected 8847// when in an optgroup 8848// eslint rule "no-unused-expressions" is disabled for this code 8849// since it considers such accessions noop 8850if ( !support.optSelected ) { 8851 jQuery.propHooks.selected = { 8852 get: function( elem ) { 8853 8854 /* eslint no-unused-expressions: "off" */ 8855 8856 var parent = elem.parentNode; 8857 if ( parent && parent.parentNode ) { 8858 parent.parentNode.selectedIndex; 8859 } 8860 return null; 8861 }, 8862 set: function( elem ) { 8863 8864 /* eslint no-unused-expressions: "off" */ 8865 8866 var parent = elem.parentNode; 8867 if ( parent ) { 8868 parent.selectedIndex; 8869 8870 if ( parent.parentNode ) { 8871 parent.parentNode.selectedIndex; 8872 } 8873 } 8874 } 8875 }; 8876} 8877 8878jQuery.each( [ 8879 "tabIndex", 8880 "readOnly", 8881 "maxLength", 8882 "cellSpacing", 8883 "cellPadding", 8884 "rowSpan", 8885 "colSpan", 8886 "useMap", 8887 "frameBorder", 8888 "contentEditable" 8889], function() { 8890 jQuery.propFix[ this.toLowerCase() ] = this; 8891} ); 8892 8893 8894 8895 8896 // Strip and collapse whitespace according to HTML spec 8897 // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace 8898 function stripAndCollapse( value ) { 8899 var tokens = value.match( rnothtmlwhite ) || []; 8900 return tokens.join( " " ); 8901 } 8902 8903 8904function getClass( elem ) { 8905 return elem.getAttribute && elem.getAttribute( "class" ) || ""; 8906} 8907 8908function classesToArray( value ) { 8909 if ( Array.isArray( value ) ) { 8910 return value; 8911 } 8912 if ( typeof value === "string" ) { 8913 return value.match( rnothtmlwhite ) || []; 8914 } 8915 return []; 8916} 8917 8918jQuery.fn.extend( { 8919 addClass: function( value ) { 8920 var classNames, cur, curValue, className, i, finalValue; 8921 8922 if ( isFunction( value ) ) { 8923 return this.each( function( j ) { 8924 jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); 8925 } ); 8926 } 8927 8928 classNames = classesToArray( value ); 8929 8930 if ( classNames.length ) { 8931 return this.each( function() { 8932 curValue = getClass( this ); 8933 cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 8934 8935 if ( cur ) { 8936 for ( i = 0; i < classNames.length; i++ ) { 8937 className = classNames[ i ]; 8938 if ( cur.indexOf( " " + className + " " ) < 0 ) { 8939 cur += className + " "; 8940 } 8941 } 8942 8943 // Only assign if different to avoid unneeded rendering. 8944 finalValue = stripAndCollapse( cur ); 8945 if ( curValue !== finalValue ) { 8946 this.setAttribute( "class", finalValue ); 8947 } 8948 } 8949 } ); 8950 } 8951 8952 return this; 8953 }, 8954 8955 removeClass: function( value ) { 8956 var classNames, cur, curValue, className, i, finalValue; 8957 8958 if ( isFunction( value ) ) { 8959 return this.each( function( j ) { 8960 jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); 8961 } ); 8962 } 8963 8964 if ( !arguments.length ) { 8965 return this.attr( "class", "" ); 8966 } 8967 8968 classNames = classesToArray( value ); 8969 8970 if ( classNames.length ) { 8971 return this.each( function() { 8972 curValue = getClass( this ); 8973 8974 // This expression is here for better compressibility (see addClass) 8975 cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); 8976 8977 if ( cur ) { 8978 for ( i = 0; i < classNames.length; i++ ) { 8979 className = classNames[ i ]; 8980 8981 // Remove *all* instances 8982 while ( cur.indexOf( " " + className + " " ) > -1 ) { 8983 cur = cur.replace( " " + className + " ", " " ); 8984 } 8985 } 8986 8987 // Only assign if different to avoid unneeded rendering. 8988 finalValue = stripAndCollapse( cur ); 8989 if ( curValue !== finalValue ) { 8990 this.setAttribute( "class", finalValue ); 8991 } 8992 } 8993 } ); 8994 } 8995 8996 return this; 8997 }, 8998 8999 toggleClass: function( value, stateVal ) { 9000 var classNames, className, i, self, 9001 type = typeof value, 9002 isValidValue = type === "string" || Array.isArray( value ); 9003 9004 if ( isFunction( value ) ) { 9005 return this.each( function( i ) { 9006 jQuery( this ).toggleClass( 9007 value.call( this, i, getClass( this ), stateVal ), 9008 stateVal 9009 ); 9010 } ); 9011 } 9012 9013 if ( typeof stateVal === "boolean" && isValidValue ) { 9014 return stateVal ? this.addClass( value ) : this.removeClass( value ); 9015 } 9016 9017 classNames = classesToArray( value ); 9018 9019 return this.each( function() { 9020 if ( isValidValue ) { 9021 9022 // Toggle individual class names 9023 self = jQuery( this ); 9024 9025 for ( i = 0; i < classNames.length; i++ ) { 9026 className = classNames[ i ]; 9027 9028 // Check each className given, space separated list 9029 if ( self.hasClass( className ) ) { 9030 self.removeClass( className ); 9031 } else { 9032 self.addClass( className ); 9033 } 9034 } 9035 9036 // Toggle whole class name 9037 } else if ( value === undefined || type === "boolean" ) { 9038 className = getClass( this ); 9039 if ( className ) { 9040 9041 // Store className if set 9042 dataPriv.set( this, "__className__", className ); 9043 } 9044 9045 // If the element has a class name or if we're passed `false`, 9046 // then remove the whole classname (if there was one, the above saved it). 9047 // Otherwise bring back whatever was previously saved (if anything), 9048 // falling back to the empty string if nothing was stored. 9049 if ( this.setAttribute ) { 9050 this.setAttribute( "class", 9051 className || value === false ? 9052 "" : 9053 dataPriv.get( this, "__className__" ) || "" 9054 ); 9055 } 9056 } 9057 } ); 9058 }, 9059 9060 hasClass: function( selector ) { 9061 var className, elem, 9062 i = 0; 9063 9064 className = " " + selector + " "; 9065 while ( ( elem = this[ i++ ] ) ) { 9066 if ( elem.nodeType === 1 && 9067 ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { 9068 return true; 9069 } 9070 } 9071 9072 return false;
vendor: 1,385 bytes, lines 9073-9142
9073 } 9074} ); 9075 9076 9077 9078 9079var rreturn = /\r/g; 9080 9081jQuery.fn.extend( { 9082 val: function( value ) { 9083 var hooks, ret, valueIsFunction, 9084 elem = this[ 0 ]; 9085 9086 if ( !arguments.length ) { 9087 if ( elem ) { 9088 hooks = jQuery.valHooks[ elem.type ] || 9089 jQuery.valHooks[ elem.nodeName.toLowerCase() ]; 9090 9091 if ( hooks && 9092 "get" in hooks && 9093 ( ret = hooks.get( elem, "value" ) ) !== undefined 9094 ) { 9095 return ret; 9096 } 9097 9098 ret = elem.value; 9099 9100 // Handle most common string cases 9101 if ( typeof ret === "string" ) { 9102 return ret.replace( rreturn, "" ); 9103 } 9104 9105 // Handle cases where value is null/undef or number 9106 return ret == null ? "" : ret; 9107 } 9108 9109 return; 9110 } 9111 9112 valueIsFunction = isFunction( value ); 9113 9114 return this.each( function( i ) { 9115 var val; 9116 9117 if ( this.nodeType !== 1 ) { 9118 return; 9119 } 9120 9121 if ( valueIsFunction ) { 9122 val = value.call( this, i, jQuery( this ).val() ); 9123 } else { 9124 val = value; 9125 } 9126 9127 // Treat null/undefined as ""; convert numbers to string 9128 if ( val == null ) { 9129 val = ""; 9130 9131 } else if ( typeof val === "number" ) { 9132 val += ""; 9133 9134 } else if ( Array.isArray( val ) ) { 9135 val = jQuery.map( val, function( value ) { 9136 return value == null ? "" : value + ""; 9137 } ); 9138 } 9139 9140 hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; 9141 9142 // If set returns undefined, fall back to normal setting
vendor: 43,419 bytes, lines 9143-10832
9143 if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { 9144 this.value = val; 9145 } 9146 } ); 9147 } 9148} ); 9149 9150jQuery.extend( { 9151 valHooks: { 9152 option: { 9153 get: function( elem ) { 9154 9155 var val = jQuery.find.attr( elem, "value" ); 9156 return val != null ? 9157 val : 9158 9159 // Support: IE <=10 - 11 only 9160 // option.text throws exceptions (trac-14686, trac-14858) 9161 // Strip and collapse whitespace 9162 // https://html.spec.whatwg.org/#strip-and-collapse-whitespace 9163 stripAndCollapse( jQuery.text( elem ) ); 9164 } 9165 }, 9166 select: { 9167 get: function( elem ) { 9168 var value, option, i, 9169 options = elem.options, 9170 index = elem.selectedIndex, 9171 one = elem.type === "select-one", 9172 values = one ? null : [], 9173 max = one ? index + 1 : options.length; 9174 9175 if ( index < 0 ) { 9176 i = max; 9177 9178 } else { 9179 i = one ? index : 0; 9180 } 9181 9182 // Loop through all the selected options 9183 for ( ; i < max; i++ ) { 9184 option = options[ i ]; 9185 9186 // Support: IE <=9 only 9187 // IE8-9 doesn't update selected after form reset (trac-2551) 9188 if ( ( option.selected || i === index ) && 9189 9190 // Don't return options that are disabled or in a disabled optgroup 9191 !option.disabled && 9192 ( !option.parentNode.disabled || 9193 !nodeName( option.parentNode, "optgroup" ) ) ) { 9194 9195 // Get the specific value for the option 9196 value = jQuery( option ).val(); 9197 9198 // We don't need an array for one selects 9199 if ( one ) { 9200 return value; 9201 } 9202 9203 // Multi-Selects return an array 9204 values.push( value ); 9205 } 9206 } 9207 9208 return values; 9209 }, 9210 9211 set: function( elem, value ) { 9212 var optionSet, option, 9213 options = elem.options, 9214 values = jQuery.makeArray( value ), 9215 i = options.length; 9216 9217 while ( i-- ) { 9218 option = options[ i ]; 9219 9220 /* eslint-disable no-cond-assign */ 9221 9222 if ( option.selected = 9223 jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 9224 ) { 9225 optionSet = true; 9226 } 9227 9228 /* eslint-enable no-cond-assign */ 9229 } 9230 9231 // Force browsers to behave consistently when non-matching value is set 9232 if ( !optionSet ) { 9233 elem.selectedIndex = -1; 9234 } 9235 return values; 9236 } 9237 } 9238 } 9239} ); 9240 9241// Radios and checkboxes getter/setter 9242jQuery.each( [ "radio", "checkbox" ], function() { 9243 jQuery.valHooks[ this ] = { 9244 set: function( elem, value ) { 9245 if ( Array.isArray( value ) ) { 9246 return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); 9247 } 9248 } 9249 }; 9250 if ( !support.checkOn ) { 9251 jQuery.valHooks[ this ].get = function( elem ) { 9252 return elem.getAttribute( "value" ) === null ? "on" : elem.value; 9253 }; 9254 } 9255} ); 9256 9257 9258 9259 9260// Return jQuery for attributes-only inclusion 9261var location = window.location; 9262 9263var nonce = { guid: Date.now() }; 9264 9265var rquery = ( /\?/ ); 9266 9267 9268 9269// Cross-browser xml parsing 9270jQuery.parseXML = function( data ) { 9271 var xml, parserErrorElem; 9272 if ( !data || typeof data !== "string" ) { 9273 return null; 9274 } 9275 9276 // Support: IE 9 - 11 only 9277 // IE throws on parseFromString with invalid input. 9278 try { 9279 xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); 9280 } catch ( e ) {} 9281 9282 parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; 9283 if ( !xml || parserErrorElem ) { 9284 jQuery.error( "Invalid XML: " + ( 9285 parserErrorElem ? 9286 jQuery.map( parserErrorElem.childNodes, function( el ) { 9287 return el.textContent; 9288 } ).join( "\n" ) : 9289 data 9290 ) ); 9291 } 9292 return xml; 9293}; 9294 9295 9296var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, 9297 stopPropagationCallback = function( e ) { 9298 e.stopPropagation(); 9299 }; 9300 9301jQuery.extend( jQuery.event, { 9302 9303 trigger: function( event, data, elem, onlyHandlers ) { 9304 9305 var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, 9306 eventPath = [ elem || document ], 9307 type = hasOwn.call( event, "type" ) ? event.type : event, 9308 namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; 9309 9310 cur = lastElement = tmp = elem = elem || document; 9311 9312 // Don't do events on text and comment nodes 9313 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 9314 return; 9315 } 9316 9317 // focus/blur morphs to focusin/out; ensure we're not firing them right now 9318 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { 9319 return; 9320 } 9321 9322 if ( type.indexOf( "." ) > -1 ) { 9323 9324 // Namespaced trigger; create a regexp to match event type in handle() 9325 namespaces = type.split( "." ); 9326 type = namespaces.shift(); 9327 namespaces.sort(); 9328 } 9329 ontype = type.indexOf( ":" ) < 0 && "on" + type; 9330 9331 // Caller can pass in a jQuery.Event object, Object, or just an event type string 9332 event = event[ jQuery.expando ] ? 9333 event : 9334 new jQuery.Event( type, typeof event === "object" && event ); 9335 9336 // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) 9337 event.isTrigger = onlyHandlers ? 2 : 3; 9338 event.namespace = namespaces.join( "." ); 9339 event.rnamespace = event.namespace ? 9340 new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : 9341 null; 9342 9343 // Clean up the event in case it is being reused 9344 event.result = undefined; 9345 if ( !event.target ) { 9346 event.target = elem; 9347 } 9348 9349 // Clone any incoming data and prepend the event, creating the handler arg list 9350 data = data == null ? 9351 [ event ] : 9352 jQuery.makeArray( data, [ event ] ); 9353 9354 // Allow special events to draw outside the lines 9355 special = jQuery.event.special[ type ] || {}; 9356 if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { 9357 return; 9358 } 9359 9360 // Determine event propagation path in advance, per W3C events spec (trac-9951) 9361 // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) 9362 if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { 9363 9364 bubbleType = special.delegateType || type; 9365 if ( !rfocusMorph.test( bubbleType + type ) ) { 9366 cur = cur.parentNode; 9367 } 9368 for ( ; cur; cur = cur.parentNode ) { 9369 eventPath.push( cur ); 9370 tmp = cur; 9371 } 9372 9373 // Only add window if we got to document (e.g., not plain obj or detached DOM) 9374 if ( tmp === ( elem.ownerDocument || document ) ) { 9375 eventPath.push( tmp.defaultView || tmp.parentWindow || window ); 9376 } 9377 } 9378 9379 // Fire handlers on the event path 9380 i = 0; 9381 while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { 9382 lastElement = cur; 9383 event.type = i > 1 ? 9384 bubbleType : 9385 special.bindType || type; 9386 9387 // jQuery handler 9388 handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && 9389 dataPriv.get( cur, "handle" ); 9390 if ( handle ) { 9391 handle.apply( cur, data ); 9392 } 9393 9394 // Native handler 9395 handle = ontype && cur[ ontype ]; 9396 if ( handle && handle.apply && acceptData( cur ) ) { 9397 event.result = handle.apply( cur, data ); 9398 if ( event.result === false ) { 9399 event.preventDefault(); 9400 } 9401 } 9402 } 9403 event.type = type; 9404 9405 // If nobody prevented the default action, do it now 9406 if ( !onlyHandlers && !event.isDefaultPrevented() ) { 9407 9408 if ( ( !special._default || 9409 special._default.apply( eventPath.pop(), data ) === false ) && 9410 acceptData( elem ) ) { 9411 9412 // Call a native DOM method on the target with the same name as the event. 9413 // Don't do default actions on window, that's where global variables be (trac-6170) 9414 if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { 9415 9416 // Don't re-trigger an onFOO event when we call its FOO() method 9417 tmp = elem[ ontype ]; 9418 9419 if ( tmp ) { 9420 elem[ ontype ] = null; 9421 } 9422 9423 // Prevent re-triggering of the same event, since we already bubbled it above 9424 jQuery.event.triggered = type; 9425 9426 if ( event.isPropagationStopped() ) { 9427 lastElement.addEventListener( type, stopPropagationCallback ); 9428 } 9429 9430 elem[ type ](); 9431 9432 if ( event.isPropagationStopped() ) { 9433 lastElement.removeEventListener( type, stopPropagationCallback ); 9434 } 9435 9436 jQuery.event.triggered = undefined; 9437 9438 if ( tmp ) { 9439 elem[ ontype ] = tmp; 9440 } 9441 } 9442 } 9443 } 9444 9445 return event.result; 9446 }, 9447 9448 // Piggyback on a donor event to simulate a different one 9449 // Used only for `focus(in | out)` events 9450 simulate: function( type, elem, event ) { 9451 var e = jQuery.extend( 9452 new jQuery.Event(), 9453 event, 9454 { 9455 type: type, 9456 isSimulated: true 9457 } 9458 ); 9459 9460 jQuery.event.trigger( e, null, elem ); 9461 } 9462 9463} ); 9464 9465jQuery.fn.extend( { 9466 9467 trigger: function( type, data ) { 9468 return this.each( function() { 9469 jQuery.event.trigger( type, data, this ); 9470 } ); 9471 }, 9472 triggerHandler: function( type, data ) { 9473 var elem = this[ 0 ]; 9474 if ( elem ) { 9475 return jQuery.event.trigger( type, data, elem, true ); 9476 } 9477 } 9478} ); 9479 9480 9481var 9482 rbracket = /\[\]$/, 9483 rCRLF = /\r?\n/g, 9484 rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, 9485 rsubmittable = /^(?:input|select|textarea|keygen)/i; 9486 9487function buildParams( prefix, obj, traditional, add ) { 9488 var name; 9489 9490 if ( Array.isArray( obj ) ) { 9491 9492 // Serialize array item. 9493 jQuery.each( obj, function( i, v ) { 9494 if ( traditional || rbracket.test( prefix ) ) { 9495 9496 // Treat each array item as a scalar. 9497 add( prefix, v ); 9498 9499 } else { 9500 9501 // Item is non-scalar (array or object), encode its numeric index. 9502 buildParams( 9503 prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", 9504 v, 9505 traditional, 9506 add 9507 ); 9508 } 9509 } ); 9510 9511 } else if ( !traditional && toType( obj ) === "object" ) { 9512 9513 // Serialize object item. 9514 for ( name in obj ) { 9515 buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); 9516 } 9517 9518 } else { 9519 9520 // Serialize scalar item. 9521 add( prefix, obj ); 9522 } 9523} 9524 9525// Serialize an array of form elements or a set of 9526// key/values into a query string 9527jQuery.param = function( a, traditional ) { 9528 var prefix, 9529 s = [], 9530 add = function( key, valueOrFunction ) { 9531 9532 // If value is a function, invoke it and use its return value 9533 var value = isFunction( valueOrFunction ) ? 9534 valueOrFunction() : 9535 valueOrFunction; 9536 9537 s[ s.length ] = encodeURIComponent( key ) + "=" + 9538 encodeURIComponent( value == null ? "" : value ); 9539 }; 9540 9541 if ( a == null ) { 9542 return ""; 9543 } 9544 9545 // If an array was passed in, assume that it is an array of form elements. 9546 if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { 9547 9548 // Serialize the form elements 9549 jQuery.each( a, function() { 9550 add( this.name, this.value ); 9551 } ); 9552 9553 } else { 9554 9555 // If traditional, encode the "old" way (the way 1.3.2 or older 9556 // did it), otherwise encode params recursively. 9557 for ( prefix in a ) { 9558 buildParams( prefix, a[ prefix ], traditional, add ); 9559 } 9560 } 9561 9562 // Return the resulting serialization 9563 return s.join( "&" ); 9564}; 9565 9566jQuery.fn.extend( { 9567 serialize: function() { 9568 return jQuery.param( this.serializeArray() ); 9569 }, 9570 serializeArray: function() { 9571 return this.map( function() { 9572 9573 // Can add propHook for "elements" to filter or add form elements 9574 var elements = jQuery.prop( this, "elements" ); 9575 return elements ? jQuery.makeArray( elements ) : this; 9576 } ).filter( function() { 9577 var type = this.type; 9578 9579 // Use .is( ":disabled" ) so that fieldset[disabled] works 9580 return this.name && !jQuery( this ).is( ":disabled" ) && 9581 rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && 9582 ( this.checked || !rcheckableType.test( type ) ); 9583 } ).map( function( _i, elem ) { 9584 var val = jQuery( this ).val(); 9585 9586 if ( val == null ) { 9587 return null; 9588 } 9589 9590 if ( Array.isArray( val ) ) { 9591 return jQuery.map( val, function( val ) { 9592 return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 9593 } ); 9594 } 9595 9596 return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 9597 } ).get(); 9598 } 9599} ); 9600 9601 9602var 9603 r20 = /%20/g, 9604 rhash = /#.*$/, 9605 rantiCache = /([?&])_=[^&]*/, 9606 rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, 9607 9608 // trac-7653, trac-8125, trac-8152: local protocol detection 9609 rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, 9610 rnoContent = /^(?:GET|HEAD)$/, 9611 rprotocol = /^\/\//, 9612 9613 /* Prefilters 9614 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) 9615 * 2) These are called: 9616 * - BEFORE asking for a transport 9617 * - AFTER param serialization (s.data is a string if s.processData is true) 9618 * 3) key is the dataType 9619 * 4) the catchall symbol "*" can be used 9620 * 5) execution will start with transport dataType and THEN continue down to "*" if needed 9621 */ 9622 prefilters = {}, 9623 9624 /* Transports bindings 9625 * 1) key is the dataType 9626 * 2) the catchall symbol "*" can be used 9627 * 3) selection will start with transport dataType and THEN go to "*" if needed 9628 */ 9629 transports = {}, 9630 9631 // Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression 9632 allTypes = "*/".concat( "*" ), 9633 9634 // Anchor tag for parsing the document origin 9635 originAnchor = document.createElement( "a" ); 9636 9637originAnchor.href = location.href; 9638 9639// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport 9640function addToPrefiltersOrTransports( structure ) { 9641 9642 // dataTypeExpression is optional and defaults to "*" 9643 return function( dataTypeExpression, func ) { 9644 9645 if ( typeof dataTypeExpression !== "string" ) { 9646 func = dataTypeExpression; 9647 dataTypeExpression = "*"; 9648 } 9649 9650 var dataType, 9651 i = 0, 9652 dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; 9653 9654 if ( isFunction( func ) ) { 9655 9656 // For each dataType in the dataTypeExpression 9657 while ( ( dataType = dataTypes[ i++ ] ) ) { 9658 9659 // Prepend if requested 9660 if ( dataType[ 0 ] === "+" ) { 9661 dataType = dataType.slice( 1 ) || "*"; 9662 ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); 9663 9664 // Otherwise append 9665 } else { 9666 ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); 9667 } 9668 } 9669 } 9670 }; 9671} 9672 9673// Base inspection function for prefilters and transports 9674function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { 9675 9676 var inspected = {}, 9677 seekingTransport = ( structure === transports ); 9678 9679 function inspect( dataType ) { 9680 var selected; 9681 inspected[ dataType ] = true; 9682 jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { 9683 var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); 9684 if ( typeof dataTypeOrTransport === "string" && 9685 !seekingTransport && !inspected[ dataTypeOrTransport ] ) { 9686 9687 options.dataTypes.unshift( dataTypeOrTransport ); 9688 inspect( dataTypeOrTransport ); 9689 return false; 9690 } else if ( seekingTransport ) { 9691 return !( selected = dataTypeOrTransport ); 9692 } 9693 } ); 9694 return selected; 9695 } 9696 9697 return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); 9698} 9699 9700// A special extend for ajax options 9701// that takes "flat" options (not to be deep extended) 9702// Fixes trac-9887 9703function ajaxExtend( target, src ) { 9704 var key, deep, 9705 flatOptions = jQuery.ajaxSettings.flatOptions || {}; 9706 9707 for ( key in src ) { 9708 if ( src[ key ] !== undefined ) { 9709 ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; 9710 } 9711 } 9712 if ( deep ) { 9713 jQuery.extend( true, target, deep ); 9714 } 9715 9716 return target; 9717} 9718 9719/* Handles responses to an ajax request: 9720 * - finds the right dataType (mediates between content-type and expected dataType) 9721 * - returns the corresponding response 9722 */ 9723function ajaxHandleResponses( s, jqXHR, responses ) { 9724 9725 var ct, type, finalDataType, firstDataType, 9726 contents = s.contents, 9727 dataTypes = s.dataTypes; 9728 9729 // Remove auto dataType and get content-type in the process 9730 while ( dataTypes[ 0 ] === "*" ) { 9731 dataTypes.shift(); 9732 if ( ct === undefined ) { 9733 ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); 9734 } 9735 } 9736 9737 // Check if we're dealing with a known content-type 9738 if ( ct ) { 9739 for ( type in contents ) { 9740 if ( contents[ type ] && contents[ type ].test( ct ) ) { 9741 dataTypes.unshift( type ); 9742 break; 9743 } 9744 } 9745 } 9746 9747 // Check to see if we have a response for the expected dataType 9748 if ( dataTypes[ 0 ] in responses ) { 9749 finalDataType = dataTypes[ 0 ]; 9750 } else { 9751 9752 // Try convertible dataTypes 9753 for ( type in responses ) { 9754 if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { 9755 finalDataType = type; 9756 break; 9757 } 9758 if ( !firstDataType ) { 9759 firstDataType = type; 9760 } 9761 } 9762 9763 // Or just use first one 9764 finalDataType = finalDataType || firstDataType; 9765 } 9766 9767 // If we found a dataType 9768 // We add the dataType to the list if needed 9769 // and return the corresponding response 9770 if ( finalDataType ) { 9771 if ( finalDataType !== dataTypes[ 0 ] ) { 9772 dataTypes.unshift( finalDataType ); 9773 } 9774 return responses[ finalDataType ]; 9775 } 9776} 9777 9778/* Chain conversions given the request and the original response 9779 * Also sets the responseXXX fields on the jqXHR instance 9780 */ 9781function ajaxConvert( s, response, jqXHR, isSuccess ) { 9782 var conv2, current, conv, tmp, prev, 9783 converters = {}, 9784 9785 // Work with a copy of dataTypes in case we need to modify it for conversion 9786 dataTypes = s.dataTypes.slice(); 9787 9788 // Create converters map with lowercased keys 9789 if ( dataTypes[ 1 ] ) { 9790 for ( conv in s.converters ) { 9791 converters[ conv.toLowerCase() ] = s.converters[ conv ]; 9792 } 9793 } 9794 9795 current = dataTypes.shift(); 9796 9797 // Convert to each sequential dataType 9798 while ( current ) { 9799 9800 if ( s.responseFields[ current ] ) { 9801 jqXHR[ s.responseFields[ current ] ] = response; 9802 } 9803 9804 // Apply the dataFilter if provided 9805 if ( !prev && isSuccess && s.dataFilter ) { 9806 response = s.dataFilter( response, s.dataType ); 9807 } 9808 9809 prev = current; 9810 current = dataTypes.shift(); 9811 9812 if ( current ) { 9813 9814 // There's only work to do if current dataType is non-auto 9815 if ( current === "*" ) { 9816 9817 current = prev; 9818 9819 // Convert response if prev dataType is non-auto and differs from current 9820 } else if ( prev !== "*" && prev !== current ) { 9821 9822 // Seek a direct converter 9823 conv = converters[ prev + " " + current ] || converters[ "* " + current ]; 9824 9825 // If none found, seek a pair 9826 if ( !conv ) { 9827 for ( conv2 in converters ) { 9828 9829 // If conv2 outputs current 9830 tmp = conv2.split( " " ); 9831 if ( tmp[ 1 ] === current ) { 9832 9833 // If prev can be converted to accepted input 9834 conv = converters[ prev + " " + tmp[ 0 ] ] || 9835 converters[ "* " + tmp[ 0 ] ]; 9836 if ( conv ) { 9837 9838 // Condense equivalence converters 9839 if ( conv === true ) { 9840 conv = converters[ conv2 ]; 9841 9842 // Otherwise, insert the intermediate dataType 9843 } else if ( converters[ conv2 ] !== true ) { 9844 current = tmp[ 0 ]; 9845 dataTypes.unshift( tmp[ 1 ] ); 9846 } 9847 break; 9848 } 9849 } 9850 } 9851 } 9852 9853 // Apply converter (if not an equivalence) 9854 if ( conv !== true ) { 9855 9856 // Unless errors are allowed to bubble, catch and return them 9857 if ( conv && s.throws ) { 9858 response = conv( response ); 9859 } else { 9860 try { 9861 response = conv( response ); 9862 } catch ( e ) { 9863 return { 9864 state: "parsererror", 9865 error: conv ? e : "No conversion from " + prev + " to " + current 9866 }; 9867 } 9868 } 9869 } 9870 } 9871 } 9872 } 9873 9874 return { state: "success", data: response }; 9875} 9876 9877jQuery.extend( { 9878 9879 // Counter for holding the number of active queries 9880 active: 0, 9881 9882 // Last-Modified header cache for next request 9883 lastModified: {}, 9884 etag: {}, 9885 9886 ajaxSettings: { 9887 url: location.href, 9888 type: "GET", 9889 isLocal: rlocalProtocol.test( location.protocol ), 9890 global: true, 9891 processData: true, 9892 async: true, 9893 contentType: "application/x-www-form-urlencoded; charset=UTF-8", 9894 9895 /* 9896 timeout: 0, 9897 data: null, 9898 dataType: null, 9899 username: null, 9900 password: null, 9901 cache: null, 9902 throws: false, 9903 traditional: false, 9904 headers: {}, 9905 */ 9906 9907 accepts: { 9908 "*": allTypes, 9909 text: "text/plain", 9910 html: "text/html", 9911 xml: "application/xml, text/xml", 9912 json: "application/json, text/javascript" 9913 }, 9914 9915 contents: { 9916 xml: /\bxml\b/, 9917 html: /\bhtml/, 9918 json: /\bjson\b/ 9919 }, 9920 9921 responseFields: { 9922 xml: "responseXML", 9923 text: "responseText", 9924 json: "responseJSON" 9925 }, 9926 9927 // Data converters 9928 // Keys separate source (or catchall "*") and destination types with a single space 9929 converters: { 9930 9931 // Convert anything to text 9932 "* text": String, 9933 9934 // Text to html (true = no transformation) 9935 "text html": true, 9936 9937 // Evaluate text as a json expression 9938 "text json": JSON.parse, 9939 9940 // Parse text as xml 9941 "text xml": jQuery.parseXML 9942 }, 9943 9944 // For options that shouldn't be deep extended: 9945 // you can add your own custom options here if 9946 // and when you create one that shouldn't be 9947 // deep extended (see ajaxExtend) 9948 flatOptions: { 9949 url: true, 9950 context: true 9951 } 9952 }, 9953 9954 // Creates a full fledged settings object into target 9955 // with both ajaxSettings and settings fields. 9956 // If target is omitted, writes into ajaxSettings. 9957 ajaxSetup: function( target, settings ) { 9958 return settings ? 9959 9960 // Building a settings object 9961 ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : 9962 9963 // Extending ajaxSettings 9964 ajaxExtend( jQuery.ajaxSettings, target ); 9965 }, 9966 9967 ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), 9968 ajaxTransport: addToPrefiltersOrTransports( transports ), 9969 9970 // Main method 9971 ajax: function( url, options ) { 9972 9973 // If url is an object, simulate pre-1.5 signature 9974 if ( typeof url === "object" ) { 9975 options = url; 9976 url = undefined; 9977 } 9978 9979 // Force options to be an object 9980 options = options || {}; 9981 9982 var transport, 9983 9984 // URL without anti-cache param 9985 cacheURL, 9986 9987 // Response headers 9988 responseHeadersString, 9989 responseHeaders, 9990 9991 // timeout handle 9992 timeoutTimer, 9993 9994 // Url cleanup var 9995 urlAnchor, 9996 9997 // Request state (becomes false upon send and true upon completion) 9998 completed, 9999 10000 // To know if global events are to be dispatched 10001 fireGlobals, 10002 10003 // Loop variable 10004 i, 10005 10006 // uncached part of the url 10007 uncached, 10008 10009 // Create the final options object 10010 s = jQuery.ajaxSetup( {}, options ), 10011 10012 // Callbacks context 10013 callbackContext = s.context || s, 10014 10015 // Context for global events is callbackContext if it is a DOM node or jQuery collection 10016 globalEventContext = s.context && 10017 ( callbackContext.nodeType || callbackContext.jquery ) ? 10018 jQuery( callbackContext ) : 10019 jQuery.event, 10020 10021 // Deferreds 10022 deferred = jQuery.Deferred(), 10023 completeDeferred = jQuery.Callbacks( "once memory" ), 10024 10025 // Status-dependent callbacks 10026 statusCode = s.statusCode || {}, 10027 10028 // Headers (they are sent all at once) 10029 requestHeaders = {}, 10030 requestHeadersNames = {}, 10031 10032 // Default abort message 10033 strAbort = "canceled", 10034 10035 // Fake xhr 10036 jqXHR = { 10037 readyState: 0, 10038 10039 // Builds headers hashtable if needed 10040 getResponseHeader: function( key ) { 10041 var match; 10042 if ( completed ) { 10043 if ( !responseHeaders ) { 10044 responseHeaders = {}; 10045 while ( ( match = rheaders.exec( responseHeadersString ) ) ) { 10046 responseHeaders[ match[ 1 ].toLowerCase() + " " ] = 10047 ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) 10048 .concat( match[ 2 ] ); 10049 } 10050 } 10051 match = responseHeaders[ key.toLowerCase() + " " ]; 10052 } 10053 return match == null ? null : match.join( ", " ); 10054 }, 10055 10056 // Raw string 10057 getAllResponseHeaders: function() { 10058 return completed ? responseHeadersString : null; 10059 }, 10060 10061 // Caches the header 10062 setRequestHeader: function( name, value ) { 10063 if ( completed == null ) { 10064 name = requestHeadersNames[ name.toLowerCase() ] = 10065 requestHeadersNames[ name.toLowerCase() ] || name; 10066 requestHeaders[ name ] = value; 10067 } 10068 return this; 10069 }, 10070 10071 // Overrides response content-type header 10072 overrideMimeType: function( type ) { 10073 if ( completed == null ) { 10074 s.mimeType = type; 10075 } 10076 return this; 10077 }, 10078 10079 // Status-dependent callbacks 10080 statusCode: function( map ) { 10081 var code; 10082 if ( map ) { 10083 if ( completed ) { 10084 10085 // Execute the appropriate callbacks 10086 jqXHR.always( map[ jqXHR.status ] ); 10087 } else { 10088 10089 // Lazy-add the new callbacks in a way that preserves old ones 10090 for ( code in map ) { 10091 statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; 10092 } 10093 } 10094 } 10095 return this; 10096 }, 10097 10098 // Cancel the request 10099 abort: function( statusText ) { 10100 var finalText = statusText || strAbort; 10101 if ( transport ) { 10102 transport.abort( finalText ); 10103 } 10104 done( 0, finalText ); 10105 return this; 10106 } 10107 }; 10108 10109 // Attach deferreds 10110 deferred.promise( jqXHR ); 10111 10112 // Add protocol if not provided (prefilters might expect it) 10113 // Handle falsy url in the settings object (trac-10093: consistency with old signature) 10114 // We also use the url parameter if available 10115 s.url = ( ( url || s.url || location.href ) + "" ) 10116 .replace( rprotocol, location.protocol + "//" ); 10117 10118 // Alias method option to type as per ticket trac-12004 10119 s.type = options.method || options.type || s.method || s.type; 10120 10121 // Extract dataTypes list 10122 s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; 10123 10124 // A cross-domain request is in order when the origin doesn't match the current origin. 10125 if ( s.crossDomain == null ) { 10126 urlAnchor = document.createElement( "a" ); 10127 10128 // Support: IE <=8 - 11, Edge 12 - 15 10129 // IE throws exception on accessing the href property if url is malformed, 10130 // e.g. http://example.com:80x/ 10131 try { 10132 urlAnchor.href = s.url; 10133 10134 // Support: IE <=8 - 11 only 10135 // Anchor's host property isn't correctly set when s.url is relative 10136 urlAnchor.href = urlAnchor.href; 10137 s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== 10138 urlAnchor.protocol + "//" + urlAnchor.host; 10139 } catch ( e ) { 10140 10141 // If there is an error parsing the URL, assume it is crossDomain, 10142 // it can be rejected by the transport if it is invalid 10143 s.crossDomain = true; 10144 } 10145 } 10146 10147 // Convert data if not already a string 10148 if ( s.data && s.processData && typeof s.data !== "string" ) { 10149 s.data = jQuery.param( s.data, s.traditional ); 10150 } 10151 10152 // Apply prefilters 10153 inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); 10154 10155 // If request was aborted inside a prefilter, stop there 10156 if ( completed ) { 10157 return jqXHR; 10158 } 10159 10160 // We can fire global events as of now if asked to 10161 // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118) 10162 fireGlobals = jQuery.event && s.global; 10163 10164 // Watch for a new set of requests 10165 if ( fireGlobals && jQuery.active++ === 0 ) { 10166 jQuery.event.trigger( "ajaxStart" ); 10167 } 10168 10169 // Uppercase the type 10170 s.type = s.type.toUpperCase(); 10171 10172 // Determine if request has content 10173 s.hasContent = !rnoContent.test( s.type ); 10174 10175 // Save the URL in case we're toying with the If-Modified-Since 10176 // and/or If-None-Match header later on 10177 // Remove hash to simplify url manipulation 10178 cacheURL = s.url.replace( rhash, "" ); 10179 10180 // More options handling for requests with no content 10181 if ( !s.hasContent ) { 10182 10183 // Remember the hash so we can put it back 10184 uncached = s.url.slice( cacheURL.length ); 10185 10186 // If data is available and should be processed, append data to url 10187 if ( s.data && ( s.processData || typeof s.data === "string" ) ) { 10188 cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; 10189 10190 // trac-9682: remove data so that it's not used in an eventual retry 10191 delete s.data; 10192 } 10193 10194 // Add or update anti-cache param if needed 10195 if ( s.cache === false ) { 10196 cacheURL = cacheURL.replace( rantiCache, "$1" ); 10197 uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + 10198 uncached; 10199 } 10200 10201 // Put hash and anti-cache on the URL that will be requested (gh-1732) 10202 s.url = cacheURL + uncached; 10203 10204 // Change '%20' to '+' if this is encoded form body content (gh-2658) 10205 } else if ( s.data && s.processData && 10206 ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { 10207 s.data = s.data.replace( r20, "+" ); 10208 } 10209 10210 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 10211 if ( s.ifModified ) { 10212 if ( jQuery.lastModified[ cacheURL ] ) { 10213 jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); 10214 } 10215 if ( jQuery.etag[ cacheURL ] ) { 10216 jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); 10217 } 10218 } 10219 10220 // Set the correct header, if data is being sent 10221 if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { 10222 jqXHR.setRequestHeader( "Content-Type", s.contentType ); 10223 } 10224 10225 // Set the Accepts header for the server, depending on the dataType 10226 jqXHR.setRequestHeader( 10227 "Accept", 10228 s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? 10229 s.accepts[ s.dataTypes[ 0 ] ] + 10230 ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : 10231 s.accepts[ "*" ] 10232 ); 10233 10234 // Check for headers option 10235 for ( i in s.headers ) { 10236 jqXHR.setRequestHeader( i, s.headers[ i ] ); 10237 } 10238 10239 // Allow custom headers/mimetypes and early abort 10240 if ( s.beforeSend && 10241 ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { 10242 10243 // Abort if not done already and return 10244 return jqXHR.abort(); 10245 } 10246 10247 // Aborting is no longer a cancellation 10248 strAbort = "abort"; 10249 10250 // Install callbacks on deferreds 10251 completeDeferred.add( s.complete ); 10252 jqXHR.done( s.success ); 10253 jqXHR.fail( s.error ); 10254 10255 // Get transport 10256 transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); 10257 10258 // If no transport, we auto-abort 10259 if ( !transport ) { 10260 done( -1, "No Transport" ); 10261 } else { 10262 jqXHR.readyState = 1; 10263 10264 // Send global event 10265 if ( fireGlobals ) { 10266 globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); 10267 } 10268 10269 // If request was aborted inside ajaxSend, stop there 10270 if ( completed ) { 10271 return jqXHR; 10272 } 10273 10274 // Timeout 10275 if ( s.async && s.timeout > 0 ) { 10276 timeoutTimer = window.setTimeout( function() { 10277 jqXHR.abort( "timeout" ); 10278 }, s.timeout ); 10279 } 10280 10281 try { 10282 completed = false; 10283 transport.send( requestHeaders, done ); 10284 } catch ( e ) { 10285 10286 // Rethrow post-completion exceptions 10287 if ( completed ) { 10288 throw e; 10289 } 10290 10291 // Propagate others as results 10292 done( -1, e ); 10293 } 10294 } 10295 10296 // Callback for when everything is done 10297 function done( status, nativeStatusText, responses, headers ) { 10298 var isSuccess, success, error, response, modified, 10299 statusText = nativeStatusText; 10300 10301 // Ignore repeat invocations 10302 if ( completed ) { 10303 return; 10304 } 10305 10306 completed = true; 10307 10308 // Clear timeout if it exists 10309 if ( timeoutTimer ) { 10310 window.clearTimeout( timeoutTimer ); 10311 } 10312 10313 // Dereference transport for early garbage collection 10314 // (no matter how long the jqXHR object will be used) 10315 transport = undefined; 10316 10317 // Cache response headers 10318 responseHeadersString = headers || ""; 10319 10320 // Set readyState 10321 jqXHR.readyState = status > 0 ? 4 : 0; 10322 10323 // Determine if successful 10324 isSuccess = status >= 200 && status < 300 || status === 304; 10325 10326 // Get response data 10327 if ( responses ) { 10328 response = ajaxHandleResponses( s, jqXHR, responses ); 10329 } 10330 10331 // Use a noop converter for missing script but not if jsonp 10332 if ( !isSuccess && 10333 jQuery.inArray( "script", s.dataTypes ) > -1 && 10334 jQuery.inArray( "json", s.dataTypes ) < 0 ) { 10335 s.converters[ "text script" ] = function() {}; 10336 } 10337 10338 // Convert no matter what (that way responseXXX fields are always set) 10339 response = ajaxConvert( s, response, jqXHR, isSuccess ); 10340 10341 // If successful, handle type chaining 10342 if ( isSuccess ) { 10343 10344 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 10345 if ( s.ifModified ) { 10346 modified = jqXHR.getResponseHeader( "Last-Modified" ); 10347 if ( modified ) { 10348 jQuery.lastModified[ cacheURL ] = modified; 10349 } 10350 modified = jqXHR.getResponseHeader( "etag" ); 10351 if ( modified ) { 10352 jQuery.etag[ cacheURL ] = modified; 10353 } 10354 } 10355 10356 // if no content 10357 if ( status === 204 || s.type === "HEAD" ) { 10358 statusText = "nocontent"; 10359 10360 // if not modified 10361 } else if ( status === 304 ) { 10362 statusText = "notmodified"; 10363 10364 // If we have data, let's convert it 10365 } else { 10366 statusText = response.state; 10367 success = response.data; 10368 error = response.error; 10369 isSuccess = !error; 10370 } 10371 } else { 10372 10373 // Extract error from statusText and normalize for non-aborts 10374 error = statusText; 10375 if ( status || !statusText ) { 10376 statusText = "error"; 10377 if ( status < 0 ) { 10378 status = 0; 10379 } 10380 } 10381 } 10382 10383 // Set data for the fake xhr object 10384 jqXHR.status = status; 10385 jqXHR.statusText = ( nativeStatusText || statusText ) + ""; 10386 10387 // Success/Error 10388 if ( isSuccess ) { 10389 deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); 10390 } else { 10391 deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); 10392 } 10393 10394 // Status-dependent callbacks 10395 jqXHR.statusCode( statusCode ); 10396 statusCode = undefined; 10397 10398 if ( fireGlobals ) { 10399 globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", 10400 [ jqXHR, s, isSuccess ? success : error ] ); 10401 } 10402 10403 // Complete 10404 completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); 10405 10406 if ( fireGlobals ) { 10407 globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); 10408 10409 // Handle the global AJAX counter 10410 if ( !( --jQuery.active ) ) { 10411 jQuery.event.trigger( "ajaxStop" ); 10412 } 10413 } 10414 } 10415 10416 return jqXHR; 10417 }, 10418 10419 getJSON: function( url, data, callback ) { 10420 return jQuery.get( url, data, callback, "json" ); 10421 }, 10422 10423 getScript: function( url, callback ) { 10424 return jQuery.get( url, undefined, callback, "script" ); 10425 } 10426} ); 10427 10428jQuery.each( [ "get", "post" ], function( _i, method ) { 10429 jQuery[ method ] = function( url, data, callback, type ) { 10430 10431 // Shift arguments if data argument was omitted 10432 if ( isFunction( data ) ) { 10433 type = type || callback; 10434 callback = data; 10435 data = undefined; 10436 } 10437 10438 // The url can be an options object (which then must have .url) 10439 return jQuery.ajax( jQuery.extend( { 10440 url: url, 10441 type: method, 10442 dataType: type, 10443 data: data, 10444 success: callback 10445 }, jQuery.isPlainObject( url ) && url ) ); 10446 }; 10447} ); 10448 10449jQuery.ajaxPrefilter( function( s ) { 10450 var i; 10451 for ( i in s.headers ) { 10452 if ( i.toLowerCase() === "content-type" ) { 10453 s.contentType = s.headers[ i ] || ""; 10454 } 10455 } 10456} ); 10457 10458 10459jQuery._evalUrl = function( url, options, doc ) { 10460 return jQuery.ajax( { 10461 url: url, 10462 10463 // Make this explicit, since user can override this through ajaxSetup (trac-11264) 10464 type: "GET", 10465 dataType: "script", 10466 cache: true, 10467 async: false, 10468 global: false, 10469 10470 // Only evaluate the response if it is successful (gh-4126) 10471 // dataFilter is not invoked for failure responses, so using it instead 10472 // of the default converter is kludgy but it works. 10473 converters: { 10474 "text script": function() {} 10475 }, 10476 dataFilter: function( response ) { 10477 jQuery.globalEval( response, options, doc ); 10478 } 10479 } ); 10480}; 10481 10482 10483jQuery.fn.extend( { 10484 wrapAll: function( html ) { 10485 var wrap; 10486 10487 if ( this[ 0 ] ) { 10488 if ( isFunction( html ) ) { 10489 html = html.call( this[ 0 ] ); 10490 } 10491 10492 // The elements to wrap the target around 10493 wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); 10494 10495 if ( this[ 0 ].parentNode ) { 10496 wrap.insertBefore( this[ 0 ] ); 10497 } 10498 10499 wrap.map( function() { 10500 var elem = this; 10501 10502 while ( elem.firstElementChild ) { 10503 elem = elem.firstElementChild; 10504 } 10505 10506 return elem; 10507 } ).append( this ); 10508 } 10509 10510 return this; 10511 }, 10512 10513 wrapInner: function( html ) { 10514 if ( isFunction( html ) ) { 10515 return this.each( function( i ) { 10516 jQuery( this ).wrapInner( html.call( this, i ) ); 10517 } ); 10518 } 10519 10520 return this.each( function() { 10521 var self = jQuery( this ), 10522 contents = self.contents(); 10523 10524 if ( contents.length ) { 10525 contents.wrapAll( html ); 10526 10527 } else { 10528 self.append( html ); 10529 } 10530 } ); 10531 }, 10532 10533 wrap: function( html ) { 10534 var htmlIsFunction = isFunction( html ); 10535 10536 return this.each( function( i ) { 10537 jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); 10538 } ); 10539 }, 10540 10541 unwrap: function( selector ) { 10542 this.parent( selector ).not( "body" ).each( function() { 10543 jQuery( this ).replaceWith( this.childNodes ); 10544 } ); 10545 return this; 10546 } 10547} ); 10548 10549 10550jQuery.expr.pseudos.hidden = function( elem ) { 10551 return !jQuery.expr.pseudos.visible( elem ); 10552}; 10553jQuery.expr.pseudos.visible = function( elem ) { 10554 return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); 10555}; 10556 10557 10558 10559 10560jQuery.ajaxSettings.xhr = function() { 10561 try { 10562 return new window.XMLHttpRequest(); 10563 } catch ( e ) {} 10564}; 10565 10566var xhrSuccessStatus = { 10567 10568 // File protocol always yields status code 0, assume 200 10569 0: 200, 10570 10571 // Support: IE <=9 only 10572 // trac-1450: sometimes IE returns 1223 when it should be 204 10573 1223: 204 10574 }, 10575 xhrSupported = jQuery.ajaxSettings.xhr(); 10576 10577support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); 10578support.ajax = xhrSupported = !!xhrSupported; 10579 10580jQuery.ajaxTransport( function( options ) { 10581 var callback, errorCallback; 10582 10583 // Cross domain only allowed if supported through XMLHttpRequest 10584 if ( support.cors || xhrSupported && !options.crossDomain ) { 10585 return { 10586 send: function( headers, complete ) { 10587 var i, 10588 xhr = options.xhr(); 10589 10590 xhr.open( 10591 options.type, 10592 options.url, 10593 options.async, 10594 options.username, 10595 options.password 10596 ); 10597 10598 // Apply custom fields if provided 10599 if ( options.xhrFields ) { 10600 for ( i in options.xhrFields ) { 10601 xhr[ i ] = options.xhrFields[ i ]; 10602 } 10603 } 10604 10605 // Override mime type if needed 10606 if ( options.mimeType && xhr.overrideMimeType ) { 10607 xhr.overrideMimeType( options.mimeType ); 10608 } 10609 10610 // X-Requested-With header 10611 // For cross-domain requests, seeing as conditions for a preflight are 10612 // akin to a jigsaw puzzle, we simply never set it to be sure. 10613 // (it can always be set on a per-request basis or even using ajaxSetup) 10614 // For same-domain requests, won't change header if already provided. 10615 if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { 10616 headers[ "X-Requested-With" ] = "XMLHttpRequest"; 10617 } 10618 10619 // Set headers 10620 for ( i in headers ) { 10621 xhr.setRequestHeader( i, headers[ i ] ); 10622 } 10623 10624 // Callback 10625 callback = function( type ) { 10626 return function() { 10627 if ( callback ) { 10628 callback = errorCallback = xhr.onload = 10629 xhr.onerror = xhr.onabort = xhr.ontimeout = 10630 xhr.onreadystatechange = null; 10631 10632 if ( type === "abort" ) { 10633 xhr.abort(); 10634 } else if ( type === "error" ) { 10635 10636 // Support: IE <=9 only 10637 // On a manual native abort, IE9 throws 10638 // errors on any property access that is not readyState 10639 if ( typeof xhr.status !== "number" ) { 10640 complete( 0, "error" ); 10641 } else { 10642 complete( 10643 10644 // File: protocol always yields status 0; see trac-8605, trac-14207 10645 xhr.status, 10646 xhr.statusText 10647 ); 10648 } 10649 } else { 10650 complete( 10651 xhrSuccessStatus[ xhr.status ] || xhr.status, 10652 xhr.statusText, 10653 10654 // Support: IE <=9 only 10655 // IE9 has no XHR2 but throws on binary (trac-11426) 10656 // For XHR2 non-text, let the caller handle it (gh-2498) 10657 ( xhr.responseType || "text" ) !== "text" || 10658 typeof xhr.responseText !== "string" ? 10659 { binary: xhr.response } : 10660 { text: xhr.responseText }, 10661 xhr.getAllResponseHeaders() 10662 ); 10663 } 10664 } 10665 }; 10666 }; 10667 10668 // Listen to events 10669 xhr.onload = callback(); 10670 errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); 10671 10672 // Support: IE 9 only 10673 // Use onreadystatechange to replace onabort 10674 // to handle uncaught aborts 10675 if ( xhr.onabort !== undefined ) { 10676 xhr.onabort = errorCallback; 10677 } else { 10678 xhr.onreadystatechange = function() { 10679 10680 // Check readyState before timeout as it changes 10681 if ( xhr.readyState === 4 ) { 10682 10683 // Allow onerror to be called first, 10684 // but that will not handle a native abort 10685 // Also, save errorCallback to a variable 10686 // as xhr.onerror cannot be accessed 10687 window.setTimeout( function() { 10688 if ( callback ) { 10689 errorCallback(); 10690 } 10691 } ); 10692 } 10693 }; 10694 } 10695 10696 // Create the abort callback 10697 callback = callback( "abort" ); 10698 10699 try { 10700 10701 // Do send the request (this may raise an exception) 10702 xhr.send( options.hasContent && options.data || null ); 10703 } catch ( e ) { 10704 10705 // trac-14683: Only rethrow if this hasn't been notified as an error yet 10706 if ( callback ) { 10707 throw e; 10708 } 10709 } 10710 }, 10711 10712 abort: function() { 10713 if ( callback ) { 10714 callback(); 10715 } 10716 } 10717 }; 10718 } 10719} ); 10720 10721 10722 10723 10724// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) 10725jQuery.ajaxPrefilter( function( s ) { 10726 if ( s.crossDomain ) { 10727 s.contents.script = false; 10728 } 10729} ); 10730 10731// Install script dataType 10732jQuery.ajaxSetup( { 10733 accepts: { 10734 script: "text/javascript, application/javascript, " + 10735 "application/ecmascript, application/x-ecmascript" 10736 }, 10737 contents: { 10738 script: /\b(?:java|ecma)script\b/ 10739 }, 10740 converters: { 10741 "text script": function( text ) { 10742 jQuery.globalEval( text ); 10743 return text; 10744 } 10745 } 10746} ); 10747 10748// Handle cache's special case and crossDomain 10749jQuery.ajaxPrefilter( "script", function( s ) { 10750 if ( s.cache === undefined ) { 10751 s.cache = false; 10752 } 10753 if ( s.crossDomain ) { 10754 s.type = "GET"; 10755 } 10756} ); 10757 10758// Bind script tag hack transport 10759jQuery.ajaxTransport( "script", function( s ) { 10760 10761 // This transport only deals with cross domain or forced-by-attrs requests 10762 if ( s.crossDomain || s.scriptAttrs ) { 10763 var script, callback; 10764 return { 10765 send: function( _, complete ) { 10766 script = jQuery( "<script>" ) 10767 .attr( s.scriptAttrs || {} ) 10768 .prop( { charset: s.scriptCharset, src: s.url } ) 10769 .on( "load error", callback = function( evt ) { 10770 script.remove(); 10771 callback = null; 10772 if ( evt ) { 10773 complete( evt.type === "error" ? 404 : 200, evt.type ); 10774 } 10775 } ); 10776 10777 // Use native DOM manipulation to avoid our domManip AJAX trickery 10778 document.head.appendChild( script[ 0 ] ); 10779 }, 10780 abort: function() { 10781 if ( callback ) { 10782 callback(); 10783 } 10784 } 10785 }; 10786 } 10787} ); 10788 10789 10790 10791 10792var oldCallbacks = [], 10793 rjsonp = /(=)\?(?=&|$)|\?\?/; 10794 10795// Default jsonp settings 10796jQuery.ajaxSetup( { 10797 jsonp: "callback", 10798 jsonpCallback: function() { 10799 var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) ); 10800 this[ callback ] = true; 10801 return callback; 10802 } 10803} ); 10804 10805// Detect, normalize options and install callbacks for jsonp requests 10806jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { 10807 10808 var callbackName, overwritten, responseContainer, 10809 jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? 10810 "url" : 10811 typeof s.data === "string" && 10812 ( s.contentType || "" ) 10813 .indexOf( "application/x-www-form-urlencoded" ) === 0 && 10814 rjsonp.test( s.data ) && "data" 10815 ); 10816 10817 // Handle iff the expected data type is "jsonp" or we have a parameter to set 10818 if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { 10819 10820 // Get callback name, remembering preexisting value associated with it 10821 callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ? 10822 s.jsonpCallback() : 10823 s.jsonpCallback; 10824 10825 // Insert callback into url or form data 10826 if ( jsonProp ) { 10827 s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); 10828 } else if ( s.jsonp !== false ) { 10829 s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; 10830 } 10831 10832 // Use data converter to retrieve json after script execution
vendor: 2,987 bytes, lines 10833-10942
10833 s.converters[ "script json" ] = function() { 10834 if ( !responseContainer ) { 10835 jQuery.error( callbackName + " was not called" ); 10836 } 10837 return responseContainer[ 0 ]; 10838 }; 10839 10840 // Force json dataType 10841 s.dataTypes[ 0 ] = "json"; 10842 10843 // Install callback 10844 overwritten = window[ callbackName ]; 10845 window[ callbackName ] = function() { 10846 responseContainer = arguments; 10847 }; 10848 10849 // Clean-up function (fires after converters) 10850 jqXHR.always( function() { 10851 10852 // If previous value didn't exist - remove it 10853 if ( overwritten === undefined ) { 10854 jQuery( window ).removeProp( callbackName ); 10855 10856 // Otherwise restore preexisting value 10857 } else { 10858 window[ callbackName ] = overwritten; 10859 } 10860 10861 // Save back as free 10862 if ( s[ callbackName ] ) { 10863 10864 // Make sure that re-using the options doesn't screw things around 10865 s.jsonpCallback = originalSettings.jsonpCallback; 10866 10867 // Save the callback name for future use 10868 oldCallbacks.push( callbackName ); 10869 } 10870 10871 // Call if it was a function and we have a response 10872 if ( responseContainer && isFunction( overwritten ) ) { 10873 overwritten( responseContainer[ 0 ] ); 10874 } 10875 10876 responseContainer = overwritten = undefined; 10877 } ); 10878 10879 // Delegate to script 10880 return "script"; 10881 } 10882} ); 10883 10884 10885 10886 10887// Support: Safari 8 only 10888// In Safari 8 documents created via document.implementation.createHTMLDocument 10889// collapse sibling forms: the second one becomes a child of the first one. 10890// Because of that, this security measure has to be disabled in Safari 8. 10891// https://bugs.webkit.org/show_bug.cgi?id=137337 10892support.createHTMLDocument = ( function() { 10893 var body = document.implementation.createHTMLDocument( "" ).body; 10894 body.innerHTML = "<form></form><form></form>"; 10895 return body.childNodes.length === 2; 10896} )(); 10897 10898 10899// Argument "data" should be string of html 10900// context (optional): If specified, the fragment will be created in this context, 10901// defaults to document 10902// keepScripts (optional): If true, will include scripts passed in the html string 10903jQuery.parseHTML = function( data, context, keepScripts ) { 10904 if ( typeof data !== "string" ) { 10905 return []; 10906 } 10907 if ( typeof context === "boolean" ) { 10908 keepScripts = context; 10909 context = false; 10910 } 10911 10912 var base, parsed, scripts; 10913 10914 if ( !context ) { 10915 10916 // Stop scripts or inline event handlers from being executed immediately 10917 // by using document.implementation 10918 if ( support.createHTMLDocument ) { 10919 context = document.implementation.createHTMLDocument( "" ); 10920 10921 // Set the base href for the created document 10922 // so any parsed elements with URLs 10923 // are based on the document's URL (gh-2965) 10924 base = context.createElement( "base" ); 10925 base.href = document.location.href; 10926 context.head.appendChild( base ); 10927 } else { 10928 context = document; 10929 } 10930 } 10931 10932 parsed = rsingleTag.exec( data ); 10933 scripts = !keepScripts && []; 10934 10935 // Single tag 10936 if ( parsed ) { 10937 return [ context.createElement( parsed[ 1 ] ) ]; 10938 } 10939 10940 parsed = buildFragment( [ data ], context, scripts ); 10941 10942 if ( scripts && scripts.length ) {
vendor: 14,891 bytes, lines 10943-11479
10943 jQuery( scripts ).remove(); 10944 } 10945 10946 return jQuery.merge( [], parsed.childNodes ); 10947}; 10948 10949 10950/** 10951 * Load a url into a page 10952 */ 10953jQuery.fn.load = function( url, params, callback ) { 10954 var selector, type, response, 10955 self = this, 10956 off = url.indexOf( " " ); 10957 10958 if ( off > -1 ) { 10959 selector = stripAndCollapse( url.slice( off ) ); 10960 url = url.slice( 0, off ); 10961 } 10962 10963 // If it's a function 10964 if ( isFunction( params ) ) { 10965 10966 // We assume that it's the callback 10967 callback = params; 10968 params = undefined; 10969 10970 // Otherwise, build a param string 10971 } else if ( params && typeof params === "object" ) { 10972 type = "POST"; 10973 } 10974 10975 // If we have elements to modify, make the request 10976 if ( self.length > 0 ) { 10977 jQuery.ajax( { 10978 url: url, 10979 10980 // If "type" variable is undefined, then "GET" method will be used. 10981 // Make value of this field explicit since 10982 // user can override it through ajaxSetup method 10983 type: type || "GET", 10984 dataType: "html", 10985 data: params 10986 } ).done( function( responseText ) { 10987 10988 // Save response for use in complete callback 10989 response = arguments; 10990 10991 self.html( selector ? 10992 10993 // If a selector was specified, locate the right elements in a dummy div 10994 // Exclude scripts to avoid IE 'Permission Denied' errors 10995 jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) : 10996 10997 // Otherwise use the full result 10998 responseText ); 10999 11000 // If the request succeeds, this function gets "data", "status", "jqXHR" 11001 // but they are ignored because response was set above. 11002 // If it fails, this function gets "jqXHR", "status", "error" 11003 } ).always( callback && function( jqXHR, status ) { 11004 self.each( function() { 11005 callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] ); 11006 } ); 11007 } ); 11008 } 11009 11010 return this; 11011}; 11012 11013 11014 11015 11016jQuery.expr.pseudos.animated = function( elem ) { 11017 return jQuery.grep( jQuery.timers, function( fn ) { 11018 return elem === fn.elem; 11019 } ).length; 11020}; 11021 11022 11023 11024 11025jQuery.offset = { 11026 setOffset: function( elem, options, i ) { 11027 var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, 11028 position = jQuery.css( elem, "position" ), 11029 curElem = jQuery( elem ), 11030 props = {}; 11031 11032 // Set position first, in-case top/left are set even on static elem 11033 if ( position === "static" ) { 11034 elem.style.position = "relative"; 11035 } 11036 11037 curOffset = curElem.offset(); 11038 curCSSTop = jQuery.css( elem, "top" ); 11039 curCSSLeft = jQuery.css( elem, "left" ); 11040 calculatePosition = ( position === "absolute" || position === "fixed" ) && 11041 ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1; 11042 11043 // Need to be able to calculate position if either 11044 // top or left is auto and position is either absolute or fixed 11045 if ( calculatePosition ) { 11046 curPosition = curElem.position(); 11047 curTop = curPosition.top; 11048 curLeft = curPosition.left; 11049 11050 } else { 11051 curTop = parseFloat( curCSSTop ) || 0; 11052 curLeft = parseFloat( curCSSLeft ) || 0; 11053 } 11054 11055 if ( isFunction( options ) ) { 11056 11057 // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) 11058 options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); 11059 } 11060 11061 if ( options.top != null ) { 11062 props.top = ( options.top - curOffset.top ) + curTop; 11063 } 11064 if ( options.left != null ) { 11065 props.left = ( options.left - curOffset.left ) + curLeft; 11066 } 11067 11068 if ( "using" in options ) { 11069 options.using.call( elem, props ); 11070 11071 } else { 11072 curElem.css( props ); 11073 } 11074 } 11075}; 11076 11077jQuery.fn.extend( { 11078 11079 // offset() relates an element's border box to the document origin 11080 offset: function( options ) { 11081 11082 // Preserve chaining for setter 11083 if ( arguments.length ) { 11084 return options === undefined ? 11085 this : 11086 this.each( function( i ) { 11087 jQuery.offset.setOffset( this, options, i ); 11088 } ); 11089 } 11090 11091 var rect, win, 11092 elem = this[ 0 ]; 11093 11094 if ( !elem ) { 11095 return; 11096 } 11097 11098 // Return zeros for disconnected and hidden (display: none) elements (gh-2310) 11099 // Support: IE <=11 only 11100 // Running getBoundingClientRect on a 11101 // disconnected node in IE throws an error 11102 if ( !elem.getClientRects().length ) { 11103 return { top: 0, left: 0 }; 11104 } 11105 11106 // Get document-relative position by adding viewport scroll to viewport-relative gBCR 11107 rect = elem.getBoundingClientRect(); 11108 win = elem.ownerDocument.defaultView; 11109 return { 11110 top: rect.top + win.pageYOffset, 11111 left: rect.left + win.pageXOffset 11112 }; 11113 }, 11114 11115 // position() relates an element's margin box to its offset parent's padding box 11116 // This corresponds to the behavior of CSS absolute positioning 11117 position: function() { 11118 if ( !this[ 0 ] ) { 11119 return; 11120 } 11121 11122 var offsetParent, offset, doc, 11123 elem = this[ 0 ], 11124 parentOffset = { top: 0, left: 0 }; 11125 11126 // position:fixed elements are offset from the viewport, which itself always has zero offset 11127 if ( jQuery.css( elem, "position" ) === "fixed" ) { 11128 11129 // Assume position:fixed implies availability of getBoundingClientRect 11130 offset = elem.getBoundingClientRect(); 11131 11132 } else { 11133 offset = this.offset(); 11134 11135 // Account for the *real* offset parent, which can be the document or its root element 11136 // when a statically positioned element is identified 11137 doc = elem.ownerDocument; 11138 offsetParent = elem.offsetParent || doc.documentElement; 11139 while ( offsetParent && 11140 ( offsetParent === doc.body || offsetParent === doc.documentElement ) && 11141 jQuery.css( offsetParent, "position" ) === "static" ) { 11142 11143 offsetParent = offsetParent.parentNode; 11144 } 11145 if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { 11146 11147 // Incorporate borders into its offset, since they are outside its content origin 11148 parentOffset = jQuery( offsetParent ).offset(); 11149 parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); 11150 parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true ); 11151 } 11152 } 11153 11154 // Subtract parent offsets and element margins 11155 return { 11156 top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), 11157 left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) 11158 }; 11159 }, 11160 11161 // This method will return documentElement in the following cases: 11162 // 1) For the element inside the iframe without offsetParent, this method will return 11163 // documentElement of the parent window 11164 // 2) For the hidden or detached element 11165 // 3) For body or html element, i.e. in case of the html node - it will return itself 11166 // 11167 // but those exceptions were never presented as a real life use-cases 11168 // and might be considered as more preferable results. 11169 // 11170 // This logic, however, is not guaranteed and can change at any point in the future 11171 offsetParent: function() { 11172 return this.map( function() { 11173 var offsetParent = this.offsetParent; 11174 11175 while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) { 11176 offsetParent = offsetParent.offsetParent; 11177 } 11178 11179 return offsetParent || documentElement; 11180 } ); 11181 } 11182} ); 11183 11184// Create scrollLeft and scrollTop methods 11185jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { 11186 var top = "pageYOffset" === prop; 11187 11188 jQuery.fn[ method ] = function( val ) { 11189 return access( this, function( elem, method, val ) { 11190 11191 // Coalesce documents and windows 11192 var win; 11193 if ( isWindow( elem ) ) { 11194 win = elem; 11195 } else if ( elem.nodeType === 9 ) { 11196 win = elem.defaultView; 11197 } 11198 11199 if ( val === undefined ) { 11200 return win ? win[ prop ] : elem[ method ]; 11201 } 11202 11203 if ( win ) { 11204 win.scrollTo( 11205 !top ? val : win.pageXOffset, 11206 top ? val : win.pageYOffset 11207 ); 11208 11209 } else { 11210 elem[ method ] = val; 11211 } 11212 }, method, val, arguments.length ); 11213 }; 11214} ); 11215 11216// Support: Safari <=7 - 9.1, Chrome <=37 - 49 11217// Add the top/left cssHooks using jQuery.fn.position 11218// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 11219// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 11220// getComputedStyle returns percent when specified for top/left/bottom/right; 11221// rather than make the css module depend on the offset module, just check for it here 11222jQuery.each( [ "top", "left" ], function( _i, prop ) { 11223 jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, 11224 function( elem, computed ) { 11225 if ( computed ) { 11226 computed = curCSS( elem, prop ); 11227 11228 // If curCSS returns percentage, fallback to offset 11229 return rnumnonpx.test( computed ) ? 11230 jQuery( elem ).position()[ prop ] + "px" : 11231 computed; 11232 } 11233 } 11234 ); 11235} ); 11236 11237 11238// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods 11239jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { 11240 jQuery.each( { 11241 padding: "inner" + name, 11242 content: type, 11243 "": "outer" + name 11244 }, function( defaultExtra, funcName ) { 11245 11246 // Margin is only for outerHeight, outerWidth 11247 jQuery.fn[ funcName ] = function( margin, value ) { 11248 var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), 11249 extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); 11250 11251 return access( this, function( elem, type, value ) { 11252 var doc; 11253 11254 if ( isWindow( elem ) ) { 11255 11256 // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) 11257 return funcName.indexOf( "outer" ) === 0 ? 11258 elem[ "inner" + name ] : 11259 elem.document.documentElement[ "client" + name ]; 11260 } 11261 11262 // Get document width or height 11263 if ( elem.nodeType === 9 ) { 11264 doc = elem.documentElement; 11265 11266 // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], 11267 // whichever is greatest 11268 return Math.max( 11269 elem.body[ "scroll" + name ], doc[ "scroll" + name ], 11270 elem.body[ "offset" + name ], doc[ "offset" + name ], 11271 doc[ "client" + name ] 11272 ); 11273 } 11274 11275 return value === undefined ? 11276 11277 // Get width or height on the element, requesting but not forcing parseFloat 11278 jQuery.css( elem, type, extra ) : 11279 11280 // Set width or height on the element 11281 jQuery.style( elem, type, value, extra ); 11282 }, type, chainable ? margin : undefined, chainable ); 11283 }; 11284 } ); 11285} ); 11286 11287 11288jQuery.each( [ 11289 "ajaxStart", 11290 "ajaxStop", 11291 "ajaxComplete", 11292 "ajaxError", 11293 "ajaxSuccess", 11294 "ajaxSend" 11295], function( _i, type ) { 11296 jQuery.fn[ type ] = function( fn ) { 11297 return this.on( type, fn ); 11298 }; 11299} ); 11300 11301 11302 11303 11304jQuery.fn.extend( { 11305 11306 bind: function( types, data, fn ) { 11307 return this.on( types, null, data, fn ); 11308 }, 11309 unbind: function( types, fn ) { 11310 return this.off( types, null, fn ); 11311 }, 11312 11313 delegate: function( selector, types, data, fn ) { 11314 return this.on( types, selector, data, fn ); 11315 }, 11316 undelegate: function( selector, types, fn ) { 11317 11318 // ( namespace ) or ( selector, types [, fn] ) 11319 return arguments.length === 1 ? 11320 this.off( selector, "**" ) : 11321 this.off( types, selector || "**", fn ); 11322 }, 11323 11324 hover: function( fnOver, fnOut ) { 11325 return this 11326 .on( "mouseenter", fnOver ) 11327 .on( "mouseleave", fnOut || fnOver ); 11328 } 11329} ); 11330 11331jQuery.each( 11332 ( "blur focus focusin focusout resize scroll click dblclick " + 11333 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 11334 "change select submit keydown keypress keyup contextmenu" ).split( " " ), 11335 function( _i, name ) { 11336 11337 // Handle event binding 11338 jQuery.fn[ name ] = function( data, fn ) { 11339 return arguments.length > 0 ? 11340 this.on( name, null, data, fn ) : 11341 this.trigger( name ); 11342 }; 11343 } 11344); 11345 11346 11347 11348 11349// Support: Android <=4.0 only 11350// Make sure we trim BOM and NBSP 11351// Require that the "whitespace run" starts from a non-whitespace 11352// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position. 11353var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g; 11354 11355// Bind a function to a context, optionally partially applying any 11356// arguments. 11357// jQuery.proxy is deprecated to promote standards (specifically Function#bind) 11358// However, it is not slated for removal any time soon 11359jQuery.proxy = function( fn, context ) { 11360 var tmp, args, proxy; 11361 11362 if ( typeof context === "string" ) { 11363 tmp = fn[ context ]; 11364 context = fn; 11365 fn = tmp; 11366 } 11367 11368 // Quick check to determine if target is callable, in the spec 11369 // this throws a TypeError, but we will just return undefined. 11370 if ( !isFunction( fn ) ) { 11371 return undefined; 11372 } 11373 11374 // Simulated bind 11375 args = slice.call( arguments, 2 ); 11376 proxy = function() { 11377 return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); 11378 }; 11379 11380 // Set the guid of unique handler to the same of original handler, so it can be removed 11381 proxy.guid = fn.guid = fn.guid || jQuery.guid++; 11382 11383 return proxy; 11384}; 11385 11386jQuery.holdReady = function( hold ) { 11387 if ( hold ) { 11388 jQuery.readyWait++; 11389 } else { 11390 jQuery.ready( true ); 11391 } 11392}; 11393jQuery.isArray = Array.isArray; 11394jQuery.parseJSON = JSON.parse; 11395jQuery.nodeName = nodeName; 11396jQuery.isFunction = isFunction; 11397jQuery.isWindow = isWindow; 11398jQuery.camelCase = camelCase; 11399jQuery.type = toType; 11400 11401jQuery.now = Date.now; 11402 11403jQuery.isNumeric = function( obj ) { 11404 11405 // As of jQuery 3.0, isNumeric is limited to 11406 // strings and numbers (primitives or objects) 11407 // that can be coerced to finite numbers (gh-2662) 11408 var type = jQuery.type( obj ); 11409 return ( type === "number" || type === "string" ) && 11410 11411 // parseFloat NaNs numeric-cast false positives ("") 11412 // ...but misinterprets leading-number strings, particularly hex literals ("0x...") 11413 // subtraction forces infinities to NaN 11414 !isNaN( obj - parseFloat( obj ) ); 11415}; 11416 11417jQuery.trim = function( text ) { 11418 return text == null ? 11419 "" : 11420 ( text + "" ).replace( rtrim, "$1" ); 11421}; 11422 11423 11424 11425// Register as a named AMD module, since jQuery can be concatenated with other 11426// files that may use define, but not via a proper concatenation script that 11427// understands anonymous AMD modules. A named AMD is safest and most robust 11428// way to register. Lowercase jquery is used because AMD module names are 11429// derived from file names, and jQuery is normally delivered in a lowercase 11430// file name. Do this after creating the global so that if an AMD module wants 11431// to call noConflict to hide this version of jQuery, it will work. 11432 11433// Note that for maximum portability, libraries that are not jQuery should 11434// declare themselves as anonymous modules, and avoid setting a global if an 11435// AMD loader is present. jQuery is a special case. For more information, see 11436// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon 11437 11438if ( typeof define === "function" && define.amd ) { 11439 define( "jquery", [], function() { 11440 return jQuery; 11441 } ); 11442} 11443 11444 11445 11446 11447var 11448 11449 // Map over jQuery in case of overwrite 11450 _jQuery = window.jQuery, 11451 11452 // Map over the $ in case of overwrite 11453 _$ = window.$; 11454 11455jQuery.noConflict = function( deep ) { 11456 if ( window.$ === jQuery ) { 11457 window.$ = _$; 11458 } 11459 11460 if ( deep && window.jQuery === jQuery ) { 11461 window.jQuery = _jQuery; 11462 } 11463 11464 return jQuery; 11465}; 11466 11467// Expose jQuery and $ identifiers, even in AMD 11468// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557) 11469// and CommonJS for browser emulators (trac-13566) 11470if ( typeof noGlobal === "undefined" ) { 11471 window.jQuery = window.$ = jQuery; 11472} 11473 11474 11475 11476 11477return jQuery; 11478} ); 11479/*
11479 jshint node: true */ 11480 11481/** 11482 * Unobtrusive scripting adapter for jQuery 11483 * https://github.com/rails/jquery-ujs 11484 * 11485 * Requires jQuery 1.8.0 or later. 11486 * 11487 * Released under the MIT license 11488 * 11489 */ 11490 11491(function() { 11492 'use strict'; 11493 11494 var jqueryUjsInit = function($, undefined) { 11495 11496 // Cut down on the number of issues from people inadvertently including jquery_ujs twice 11497 // by detecting and raising an error when it happens. 11498 if ( $.rails !== undefined ) { 11499 $.error('jquery-ujs has already been loaded!'); 11500 } 11501 11502 // Shorthand to make it a little easier to call public rails functions from within rails.js 11503 var rails; 11504 var $document = $(document); 11505 11506 $.rails = rails = { 11507 // Link elements bound by jquery-ujs 11508 linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]', 11509 11510 // Button elements bound by jquery-ujs 11511 buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)', 11512 11513 // Select elements bound by jquery-ujs 11514 inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', 11515 11516 // Form elements bound by jquery-ujs 11517 formSubmitSelector: 'form:not([data-turbo=true])', 11518 11519 // Form input elements bound by jquery-ujs 11520 formInputClickSelector: 'form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])', 11521 11522 // Form input elements disabled during form submission 11523 disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled', 11524 11525 // Form input elements re-enabled after form submission 11526 enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled', 11527 11528 // Form required input elements 11529 requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])', 11530 11531 // Form file input elements 11532 fileInputSelector: 'input[name][type=file]:not([disabled])', 11533 11534 // Link onClick disable selector with possible reenable after remote submission 11535 linkDisableSelector: 'a[data-disable-with], a[data-disable]', 11536 11537 // Button onClick disable selector with possible reenable after remote submission 11538 buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]', 11539 11540 // Up-to-date Cross-Site Request Forgery token 11541 csrfToken: function() { 11542 return $('meta[name=csrf-token]').attr('content'); 11543 }, 11544 11545 // URL param that must contain the CSRF token 11546 csrfParam: function() { 11547 return $('meta[name=csrf-param]').attr('content'); 11548 }, 11549 11550 // Make sure that every Ajax request sends the CSRF token 11551 CSRFProtection: function(xhr) { 11552 var token = rails.csrfToken(); 11553 if (token) xhr.setRequestHeader('X-CSRF-Token', token); 11554 }, 11555 11556 // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones) 11557 refreshCSRFTokens: function(){ 11558 $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken()); 11559 }, 11560 11561 // Triggers an event on an element and returns false if the event result is false 11562 fire: function(obj, name, data) { 11563 var event = $.Event(name); 11564 obj.trigger(event, data); 11565 return event.result !== false; 11566 }, 11567 11568 // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm 11569 confirm: function(message) { 11570 return confirm(message); 11571 }, 11572 11573 // Default ajax function, may be overridden with custom function in $.rails.ajax 11574 ajax: function(options) { 11575 return $.ajax(options); 11576 }, 11577 11578 // Default way to get an element's href. May be overridden at $.rails.href. 11579 href: function(element) { 11580 return element[0].href; 11581 }, 11582 11583 // Checks "data-remote" if true to handle the request through a XHR request. 11584 isRemote: function(element) { 11585 return element.data('remote') !== undefined && element.data('remote') !== false; 11586 }, 11587 11588 // Submits "remote" forms and links with ajax 11589 handleRemote: function(element) { 11590 var method, url, data, withCredentials, dataType, options; 11591 11592 if (rails.fire(element, 'ajax:before')) { 11593 withCredentials = element.data('with-credentials') || null; 11594 dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); 11595 11596 if (element.is('form')) { 11597 method = element.data('ujs:submit-button-formmethod') || element.attr('method'); 11598 url = element.data('ujs:submit-button-formaction') || element.attr('action'); 11599 data = $(element[0]).serializeArray(); 11600 // memoized value from clicked submit button 11601 var button = element.data('ujs:submit-button'); 11602 if (button) { 11603 data.push(button); 11604 element.data('ujs:submit-button', null); 11605 } 11606 element.data('ujs:submit-button-formmethod', null); 11607 element.data('ujs:submit-button-formaction', null); 11608 } else if (element.is(rails.inputChangeSelector)) { 11609 method = element.data('method'); 11610 url = element.data('url'); 11611 data = element.serialize(); 11612 if (element.data('params')) data = data + '&' + element.data('params'); 11613 } else if (element.is(rails.buttonClickSelector)) { 11614 method = element.data('method') || 'get'; 11615 url = element.data('url'); 11616 data = element.serialize(); 11617 if (element.data('params')) data = data + '&' + element.data('params'); 11618 } else { 11619 method = element.data('method'); 11620 url = rails.href(element); 11621 data = element.data('params') || null; 11622 } 11623 11624 options = { 11625 type: method || 'GET', data: data, dataType: dataType, 11626 // stopping the "ajax:beforeSend" event will cancel the ajax request 11627 beforeSend: function(xhr, settings) { 11628 if (settings.dataType === undefined) { 11629 xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); 11630 } 11631 if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
11632 element.trigger('ajax:send', xhr); 11633 } else { 11634 return false; 11635 } 11636 }, 11637 success: function(data, status, xhr) { 11638 element.trigger('ajax:success', [data, status, xhr]); 11639 }, 11640 complete: function(xhr, status) { 11641 element.trigger('ajax:complete', [xhr, status]); 11642 }, 11643 error: function(xhr, status, error) { 11644 element.trigger('ajax:error', [xhr, status, error]); 11645 }, 11646 crossDomain: rails.isCrossDomain(url) 11647 }; 11648 11649 // There is no withCredentials for IE6-8 when 11650 // "Enable native XMLHTTP support" is disabled 11651 if (withCredentials) { 11652 options.xhrFields = { 11653 withCredentials: withCredentials 11654 }; 11655 } 11656 11657 // Only pass url to `ajax` options if not blank 11658 if (url) { options.url = url; } 11659 11660 return rails.ajax(options); 11661 } else { 11662 return false; 11663 } 11664 }, 11665 11666 // Determines if the request is a cross domain request. 11667 isCrossDomain: function(url) { 11668 var originAnchor = document.createElement('a'); 11669 originAnchor.href = location.href; 11670 var urlAnchor = document.createElement('a'); 11671 11672 try { 11673 urlAnchor.href = url; 11674 // This is a workaround to a IE bug. 11675 urlAnchor.href = urlAnchor.href; 11676 11677 // If URL protocol is false or is a string containing a single colon 11678 // *and* host are false, assume it is not a cross-domain request 11679 // (should only be the case for IE7 and IE compatibility mode). 11680 // Otherwise, evaluate protocol and host of the URL against the origin 11681 // protocol and host. 11682 return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) || 11683 (originAnchor.protocol + '//' + originAnchor.host === 11684 urlAnchor.protocol + '//' + urlAnchor.host)); 11685 } catch (e) { 11686 // If there is an error parsing the URL, assume it is crossDomain. 11687 return true; 11688 } 11689 }, 11690 11691 // Handles "data-method" on links such as: 11692 // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a> 11693 handleMethod: function(link) { 11694 var href = rails.href(link), 11695 method = link.data('method'), 11696 target = link.attr('target'), 11697 csrfToken = rails.csrfToken(), 11698 csrfParam = rails.csrfParam(), 11699 form = $('<form method="post" action="' + href + '"></form>'), 11700 metadataInput = '<input name="_method" value="' + method + '" type="hidden" />'; 11701 11702 if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) { 11703 metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />'; 11704 } 11705 11706 if (target) { form.attr('target', target); } 11707 11708 form.hide().append(metadataInput).appendTo('body'); 11709 form.submit(); 11710 }, 11711 11712 // Helper function that returns form elements that match the specified CSS selector 11713 // If form is actually a "form" element this will return associated elements outside the from that have 11714 // the html form attribute set 11715 formElements: function(form, selector) { 11716 return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector); 11717 }, 11718 11719 /* Disables form elements: 11720 - Caches element value in 'ujs:enable-with' data store 11721 - Replaces element text with value of 'data-disable-with' attribute 11722 - Sets disabled property to true 11723 */ 11724 disableFormElements: function(form) { 11725 rails.formElements(form, rails.disableSelector).each(function() { 11726 rails.disableFormElement($(this)); 11727 }); 11728 }, 11729 11730 disableFormElement: function(element) { 11731 var method, replacement; 11732 11733 method = element.is('button') ? 'html' : 'val'; 11734 replacement = element.data('disable-with'); 11735 11736 if (replacement !== undefined) { 11737 element.data('ujs:enable-with', element[method]()); 11738 element[method](replacement); 11739 } 11740 11741 element.prop('disabled', true); 11742 element.data('ujs:disabled', true); 11743 }, 11744 11745 /* Re-enables disabled form elements: 11746 - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) 11747 - Sets disabled property to false 11748 */ 11749 enableFormElements: function(form) { 11750 rails.formElements(form, rails.enableSelector).each(function() { 11751 rails.enableFormElement($(this)); 11752 }); 11753 }, 11754 11755 enableFormElement: function(element) { 11756 var method = element.is('button') ? 'html' : 'val'; 11757 if (element.data('ujs:enable-with') !== undefined) { 11758 element[method](element.data('ujs:enable-with')); 11759 element.removeData('ujs:enable-with'); // clean up cache 11760 } 11761 element.prop('disabled', false); 11762 element.removeData('ujs:disabled'); 11763 }, 11764 11765 /* For 'data-confirm' attribute: 11766 - Fires `confirm` event 11767 - Shows the confirmation dialog 11768 - Fires the `confirm:complete` event 11769 11770 Returns `true` if no function stops the chain and user chose yes; `false` otherwise. 11771 Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. 11772 Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
11773 return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. 11774 */ 11775 allowAction: function(element) { 11776 var message = element.data('confirm'), 11777 answer = false, callback; 11778 if (!message) { return true; } 11779 11780 if (rails.fire(element, 'confirm')) { 11781 try { 11782 answer = rails.confirm(message); 11783 } catch (e) { 11784 (console.error || console.log).call(console, e.stack || e); 11785 } 11786 callback = rails.fire(element, 'confirm:complete', [answer]); 11787 } 11788 return answer && callback; 11789 }, 11790 11791 // Helper function which checks for blank inputs in a form that match the specified CSS selector 11792 blankInputs: function(form, specifiedSelector, nonBlank) { 11793 var foundInputs = $(), 11794 input, 11795 valueToCheck, 11796 radiosForNameWithNoneSelected, 11797 radioName, 11798 selector = specifiedSelector || 'input,textarea', 11799 requiredInputs = form.find(selector), 11800 checkedRadioButtonNames = {}; 11801 11802 requiredInputs.each(function() { 11803 input = $(this); 11804 if (input.is('input[type=radio]')) { 11805 11806 // Don't count unchecked required radio as blank if other radio with same name is checked, 11807 // regardless of whether same-name radio input has required attribute or not. The spec 11808 // states https://www.w3.org/TR/html5/forms.html#the-required-attribute 11809 radioName = input.attr('name'); 11810 11811 // Skip if we've already seen the radio with this name. 11812 if (!checkedRadioButtonNames[radioName]) { 11813 11814 // If none checked 11815 if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) { 11816 radiosForNameWithNoneSelected = form.find( 11817 'input[type=radio][name="' + radioName + '"]'); 11818 foundInputs = foundInputs.add(radiosForNameWithNoneSelected); 11819 } 11820 11821 // We only need to check each name once. 11822 checkedRadioButtonNames[radioName] = radioName; 11823 } 11824 } else { 11825 valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val(); 11826 if (valueToCheck === nonBlank) { 11827 foundInputs = foundInputs.add(input); 11828 } 11829 } 11830 }); 11831 return foundInputs.length ? foundInputs : false; 11832 }, 11833 11834 // Helper function which checks for non-blank inputs in a form that match the specified CSS selector 11835 nonBlankInputs: function(form, specifiedSelector) { 11836 return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank 11837 }, 11838 11839 // Helper function, needed to provide consistent behavior in IE 11840 stopEverything: function(e) { 11841 $(e.target).trigger('ujs:everythingStopped'); 11842 e.stopImmediatePropagation(); 11843 return false; 11844 }, 11845 11846 // Replace element's html with the 'data-disable-with' after storing original html 11847 // and prevent clicking on it 11848 disableElement: function(element) { 11849 var replacement = element.data('disable-with'); 11850 11851 if (replacement !== undefined) { 11852 element.data('ujs:enable-with', element.html()); // store enabled state 11853 element.html(replacement); 11854 } 11855 11856 element.on('click.railsDisable', function(e) { // prevent further clicking 11857 return rails.stopEverything(e); 11858 }); 11859 element.data('ujs:disabled', true); 11860 }, 11861 11862 // Restore element to its original state which was disabled by 'disableElement' above 11863 enableElement: function(element) { 11864 if (element.data('ujs:enable-with') !== undefined) { 11865 element.html(element.data('ujs:enable-with')); // set to old enabled state 11866 element.removeData('ujs:enable-with'); // clean up cache 11867 } 11868 element.off('click.railsDisable'); // enable element 11869 element.removeData('ujs:disabled'); 11870 } 11871 }; 11872 11873 if (rails.fire($document, 'rails:attachBindings')) { 11874 11875 $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); 11876 11877 // This event works the same as the load event, except that it fires every 11878 // time the page is loaded. 11879 // 11880 // See https://github.com/rails/jquery-ujs/issues/357 11881 // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching 11882 $(window).on('pageshow.rails', function () { 11883 $($.rails.enableSelector).each(function () { 11884 var element = $(this); 11885 11886 if (element.data('ujs:disabled')) { 11887 $.rails.enableFormElement(element); 11888 } 11889 }); 11890 11891 $($.rails.linkDisableSelector).each(function () { 11892 var element = $(this); 11893 11894 if (element.data('ujs:disabled')) { 11895 $.rails.enableElement(element); 11896 } 11897 }); 11898 }); 11899 11900 $document.on('ajax:complete', rails.linkDisableSelector, function() { 11901 rails.enableElement($(this)); 11902 }); 11903 11904 $document.on('ajax:complete', rails.buttonDisableSelector, function() { 11905 rails.enableFormElement($(this)); 11906 }); 11907 11908 $document.on('click.rails', rails.linkClickSelector, function(e) { 11909 var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey; 11910 if (!rails.allowAction(link)) return rails.stopEverything(e); 11911 11912 if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link); 11913 11914 if (rails.isRemote(link)) { 11915 if (metaClick && (!method || method === 'GET') && !data) { return true; } 11916 11917 var handleRemote = rails.handleRemote(link); 11918 // Response from rails.handleRemote() will either be false or a deferred object promise. 11919 if (handleRemote === false) { 11920 rails.enableElement(link); 11921 } else { 11922 handleRemote.fail( function() { rails.enableElement(link); } ); 11923 } 11924 return false;
11925 11926 } else if (method) { 11927 rails.handleMethod(link); 11928 return false; 11929 } 11930 }); 11931 11932 $document.on('click.rails', rails.buttonClickSelector, function(e) { 11933 var button = $(this); 11934 11935 if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e); 11936 11937 if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button); 11938 11939 var handleRemote = rails.handleRemote(button); 11940 // Response from rails.handleRemote() will either be false or a deferred object promise. 11941 if (handleRemote === false) { 11942 rails.enableFormElement(button); 11943 } else { 11944 handleRemote.fail( function() { rails.enableFormElement(button); } ); 11945 } 11946 return false; 11947 }); 11948 11949 $document.on('change.rails', rails.inputChangeSelector, function(e) { 11950 var link = $(this); 11951 if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e); 11952 11953 rails.handleRemote(link); 11954 return false; 11955 }); 11956 11957 $document.on('submit.rails', rails.formSubmitSelector, function(e) { 11958 var form = $(this), 11959 remote = rails.isRemote(form), 11960 blankRequiredInputs, 11961 nonBlankFileInputs; 11962 11963 if (!rails.allowAction(form)) return rails.stopEverything(e); 11964 11965 // Skip other logic when required values are missing or file upload is present 11966 if (form.attr('novalidate') === undefined) { 11967 if (form.data('ujs:formnovalidate-button') === undefined) { 11968 blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false); 11969 if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { 11970 return rails.stopEverything(e); 11971 } 11972 } else { 11973 // Clear the formnovalidate in case the next button click is not on a formnovalidate button 11974 // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain 11975 form.data('ujs:formnovalidate-button', undefined); 11976 } 11977 } 11978 11979 if (remote) { 11980 nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); 11981 if (nonBlankFileInputs) { 11982 // Slight timeout so that the submit button gets properly serialized 11983 // (make it easy for event handler to serialize form without disabled values) 11984 setTimeout(function(){ rails.disableFormElements(form); }, 13); 11985 var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); 11986 11987 // Re-enable form elements if event bindings return false (canceling normal form submission) 11988 if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); } 11989 11990 return aborted; 11991 } 11992 11993 rails.handleRemote(form); 11994 return false; 11995 11996 } else { 11997 // Slight timeout so that the submit button gets properly serialized 11998 setTimeout(function(){ rails.disableFormElements(form); }, 13); 11999 } 12000 }); 12001 12002 $document.on('click.rails', rails.formInputClickSelector, function(event) { 12003 var button = $(this); 12004 12005 if (!rails.allowAction(button)) return rails.stopEverything(event); 12006 12007 // Register the pressed submit button 12008 var name = button.attr('name'), 12009 data = name ? {name:name, value:button.val()} : null; 12010 12011 var form = button.closest('form'); 12012 if (form.length === 0) { 12013 form = $('#' + button.attr('form')); 12014 } 12015 form.data('ujs:submit-button', data); 12016 12017 // Save attributes from button 12018 form.data('ujs:formnovalidate-button', button.attr('formnovalidate')); 12019 form.data('ujs:submit-button-formaction', button.attr('formaction')); 12020 form.data('ujs:submit-button-formmethod', button.attr('formmethod')); 12021 }); 12022 12023 $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) { 12024 if (this === event.target) rails.disableFormElements($(this)); 12025 }); 12026 12027 $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) { 12028 if (this === event.target) rails.enableFormElements($(this)); 12029 }); 12030 12031 $(function(){ 12032 rails.refreshCSRFTokens(); 12033 }); 12034 } 12035 12036 }; 12037 12038 if (window.jQuery) { 12039 jqueryUjsInit(jQuery); 12040 } else if (typeof exports === 'object' && typeof module === 'object') { 12041 module.exports = jqueryUjsInit; 12042 } 12043})();
vendor: 20,791 bytes, lines 12044-12047
12044/*! jQuery Validation Plugin - v1.19.4 - 5/19/2022 12045 * https://jqueryvalidation.org/ 12046 * Copyright (c) 2022 Jörn Zaefferer; Licensed MIT */ 12047!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.submitButton=b.currentTarget,a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.submitButton&&(c.settings.submitHandler||c.formSubmitted)&&(d=a("<input type='hidden'/>").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),!(c.settings.submitHandler&&!c.settings.debug)||(e=c.settings.submitHandler.call(c,c.currentForm,b),d&&d.remove(),void 0!==e&&e)}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,b||(d=d.concat(c.errorList))}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0],k="undefined"!=typeof this.attr("contenteditable")&&"false"!==this.attr("contenteditable");if(null!=j&&(!j.form&&k&&(j.form=this.closest("form")[0],j.name=this.attr("name")),null!=j.form)){if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(a,b){i[b]=f[b],delete f[b]}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g)),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}});var b=function(a){return a.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")};a.extend(a.expr.pseudos||a.expr[":"],{blank:function(c){return!b(""+a(c).val())},filled:function(c){var d=a(c).val();return null!==d&&!!b(""+d)},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:void 0===c?b:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",pendingClass:"pending",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||a.inArray(c.keyCode,d)!==-1||(b.name in this.submitted||b.name in this.invalid)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}."),step:a.validator.format("Please enter a multiple of {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");if(!this.form&&c&&(this.form=a(this).closest("form")[0],this.name=a(this).attr("name")),d===this.form){var e=a.data(this.form,"validator"),f="on"+b.type.replace(/^validate/,""),g=e.settings;g[f]&&!a(this).is(g.ignore)&&g[f].call(e,this,b)}}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.currentForm,e=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){e[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox'], [contenteditable], [type='button']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler)},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c,d,e=this.clean(b),f=this.validationTargetFor(e),g=this,h=!0;return void 0===f?delete this.invalid[e.name]:(this.prepareElement(f),this.currentElements=a(f),d=this.groups[f.name],d&&a.each(this.groups,function(a,b){b===d&&a!==f.name&&(e=g.validationTargetFor(g.clean(g.findByName(a))),e&&e.name in g.invalid&&(g.currentElements.push(e),h=g.check(e)&&h))}),c=this.check(f)!==!1,h=h&&c,c?this.invalid[f.name]=!1:this.invalid[f.name]=!0,this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),a(b).attr("aria-invalid",!c)),h},showErrors:function(b){if(b){var c=this;a.extend(this.errorMap,b),this.errorList=a.map(this.errorMap,function(a,b){return{message:a,element:c.findByName(b)[0]}}),this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.invalid={},this.submitted={},this.prepareForm(),this.hideErrors();var b=this.elements().removeData("previousValue").removeAttr("aria-invalid");this.resetElements(b)},resetElements:function(a){var b;if(this.settings.unhighlight)for(b=0;a[b];b++)this.settings.unhighlight.call(this,a[b],this.settings.errorClass,""),this.findByName(a[b].name).removeClass(this.settings.validClass);else a.removeClass(this.settings.errorClass).removeClass(this.settings.validClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)void 0!==a[b]&&null!==a[b]&&a[b]!==!1&&c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").trigger("focus").trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea, [contenteditable]").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){var d=this.name||a(this).attr("name"),e="undefined"!=typeof a(this).attr("contenteditable")&&"false"!==a(this).attr("contenteditable");return!d&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),e&&(this.form=a(this).closest("form")[0],this.name=d),this.form===b.currentForm&&(!(d in c||!b.objectLength(a(this).rules()))&&(c[d]=!0,!0))})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},resetInternals:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([])},reset:function(){this.resetInternals(),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d,e=a(b),f=b.type,g="undefined"!=typeof e.attr("contenteditable")&&"false"!==e.attr("contenteditable");return"radio"===f||"checkbox"===f?this.findByName(b.name).filter(":checked").val():"number"===f&&"undefined"!=typeof b.validity?b.validity.badInput?"NaN":e.val():(c=g?e.text():e.val(),"file"===f?"C:\\fakepath\\"===c.substr(0,12)?c.substr(12):(d=c.lastIndexOf("/"),d>=0?c.substr(d+1):(d=c.lastIndexOf("\\"),d>=0?c.substr(d+1):c)):"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f,g=a(b).rules(),h=a.map(g,function(a,b){return b}).length,i=!1,j=this.elementValue(b);"function"==typeof g.normalizer?f=g.normalizer:"function"==typeof this.settings.normalizer&&(f=this.settings.normalizer),f&&(j=f.call(b,j),delete g.normalizer);for(d in g){e={method:d,parameters:g[d]};try{if(c=a.validator.methods[d].call(this,j,b,e.parameters),"dependency-mismatch"===c&&1===h){i=!0;continue}if(i=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(k){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",k),k instanceof TypeError&&(k.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),k}}if(!i)return this.objectLength(g)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]},defaultMessage:function(b,c){"string"==typeof c&&(c={method:c});var d=this.findDefined(this.customMessage(b.name,c.method),this.customDataMessage(b,c.method),!this.settings.ignoreTitle&&b.title||void 0,a.validator.messages[c.method],"<strong>Warning: No message defined for "+b.name+"</strong>"),e=/\$?\{(\d+)\}/g;return"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),d},formatAndAdd:function(a,b){var c=this.defaultMessage(a,b);this.errorList.push({message:c,element:a,method:b.method}),this.errorMap[a.name]=c,this.submitted[a.name]=c},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g,h=this.errorsFor(b),i=this.idOrName(b),j=a(b).attr("aria-describedby");h.length?(h.removeClass(this.settings.validClass).addClass(this.settings.errorClass),h.html(c)):(h=a("<"+this.settings.errorElement+">").attr("id",i+"-error").addClass(this.settings.errorClass).html(c||""),d=h,this.settings.wrapper&&(d=h.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement.call(this,d,a(b)):d.insertAfter(b),h.is("label")?h.attr("for",i):0===h.parents("label[for='"+this.escapeCssMeta(i)+"']").length&&(f=h.attr("id"),j?j.match(new RegExp("\\b"+this.escapeCssMeta(f)+"\\b"))||(j+=" "+f):j=f,a(b).attr("aria-describedby",j),e=this.groups[b.name],e&&(g=this,a.each(g.groups,function(b,c){c===e&&a("[name='"+g.escapeCssMeta(b)+"']",g.currentForm).attr("aria-describedby",h.attr("id"))})))),!c&&this.settings.success&&(h.text(""),"string"==typeof this.settings.success?h.addClass(this.settings.success):this.settings.success(h,b)),this.toShow=this.toShow.add(h)},errorsFor:function(b){var c=this.escapeCssMeta(this.idOrName(b)),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+this.escapeCssMeta(d).replace(/\s+/g,", #")),this.errors().filter(e)},escapeCssMeta:function(a){return void 0===a?"":a.replace(/([\\!"#$%&'()*+,./:;<=>?@\[\]^`{|}~])/g,"\\$1")},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+this.escapeCssMeta(b)+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return!this.dependTypes[typeof a]||this.dependTypes[typeof a](a,b)},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(b){this.pending[b.name]||(this.pendingRequest++,a(b).addClass(this.settings.pendingClass),this.pending[b.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],a(b).removeClass(this.settings.pendingClass),c&&0===this.pendingRequest&&this.formSubmitted&&this.form()&&0===this.pendingRequest?(a(this.currentForm).submit(),this.submitButton&&a("input:hidden[name='"+this.submitButton.name+"']",this.currentForm).remove(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b,c){return c="string"==typeof c&&c||"remote",a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,{method:c})})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator").find(".validate-equalTo-blur").off(".validate-equalTo").removeClass("validate-equalTo-blur").find(".validate-lessThan-blur").off(".validate-lessThan").removeClass("validate-lessThan-blur").find(".validate-lessThanEqual-blur").off(".validate-lessThanEqual").removeClass("validate-lessThanEqual-blur").find(".validate-greaterThanEqual-blur").off(".validate-greaterThanEqual").removeClass("validate-greaterThanEqual-blur").find(".validate-greaterThan-blur").off(".validate-greaterThan").removeClass("validate-greaterThan-blur")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max|step/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a["date"===b?"dateISO":c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),""===d&&(d=!0),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0===e.param||e.param:(a.data(c.form,"validator").resetElements(a(c)),delete b[d])}}),a.each(b,function(a,d){b[a]="function"==typeof d&&"normalizer"!==a?d(c):d}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var a;b[this]&&(Array.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(a=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(a[0]),Number(a[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:void 0!==b&&null!==b&&b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)}
12047,url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(){var a=!1;return function(b,c){return a||(a=!0,this.settings.debug&&window.console&&console.warn("The `date` method is deprecated and will be removed in version '2.0.0'.\nPlease don't use it, since it relies on the Date constructor, which\nbehaves very differently across browsers and locales. Use `dateISO`\ninstead or one of the locale specific methods in `localizations/`\nand `additional-methods.js`.")),this.optional(c)||!/Invalid|NaN/.test(new Date(b).toString())}}(),dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},minlength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d>=c},maxlength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d<=c},rangelength:function(a,b,c){var d=Array.isArray(a)?a.length:this.getLength(a,b);return this.optional(b)||d>=c[0]&&d<=c[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||a<=c},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},step:function(b,c,d){var e,f=a(c).attr("type"),g="Step attribute on input type "+f+" is not supported.",h=["text","number","range"],i=new RegExp("\\b"+f+"\\b"),j=f&&!i.test(h.join()),k=function(a){var b=(""+a).match(/(?:\.(\d+))?$/);return b&&b[1]?b[1].length:0},l=function(a){return Math.round(a*Math.pow(10,e))},m=!0;if(j)throw new Error(g);return e=k(d),(k(b)>e||l(b)%l(d)!==0)&&(m=!1),this.optional(c)||m},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-equalTo-blur").length&&e.addClass("validate-equalTo-blur").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d,e){if(this.optional(c))return"dependency-mismatch";e="string"==typeof e&&e||"remote";var f,g,h,i=this.previousValue(c,e);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),i.originalMessage=i.originalMessage||this.settings.messages[c.name][e],this.settings.messages[c.name][e]=i.message,d="string"==typeof d&&{url:d}||d,h=a.param(a.extend({data:b},d.data)),i.old===h?i.valid:(i.old=h,f=this,this.startRequest(c),g={},g[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:g,context:f.currentForm,success:function(a){var d,g,h,j=a===!0||"true"===a;f.settings.messages[c.name][e]=i.originalMessage,j?(h=f.formSubmitted,f.resetInternals(),f.toHide=f.errorsFor(c),f.formSubmitted=h,f.successList.push(c),f.invalid[c.name]=!1,f.showErrors()):(d={},g=a||f.defaultMessage(c,{method:e,parameters:b}),d[c.name]=i.message=g,f.invalid[c.name]=!0,f.showErrors(d)),i.valid=j,f.stopRequest(c,j)}},d)),"pending")}}});var c,d={};return a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,c){var e=a.port;"abort"===a.mode&&(d[e]&&d[e].abort(),d[e]=c)}):(c=a.ajax,a.ajax=function(b){var e=("mode"in b?b:a.ajaxSettings).mode,f=("port"in b?b:a.ajaxSettings).port;return"abort"===e?(d[f]&&d[f].abort(),d[f]=c.apply(this,arguments),d[f]):c.apply(this,arguments)}),a}); 12048/*! jQuery Validation Plugin - v1.19.5 - 7/1/2022 12049 * https://jqueryvalidation.org/ 12050 * Copyright (c) 2022 Jörn Zaefferer; Licensed MIT */ 12051!function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-âââ]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("abaRoutingNumber",function(a){var b=0,c=a.split(""),d=c.length;if(9!==d)return!1;for(var e=0;e<d;e+=3)b+=3*parseInt(c[e],10)+7*parseInt(c[e+1],10)+parseInt(c[e+2],10);return 0!==b&&b%10===0},"Please enter a valid routing number."),a.validator.addMethod("accept",function(b,c,d){var e,f,g,h="string"==typeof d?d.replace(/\s/g,""):"image/*",i=this.optional(c);if(i)return i;if("file"===a(c).attr("type")&&(h=h.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g,"\\$&").replace(/,/g,"|").replace(/\/\*/g,"/.*"),c.files&&c.files.length))for(g=new RegExp(".?("+h+")$","i"),e=0;e<c.files.length;e++)if(f=c.files[e],!f.type.match(g))return!1;return!0},a.validator.format("Please enter a value with a valid mimetype.")),a.validator.addMethod("alphanumeric",function(a,b){return this.optional(b)||/^\w+$/i.test(a)},"Letters, numbers, and underscores only please."),a.validator.addMethod("bankaccountNL",function(a,b){if(this.optional(b))return!0;
vendor: 1,119 bytes, line 12051
12051if(!/^[0-9]{9}|([0-9]{2} ){3}[0-9]{3}$/.test(a))return!1;var c,d,e,f=a.replace(/ /g,""),g=0,h=f.length;for(c=0;c<h;c++)d=h-c,e=f.substring(c,c+1),g+=d*e;return g%11===0},"Please specify a valid bank account number."),a.validator.addMethod("bankorgiroaccountNL",function(b,c){return this.optional(c)||a.validator.methods.bankaccountNL.call(this,b,c)||a.validator.methods.giroaccountNL.call(this,b,c)},"Please specify a valid bank or giro account number."),a.validator.addMethod("bic",function(a,b){return this.optional(b)||/^([A-Z]{6}[A-Z2-9][A-NP-Z1-9])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test(a.toUpperCase())},"Please specify a valid BIC code."),a.validator.addMethod("cifES",function(a,b){"use strict";function c(a){return a%2===0}if(this.optional(b))return!0;var d,e,f,g,h=new RegExp(/^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi),i=a.substring(0,1),j=a.substring(1,8),k=a.substring(8,9),l=0,m=0,n=0;if(9!==a.length||!h.test(a))return!1;for(d=0;d<j.length;d++)e=parseInt(j[d],10),c(d)?(e*=2,n+=e<10?e:e-9):m+=e;return l=m+n,f=(10-l.toString().substr(-1)).toString(),f=parseInt(f,10)>9?"0":f,g="JABCDEFGHI".substr(f,1
vendor: 18,499 bytes, line 12051
12051).toString(),i.match(/[ABEH]/)?k===f:i.match(/[KPQS]/)?k===g:k===f||k===g},"Please specify a valid CIF number."),a.validator.addMethod("cnhBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f,g,h=0,i=0;if(b=a.charAt(0),new Array(12).join(b)===a)return!1;for(e=0,f=9,g=0;e<9;++e,--f)h+=+(a.charAt(e)*f);for(c=h%11,c>=10&&(c=0,i=2),h=0,e=0,f=1,g=0;e<9;++e,++f)h+=+(a.charAt(e)*f);return d=h%11,d>=10?d=0:d-=i,String(c).concat(d)===a.substr(-2)},"Please specify a valid CNH number."),a.validator.addMethod("cnpjBR",function(a,b){"use strict";if(this.optional(b))return!0;if(a=a.replace(/[^\d]+/g,""),14!==a.length)return!1;if("00000000000000"===a||"11111111111111"===a||"22222222222222"===a||"33333333333333"===a||"44444444444444"===a||"55555555555555"===a||"66666666666666"===a||"77777777777777"===a||"88888888888888"===a||"99999999999999"===a)return!1;for(var c=a.length-2,d=a.substring(0,c),e=a.substring(c),f=0,g=c-7,h=c;h>=1;h--)f+=d.charAt(c-h)*g--,g<2&&(g=9);var i=f%11<2?0:11-f%11;if(i!==parseInt(e.charAt(0),10))return!1;c+=1,d=a.substring(0,c),f=0,g=c-7;for(var j=c;j>=1;j--)f+=d.charAt(c-j)*g--,g<2&&(g=9);return i=f%11<2?0:11-f%11,i===parseInt(e.charAt(1),10)},"Please specify a CNPJ value number."),a.validator.addMethod("cpfBR",function(a,b){"use strict";if(this.optional(b))return!0;if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var c,d,e,f,g=0;if(c=parseInt(a.substring(9,10),10),d=parseInt(a.substring(10,11),10),e=function(a,b){var c=10*a%11;return 10!==c&&11!==c||(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(f=1;f<=9;f++)g+=parseInt(a.substring(f-1,f),10)*(11-f);if(e(g,c)){for(g=0,f=1;f<=10;f++)g+=parseInt(a.substring(f-1,f),10)*(12-f);return e(g,d)}return!1},"Please specify a valid CPF number."),a.validator.addMethod("creditcard",function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},"Please enter a valid credit card number."),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&(/^(5[12345])/.test(a)||/^(2[234567])/.test(a))?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:!!(128&d)},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=!!e||c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency."),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number."),a.validator.addMethod("greaterThan",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-greaterThan-blur").length&&e.addClass("validate-greaterThan-blur").on("blur.validate-greaterThan",function(){a(c).valid()}),b>e.val()},"Please enter a greater value."),a.validator.addMethod("greaterThanEqual",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-greaterThanEqual-blur").length&&e.addClass("validate-greaterThanEqual-blur").on("blur.validate-greaterThanEqual",function(){a(c).valid()}),b>=e.val()},"Please enter a greater value."),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="",q=5;if(l.length<q)return!1;if(c=l.substring(0,2),h={AL:"\\d{8}[\\dA-Z]{16}",AD:"\\d{8}[\\dA-Z]{12}",AT:"\\d{16}",AZ:"[\\dA-Z]{4}\\d{20}",BE:"\\d{12}",BH:"[A-Z]{4}[\\dA-Z]{14}",BA:"\\d{16}",BR:"\\d{23}[A-Z][\\dA-Z]",BG:"[A-Z]{4}\\d{6}[\\dA-Z]{8}",CR:"\\d{17}",HR:"\\d{17}",CY:"\\d{8}[\\dA-Z]{16}",CZ:"\\d{20}",DK:"\\d{14}",DO:"[A-Z]{4}\\d{20}",EE:"\\d{16}",FO:"\\d{14}",FI:"\\d{14}",FR:"\\d{10}[\\dA-Z]{11}\\d{2}",GE:"[\\dA-Z]{2}\\d{16}",DE:"\\d{18}",GI:"[A-Z]{4}[\\dA-Z]{15}",GR:"\\d{7}[\\dA-Z]{16}",GL:"\\d{14}",GT:"[\\dA-Z]{4}[\\dA-Z]{20}",HU:"\\d{24}",IS:"\\d{22}",IE:"[\\dA-Z]{4}\\d{14}",IL:"\\d{19}",IT:"[A-Z]\\d{10}[\\dA-Z]{12}",KZ:"\\d{3}[\\dA-Z]{13}",KW:"[A-Z]{4}[\\dA-Z]{22}",LV:"[A-Z]{4}[\\dA-Z]{13}",LB:"\\d{4}[\\dA-Z]{20}",LI:"\\d{5}[\\dA-Z]{12}",LT:"\\d{16}",LU:"\\d{3}[\\dA-Z]{13}",MK:"\\d{3}[\\dA-Z]{10}\\d{2}",MT:"[A-Z]{4}\\d{5}[\\dA-Z]{18}",MR:"\\d{23}",MU:"[A-Z]{4}\\d{19}[A-Z]{3}",MC:"\\d{10}[\\dA-Z]{11}\\d{2}",MD:"[\\dA-Z]{2}\\d{18}",ME:"\\d{18}",NL:"[A-Z]{4}\\d{10}",NO:"\\d{11}",PK:"[\\dA-Z]{4}\\d{16}",PS:"[\\dA-Z]{4}\\d{21}",PL:"\\d{24}",PT:"\\d{21}",RO:"[A-Z]{4}[\\dA-Z]{16}",SM:"[A-Z]\\d{10}[\\dA-Z]{12}",SA:"\\d{2}[\\dA-Z]{18}",RS:"\\d{18}",SK:"\\d{20}",SI:"\\d{15}",ES:"\\d{20}",SE:"\\d{20}",CH:"\\d{5}[\\dA-Z]{12}",TN:"\\d{20}",TR:"\\d{5}[\\dA-Z]{17}",AE:"\\d{3}\\d{16}",GB:"[A-Z]{4}\\d{14}",VG:"[\\dA-Z]{4}\\d{16}"},g=h[c],"undefined"!=typeof g&&(i=new RegExp("^[A-Z]{2}\\d{2}"+g+"$",""),!i.test(l)))return!1;for(d=l.substring(4,l.length)+l.substring(0,4),j=0;j<d.length;j++)e=d.charAt(j),"0"!==e&&(n=!1),n||(m+="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(e));for(k=0;k<m.length;k++)f=m.charAt(k),p=""+o+f,o=p%97;return 1===o},"Please specify a valid IBAN."),a.validator.addMethod("integer",function(a,b){return this.optional(b)||/^-?\d+$/.test(a)},"A positive or negative non-decimal number please."),a.validator.addMethod("ipv4",function(a,b){return this.optional(b)||/^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test(a)},"Please enter a valid IP v4 address."),a.validator.addMethod("ipv6",function(a,b){return this.optional(b)||/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test(a)},"Please enter a valid IP v6 address."),a.validator.addMethod("lessThan",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-lessThan-blur").length&&e.addClass("validate-lessThan-blur").on("blur.validate-lessThan",function(){a(c).valid()}),b<e.val()},"Please enter a lesser value."),a.validator.addMethod("lessThanEqual",function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.not(".validate-lessThanEqual-blur").length&&e.addClass("validate-lessThanEqual-blur").on("blur.validate-lessThanEqual",function(){a(c).valid()}),b<=e.val()},"Please enter a lesser value."),a.validator.addMethod("lettersonly",function(a,b){return this.optional(b)||/^[a-z]+$/i.test(a)},"Letters only please."),a.validator.addMethod("letterswithbasicpunc",function(a,b){return this.optional(b)||/^[a-z\-.,()'"\s]+$/i.test(a)},"Letters or punctuation only please."),a.validator.addMethod("maxfiles",function(b,c,d){return!!this.optional(c)||!("file"===a(c).attr("type")&&c.files&&c.files.length>d)},a.validator.format("Please select no more than {0} files.")),a.validator.addMethod("maxsize",function(b,c,d){if(this.optional(c))return!0;if("file"===a(c).attr("type")&&c.files&&c.files.length)for(var e=0;e<c.files.length;e++)if(c.files[e].size>d)return!1;return!0},a.validator.format("File size must not exceed {0} bytes each.")),a.validator.addMethod("maxsizetotal",function(b,c,d){if(this.optional(c))return!0;if("file"===a(c).attr("type")&&c.files&&c.files.length)for(var e=0,f=0;f<c.files.length;f++)if(e+=c.files[f].size,e>d)return!1;return!0},a.validator.format("Total size of all files must not exceed {0} bytes.")),a.validator.addMethod("mobileNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid mobile number."),a.validator.addMethod("mobileRU",function(a,b){var c=a.replace(/\(|\)|\s+|-/g,"");return this.optional(b)||c.length>9&&/^((\+7|7|8)+([0-9]){10})$/.test(c)},"Please specify a valid mobile number."),a.validator.addMethod("mobileUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number."),a.validator.addMethod("netmask",function(a,b){return this.optional(b)||/^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test(a)},"Please enter a valid netmask."),a.validator.addMethod("nieES",function(a,b){"use strict";if(this.optional(b))return!0;var c,d=new RegExp(/^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi),e="TRWAGMYFPDXBNJZSQVHLCKET",f=a.substr(a.length-1).toUpperCase();return a=a.toString().toUpperCase(),!(a.length>10||a.length<9||!d.test(a))&&(a=a.replace(/^[X]/,"0").replace(/^[Y]/,"1").replace(/^[Z]/,"2"),c=9===a.length?a.substr(0,8):a.substr(0,9),e.charAt(parseInt(c,10)%23)===f)},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a,b){"use strict";return!!this.optional(b)||(a=a.toUpperCase(),!!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")&&(/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):!!/^[KLM]{1}/.test(a)&&a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,1)%23)))},"Please specify a valid NIF number."),a.validator.addMethod("nipPL",function(a){"use strict";if(a=a.replace(/[^0-9]/g,""),10!==a.length)return!1;for(var b=[6,5,7,2,3,4,5,6,7],c=0,d=0;d<9;d++)c+=b[d]*a[d];var e=c%11,f=10===e?0:e;return f===parseInt(a[9],10)},"Please specify a valid NIP number."),a.validator.addMethod("nisBR",function(a){var b,c,d,e,f,g=0;if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;for(c=parseInt(a.substring(10,11),10),b=parseInt(a.substring(0,10),10),e=2;e<12;e++)f=e,10===e&&(f=2),11===e&&(f=3),g+=b%10*f,b=parseInt(b/10,10);return d=g%11,d=d>1?11-d:0,c===d},"Please specify a valid NIS/PIS number."),a.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please."),a.validator.addMethod("pattern",function(a,b,c){return!!this.optional(b)||("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonePL",function(a,b){a=a.replace(/\s+/g,"");var c=/^(?:(?:(?:\+|00)?48)|(?:\(\+?48\)))?(?:1[2-8]|2[2-69]|3[2-49]|4[1-68]|5[0-9]|6[0-35-9]|[7-8][1-9]|9[145])\d{7}$/;return this.optional(b)||c.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number."),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number."),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]\d{2}-?\d{4}$/)},"Please specify a valid phone number."),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test(a)},"Please specify a valid postal code."),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code."),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code."),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode."),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=!e&&"undefined"!=typeof c.caseSensitive&&c.caseSensitive,g=!e&&"undefined"!=typeof c.includeTerritories&&c.includeTerritories,h=!e&&"undefined"!=typeof c.includeMilitary&&c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state."),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters.")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59."),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format."),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})+(?::(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?)|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff])|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62}\.)))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;b<17;b++)
12051{if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c<h.length;c++)if(d.toUpperCase()===h[c]){d=i[c],d*=e,isNaN(g)&&8===c&&(g=h[c]);break}}else d*=e;k+=d}return f=k%11,10===f&&(f="X"),f===g},"The specified vehicle identification number (VIN) is invalid."),a.validator.addMethod("zipcodeUS",function(a,b){return this.optional(b)||/^\d{5}(-\d{4})?$/.test(a)},"The specified US ZIP Code is invalid."),a.validator.addMethod("ziprange",function(a,b){return this.optional(b)||/^90[2-5]\d\{2\}-\d{4}$/.test(a)},"Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx."),a}); 12052/** 12053 * Minified by jsDelivr using UglifyJS v3.4.4. 12054 * Original file: /npm/[email protected]/src/js.cookie.js 12055 * 12056 * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 12057 */ 12058!function(e){var n=!1;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var o=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=o,t}}}(function(){function g(){for(var e=0,n={};e<arguments.length;e++){var o=arguments[e];for(var t in o)n[t]=o[t]}return n}return function e(l){function C(e,n,o){var t;if("undefined"!=typeof document){if(1<arguments.length){if("number"==typeof(o=g({path:"/"},C.defaults,o)).expires){var r=new Date;r.setMilliseconds(r.getMilliseconds()+864e5*o.expires),o.expires=r}o.expires=o.expires?o.expires.toUTCString():"";try{t=JSON.stringify(n),/^[\{\[]/.test(t)&&(n=t)}catch(e){}n=l.write?l.write(n,e):encodeURIComponent(String(n)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),e=(e=(e=encodeURIComponent(String(e))).replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent)).replace(/[\(\)]/g,escape);var i="";for(var c in o)o[c]&&(i+="; "+c,!0!==o[c]&&(i+="="+o[c]));return document.cookie=e+"="+n+i}e||(t={});for(var a=document.cookie?document.cookie.split("; "):[],s=/(%[0-9A-Z]{2})+/g,f=0;f<a.length;f++){var p=a[f].split("="),d=p.slice(1).join("=");this.json||'"'!==d.charAt(0)||(d=d.slice(1,-1));try{var u=p[0].replace(s,decodeURIComponent);if(d=l.read?l.read(d,u):l(d,u)||d.replace(s,decodeURIComponent),this.json)try{d=JSON.parse(d)}catch(e){}if(e===u){t=d;break}e||(t[u]=d)}catch(e){}}return t}}return(C.set=C).get=function(e){return C.call(C,e)},C.getJSON=function(){return C.apply({json:!0},[].slice.call(arguments))},C.defaults={},C.remove=function(e,n){C(e,"",g(n,{expires:-1}))},C.withConverter=e,C}(function(){})}); 12059 12060/** 12061* Ajax Autocomplete for jQuery, version 1.2.24 12062* (c) 2014 Tomas Kirda 12063* 12064* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 12065* For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 12066*/ 12067!function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a("object"==typeof exports&&"function"==typeof require?require("jquery"):jQuery)}(function(a){"use strict";function b(c,d){var e=function(){},f=this,g={ajaxSettings:{},autoSelectFirst:!1,appendTo:document.body,serviceUrl:null,lookup:null,onSelect:null,width:"auto",minChars:1,maxHeight:300,deferRequestBy:0,params:{},formatResult:b.formatResult,delimiter:null,zIndex:9999,type:"GET",noCache:!1,onSearchStart:e,onSearchComplete:e,onSearchError:e,preserveInput:!1,containerClass:"autocomplete-suggestions",tabDisabled:!1,dataType:"text",currentRequest:null,triggerSelectOnValidInput:!0,preventBadQueries:!0,lookupFilter:function(a,b,c){return-1!==a.value.toLowerCase().indexOf(c)},paramName:"query",transformResult:function(b){return"string"==typeof b?a.parseJSON(b):b},showNoSuggestionNotice:!1,noSuggestionNotice:"No results",orientation:"bottom",forceFixPosition:!1};f.element=c,f.el=a(c),f.suggestions=[],f.badQueries=[],f.selectedIndex=-1,f.currentValue=f.element.value,f.intervalId=0,f.cachedResponse={},f.onChangeInterval=null,f.onChange=null,f.isLocal=!1,f.suggestionsContainer=null,f.noSuggestionsContainer=null,f.options=a.extend({},g,d),f.classes={selected:"autocomplete-selected",suggestion:"autocomplete-suggestion"},f.hint=null,f.hintValue="",f.selection=null,f.initialize(),f.setOptions(d)}var c=function(){return{escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},createNode:function(a){var b=document.createElement("div");return b.className=a,b.style.position="absolute",b.style.display="none",b}}}(),d={ESC:27,TAB:9,RETURN:13,LEFT:37,UP:38,RIGHT:39,DOWN:40};b.utils=c,a.Autocomplete=b,b.formatResult=function(a,b){var d="("+c.escapeRegExChars(b)+")";return a.value.replace(new RegExp(d,"gi"),"<strong>$1</strong>").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/<(\/?strong)>/g,"<$1>")},b.prototype={killerFn:null,initialize:function(){var c,d=this,e="."+d.classes.suggestion,f=d.classes.selected,g=d.options;d.element.setAttribute("autocomplete","off"),d.killerFn=function(b){0===a(b.target).closest("."+d.options.containerClass).length&&(d.killSuggestions(),d.disableKillerFn())},d.noSuggestionsContainer=a('<div class="autocomplete-no-suggestion"></div>').html(this.options.noSuggestionNotice).get(0),d.suggestionsContainer=b.utils.createNode(g.containerClass),c=a(d.suggestionsContainer),c.appendTo(g.appendTo),"auto"!==g.width&&c.width(g.width),c.on("mouseover.autocomplete",e,function(){d.activate(a(this).data("index"))}
12067),c.on("mouseout.autocomplete",function(){d.selectedIndex=-1,c.children("."+f).removeClass(f)}),c.on("click.autocomplete",e,function(){d.select(a(this).data("index"))}),d.fixPositionCapture=function(){d.visible&&d.fixPosition()},a(window).on("resize.autocomplete",d.fixPositionCapture),d.el.on("keydown.autocomplete",function(a){d.onKeyPress(a)}),d.el.on("keyup.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("blur.autocomplete",function(){d.onBlur()}),d.el.on("focus.autocomplete",function(){d.onFocus()}),d.el.on("change.autocomplete",function(a){d.onKeyUp(a)}),d.el.on("input.autocomplete",function(a){d.onKeyUp(a)})},onFocus:function(){var a=this;a.fixPosition(),0===a.options.minChars&&0===a.el.val().length&&a.onValueChange()},onBlur:function(){this.enableKillerFn()},abortAjax:function(){var a=this;a.currentRequest&&(a.currentRequest.abort(),a.currentRequest=null)},setOptions:function(b){var c=this,d=c.options;a.extend(d,b),c.isLocal=a.isArray(d.lookup),c.isLocal&&(d.lookup=c.verifySuggestionsFormat(d.lookup)),d.orientation=c.validateOrientation(d.orientation,"bottom"),a(c.suggestionsContainer).css({"max-height":d.maxHeight+"px",width:d.width+"px","z-index":d.zIndex})},clearCache:function(){this.cachedResponse={},this.badQueries=[]},clear:function(){this.clearCache(),this.currentValue="",this.suggestions=[]},disable:function(){var a=this;a.disabled=!0,clearInterval(a.onChangeInterval),a.abortAjax()},enable:function(){this.disabled=!1},fixPosition:function(){var b=this,c=a(b.suggestionsContainer),d=c.parent().get(0);if(d===document.body||b.options.forceFixPosition){var e=b.options.orientation,f=c.outerHeight(),g=b.el.outerHeight(),h=b.el.offset(),i={top:h.top,left:h.left};if("auto"===e){var j=a(window).height(),k=a(window).scrollTop(),l=-k+h.top-f,m=k+j-(h.top+g+f);e=Math.max(l,m)===l?"top":"bottom"}if("top"===e?i.top+=-f:i.top+=g,d!==document.body){var n,o=c.css("opacity");b.visible||c.css("opacity",0).show(),n=c.offsetParent().offset(),i.top-=n.top,i.left-=n.left,b.visible||c.css("opacity",o).hide()}"auto"===b.options.width&&(i.width=b.el.outerWidth()-2+"px"),c.css(i)}},enableKillerFn:function(){var b=this;a(document).on("click.autocomplete",b.killerFn)},disableKillerFn:function(){var b=this;a(document).off("click.autocomplete",b.killerFn)},killSuggestions:function(){var a=this;a.stopKillSuggestions(),a.intervalId=window.setInterval(function(){a.visible&&(a.el.val(a.currentValue),a.hide()),a.stopKillSuggestions()},50)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},isCursorAtEnd:function(){var a,b=this,c=b.el.val().length,d=b.element.selectionStart;return"number"==typeof d?d===c:document.selection?(a=document.selection.createRange(),a.moveStart("character",-c),c===a.text.length):!0},onKeyPress:function(a){var b=this;if(!b.disabled&&!b.visible&&a.which===d.DOWN&&b.currentValue)return void b.suggest();if(!b.disabled&&b.visible){switch(a.which){case d.ESC:b.el.val(b.currentValue),b.hide();break;case d.RIGHT:if(b.hint&&b.options.onHint&&b.isCursorAtEnd()){b.selectHint();break}return;case d.TAB:if(b.hint&&b.options.onHint)return void b.selectHint();if(-1===b.selectedIndex)return void b.hide();if(b.select(b.selectedIndex),b.options.tabDisabled===!1)return;break;case d.RETURN:if(-1===b.selectedIndex)return void b.hide();b.select(b.selectedIndex);break;case d.UP:b.moveUp();break;case d.DOWN:b.moveDown();break;default:return}a.stopImmediatePropagation(),a.preventDefault()}},onKeyUp:function(a){var b=this;if(!b.disabled){switch(a.which){case d.UP:case d.DOWN:return}clearInterval(b.onChangeInterval),b.currentValue!==b.el.val()&&(b.findBestHint(),b.options.deferRequestBy>0?b.onChangeInterval=setInterval(function(){b.onValueChange()},b.options.deferRequestBy):b.onValueChange())}},onValueChange:function(){var b=this,c=b.options,d=b.el.val(),e=b.getQuery(d);return b.selection&&b.currentValue!==e&&(b.selection=null,(c.onInvalidateSelection||a.noop).call(b.element)),clearInterval(b.onChangeInterval),b.currentValue=d,b.selectedIndex=-1,c.triggerSelectOnValidInput&&b.isExactMatch(e)?void b.select(0):void(e.length<c.minChars?b.hide():b.getSuggestions(e))},isExactMatch:function(a){var b=this.suggestions;return 1===b.length&&b[0].value.toLowerCase()===a.toLowerCase()},getQuery:function(b){var c,d=this.options.delimiter;return d?(c=b.split(d),a.trim(c[c.length-1])):b},getSuggestionsLocal:function(b){var c,d=this,e=d.options,f=b.toLowerCase(),g=e.lookupFilter,h=parseInt(e.lookupLimit,10);return c={suggestions:a.grep(e.lookup,function(a){return g(a,b,f)})},h&&c.suggestions.length>h&&(c.suggestions=c.suggestions.slice(0,h)),c},getSuggestions:function(b){var c,d,e,f,g=this,h=g.options,i=h.serviceUrl;if(h.params[h.paramName]=b,d=h.ignoreParams?null:h.params,h.onSearchStart.call(g.element,h.params)!==!1){if(a.isFunction(h.lookup))return void h.lookup(b,function(a){g.suggestions=a.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,a.suggestions)});g.isLocal?c=g.getSuggestionsLocal(b):(a.isFunction(i)&&(i=i.call(g.element,b)),e=i+"?"+a.param(d||{}),c=g.cachedResponse[e]),c&&a.isArray(c.suggestions)?(g.suggestions=c.suggestions,g.suggest(),h.onSearchComplete.call(g.element,b,c.suggestions)):g.isBadQuery(b)?h.onSearchComplete.call(g.element,b,[]):(g.abortAjax(),f={url:i,data:d,type:h.type,dataType:h.dataType},a.extend(f,h.ajaxSettings),g.currentRequest=a.ajax(f).done(function(a){var c;g.currentRequest=null,c=h.transformResult(a,b),g.processResponse(c,b,e),h.onSearchComplete.call(g.element,b,c.suggestions)}).fail(function(a,c,d){h.onSearchError.call(g.element,b,a,c,d)}))}}
12067,isBadQuery:function(a){if(!this.options.preventBadQueries)return!1;for(var b=this.badQueries,c=b.length;c--;)if(0===a.indexOf(b[c]))return!0;return!1},hide:function(){var b=this,c=a(b.suggestionsContainer);a.isFunction(b.options.onHide)&&b.visible&&b.options.onHide.call(b.element,c),b.visible=!1,b.selectedIndex=-1,clearInterval(b.onChangeInterval),a(b.suggestionsContainer).hide(),b.signalHint(null)},suggest:function(){if(0===this.suggestions.length)return void(this.options.showNoSuggestionNotice?this.noSuggestions():this.hide());var b,c=this,d=c.options,e=d.groupBy,f=d.formatResult,g=c.getQuery(c.currentValue),h=c.classes.suggestion,i=c.classes.selected,j=a(c.suggestionsContainer),k=a(c.noSuggestionsContainer),l=d.beforeRender,m="",n=function(a,c){var d=a.data[e];return b===d?"":(b=d,'<div class="autocomplete-group"><strong>'+b+"</strong></div>")};return d.triggerSelectOnValidInput&&c.isExactMatch(g)?void c.select(0):(a.each(c.suggestions,function(a,b){e&&(m+=n(b,g,a)),m+='<div class="'+h+'" data-index="'+a+'">'+f(b,g)+"</div>"}),this.adjustContainerWidth(),k.detach(),j.html(m),a.isFunction(l)&&l.call(c.element,j),c.fixPosition(),j.show(),d.autoSelectFirst&&(c.selectedIndex=0,j.scrollTop(0),j.children("."+h).first().addClass(i)),c.visible=!0,void c.findBestHint())},noSuggestions:function(){var b=this,c=a(b.suggestionsContainer),d=a(b.noSuggestionsContainer);this.adjustContainerWidth(),d.detach(),c.empty(),c.append(d),b.fixPosition(),c.show(),b.visible=!0},adjustContainerWidth:function(){var b,c=this,d=c.options,e=a(c.suggestionsContainer);"auto"===d.width&&(b=c.el.outerWidth()-2,e.width(b>0?b:300))},findBestHint:function(){var b=this,c=b.el.val().toLowerCase(),d=null;c&&(a.each(b.suggestions,function(a,b){var e=0===b.value.toLowerCase().indexOf(c);return e&&(d=b),!e}),b.signalHint(d))},signalHint:function(b){var c="",d=this;b&&(c=d.currentValue+b.value.substr(d.currentValue.length)),d.hintValue!==c&&(d.hintValue=c,d.hint=b,(this.options.onHint||a.noop)(c))},verifySuggestionsFormat:function(b){return b.length&&"string"==typeof b[0]?a.map(b,function(a){return{value:a,data:null}}):b},validateOrientation:function(b,c){return b=a.trim(b||"").toLowerCase(),-1===a.inArray(b,["auto","bottom","top"])&&(b=c),b},processResponse:function(a,b,c){var d=this,e=d.options;a.suggestions=d.verifySuggestionsFormat(a.suggestions),e.noCache||(d.cachedResponse[c]=a,e.preventBadQueries&&0===a.suggestions.length&&d.badQueries.push(b)),b===d.getQuery(d.currentValue)&&(d.suggestions=a.suggestions,d.suggest())},activate:function(b){var c,d=this,e=d.classes.selected,f=a(d.suggestionsContainer),g=f.find("."+d.classes.suggestion);return f.find("."+e).removeClass(e),d.selectedIndex=b,-1!==d.selectedIndex&&g.length>d.selectedIndex?(c=g.get(d.selectedIndex),a(c).addClass(e),c):null},selectHint:function(){var b=this,c=a.inArray(b.hint,b.suggestions);b.select(c)},select:function(a){var b=this;b.hide(),b.onSelect(a)},moveUp:function(){var b=this;if(-1!==b.selectedIndex)return 0===b.selectedIndex?(a(b.suggestionsContainer).children().first().removeClass(b.classes.selected),b.selectedIndex=-1,b.el.val(b.currentValue),void b.findBestHint()):void b.adjustScroll(b.selectedIndex-1)},moveDown:function(){var a=this;a.selectedIndex!==a.suggestions.length-1&&a.adjustScroll(a.selectedIndex+1)},adjustScroll:function(b){var c=this,d=c.activate(b);if(d){var e,f,g,h=a(d).outerHeight();e=d.offsetTop,f=a(c.suggestionsContainer).scrollTop(),g=f+c.options.maxHeight-h,f>e?a(c.suggestionsContainer).scrollTop(e):e>g&&a(c.suggestionsContainer).scrollTop(e-c.options.maxHeight+h),c.options.preserveInput||c.el.val(c.getValue(c.suggestions[b].value)),c.signalHint(null)}},onSelect:function(b){var c=this,d=c.options.onSelect,e=c.suggestions[b];c.currentValue=c.getValue(e.value),c.currentValue===c.el.val()||c.options.preserveInput||c.el.val(c.currentValue),c.signalHint(null),c.suggestions=[],c.selection=e,a.isFunction(d)&&d.call(c.element,e)},getValue:function(a){var b,c,d=this,e=d.options.delimiter;return e?(b=d.currentValue,c=b.split(e),1===c.length?a:b.substr(0,b.length-c[c.length-1].length)+a):a},dispose:function(){var b=this;b.el.off(".autocomplete").removeData("autocomplete"),b.disableKillerFn(),a(window).off("resize.autocomplete",b.fixPositionCapture),a(b.suggestionsContainer).remove()}},a.fn.autocomplete=a.fn.devbridgeAutocomplete=function(c,d){var e="autocomplete";return 0===arguments.length?this.first().data(e):this.each(function(){var f=a(this),g=f.data(e);"string"==typeof c?g&&"function"==typeof g[c]&&g[c](d):(g&&g.dispose&&g.dispose(),g=new b(this,c),f.data(e,g))})}}); 12068/*! 12069 * numeral.js 12070 * version : 1.5.3 12071 * author : Adam Draper 12072 * license : MIT 12073 * http://adamwdraper.github.com/Numeral-js/ 12074 */ 12075(function(){function a(a){this._value=a}function b(a,b,c,d){var e,f,g=Math.pow(10,b);return f=(c(a*g)/g).toFixed(b),d&&(e=new RegExp("0{1,"+d+"}$"),f=f.replace(e,"")),f}function c(a,b,c){var d;return d=b.indexOf("$")>-1?e(a,b,c):b.indexOf("%")>-1?f(a,b,c):b.indexOf(":")>-1?g(a,b):i(a._value,b,c)}function d(a,b){var c,d,e,f,g,i=b,j=["KB","MB","GB","TB","PB","EB","ZB","YB"],k=!1;if(b.indexOf(":")>-1)a._value=h(b);else if(b===q)a._value=0;else{for("."!==o[p].delimiters.decimal&&(b=b.replace(/\./g,"").replace(o[p].delimiters.decimal,".")),c=new RegExp("[^a-zA-Z]"+o[p].abbreviations.thousand+"(?:\\)|(\\"+o[p].currency.symbol+")?(?:\\))?)?$"),d=new RegExp("[^a-zA-Z]"+o[p].abbreviations.million+"(?:\\)|(\\"+o[p].currency.symbol+")?(?:\\))?)?$"),e=new RegExp("[^a-zA-Z]"+o[p].abbreviations.billion+"(?:\\)|(\\"+o[p].currency.symbol+")?(?:\\))?)?$"),f=new RegExp("[^a-zA-Z]"+o[p].abbreviations.trillion+"(?:\\)|(\\"+o[p].currency.symbol+")?(?:\\))?)?$"),g=0;g<=j.length&&!(k=b.indexOf(j[g])>-1?Math.pow(1024,g+1):!1);g++);a._value=(k?k:1)*(i.match(c)?Math.pow(10,3):1)*(i.match(d)?Math.pow(10,6):1)*(i.match(e)?Math.pow(10,9):1)*(i.match(f)?Math.pow(10,12):1)*(b.indexOf("%")>-1?.01:1)*((b.split("-").length+Math.min(b.split("(").length-1,b.split(")").length-1))%2?1:-1)*Number(b.replace(/[^0-9\.]+/g,"")),a._value=k?Math.ceil(a._value):a._value}return a._value}function e(a,b,c){var d,e,f=b.indexOf("$"),g=b.indexOf("("),h=b.indexOf("-"),j="";return b.indexOf(" $")>-1?(j=" ",b=b.replace(" $","")):b.indexOf("$ ")>-1?(j=" ",b=b.replace("$ ","")):b=b.replace("$",""),e=i(a._value,b,c),1>=f?e.indexOf("(")>-1||e.indexOf("-")>-1?(e=e.split(""),d=1,(g>f||h>f)&&(d=0),e.splice(d,0,o[p].currency.symbol+j),e=e.join("")):e=o[p
12075].currency.symbol+j+e:e.indexOf(")")>-1?(e=e.split(""),e.splice(-1,0,j+o[p].currency.symbol),e=e.join("")):e=e+j+o[p].currency.symbol,e}function f(a,b,c){var d,e="",f=100*a._value;return b.indexOf(" %")>-1?(e=" ",b=b.replace(" %","")):b=b.replace("%",""),d=i(f,b,c),d.indexOf(")")>-1?(d=d.split(""),d.splice(-1,0,e+"%"),d=d.join("")):d=d+e+"%",d}function g(a){var b=Math.floor(a._value/60/60),c=Math.floor((a._value-60*b*60)/60),d=Math.round(a._value-60*b*60-60*c);return b+":"+(10>c?"0"+c:c)+":"+(10>d?"0"+d:d)}function h(a){var b=a.split(":"),c=0;return 3===b.length?(c+=60*Number(b[0])*60,c+=60*Number(b[1]),c+=Number(b[2])):2===b.length&&(c+=60*Number(b[0]),c+=Number(b[1])),Number(c)}function i(a,c,d){var e,f,g,h,i,j,k=!1,l=!1,m=!1,n="",r=!1,s=!1,t=!1,u=!1,v=!1,w="",x="",y=Math.abs(a),z=["B","KB","MB","GB","TB","PB","EB","ZB","YB"],A="",B=!1;if(0===a&&null!==q)return q;if(c.indexOf("(")>-1?(k=!0,c=c.slice(1,-1)):c.indexOf("+")>-1&&(l=!0,c=c.replace(/\+/g,"")),c.indexOf("a")>-1&&(r=c.indexOf("aK")>=0,s=c.indexOf("aM")>=0,t=c.indexOf("aB")>=0,u=c.indexOf("aT")>=0,v=r||s||t||u,c.indexOf(" a")>-1?(n=" ",c=c.replace(" a","")):c=c.replace("a",""),y>=Math.pow(10,12)&&!v||u?(n+=o[p].abbreviations.trillion,a/=Math.pow(10,12)):y<Math.pow(10,12)&&y>=Math.pow(10,9)&&!v||t?(n+=o[p].abbreviations.billion,a/=Math.pow(10,9)):y<Math.pow(10,9)&&y>=Math.pow(10,6)&&!v||s?(n+=o[p].abbreviations.million,a/=Math.pow(10,6)):(y<Math.pow(10,6)&&y>=Math.pow(10,3)&&!v||r)&&(n+=o[p].abbreviations.thousand,a/=Math.pow(10,3))),c.indexOf("b")>-1)for(c.indexOf(" b")>-1?(w=" ",c=c.replace(" b","")):c=c.replace("b",""),g=0;g<=z.length;g++)if(e=Math.pow(1024,g),f=Math.pow(1024,g+1),a>=e&&f>a){w+=z[g],e>0&&(a/=e);break}return c.indexOf("o")>-1&&(c.indexOf(" o")>-1?(x=" ",c=c.replace(" o","")):c=c.replace("o",""),x+=o[p].ordinal(a)),c.indexOf("[.]")>-1&&(m=!0,c=c.replace("[.]",".")),h=a.toString().split(".")[0],i=c.split(".")[1],j=c.indexOf(","),i?(i.indexOf("[")>-1?(i=i.replace("]",""),i=i.split("["),A=b(a,i[0].length+i[1].length,d,i[1].length)):A=b(a,i.length,d),h=A.split(".")[0],A=A.split(".")[1].length?o[p].delimiters.decimal+A.split(".")[1]:"",m&&0===Number(A.slice(1))&&(A="")):h=b(a,null,d),h.indexOf("-")>-1&&(h=h.slice(1),B=!0),j>-1&&(h=h.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+o[p].delimiters.thousands)),0===c.indexOf(".")&&(h=""),(k&&B?"(":"")+(!k&&B?"-":"")+(!B&&l?"+":"")+h+A+(x?x:"")+(n?n:"")+(w?w:"")+(k&&B?")":"")}function j(a,b){o[a]=b}function k(a){var b=a.toString().split(".");return b.length<2?1:Math.pow(10,b[1].length)}function l(){var a=Array.prototype.slice.call(arguments);return a.reduce(function(a,b){var c=k(a),d=k(b);return c>d?c:d},-1/0)}var m,n="1.5.3",o={},p="en",q=null,r="0,0",s="undefined"!=typeof module&&module.exports;m=function(b){return m.isNumeral(b)?b=b.value():0===b||"undefined"==typeof b?b=0:Number(b)||(b=m.fn.unformat(b)),new a(Number(b))},m.version=n,m.isNumeral=function(b){return b instanceof a},m.language=function(a,b){if(!a)return p;if(a&&!b){if(!o[a])throw new Error("Unknown language : "+a);p=a}return(b||!o[a])&&j(a,b),m},m.languageData=function(a){if(!a)return o[p];if(!o[a])throw new Error("Unknown language : "+a);return o[a]},m.language("en",{delimiters:{thousands:",",decimal:"."},abbreviations:{thousand:"k",million:"m",billion:"b",trillion:"t"},ordinal:function(a){var b=a%10;return 1===~~(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th"},currency:{symbol:"$"}}),m.zeroFormat=function(a){q="string"==typeof a?a:null},m.defaultFormat=function(a){r="string"==typeof a?a:"0.0"},"function"!=typeof Array.prototype.reduce&&(Array.prototype.reduce=function(a,b){"use strict";if(null===this||"undefined"==typeof this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!=typeof a)throw new TypeError(a+" is not a function");var c,d,e=this.length>>>0,f=!1;for(1<arguments.length&&(d=b,f=!0),c=0;e>c;++c)this.hasOwnProperty(c)&&(f?d=a(d,this[c],c,this):(d=this[c],f=!0));if(!f)throw new TypeError("Reduce of empty array with no initial value");return d}),m.fn=a.prototype={clone:function(){return m(this)},format:function(a,b){return c(this,a?a:r,void 0!==b?b:Math.round)},unformat:function(a){return"[object Number]"===Object.prototype.toString.call(a)?a:d(this,a?a:r)},value:function(){return this._value}
12075,valueOf:function(){return this._value},set:function(a){return this._value=Number(a),this},add:function(a){function b(a,b){return a+c*b}var c=l.call(null,this._value,a);return this._value=[this._value,a].reduce(b,0)/c,this},subtract:function(a){function b(a,b){return a-c*b}var c=l.call(null,this._value,a);return this._value=[a].reduce(b,this._value*c)/c,this},multiply:function(a){function b(a,b){var c=l(a,b);return a*c*b*c/(c*c)}return this._value=[this._value,a].reduce(b,1),this},divide:function(a){function b(a,b){var c=l(a,b);return a*c/(b*c)}return this._value=[this._value,a].reduce(b),this},difference:function(a){return Math.abs(m(this._value).subtract(a).value())}},s&&(module.exports=m),"undefined"==typeof ender&&(this.numeral=m),"function"==typeof define&&define.amd&&define([],function(){return m})}).call(this); 12076/*! 12077* jquery.inputmask.bundle.js 12078* https://github.com/RobinHerbots/jquery.inputmask 12079* Copyright (c) 2010 - 2016 Robin Herbots 12080* Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php) 12081* Version: 3.3.4-47 12082*/ 12083!function(a){function b(c,d){return this instanceof b?(a.isPlainObject(c)?d=c:(d=d||{},d.alias=c),this.el=void 0,this.opts=a.extend(!0,{},this.defaults,d),this.noMasksCache=d&&void 0!==d.definitions,this.userOptions=d||{},this.events={},void e(this.opts.alias,d,this.opts)):new b(c,d)}function c(a){var b=document.createElement("input"),c="on"+a,d=c in b;return d||(b.setAttribute(c,"return;"),d="function"==typeof b[c]),b=null,d}function d(b,c){var d=b.getAttribute("type"),e="INPUT"===b.tagName&&a.inArray(d,c.supportsInputType)!==-1||b.isContentEditable||"TEXTAREA"===b.tagName;if(!e&&"INPUT"===b.tagName){var f=document.createElement("input");f.setAttribute("type",d),e="text"===f.type,f=null}return e}function e(b,c,d){var f=d.aliases[b];return f?(f.alias&&e(f.alias,void 0,d),a.extend(!0,d,f),a.extend(!0,d,c),!0):(null===d.mask&&(d.mask=b),!1)}function f(b,c,d){function f(a,c){c=void 0!==c?c:b.getAttribute("data-inputmask-"+a),null!==c&&("string"==typeof c&&(0===a.indexOf("on")?c=window[c]:"false"===c?c=!1:"true"===c&&(c=!0)),d[a]=c)}var g,h,i,j,k=b.getAttribute("data-inputmask");if(k&&""!==k&&(k=k.replace(new RegExp("'","g"),'"'),h=JSON.parse("{"+k+"}")),h){i=void 0;for(j in h)if("alias"===j.toLowerCase()){i=h[j];break}}f("alias",i),d.alias&&e(d.alias,d,c);for(g in c){if(h){i=void 0;for(j in h)if(j.toLowerCase()===g.toLowerCase()){i=h[j];break}}f(g,i)}return a.extend(!0,c,d),c}function g(c,d){function e(b){function d(a,b,c,d){this.matches=[],this.isGroup=a||!1,this.isOptional=b||!1,this.isQuantifier=c||!1,this.isAlternator=d||!1,this.quantifier={min:1,max:1}}function e(b,d,e){var f=c.definitions[d];e=void 0!==e?e:b.matches.length;var g=b.matches[e-1];if(f&&!r){f.placeholder=a.isFunction(f.placeholder)?f.placeholder(c):f.placeholder;for(var h=f.prevalidator,i=h?h.length:0,j=1;j<f.cardinality;j++){var k=i>=j?h[j-1]:[],l=k.validator,m=k.cardinality;b.matches.splice(e++,0,{fn:l?"string"==typeof l?new RegExp(l):new function(){this.test=l}:new RegExp("."),cardinality:m?m:1,optionality:b.isOptional,newBlockMarker:void 0===g||g.def!==(f.definitionSymbol||d),casing:f.casing,def:f.definitionSymbol||d,placeholder:f.placeholder,mask:d}),g=b.matches[e-1]}b.matches.splice(e++,0,{fn:f.validator?"string"==typeof f.validator?new RegExp(f.validator):new function(){this.test=f.validator}:new RegExp("."),cardinality:f.cardinality,optionality:b.isOptional,newBlockMarker:void 0===g||g.def!==(f.definitionSymbol||d),casing:f.casing,def:f.definitionSymbol||d,placeholder:f.placeholder,mask:d})}else b.matches.splice(e++,0,{fn:null,cardinality:0,optionality:b.isOptional,newBlockMarker:void 0===g||g.def!==d,casing:null,def:c.staticDefinitionSymbol||d,placeholder:void 0!==c.staticDefinitionSymbol?d:void 0,mask:d}),r=!1}function f(a,b){a.isGroup&&(a.isGroup=!1,e(a,c.groupmarker.start,0),b!==!0&&e(a,c.groupmarker.end))}function g(a,b,c,d){b.matches.length>0&&(void 0===d||d)&&(c=b.matches[b.matches.length-1],f(c)),e(b,a)}function h(){if(t.length>0){if(m=t[t.length-1],g(k,m,o,!m.isAlternator),m.isAlternator){n=t.pop();for(var a=0;a<n.matches.length;a++)n.matches[a].isGroup=!1;t.length>0?(m=t[t.length-1],m.matches.push(n)):s.matches.push(n)}}else g(k,s,o)}function i(a){function b(a){return a===c.optionalmarker.start?a=c.optionalmarker.end:a===c.optionalmarker.end?a=c.optionalmarker.start:a===c.groupmarker.start?a=c.groupmarker.end:a===c.groupmarker.end&&(a=c.groupmarker.start),a}a.matches=a.matches.reverse();for(var d in a.matches){var e=parseInt(d);if(a.matches[d].isQuantifier&&a.matches[e+1]&&a.matches[e+1].isGroup){var f=a.matches[d];a.matches.splice(d,1),a.matches.splice(e+1,0,f)}void 0!==a.matches[d].matches?a.matches[d]=i(a.matches[d]):a.matches[d]=b(a.matches[d])}return a}for(var j,k,l,m,n,o,p,q=/(?:[?*+]|\{[0-9\+\*]+(?:,[0-9\+\*]*)?\})|[^.?*+^${[]()|\\]+|./g,r=!1,s=new d,t=[],u=[];j=q.exec(b);)if(k=j[0],r)h();else switch(k.charAt(0)){case c.escapeChar:r=!0;break;case c.optionalmarker.end:case c.groupmarker.end:if(l=t.pop(),void 0!==l)if(t.length>0){if(m=t[t.length-1],m.matches.push(l),m.isAlternator){n=t.pop();for(var v=0;v<n.matches.length;v++)n.matches[v].isGroup=!1;t.length>0?(m=t[t.length-1],m.matches.push(n)):s.matches.push(n)}}else s.matches.push(l);else h();break;case c.optionalmarker.start:t.push(new d((!1),(!0)));break;case c.groupmarker.start:t.push(new d((!0)));break;case c.quantifiermarker.start:var w=new d((!1),(!1),(!0));k=k.replace(/[{}]/g,"");var x=k.split(","),y=isNaN(x[0])?x[0]:parseInt(x[0]),z=1===x.length?y:isNaN(x[1])?x[1]:parseInt(x[1]);if("*"!==z&&"+"!==z||(y="*"===z?0:1),w.quantifier={min:y,max:z},t.length>0){var A=t[t.length-1].matches;j=A.pop(),j.isGroup||(p=new d((!0)),p.matches.push(j),j=p),A.push(j),A.push(w)}else j=s.matches.pop(),j.isGroup||(p=new d((!0)),p.matches.push(j),j=p),s.matches.push(j),s.matches.push(w);break;case c.alternatormarker:t.length>0?(m=t[t.length-1],o=m.matches.pop()):o=s.matches.pop(),o.isAlternator?t.push(o):(n=new d((!1),(!1),(!1),(!0)),n.matches.push(o),t.push(n));break;default:h()}for(;t.length>0;
12083)l=t.pop(),f(l,!0),s.matches.push(l);return s.matches.length>0&&(o=s.matches[s.matches.length-1],f(o),u.push(s)),c.numericInput&&i(u[0]),u}function f(f,g){if(null!==f&&""!==f){if(1===f.length&&c.greedy===!1&&0!==c.repeat&&(c.placeholder=""),c.repeat>0||"*"===c.repeat||"+"===c.repeat){var h="*"===c.repeat?0:"+"===c.repeat?1:c.repeat;f=c.groupmarker.start+f+c.groupmarker.end+c.quantifiermarker.start+h+","+c.repeat+c.quantifiermarker.end}var i;return void 0===b.prototype.masksCache[f]||d===!0?(i={mask:f,maskToken:e(f),validPositions:{},_buffer:void 0,buffer:void 0,tests:{},metadata:g,maskLength:void 0},d!==!0&&(b.prototype.masksCache[c.numericInput?f.split("").reverse().join(""):f]=i,i=a.extend(!0,{},b.prototype.masksCache[c.numericInput?f.split("").reverse().join(""):f]))):i=a.extend(!0,{},b.prototype.masksCache[c.numericInput?f.split("").reverse().join(""):f]),i}}function g(a){return a=a.toString()}var h;if(a.isFunction(c.mask)&&(c.mask=c.mask(c)),a.isArray(c.mask)){if(c.mask.length>1){c.keepStatic=null===c.keepStatic||c.keepStatic;var i="(";return a.each(c.numericInput?c.mask.reverse():c.mask,function(b,c){i.length>1&&(i+=")|("),i+=g(void 0===c.mask||a.isFunction(c.mask)?c:c.mask)}),i+=")",f(i,c.mask)}c.mask=c.mask.pop()}return c.mask&&(h=void 0===c.mask.mask||a.isFunction(c.mask.mask)?f(g(c.mask),c.mask):f(g(c.mask.mask),c.mask)),h}function h(e,f,g){function i(a,b,c){b=b||0;var d,e,f,h=[],i=0,j=p();ja=void 0!==ha?ha.maxLength:void 0,ja===-1&&(ja=void 0);do a===!0&&n().validPositions[i]?(f=n().validPositions[i],e=f.match,d=f.locator.slice(),h.push(c===!0?f.input:I(i,e))):(f=s(i,d,i-1),e=f.match,d=f.locator.slice(),(g.jitMasking===!1||i<j||Number.isFinite(g.jitMasking)&&g.jitMasking>i)&&h.push(I(i,e))),i++;while((void 0===ja||i<ja)&&(null!==e.fn||""!==e.def)||b>i);return""===h[h.length-1]&&h.pop(),n().maskLength=i+1,h}function n(){return f}function o(a){var b=n();b.buffer=void 0,a!==!0&&(b._buffer=void 0,b.validPositions={},b.p=0)}function p(a,b,c){var d=-1,e=-1,f=c||n().validPositions;void 0===a&&(a=-1);for(var g in f){var h=parseInt(g);f[h]&&(b||null!==f[h].match.fn)&&(h<=a&&(d=h),h>=a&&(e=h))}return d!==-1&&a-d>1||e<a?d:e}function q(b,c,d,e){function f(a){var b=n().validPositions[a];if(void 0!==b&&null===b.match.fn){var c=n().validPositions[a-1],d=n().validPositions[a+1];return void 0!==c&&void 0!==d}return!1}var h,i=b,j=a.extend(!0,{},n().validPositions),k=!1;for(n().p=b,h=c-1;h>=i;h--)void 0!==n().validPositions[h]&&(d===!0||!f(h)&&g.canClearPosition(n(),h,p(),e,g)!==!1)&&delete n().validPositions[h];for(o(!0),h=i+1;h<=p();){for(;void 0!==n().validPositions[i];)i++;var l=n().validPositions[i];if(h<i&&(h=i+1),void 0===n().validPositions[h]&&D(h)||void 0!==l)h++;else{var m=s(h);k===!1&&j[i]&&j[i].match.def===m.match.def?(n().validPositions[i]=a.extend(!0,{},j[i]),n().validPositions[i].input=m.input,delete n().validPositions[h],h++):u(i,m.match.def)?C(i,m.input||I(h),!0)!==!1&&(delete n().validPositions[h],h++,k=!0):D(h)||(h++,i--),i++}}o(!0)}function r(a,b){for(var c,d=a,e=p(),f=n().validPositions[e]||w(0)[0],h=void 0!==f.alternation?f.locator[f.alternation].toString().split(","):[],i=0;i<d.length&&(c=d[i],!(c.match&&(g.greedy&&c.match.optionalQuantifier!==!0||(c.match.optionality===!1||c.match.newBlockMarker===!1)&&c.match.optionalQuantifier!==!0)&&(void 0===f.alternation||f.alternation!==c.alternation||void 0!==c.locator[f.alternation]&&B(c.locator[f.alternation].toString().split(","),h)))||b===!0&&(null!==c.match.fn||/[0-9a-bA-Z]/.test(c.match.def)));i++);return c}function s(a,b,c){return n().validPositions[a]||r(w(a,b?b.slice():b,c))}function t(a){return n().validPositions[a]?n().validPositions[a]:w(a)[0]}function u(a,b){for(var c=!1,d=w(a),e=0;e<d.length;e++)if(d[e].match&&d[e].match.def===b){c=!0;break}return c}function v(b,c){var d,e;return(n().tests[b]||n().validPositions[b])&&a.each(n().tests[b]||[n().validPositions[b]],function(a,b){var f=b.alternation?b.locator[b.alternation].toString().indexOf(c):-1;(void 0===e||f<e)&&f!==-1&&(d=b,e=f)}),d}function w(b,c,d){function e(c,d,f,h){function j(f,h,l){function q(b,c){var d=0===a.inArray(b,c.matches);return d||a.each(c.matches,function(a,e){if(e.isQuantifier===!0&&(d=q(b,c.matches[a-1]
12083)))return!1}),d}function r(a,b){var c=v(a,b);return c?c.locator.slice(c.alternation+1):void 0}function s(a,c){return null===a.match.fn&&null!==c.match.fn&&c.match.fn.test(a.match.def,n(),b,!1,g,!1)}if(k>1e4)throw"Inputmask: There is probably an error in your mask definition or in the code. Create an issue on github with an example of the mask you are using. "+n().mask;if(k===b&&void 0===f.matches)return m.push({match:f,locator:h.reverse(),cd:p}),!0;if(void 0!==f.matches){if(f.isGroup&&l!==f){if(f=j(c.matches[a.inArray(f,c.matches)+1],h))return!0}else if(f.isOptional){var t=f;if(f=e(f,d,h,l)){if(i=m[m.length-1].match,!q(i,t))return!0;o=!0,k=b}}else if(f.isAlternator){var u,w=f,x=[],y=m.slice(),z=h.length,A=d.length>0?d.shift():-1;if(A===-1||"string"==typeof A){var B,C=k,D=d.slice(),E=[];if("string"==typeof A)E=A.split(",");else for(B=0;B<w.matches.length;B++)E.push(B);for(var F=0;F<E.length;F++){if(B=parseInt(E[F]),m=[],d=r(k,B)||D.slice(),f=j(w.matches[B]||c.matches[B],[B].concat(h),l)||f,f!==!0&&void 0!==f&&E[E.length-1]<w.matches.length){var G=a.inArray(f,c.matches)+1;c.matches.length>G&&(f=j(c.matches[G],[G].concat(h.slice(1,h.length)),l),f&&(E.push(G.toString()),a.each(m,function(a,b){b.alternation=h.length-1})))}u=m.slice(),k=C,m=[];for(var H=0;H<u.length;H++){var I=u[H],J=!1;I.alternation=I.alternation||z;for(var K=0;K<x.length;K++){var L=x[K];if(("string"!=typeof A||a.inArray(I.locator[I.alternation].toString(),E)!==-1)&&(I.match.def===L.match.def||s(I,L))){J=I.match.mask===L.match.mask,L.locator[I.alternation].toString().indexOf(I.locator[I.alternation])===-1&&(L.locator[I.alternation]=L.locator[I.alternation]+","+I.locator[I.alternation],L.alternation=I.alternation,null==I.match.fn&&(L.na=L.na||I.locator[I.alternation].toString(),L.na.indexOf(I.locator[I.alternation])===-1&&(L.na=L.na+","+I.locator[I.alternation])));break}}J||x.push(I)}}"string"==typeof A&&(x=a.map(x,function(b,c){if(isFinite(c)){var d,e=b.alternation,f=b.locator[e].toString().split(",");b.locator[e]=void 0,b.alternation=void 0;for(var g=0;g<f.length;g++)d=a.inArray(f[g],E)!==-1,d&&(void 0!==b.locator[e]?(b.locator[e]+=",",b.locator[e]+=f[g]):b.locator[e]=parseInt(f[g]),b.alternation=e);if(void 0!==b.locator[e])return b}})),m=y.concat(x),k=b,o=m.length>0,d=D.slice()}else f=j(w.matches[A]||c.matches[A],[A].concat(h),l);if(f)return!0}else if(f.isQuantifier&&l!==c.matches[a.inArray(f,c.matches)-1])for(var M=f,N=d.length>0?d.shift():0;N<(isNaN(M.quantifier.max)?N+1:M.quantifier.max)&&k<=b;N++){var O=c.matches[a.inArray(M,c.matches)-1];if(f=j(O,[N].concat(h),O)){if(i=m[m.length-1].match,i.optionalQuantifier=N>M.quantifier.min-1,q(i,O)){if(N>M.quantifier.min-1){o=!0,k=b;break}return!0}return!0}}else if(f=e(f,d,h,l))return!0}else k++}for(var l=d.length>0?d.shift():0;l<c.matches.length;l++)if(c.matches[l].isQuantifier!==!0){var q=j(c.matches[l],[l].concat(f),h);if(q&&k===b)return q;if(k>b)break}}function f(b){var c=[];return a.isArray(b)||(b=[b]),b.length>0&&(void 0===b[0].alternation?(c=r(b.slice()).locator.slice(),0===c.length&&(c=b[0].locator.slice())):a.each(b,function(a,b){if(""!==b.def)if(0===c.length)c=b.locator.slice();else for(var d=0;d<c.length;d++)b.locator[d]&&c[d].toString().indexOf(b.locator[d])===-1&&(c[d]+=","+b.locator[d])})),c}function h(a){return g.keepStatic&&b>0&&a.length>1+(""===a[a.length-1].match.def?1:0)&&a[0].match.optionality!==!0&&a[0].match.optionalQuantifier!==!0&&null===a[0].match.fn&&!/[0-9a-bA-Z]/.test(a[0].match.def)?[r(a)]:a}var i,j=n().maskToken,k=c?d:0,l=c?c.slice():[0],m=[],o=!1,p=c?c.join(""):"";if(b>-1){if(void 0===c){for(var q,s=b-1;void 0===(q=n().validPositions[s]||n().tests[s])&&s>-1;)s--;void 0!==q&&s>-1&&(l=f(q),p=l.join(""),k=s)}if(n().tests[b]&&n().tests[b][0].cd===p)return h(n().tests[b]);for(var t=l.shift();t<j.length;t++){var u=e(j[t],l,[t]);if(u&&k===b||k>b)break}}return(0===m.length||o)&&m.push({match:{fn:null,cardinality:0,optionality:!0,casing:null,def:"",placeholder:""},locator:[],cd:p}),void 0!==c&&n().tests[b]?h(a.extend(!0,[],m)):(n().tests[b]=a.extend(!0,[],m),h(n().tests[b]))}function x(){return void 0===n()._buffer&&(n()._buffer=i(!1,1),void 0===n().buffer&&n()._buffer.slice()),n()._buffer}function y(a){return void 0!==n().buffer&&a!==!0||(n().buffer=i(!0,p(),!0)),n().buffer}function z(a,b,c){var d;if(a===!0)o(),a=0,b=c.length;else for(d=a;d<b;d++)delete n().validPositions[d];for(d=a;d<b;d++)o(!0),c[d]!==g.skipOptionalPartCharacter&&C(d,c[d],!0,!0)}function A(a,c,d){switch(g.casing||c.casing){case"upper":a=a.toUpperCase();break;case"lower":a=a.toLowerCase();break;case"title":var e=n().validPositions[d-1];a=0===d||e&&e.input===String.fromCharCode(b.keyCode.SPACE)?a.toUpperCase():a.toLowerCase()}return a}function B(b,c){for(var d=g.greedy?c:c.slice(0,1),e=!1,f=0;f<b.length;f++)if(a.inArray(b[f],d)!==-1){e=!0;break}return e}function C(c,d,e,f,h){function i(a){return ma?a.begin-a.end>1||a.begin-a.end===1&&g.insertMode:a.end-a.begin>1||a.end-a.begin===1&&g.insertMode}function j(b,d,e){var h=!1;return a.each(w(b),function(j,k){for(var l=k.match,r=d?1:0,s="",t=l.cardinality;t>r;t--)s+=G(b-(t-1));if(d&&(s+=d),y(!0),h=null!=l.fn?l.fn.test(s,n(),b,e,g,i(c)):(d===l.def||d===g.skipOptionalPartCharacter)&&""!==l.def&&{c:l.placeholder||l.def,pos:b},h!==!1){var u=void 0!==h.c?h.c:d;u=u===g.skipOptionalPartCharacter&&null===l.fn?l.placeholder||l.def:u;var v=b,w=y();if(void 0!==h.remove&&(a.isArray(h.remove)||(h.remove=[h.remove]),a.each(h.remove.sort(function(a,b){return b-
12083a}),function(a,b){q(b,b+1,!0)})),void 0!==h.insert&&(a.isArray(h.insert)||(h.insert=[h.insert]),a.each(h.insert.sort(function(a,b){return a-b}),function(a,b){C(b.pos,b.c,!0,f)})),h.refreshFromBuffer){var x=h.refreshFromBuffer;if(e=!0,z(x===!0?x:x.start,x.end,w),void 0===h.pos&&void 0===h.c)return h.pos=p(),!1;if(v=void 0!==h.pos?h.pos:b,v!==b)return h=a.extend(h,C(v,u,!0,f)),!1}else if(h!==!0&&void 0!==h.pos&&h.pos!==b&&(v=h.pos,z(b,v,y().slice()),v!==b))return h=a.extend(h,C(v,u,!0)),!1;return(h===!0||void 0!==h.pos||void 0!==h.c)&&(j>0&&o(!0),m(v,a.extend({},k,{input:A(u,l,v)}),f,i(c))||(h=!1),!1)}}),h}function k(b,c,d){var e,h,i,j,k,l,m,q,r=a.extend(!0,{},n().validPositions),s=!1,t=p();for(j=n().validPositions[t];t>=0;t--)if(i=n().validPositions[t],i&&void 0!==i.alternation){if(e=t,h=n().validPositions[e].alternation,j.locator[i.alternation]!==i.locator[i.alternation])break;j=i}if(void 0!==h){q=parseInt(e);var u=void 0!==j.locator[j.alternation||h]?j.locator[j.alternation||h]:m[0];u.length>0&&(u=u.split(",")[0]);var v=n().validPositions[q],x=n().validPositions[q-1];a.each(w(q,x?x.locator:void 0,q-1),function(e,i){m=i.locator[h]?i.locator[h].toString().split(","):[];for(var j=0;j<m.length;j++){var t=[],w=0,x=0,y=!1;if(u<m[j]&&(void 0===i.na||a.inArray(m[j],i.na.split(","))===-1)){n().validPositions[q]=a.extend(!0,{},i);var z=n().validPositions[q].locator;for(n().validPositions[q].locator[h]=parseInt(m[j]),null==i.match.fn?(v.input!==i.match.def&&(y=!0,v.generatedInput!==!0&&t.push(v.input)),x++,n().validPositions[q].generatedInput=!/[0-9a-bA-Z]/.test(i.match.def),n().validPositions[q].input=i.match.def):n().validPositions[q].input=v.input,k=q+1;k<p(void 0,!0)+1;k++)l=n().validPositions[k],l&&l.generatedInput!==!0&&/[0-9a-bA-Z]/.test(l.input)?t.push(l.input):k<b&&w++,delete n().validPositions[k];for(y&&t[0]===i.match.def&&t.shift(),o(!0),s=!0;t.length>0;){var A=t.shift();if(A!==g.skipOptionalPartCharacter&&!(s=C(p(void 0,!0)+1,A,!1,f,!0)))break}if(s){n().validPositions[q].locator=z;var B=p(b)+1;for(k=q+1;k<p()+1;k++)l=n().validPositions[k],(void 0===l||null==l.match.fn)&&k<b+(x-w)&&x++;b+=x-w,s=C(b>B?B:b,c,d,f,!0)}if(s)return!1;o(),n().validPositions=a.extend(!0,{},r)}}})}return s}function l(b,c){var d=n().validPositions[c];if(d)for(var e=d.locator,f=e.length,g=b;g<c;g++)if(void 0===n().validPositions[g]&&!D(g,!0)){var h=w(g),i=h[0],j=-1;a.each(h,function(a,b){for(var c=0;c<f&&(void 0!==b.locator[c]&&B(b.locator[c].toString().split(","),e[c].toString().split(",")));c++)j<c&&(j=c,i=b)}),m(g,a.extend({},i,{input:i.match.placeholder||i.match.def}),!0)}}function m(b,c,d,e){if(e||g.insertMode&&void 0!==n().validPositions[b]&&void 0===d){var f,h=a.extend(!0,{},n().validPositions),i=p(void 0,!0);for(f=b;f<=i;f++)delete n().validPositions[f];n().validPositions[b]=a.extend(!0,{},c);var j,k=!0,l=n().validPositions,m=!1,q=n().maskLength;for(f=j=b;f<=i;f++){var r=h[f];if(void 0!==r)for(var s=j;s<n().maskLength&&(null==r.match.fn&&l[f]&&(l[f].match.optionalQuantifier===!0||l[f].match.optionality===!0)||null!=r.match.fn);){if(s++,m===!1&&h[s]&&h[s].match.def===r.match.def)n().validPositions[s]=a.extend(!0,{},h[s]),n().validPositions[s].input=r.input,t(s),j=s,k=!0;else if(u(s,r.match.def)){var v=C(s,r.input,!0,!0);k=v!==!1,j=v.caret||v.insert?p():s,m=!0}else k=r.generatedInput===!0;if(n().maskLength<q&&(n().maskLength=q),k)break}if(!k)break}if(!k)return n().validPositions=a.extend(!0,{},h),o(!0),!1}else n().validPositions[b]=a.extend(!0,{},c);return o(!0),!0}function t(b){for(var c=b-1;c>-1&&!n().validPositions[c];c--);var d,e;for(c++;c<b;c++)void 0===n().validPositions[c]&&(g.jitMasking===!1||g.jitMasking>c)&&(e=w(c,s(c-1).locator,c-1).slice(),""===e[e.length-1].match.def&&e.pop(),d=r(e),d&&(d.match.def===g.radixPointDefinitionSymbol||!D(c,!0)||a.inArray(g.radixPoint,y())<c&&d.match.fn&&d.match.fn.test(I(c),n(),c,!1,g))&&(x=j(c,d.match.placeholder||(null==d.match.fn?d.match.def:""!==I(c)?I(c):y()[c]),!0),x!==!1&&(n().validPositions[x.pos||c].generatedInput=!0)))}e=e===!0;var v=c;void 0!==c.begin&&(v=ma&&!i(c)?c.end:c.begin);var x=!1,F=a.extend(!0,{},n().validPositions);if(t(v),i(c)&&(Q(void 0,b.keyCode.DELETE,c),v=n().p),v<n().maskLength&&(x=j(v,d,e),(!e||f===!0)&&x===!1)){var H=n().validPositions[v];if(!H||null!==H.match.fn||H.match.def!==d&&d!==g.skipOptionalPartCharacter){if((g.insertMode||void 0===n().validPositions[E(v)])&&!D(v,!0)){var J=w(v).slice();""===J[J.length-1].match.def&&J.pop();var K=r(J,!0);
12083K&&null===K.match.fn&&(K=K.match.placeholder||K.match.def,j(v,K,e),n().validPositions[v].generatedInput=!0);for(var L=v+1,M=E(v);L<=M;L++)if(x=j(L,d,e),x!==!1){l(v,void 0!==x.pos?x.pos:L),v=L;break}}}else x={caret:E(v)}}return x===!1&&g.keepStatic&&!e&&h!==!0&&(x=k(v,d,e)),x===!0&&(x={pos:v}),a.isFunction(g.postValidation)&&x!==!1&&!e&&f!==!0&&(x=!!g.postValidation(y(!0),x,g)&&x),void 0===x.pos&&(x.pos=v),x===!1&&(o(!0),n().validPositions=a.extend(!0,{},F)),x}function D(a,b){var c;if(b?(c=s(a).match,""===c.def&&(c=t(a).match)):c=t(a).match,null!=c.fn)return c.fn;if(b!==!0&&a>-1){var d=w(a);return d.length>1+(""===d[d.length-1].match.def?1:0)}return!1}function E(a,b){var c=n().maskLength;if(a>=c)return c;for(var d=a;++d<c&&(b===!0&&(t(d).match.newBlockMarker!==!0||!D(d))||b!==!0&&!D(d)););return d}function F(a,b){var c,d=a;if(d<=0)return 0;for(;--d>0&&(b===!0&&t(d).match.newBlockMarker!==!0||b!==!0&&!D(d)&&(c=w(d),c.length<2||2===c.length&&""===c[1].match.def)););return d}function G(a){return void 0===n().validPositions[a]?I(a):n().validPositions[a].input}function H(b,c,d,e,f){if(e&&a.isFunction(g.onBeforeWrite)){var h=g.onBeforeWrite(e,c,d,g);if(h){if(h.refreshFromBuffer){var i=h.refreshFromBuffer;z(i===!0?i:i.start,i.end,h.buffer||c),c=y(!0)}void 0!==d&&(d=void 0!==h.caret?h.caret:d)}}b.inputmask._valueSet(c.join("")),void 0===d||void 0!==e&&"blur"===e.type?ea(b,c,d):L(b,d),f===!0&&(oa=!0,a(b).trigger("input"))}function I(a,b){if(b=b||t(a).match,void 0!==b.placeholder)return b.placeholder;if(null===b.fn){if(a>-1&&void 0===n().validPositions[a]){var c,d=w(a),e=[];if(d.length>1+(""===d[d.length-1].match.def?1:0))for(var f=0;f<d.length;f++)if(d[f].match.optionality!==!0&&d[f].match.optionalQuantifier!==!0&&(null===d[f].match.fn||void 0===c||d[f].match.fn.test(c.match.def,n(),a,!0,g)!==!1)&&(e.push(d[f]),null===d[f].match.fn&&(c=d[f]),e.length>1&&/[0-9a-bA-Z]/.test(e[0].match.def)))return g.placeholder.charAt(a%g.placeholder.length)}return b.def}return g.placeholder.charAt(a%g.placeholder.length)}function J(c,d,e,f,h,i){function j(){var a=!1,b=x().slice(m,E(m)).join("").indexOf(l);if(b!==-1&&!D(m)){a=!0;for(var c=x().slice(m,m+b),d=0;d<c.length;d++)if(" "!==c[d]){a=!1;break}}return a}var k=f.slice(),l="",m=0,q=void 0;if(o(),n().p=E(-1),!e)if(g.autoUnmask!==!0){var r=x().slice(0,E(-1)).join(""),t=k.join("").match(new RegExp("^"+b.escapeRegex(r),"g"));t&&t.length>0&&(k.splice(0,t.length*r.length),m=E(m))}else m=E(m);if(a.each(k,function(b,d){if(void 0!==d){var f=new a.Event("keypress");f.which=d.charCodeAt(0),l+=d;var h=p(void 0,!0),i=n().validPositions[h],k=s(h+1,i?i.locator.slice():void 0,h);if(!j()||e||g.autoUnmask){var r=e?b:null==k.match.fn&&k.match.optionality&&h+1<n().p?h+1:n().p;q=S.call(c,f,!0,!1,e,r),m=r+1,l=""}else q=S.call(c,f,!0,!1,!0,h+1);if(!e&&a.isFunction(g.onBeforeWrite)&&(q=g.onBeforeWrite(f,y(),q.forwardPosition,g),q&&q.refreshFromBuffer)){var t=q.refreshFromBuffer;z(t===!0?t:t.start,t.end,q.buffer),o(!0),q.caret&&(n().p=q.caret)}}}),d){var u=void 0,v=p();document.activeElement===c&&(h||q)&&(u=L(c).begin,h&&q===!1&&(u=E(p(u))),q&&i!==!0&&(u<v+1||v===-1)&&(u=g.numericInput&&void 0===q.caret?F(q.forwardPosition):q.forwardPosition)),H(c,y(),u,h||new a.Event("checkval"))}}function K(b){if(b&&void 0===b.inputmask)return b.value;var c=[],d=n().validPositions;for(var e in d)d[e].match&&null!=d[e].match.fn&&c.push(d[e].input);var f=0===c.length?"":(ma?c.reverse():c).join("");if(a.isFunction(g.onUnMask)){var h=(ma?y().slice().reverse():y()).join("");f=g.onUnMask(h,f,g)||f}return f}function L(a,b,c,d){function e(a){if(d!==!0&&ma&&"number"==typeof a&&(!g.greedy||""!==g.placeholder)){var b=y().join("").length;a=b-a}return a}var f;if("number"!=typeof b)return a.setSelectionRange?(b=a.selectionStart,c=a.selectionEnd):window.getSelection?(f=window.getSelection().getRangeAt(0),f.commonAncestorContainer.parentNode!==a&&f.commonAncestorContainer!==a||(b=f.startOffset,c=f.endOffset)):document.selection&&document.selection.createRange&&(f=document.selection.createRange(),b=0-f.duplicate().moveStart("character",-a.inputmask._valueGet().length),c=b+f.text.length),{begin:e(b),end:e(c)};b=e(b),c=e(c),c="number"==typeof c?c:b;var h=parseInt(((a.ownerDocument.defaultView||window).getComputedStyle?(a.ownerDocument.defaultView||window).getComputedStyle(a,null):a.currentStyle).fontSize)*c;if(a.scrollLeft=h>a.scrollWidth?h:0,j||g.insertMode!==!1||b!==c||c++,a.setSelectionRange)a.selectionStart=b,a.selectionEnd=c;else if(window.getSelection){if(f=document.createRange(),void 0===a.firstChild||null===a.firstChild){var i=document.createTextNode("");a.appendChild(i)}f.setStart(a.firstChild,b<a.inputmask._valueGet().length?b:a.inputmask._valueGet().length),f.setEnd(a.firstChild,c<a.inputmask._valueGet().length?c:a.inputmask._valueGet().length),f.collapse(!0);var k=window.getSelection();k.removeAllRanges(),k.addRange(f)}else a.createTextRange&&(f=a.createTextRange(),f.collapse(!0),f.moveEnd("character",c),f.moveStart("character",b),f.select());ea(a,void 0,{begin:b,end:c})}function M(b){var c,d,e=y(),f=e.length,g=p(),h={},i=n().validPositions[g],j=void 0!==i?i.locator.slice():void 0;for(c=g+1;c<e.length;c++)d=s(c,j,c-1),j=d.locator.slice(),h[c]=a.extend(!0,{},d);var k=i&&void 0!==i.alternation?i.locator[i.alternation]:void 0;for(c=f-1;c>g&&(d=h[c],(d.match.optionality||d.match.optionalQuantifier||k&&(k!==h[c].locator[i.alternation]&&null!=d.match.fn||null===d.match.fn&&d.locator[i.alternation]&&B(d.locator[i.alternation].toString().split(","),k.toString().split(","))&&""!==w(c)[0].def))&&e[c]===I(c,d.match));c--)f--;return b?{l:f,def:h[f]?h[f].match:void 0}:f}function N(a){for(var b=M(),c=a.length-1;c>b&&!D(c);c--);return a.splice(b,c+1-b),a}function O(b){if(a.isFunction(g.isComplete))return g.isComplete(b,g);if("*"!==g.repeat){var c=!1,d=M(!0),e=F(d.l);if(void 0===d.def||d.def.newBlockMarker||d.def.optionality||d.def.optionalQuantifier){c=!0;for(var f=0;f<=e;f++){var h=s(f).match;if(null!==h.fn&&void 0===n().validPositions[f]&&h.optionality!==!0&&h.optionalQuantifier!==!0||null===h.fn&&b[f]!==I(f,h)){c=!1;break}}}return c}}function P(b){function c(b){if(a.valHooks&&(void 0===a.valHooks[b]||a.valHooks[b].inputmaskpatch!==!0)){var c=a.valHooks[b]&&a.valHooks[b].get?a.valHooks[b].get:function(a){return a.value},d=a.valHooks[b]&&a.valHooks[b].set?a.valHooks[b].set:function(a,b){return a.value=b,a};
12083a.valHooks[b]={get:function(a){if(a.inputmask){if(a.inputmask.opts.autoUnmask)return a.inputmask.unmaskedvalue();var b=c(a);return p(void 0,void 0,a.inputmask.maskset.validPositions)!==-1||g.nullable!==!0?b:""}return c(a)},set:function(b,c){var e,f=a(b);return e=d(b,c),b.inputmask&&f.trigger("setvalue"),e},inputmaskpatch:!0}}}function d(){return this.inputmask?this.inputmask.opts.autoUnmask?this.inputmask.unmaskedvalue():p()!==-1||g.nullable!==!0?document.activeElement===this&&g.clearMaskOnLostFocus?(ma?N(y().slice()).reverse():N(y().slice())).join(""):h.call(this):"":h.call(this)}function e(b){i.call(this,b),this.inputmask&&a(this).trigger("setvalue")}function f(b){ra.on(b,"mouseenter",function(b){var c=a(this),d=this,e=d.inputmask._valueGet();e!==y().join("")&&c.trigger("setvalue")})}var h,i;if(!b.inputmask.__valueGet){if(g.noValuePatching!==!0){if(Object.getOwnPropertyDescriptor){"function"!=typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype});var j=Object.getPrototypeOf?Object.getOwnPropertyDescriptor(Object.getPrototypeOf(b),"value"):void 0;j&&j.get&&j.set?(h=j.get,i=j.set,Object.defineProperty(b,"value",{get:d,set:e,configurable:!0})):"INPUT"!==b.tagName&&(h=function(){return this.textContent},i=function(a){this.textContent=a},Object.defineProperty(b,"value",{get:d,set:e,configurable:!0}))}else document.__lookupGetter__&&b.__lookupGetter__("value")&&(h=b.__lookupGetter__("value"),i=b.__lookupSetter__("value"),b.__defineGetter__("value",d),b.__defineSetter__("value",e));b.inputmask.__valueGet=h,b.inputmask.__valueSet=i}b.inputmask._valueGet=function(a){return ma&&a!==!0?h.call(this.el).split("").reverse().join(""):h.call(this.el)},b.inputmask._valueSet=function(a,b){i.call(this.el,null===a||void 0===a?"":b!==!0&&ma?a.split("").reverse().join(""):a)},void 0===h&&(h=function(){return this.value},i=function(a){this.value=a},c(b.type),f(b))}}function Q(c,d,e,f){function h(){if(g.keepStatic){for(var b=[],d=p(-1,!0),e=a.extend(!0,{},n().validPositions),f=n().validPositions[d];d>=0;d--){var h=n().validPositions[d];if(h){if(h.generatedInput!==!0&&/[0-9a-bA-Z]/.test(h.input)&&b.push(h.input),delete n().validPositions[d],void 0!==h.alternation&&h.locator[h.alternation]!==f.locator[h.alternation])break;f=h}}if(d>-1)for(n().p=E(p(-1,!0));b.length>0;){var i=new a.Event("keypress");i.which=b.pop().charCodeAt(0),S.call(c,i,!0,!1,!1,n().p)}else n().validPositions=a.extend(!0,{},e)}}if((g.numericInput||ma)&&(d===b.keyCode.BACKSPACE?d=b.keyCode.DELETE:d===b.keyCode.DELETE&&(d=b.keyCode.BACKSPACE),ma)){var i=e.end;e.end=e.begin,e.begin=i}d===b.keyCode.BACKSPACE&&(e.end-e.begin<1||g.insertMode===!1)?(e.begin=F(e.begin),void 0===n().validPositions[e.begin]||n().validPositions[e.begin].input!==g.groupSeparator&&n().validPositions[e.begin].input!==g.radixPoint||e.begin--):d===b.keyCode.DELETE&&e.begin===e.end&&(e.end=D(e.end,!0)?e.end+1:E(e.end)+1,void 0===n().validPositions[e.begin]||n().validPositions[e.begin].input!==g.groupSeparator&&n().validPositions[e.begin].input!==g.radixPoint||e.end++),q(e.begin,e.end,!1,f),f!==!0&&h();var j=p(e.begin,!0);j<e.begin?n().p=E(j):f!==!0&&(n().p=e.begin)}function R(d){var e=this,f=a(e),h=d.keyCode,i=L(e);if(h===b.keyCode.BACKSPACE||h===b.keyCode.DELETE||l&&h===b.keyCode.BACKSPACE_SAFARI||d.ctrlKey&&h===b.keyCode.X&&!c("cut"))d.preventDefault(),Q(e,h,i),H(e,y(!0),n().p,d,e.inputmask._valueGet()!==y().join("")),e.inputmask._valueGet()===x().join("")?f.trigger("cleared"):O(y())===!0&&f.trigger("complete"),g.showTooltip&&(e.title=g.tooltip||n().mask);else if(h===b.keyCode.END||h===b.keyCode.PAGE_DOWN){d.preventDefault();var j=E(p());g.insertMode||j!==n().maskLength||d.shiftKey||j--,L(e,d.shiftKey?i.begin:j,j,!0)}else h===b.keyCode.HOME&&!d.shiftKey||h===b.keyCode.PAGE_UP?(d.preventDefault(),L(e,0,d.shiftKey?i.begin:0,!0)):(g.undoOnEscape&&h===b.keyCode.ESCAPE||90===h&&d.ctrlKey)&&d.altKey!==!0?(J(e,!0,!1,ga.split("")),f.trigger("click")):h!==b.keyCode.INSERT||d.shiftKey||d.ctrlKey?g.tabThrough===!0&&h===b.keyCode.TAB?(d.shift
12083Key===!0?(null===t(i.begin).match.fn&&(i.begin=E(i.begin)),i.end=F(i.begin,!0),i.begin=F(i.end,!0)):(i.begin=E(i.begin,!0),i.end=E(i.begin,!0),i.end<n().maskLength&&i.end--),i.begin<n().maskLength&&(d.preventDefault(),L(e,i.begin,i.end))):d.shiftKey||(g.insertMode===!1?h===b.keyCode.RIGHT?setTimeout(function(){var a=L(e);L(e,a.begin)},0):h===b.keyCode.LEFT&&setTimeout(function(){var a=L(e);L(e,ma?a.begin+1:a.begin-1)},0):setTimeout(function(){ea(e)},0)):(g.insertMode=!g.insertMode,L(e,g.insertMode||i.begin!==n().maskLength?i.begin:i.begin-1));g.onKeyDown.call(this,d,y(),L(e).begin,g),pa=a.inArray(h,g.ignorables)!==-1}function S(c,d,e,f,h){var i=this,j=a(i),k=c.which||c.charCode||c.keyCode;if(!(d===!0||c.ctrlKey&&c.altKey)&&(c.ctrlKey||c.metaKey||pa))return k===b.keyCode.ENTER&&ga!==y().join("")&&(ga=y().join(""),setTimeout(function(){j.trigger("change")},0)),!0;if(k){46===k&&c.shiftKey===!1&&","===g.radixPoint&&(k=44);var l,m=d?{begin:h,end:h}:L(i),p=String.fromCharCode(k);n().writeOutBuffer=!0;var q=C(m,p,f);if(q!==!1&&(o(!0),l=void 0!==q.caret?q.caret:d?q.pos+1:E(q.pos),n().p=l),e!==!1){var r=this;if(setTimeout(function(){g.onKeyValidation.call(r,k,q,g)},0),n().writeOutBuffer&&q!==!1){var s=y();H(i,s,g.numericInput&&void 0===q.caret?F(l):l,c,d!==!0),d!==!0&&setTimeout(function(){O(s)===!0&&j.trigger("complete")},0)}}if(g.showTooltip&&(i.title=g.tooltip||n().mask),c.preventDefault(),d)return q.forwardPosition=l,q}}function T(b){var c,d=this,e=b.originalEvent||b,f=a(d),h=d.inputmask._valueGet(!0),i=L(d);ma&&(c=i.end,i.end=i.begin,i.begin=c);var j=h.substr(0,i.begin),k=h.substr(i.end,h.length);if(j===(ma?x().reverse():x()).slice(0,i.begin).join("")&&(j=""),k===(ma?x().reverse():x()).slice(i.end).join("")&&(k=""),ma&&(c=j,j=k,k=c),window.clipboardData&&window.clipboardData.getData)h=j+window.clipboardData.getData("Text")+k;else{if(!e.clipboardData||!e.clipboardData.getData)return!0;h=j+e.clipboardData.getData("text/plain")+k}var l=h;if(a.isFunction(g.onBeforePaste)){if(l=g.onBeforePaste(h,g),l===!1)return b.preventDefault();l||(l=h)}return J(d,!1,!1,ma?l.split("").reverse():l.toString().split("")),H(d,y(),E(p()),b,ga!==y().join("")),O(y())===!0&&f.trigger("complete"),b.preventDefault()}function U(c){var d=this,e=d.inputmask._valueGet();if(y().join("")!==e){ 12084var f=L(d);if(e=e.replace(new RegExp("("+b.escapeRegex(x().join(""))+")*"),""),k){var g=e.replace(y().join(""),"");if(1===g.length){var h=new a.Event("keypress");return h.which=g.charCodeAt(0),S.call(d,h,!0,!0,!1,n().validPositions[f.begin-1]?f.begin:f.begin-1),!1}}if(f.begin>e.length&&(L(d,e.length),f=L(d)),y().length-e.length!==1||e.charAt(f.begin)===y()[f.begin]||e.charAt(f.begin+1)===y()[f.begin]||D(f.begin)){for(var i=p()+1,j=y().slice(i).join("");null===e.match(b.escapeRegex(j)+"$");)j=j.slice(1);e=e.replace(j,""),e=e.split(""),J(d,!0,!1,e,c,f.begin<i),O(y())===!0&&a(d).trigger("complete")}else c.keyCode=b.keyCode.BACKSPACE,R.call(d,c);c.preventDefault()}}function V(b){var c=this,d=c.inputmask._valueGet();J(c,!0,!1,(a.isFunction(g.onBeforeMask)?g.onBeforeMask(d,g)||d:d).split("")),ga=y().join(""),(g.clearMaskOnLostFocus||g.clearIncomplete)&&c.inputmask._valueGet()===x().join("")&&c.inputmask._valueSet("")}function W(a){var b=this,c=b.inputmask._valueGet();g.showMaskOnFocus&&(!g.showMaskOnHover||g.showMaskOnHover&&""===c)?b.inputmask._valueGet()!==y().join("")&&H(b,y(),E(p())):qa===!1&&L(b,E(p())),g.positionCaretOnTab===!0&&setTimeout(function(){Y.apply(this,[a])},0),ga=y().join("")}function X(a){var b=this;if(qa=!1,g.clearMaskOnLostFocus&&document.activeElement!==b){var c=y().slice(),d=b.inputmask._valueGet();d!==b.getAttribute("placeholder")&&""!==d&&(p()===-1&&d===x().join("")?c=[]:N(c),H(b,c))}}function Y(b){function c(b){if(""!==g.radixPoint){var c=n().validPositions;if(void 0===c[b]||c[b].input===I(b)){if(b<E(-1))return!0;var d=a.inArray(g.radixPoint,y());if(d!==-1){for(var e in c)if(d<e&&c[e].input!==I(e))return!1;return!0}}}return!1}var d=this;setTimeout(function(){if(document.activeElement===d){var b=L(d);if(b.begin===b.end)switch(g.positionCaretOnClick){case"none":break;case"radixFocus":if(c(b.begin)){var e=a.inArray(g.radixPoint,y().join(""));L(d,g.numericInput?E(e):e);break}default:var f=b.begin,h=p(f,!0),i=E(h);if(f<i)L(d,D(f)||D(f-1)?f:E(f));else{var j=I(i);
12084(""!==j&&y()[i]!==j&&t(i).match.optionalQuantifier!==!0||!D(i,!0)&&t(i).match.def===j)&&(i=E(i)),L(d,i)}}}},0)}function Z(a){var b=this;setTimeout(function(){L(b,0,E(p()))},0)}function $(c){var d=this,e=a(d),f=L(d),h=c.originalEvent||c,i=window.clipboardData||h.clipboardData,j=ma?y().slice(f.end,f.begin):y().slice(f.begin,f.end);i.setData("text",ma?j.reverse().join(""):j.join("")),document.execCommand&&document.execCommand("copy"),Q(d,b.keyCode.DELETE,f),H(d,y(),n().p,c,ga!==y().join("")),d.inputmask._valueGet()===x().join("")&&e.trigger("cleared"),g.showTooltip&&(d.title=g.tooltip||n().mask)}function _(b){var c=a(this),d=this;if(d.inputmask){var e=d.inputmask._valueGet(),f=y().slice();ga!==f.join("")&&setTimeout(function(){c.trigger("change"),ga=f.join("")},0),""!==e&&(g.clearMaskOnLostFocus&&(p()===-1&&e===x().join("")?f=[]:N(f)),O(f)===!1&&(setTimeout(function(){c.trigger("incomplete")},0),g.clearIncomplete&&(o(),f=g.clearMaskOnLostFocus?[]:x().slice())),H(d,f,void 0,b))}}function aa(a){var b=this;qa=!0,document.activeElement!==b&&g.showMaskOnHover&&b.inputmask._valueGet()!==y().join("")&&H(b,y())}function ba(a){ga!==y().join("")&&ia.trigger("change"),g.clearMaskOnLostFocus&&p()===-1&&ha.inputmask._valueGet&&ha.inputmask._valueGet()===x().join("")&&ha.inputmask._valueSet(""),g.removeMaskOnSubmit&&(ha.inputmask._valueSet(ha.inputmask.unmaskedvalue(),!0),setTimeout(function(){H(ha,y())},0))}function ca(a){setTimeout(function(){ia.trigger("setvalue")},0)}function da(b){function c(a){var c,d=document.createElement("span");for(var e in f)isNaN(e)&&e.indexOf("font")!==-1&&(d.style[e]=f[e]);d.style.textTransform=f.textTransform,d.style.letterSpacing=f.letterSpacing,d.style.position="absolute",d.style.height="auto",d.style.width="auto",d.style.visibility="hidden",d.style.whiteSpace="nowrap",document.body.appendChild(d);var g,h=b.inputmask._valueGet(),i=0;for(c=0,g=h.length;c<=g;c++){if(d.innerHTML+=h.charAt(c)||"_",d.offsetWidth>=a){var j=a-i,k=d.offsetWidth-a;d.innerHTML=h.charAt(c),j-=d.offsetWidth/3,c=j<k?c-1:c;break}i=d.offsetWidth}return document.body.removeChild(d),c}function d(){ka.style.position="absolute",ka.style.top=e.top+"px",ka.style.left=e.left+"px",ka.style.width=parseInt(b.offsetWidth)-parseInt(f.paddingLeft)-parseInt(f.paddingRight)-parseInt(f.borderLeftWidth)-parseInt(f.borderRightWidth)+"px",ka.style.height=parseInt(b.offsetHeight)-parseInt(f.paddingTop)-parseInt(f.paddingBottom)-parseInt(f.borderTopWidth)-parseInt(f.borderBottomWidth)+"px",ka.style.lineHeight=ka.style.height,ka.style.zIndex=isNaN(f.zIndex)?-1:f.zIndex-1,ka.style.webkitAppearance="textfield",ka.style.mozAppearance="textfield",ka.style.Appearance="textfield"}var e=a(b).position(),f=(b.ownerDocument.defaultView||window).getComputedStyle(b,null);b.parentNode;ka=document.createElement("div"),document.body.appendChild(ka);for(var g in f)isNaN(g)&&"cssText"!==g&&g.indexOf("webkit")==-1&&(ka.style[g]=f[g]);b.style.backgroundColor="transparent",b.style.color="transparent",b.style.webkitAppearance="caret",b.style.mozAppearance="caret",b.style.Appearance="caret",d(),a(window).on("resize",function(c){e=a(b).position(),f=(b.ownerDocument.defaultView||window).getComputedStyle(b,null),d()}),a(b).on("click",function(a){return L(b,c(a.clientX)),Y.call(this,[a])})}function ea(a,b,c){function d(){f||null!==i.fn&&void 0!==j.input?f&&null!==i.fn&&void 0!==j.input&&(f=!1,e+="</span>"):(f=!0,e+="<span class='im-static''>")}if(void 0!==ka){b=b||y(),void 0===c?c=L(a):void 0===c.begin&&(c={begin:c,end:c});var e="",f=!1;if(""!=b){var h,i,j,k=0,l=p();do k===c.begin&&document.activeElement===a&&(e+="<span class='im-caret' style='border-right-width: 1px;border-right-style: solid;'></span>"),n().validPositions[k]?(j=n().validPositions[k],i=j.match,h=j.locator.slice(),d(),e+=j.input):(j=s(k,h,k-1),i=j.match,h=j.locator.slice(),(g.jitMasking===!1||k<l||Number.isFinite(g.jitMasking)&&g.jitMasking>k)&&(d(),e+=I(k,i))),k++;while((void 0===ja||k<ja)&&(null!==i.fn||""!==i.def)||l>k)}ka.innerHTML=e}}function fa(b){if(ha=b,ia=a(ha),g.showTooltip&&(ha.title=g.tooltip||n().mask),("rtl"===ha.dir||g.rightAlign)&&(ha.style.textAlign="right"),("rtl"===ha.dir||g.numericInput)&&(ha.dir="ltr",ha.removeAttribute("dir"),ha.inputmask.isRTL=!0,ma=!0),g.colorMask===!0&&da(ha),m&&(ha.hasOwnProperty("inputmode")&&(ha.inputmode=g.inputmode,ha.setAttribute("inputmode",g.inputmode)),"rtfm"===g.androidHack&&(g.colorMask!==!0&&da(ha),ha.type="password")),ra.off(ha),P(ha),d(ha,g)&&(ra.on(ha,"submit",ba),ra.on(ha,"reset",ca),ra.on(ha,"mouseenter",aa),ra.on(ha,"blur",_),ra.on(ha,"focus",W),ra.on(ha,"mouseleave",X),g.colorMask!==!0&&ra.on(ha,"click",Y),ra.on(ha,"dblclick",Z),ra.on(ha,"paste",T),ra.on(ha,"dragdrop",T),ra.on(ha,"drop",T),ra.on(ha,"cut",$),ra.on(ha,"complete",g.oncomplete),ra.on(ha,"incomplete",g.onincomplete),ra.on(ha,"cleared",g.oncleared),g.inputEventOnly!==!0&&(ra.on(ha,"keydown",R),ra.on(ha,"keypress",S)),ra.on(ha,"compositionstart",a.noop),ra.on(ha,"compositionupdate",a.noop),ra.on(ha,"compositionend",a.noop),ra.on(ha,"keyup",a.noop),ra.on(ha,"input",U)),ra.on(ha,"setvalue",V),x(),""!==ha.inputmask._valueGet()||g.clearMaskOnLostFocus===!1||document.activeElement===ha){var c=a.isFunction(g.onBeforeMask)?g.onBeforeMask(h
12084a.inputmask._valueGet(),g)||ha.inputmask._valueGet():ha.inputmask._valueGet();J(ha,!0,!1,c.split(""));var e=y().slice();ga=e.join(""),O(e)===!1&&g.clearIncomplete&&o(),g.clearMaskOnLostFocus&&document.activeElement!==ha&&(p()===-1?e=[]:N(e)),H(ha,e),document.activeElement===ha&&L(ha,E(p()))}}var ga,ha,ia,ja,ka,la,ma=!1,na=!1,oa=!1,pa=!1,qa=!0,ra={on:function(c,d,e){var f=function(c){if(void 0===this.inputmask&&"FORM"!==this.nodeName){var d=a.data(this,"_inputmask_opts");d?new b(d).mask(this):ra.off(this)}else{if("setvalue"===c.type||!(this.disabled||this.readOnly&&!("keydown"===c.type&&c.ctrlKey&&67===c.keyCode||g.tabThrough===!1&&c.keyCode===b.keyCode.TAB))){switch(c.type){case"input":if(oa===!0)return oa=!1,c.preventDefault();break;case"keydown":na=!1,oa=!1;break;case"keypress":if(na===!0)return c.preventDefault();na=!0;break;case"click":if(k||l){var f=this,h=arguments;return setTimeout(function(){e.apply(f,h)},0),!1}}var i=e.apply(this,arguments);return i===!1&&(c.preventDefault(),c.stopPropagation()),i}c.preventDefault()}};c.inputmask.events[d]=c.inputmask.events[d]||[],c.inputmask.events[d].push(f),a.inArray(d,["submit","reset"])!==-1?null!=c.form&&a(c.form).on(d,f):a(c).on(d,f)},off:function(b,c){if(b.inputmask&&b.inputmask.events){var d;c?(d=[],d[c]=b.inputmask.events[c]):d=b.inputmask.events,a.each(d,function(c,d){for(;d.length>0;){var e=d.pop();a.inArray(c,["submit","reset"])!==-1?null!=b.form&&a(b.form).off(c,e):a(b).off(c,e)}delete b.inputmask.events[c]})}}};if(void 0!==e)switch(e.action){case"isComplete":return ha=e.el,O(y());case"unmaskedvalue":return ha=e.el,void 0!==ha&&void 0!==ha.inputmask?(f=ha.inputmask.maskset,g=ha.inputmask.opts,ma=ha.inputmask.isRTL):(la=e.value,g.numericInput&&(ma=!0),la=(a.isFunction(g.onBeforeMask)?g.onBeforeMask(la,g)||la:la).split(""),J(void 0,!1,!1,ma?la.reverse():la),a.isFunction(g.onBeforeWrite)&&g.onBeforeWrite(void 0,y(),0,g)),K(ha);case"mask":ha=e.el,f=ha.inputmask.maskset,g=ha.inputmask.opts,ma=ha.inputmask.isRTL,fa(ha);break;case"format":return g.numericInput&&(ma=!0),la=(a.isFunction(g.onBeforeMask)?g.onBeforeMask(e.value,g)||e.value:e.value).split(""),J(void 0,!1,!1,ma?la.reverse():la),a.isFunction(g.onBeforeWrite)&&g.onBeforeWrite(void 0,y(),0,g),e.metadata?{value:ma?y().slice().reverse().join(""):y().join(""),metadata:h({action:"getmetadata"},f,g)}:ma?y().slice().reverse().join(""):y().join("");case"isValid":g.numericInput&&(ma=!0),e.value?(la=e.value.split(""),J(void 0,!1,!0,ma?la.reverse():la)):e.value=y().join("");for(var sa=y(),ta=M(),ua=sa.length-1;ua>ta&&!D(ua);ua--);return sa.splice(ta,ua+1-ta),O(sa)&&e.value===y().join("");case"getemptymask":return x().join("");case"remove":ha=e.el,ia=a(ha),f=ha.inputmask.maskset,g=ha.inputmask.opts,ha.inputmask._valueSet(K(ha)),ra.off(ha);var va;Object.getOwnPropertyDescriptor&&Object.getPrototypeOf?(va=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(ha),"value"),va&&ha.inputmask.__valueGet&&Object.defineProperty(ha,"value",{get:ha.inputmask.__valueGet,set:ha.inputmask.__valueSet,configurable:!0})):document.__lookupGetter__&&ha.__lookupGetter__("value")&&ha.inputmask.__valueGet&&(ha.__defineGetter__("value",ha.inputmask.__valueGet),ha.__defineSetter__("value",ha.inputmask.__valueSet)),ha.inputmask=void 0;break;case"getmetadata":if(a.isArray(f.metadata)){for(var wa,xa=p(void 0,!0),ya=xa;ya>=0;ya--)if(n().validPositions[ya]&&void 0!==n().validPositions[ya].alternation){wa=n().validPositions[ya].alternation;break}return void 0!==wa?f.metadata[n().validPositions[ya].locator[wa]]:[]}return f.metadata}}b.prototype={defaults:{placeholder:"_",optionalmarker:{start:"[",end:"]"},quantifiermarker:{start:"{",end:"}"},groupmarker:{start:"(",end:")"},alternatormarker:"|",escapeChar:"\\",mask:null,oncomplete:a.noop,onincomplete:a.noop,oncleared:a.noop,repeat:0,greedy:!0,autoUnmask:!1,removeMaskOnSubmit:!1,clearMaskOnLostFocus:!0,insertMode:!0,clearIncomplete:!1,aliases:{},alias:null,onKeyDown:a.noop,onBeforeMask:null,onBeforePaste:function(b,c){return a.isFunction(c.onBeforeMask)?c.onBeforeMask(b,c):b},onBeforeWrite:null,onUnMask:null,showMaskOnFocus:!0,showMaskOnHover:!0,onKeyValidation:a.noop,skipOptionalPartCharacter:" ",showTooltip:!1,tooltip:void 0,numericInput:!1,rightAlign:!1,undoOnEscape:!0,radixPoint:"",radixPointDefinitionSymbol:void 0,groupSeparator:"",keepStatic:null,positionCaretOnTab:!0,tabThrough:!1,supportsInputType:["text","tel","password"],definitions:{9:{validator:"[0-9]",cardinality:1,definitionSymbol:"*"},a:{validator:"[A-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]",cardinality:1,definitionSymbol:"*"},"*":{validator:"[0-9A-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]",cardinality:1}},ignorables:[8,9,13,19,27,33,34,35,36,37,38,39,40,45,46,93,112,113,114,115,116,117,118,119,120,121,122,123],isComplete:null,canClearPosition:a.noop,postValidation:null,staticDefinitionSymbol:void 0,jitMasking:!1,nullable:!0,inputEventOnly:!1,noValuePatching:!1,positionCaretOnClick:"lvp",casing:null,inputmode:"verbatim",colorMask:!1,androidHack:!1},masksCache:{},mask:function(c){var d=this;return"string"==typeof c&&(c=document.getElementById(c)||document.querySelectorAll(c)),c=c.nodeName?[c]:c,a.each(c,function(c,e){var i=a.extend(!0,{},d.opts);f(e,i,a.extend(!0,{},d.userOptions));var j=g(i,d.noMasksCache);void 0!==j&&(void 0!==e.inputmask&&e.inputmask.remove(),e.inputmask=new b,e.inputmask.opts=i,e.inputmask.noMasksCache=d.noMasksCache,e.inputmask.userOptions=a.extend(!0,{},d.userOptions),e.inputmask.el=e,e.inputmask.maskset=j,e.inputmask.isRTL=!1,a.data(e,"_inputmask_opts",i),h({action:"mask",el:e}))}),c&&c[0]?c[0].inputmask||this:this},option:function(b,c){return"string"==typeof b?this.opts[b]:"object"==typeof b?(a.extend(this.userOptions,b),this.el&&c!==!0&&this.mask(this.el),this):void 0},unmaskedvalue:function(a){return h({action:"unmaskedvalue",el:this.el,value:a},this.el&&this.el.inputmask?this.el.inputmask.maskset:g(this.opts,this.noMasksCache),this.opts)},remove:function(){if(this.el)return h({action:"remove",el:this.el}),this.el.inputmask=void 0,this.el},getemptymask:function(){return h({action:"getemptymask"},this.maskset||g(this.opts,this.noMasksCache),this.opts)},hasMaskedValue:function(){return!this.opts.autoUnmask},isComplete:function(){return h({action:"isComplete",el:this.el},this.maskset||g(this.opts,this.noMasksCache),this.opts)},getmetadata:function(){return h({action:"getmetadata"},this.maskset||g(this.opts,this.noMasksCache),this.opts)},isValid:function(a){return h({action:"isValid",value:a},this.maskset||g(this.opts,this.noMasksCache),this.opts)},format:function(a,b){return h({action:"format",value:a,metadata:b},this.maskset||g(this.opts,this.noMasksCache),this.opts)}},b.extendDefaults=function(c){a.extend(!0,b.prototype.defaults,c)},b.extendDefinitions=function(c){a.extend(!0,b.prototype.defaults.definitions,c)},b.extendAliases=function(c){a.extend(!0,b.prototype.defaults.aliases,c)},b.format=function(a,c,d){return b(c).format(a,d)}
12084,b.unmask=function(a,c){return b(c).unmaskedvalue(a)},b.isValid=function(a,c){return b(c).isValid(a)},b.remove=function(b){a.each(b,function(a,b){b.inputmask&&b.inputmask.remove()})},b.escapeRegex=function(a){var b=["/",".","*","+","?","|","(",")","[","]","{","}","\\","$","^"];return a.replace(new RegExp("(\\"+b.join("|\\")+")","gim"),"\\$1")},b.keyCode={ALT:18,BACKSPACE:8,BACKSPACE_SAFARI:127,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91,X:88};var i=navigator.userAgent,j=/mobile/i.test(i),k=/iemobile/i.test(i),l=/iphone/i.test(i)&&!k,m=/android/i.test(i)&&!k;return window.Inputmask=b,b}(jQuery),function(a,b){return void 0===a.fn.inputmask&&(a.fn.inputmask=function(c,d){var e,f=this[0];if(void 0===d&&(d={}),"string"==typeof c)switch(c){case"unmaskedvalue":return f&&f.inputmask?f.inputmask.unmaskedvalue():a(f).val();case"remove":return this.each(function(){this.inputmask&&this.inputmask.remove()});case"getemptymask":return f&&f.inputmask?f.inputmask.getemptymask():"";case"hasMaskedValue":return!(!f||!f.inputmask)&&f.inputmask.hasMaskedValue();case"isComplete":return!f||!f.inputmask||f.inputmask.isComplete();case"getmetadata":return f&&f.inputmask?f.inputmask.getmetadata():void 0;case"setvalue":a(f).val(d),f&&void 0===f.inputmask&&a(f).triggerHandler("setvalue");break;case"option":if("string"!=typeof d)return this.each(function(){if(void 0!==this.inputmask)return this.inputmask.option(d)});if(f&&void 0!==f.inputmask)return f.inputmask.option(d);break;default:return d.alias=c,e=new b(d),this.each(function(){e.mask(this)})}else{if("object"==typeof c)return e=new b(c),void 0===c.mask&&void 0===c.alias?this.each(function(){return void 0!==this.inputmask?this.inputmask.option(c):void e.mask(this)}):this.each(function(){e.mask(this)});if(void 0===c)return this.each(function(){e=new b(d),e.mask(this)})}}),a.fn.inputmask}(jQuery,Inputmask),function(a,b){return b.extendDefinitions({h:{validator:"[01][0-9]|2[0-3]",cardinality:2,prevalidator:[{validator:"[0-2]",cardinality:1}]},s:{validator:"[0-5][0-9]",cardinality:2,prevalidator:[{validator:"[0-5]",cardinality:1}]},d:{validator:"0[1-9]|[12][0-9]|3[01]",cardinality:2,prevalidator:[{validator:"[0-3]",cardinality:1}]},m:{validator:"0[1-9]|1[012]",cardinality:2,prevalidator:[{validator:"[01]",cardinality:1}]},y:{validator:"(19|20)\\d{2}",cardinality:4,prevalidator:[{validator:"[12]",cardinality:1},{validator:"(19|20)",cardinality:2},{validator:"(19|20)\\d",cardinality:3}]}}),b.extendAliases({"dd/mm/yyyy":{mask:"1/2/y",placeholder:"dd/mm/yyyy",regex:{val1pre:new RegExp("[0-3]"),val1:new RegExp("0[1-9]|[12][0-9]|3[01]"),val2pre:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|[12][0-9]|3[01])"+c+"[01])")},val2:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|[12][0-9])"+c+"(0[1-9]|1[012]))|(30"+c+"(0[13-9]|1[012]))|(31"+c+"(0[13578]|1[02]))")}},leapday:"29/02/",separator:"/",yearrange:{minyear:1900,maxyear:2099},isInYearRange:function(a,b,c){if(isNaN(a))return!1;var d=parseInt(a.concat(b.toString().slice(a.length))),e=parseInt(a.concat(c.toString().slice(a.length)));return!isNaN(d)&&(b<=d&&d<=c)||!isNaN(e)&&(b<=e&&e<=c)},determinebaseyear:function(a,b,c){var d=(new Date).getFullYear();if(a>d)return a;if(b<d){for(var e=b.toString().slice(0,2),f=b.toString().slice(2,4);b<e+c;)e--;var g=e+f;return a>g?a:g}if(a<=d&&d<=b){for(var h=d.toString().slice(0,2);b<h+c;)h--;var i=h+c;return i<a?a:i}return d},onKeyDown:function(c,d,e,f){var g=a(this);if(c.ctrlKey&&c.keyCode===b.keyCode.RIGHT){var h=new Date;g.val(h.getDate().toString()+(h.getMonth()+1).toString()+h.getFullYear().toString()),g.trigger("setvalue")}},getFrontValue:function(a,b,c){for(var d=0,e=0,f=0;f<a.length&&"2"!==a.charAt(f);f++){var g=c.definitions[a.charAt(f)];g?(d+=e,e=g.cardinality):e++}return b.join("").substr(d,e)},definitions:{1:{validator:function(a,b,c,d,e){var f=e.regex.val1.test(a);return d||f||a.charAt(1)!==e.separator&&"-./".indexOf(a.charAt(1))===-1||!(f=e.regex.val1.test("0"+a.charAt(0)))?f:(b.buffer[c-1]="0",{refreshFromBuffer:{start:c-1,end:c},pos:c,c:a.charAt(0)})}
12084,cardinality:2,prevalidator:[{validator:function(a,b,c,d,e){var f=a;isNaN(b.buffer[c+1])||(f+=b.buffer[c+1]);var g=1===f.length?e.regex.val1pre.test(f):e.regex.val1.test(f);if(!d&&!g){if(g=e.regex.val1.test(a+"0"))return b.buffer[c]=a,b.buffer[++c]="0",{pos:c,c:"0"};if(g=e.regex.val1.test("0"+a))return b.buffer[c]="0",c++,{pos:c}}return g},cardinality:1}]},2:{validator:function(a,b,c,d,e){var f=e.getFrontValue(b.mask,b.buffer,e);f.indexOf(e.placeholder[0])!==-1&&(f="01"+e.separator);var g=e.regex.val2(e.separator).test(f+a);if(!d&&!g&&(a.charAt(1)===e.separator||"-./".indexOf(a.charAt(1))!==-1)&&(g=e.regex.val2(e.separator).test(f+"0"+a.charAt(0))))return b.buffer[c-1]="0",{refreshFromBuffer:{start:c-1,end:c},pos:c,c:a.charAt(0)};if(e.mask.indexOf("2")===e.mask.length-1&&g){var h=b.buffer.join("").substr(4,4)+a;if(h!==e.leapday)return!0;var i=parseInt(b.buffer.join("").substr(0,4),10);return i%4===0&&(i%100!==0||i%400===0)}return g},cardinality:2,prevalidator:[{validator:function(a,b,c,d,e){isNaN(b.buffer[c+1])||(a+=b.buffer[c+1]);var f=e.getFrontValue(b.mask,b.buffer,e);f.indexOf(e.placeholder[0])!==-1&&(f="01"+e.separator);var g=1===a.length?e.regex.val2pre(e.separator).test(f+a):e.regex.val2(e.separator).test(f+a);return d||g||!(g=e.regex.val2(e.separator).test(f+"0"+a))?g:(b.buffer[c]="0",c++,{pos:c})},cardinality:1}]},y:{validator:function(a,b,c,d,e){if(e.isInYearRange(a,e.yearrange.minyear,e.yearrange.maxyear)){var f=b.buffer.join("").substr(0,6);if(f!==e.leapday)return!0;var g=parseInt(a,10);return g%4===0&&(g%100!==0||g%400===0)}return!1},cardinality:4,prevalidator:[{validator:function(a,b,c,d,e){var f=e.isInYearRange(a,e.yearrange.minyear,e.yearrange.maxyear);if(!d&&!f){var g=e.determinebaseyear(e.yearrange.minyear,e.yearrange.maxyear,a+"0").toString().slice(0,1);if(f=e.isInYearRange(g+a,e.yearrange.minyear,e.yearrange.maxyear))return b.buffer[c++]=g.charAt(0),{pos:c};if(g=e.determinebaseyear(e.yearrange.minyear,e.yearrange.maxyear,a+"0").toString().slice(0,2),f=e.isInYearRange(g+a,e.yearrange.minyear,e.yearrange.maxyear))return b.buffer[c++]=g.charAt(0),b.buffer[c++]=g.charAt(1),{pos:c}}return f},cardinality:1},{validator:function(a,b,c,d,e){var f=e.isInYearRange(a,e.yearrange.minyear,e.yearrange.maxyear);if(!d&&!f){var g=e.determinebaseyear(e.yearrange.minyear,e.yearrange.maxyear,a).toString().slice(0,2);if(f=e.isInYearRange(a[0]+g[1]+a[1],e.yearrange.minyear,e.yearrange.maxyear))return b.buffer[c++]=g.charAt(1),{pos:c};if(g=e.determinebaseyear(e.yearrange.minyear,e.yearrange.maxyear,a).toString().slice(0,2),e.isInYearRange(g+a,e.yearrange.minyear,e.yearrange.maxyear)){var h=b.buffer.join("").substr(0,6);if(h!==e.leapday)f=!0;else{var i=parseInt(a,10);f=i%4===0&&(i%100!==0||i%400===0)}}else f=!1;if(f)return b.buffer[c-1]=g.charAt(0),b.buffer[c++]=g.charAt(1),b.buffer[c++]=a.charAt(0),{refreshFromBuffer:{start:c-3,end:c},pos:c}}return f},cardinality:2},{validator:function(a,b,c,d,e){return e.isInYearRange(a,e.yearrange.minyear,e.yearrange.maxyear)},cardinality:3}]}},insertMode:!1,autoUnmask:!1},"mm/dd/yyyy":{placeholder:"mm/dd/yyyy",alias:"dd/mm/yyyy",regex:{val2pre:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[13-9]|1[012])"+c+"[0-3])|(02"+c+"[0-2])")},val2:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|1[012])"+c+"(0[1-9]|[12][0-9]))|((0[13-9]|1[012])"+c+"30)|((0[13578]|1[02])"+c+"31)")},val1pre:new RegExp("[01]"),val1:new RegExp("0[1-9]|1[012]")},leapday:"02/29/",onKeyDown:function(c,d,e,f){var g=a(this);if(c.ctrlKey&&c.keyCode===b.keyCode.RIGHT){var h=new Date;g.val((h.getMonth()+1).toString()+h.getDate().toString()+h.getFullYear().toString()),g.trigger("setvalue")}}},"yyyy/mm/dd":{mask:"y/1/2",placeholder:"yyyy/mm/dd",alias:"mm/dd/yyyy",leapday:"/02/29",onKeyDown:function(c,d,e,f){var g=a(this);if(c.ctrlKey&&c.keyCode===b.keyCode.RIGHT){var h=new Date;g.val(h.getFullYear().toString()+(h.getMonth()+1).toString()+h.getDate().toString()),g.trigger("setvalue")}}},"dd.mm.yyyy":{mask:"1.2.y",placeholder:"dd.mm.yyyy",leapday:"29.02.",separator:".",alias:"dd/mm/yyyy"},"dd-mm-yyyy":{mask:"1-2-y",placeholder:"dd-mm-yyyy",leapday:"29-02-",separator:"-",alias:"dd/mm/yyyy"},"mm.dd.yyyy":{mask:"1.2.y",placeholder:"mm.dd.yyyy",leapday:"02.29.",separator:".",alias:"mm/dd/yyyy"},"mm-dd-yyyy":{mask:"1-2-y",placeholder:"mm-dd-yyyy",leapday:"02-29-",separator:"-",alias:"mm/dd/yyyy"},"yyyy.mm.dd":{mask:"y.1.2",placeholder:"yyyy.mm.dd",leapday:".02.29",separator:".",alias:"yyyy/mm/dd"},"yyyy-mm-dd":{mask:"y-1-2",placeholder:"yyyy-mm-dd",leapday:"-02-29",separator:"-",alias:"yyyy/mm/dd"},datetime:{mask:"1/2/y h:s",placeholder:"dd/mm/yyyy hh:mm",alias:"dd/mm/yyyy",regex:{hrspre:new RegExp("[012]"),hrs24:new RegExp("2[0-4]|1[3-9]"),hrs:new RegExp("[01][0-9]|2[0-4]"),ampm:new RegExp("^[a|p|A|P][m|M]"),mspre:new RegExp("[0-5]"),ms:new RegExp("[0-5][0-9]")},timeseparator:":",hourFormat:"24",definitions:{h:{validator:function(a,b,c,d,e){if("24"===e.hourFormat&&24===parseInt(a,10))return b.buffer[c-1]="0",b.buffer[c]="0",{refreshFromBuffer:{start:c-1,end:c},c:"0"};var f=e.regex.hrs.test(a);if(!d&&!f&&(a.charAt(1)===e.timeseparator||"-.:".indexOf(a.charAt(1))!==-1)&&(f=e.regex.hrs.test("0"+a.charAt(0))))return b.buffer[c-1]="0",b.buffer[c]=a.charAt(0),c++,{refreshFromBuffer:{start:c-2,end:c},pos:c,c:e.timeseparator};if(f&&"24"!==e.hourFormat&&e.regex.hrs24.test(a)){var g=parseInt(a,10);return 24===g?(b.buffer[c+5]="a",b.buffer[c+6]="m"):(b.buffer[c+5]="p",b.buffer[c+6]="m"),g-=12,g<10?(b.buffer[c]=g.toString(),b.buffer[c-1]="0"):(b.buffer[c]=g.toString().charAt(1),b.buffer[c-1]=g.toString().charAt(0)),{refreshFromBuffer:{start:c-1,end:c+6},c:b.buffer[c]}}return f}
12084,cardinality:2,prevalidator:[{validator:function(a,b,c,d,e){var f=e.regex.hrspre.test(a);return d||f||!(f=e.regex.hrs.test("0"+a))?f:(b.buffer[c]="0",c++,{pos:c})},cardinality:1}]},s:{validator:"[0-5][0-9]",cardinality:2,prevalidator:[{validator:function(a,b,c,d,e){var f=e.regex.mspre.test(a);return d||f||!(f=e.regex.ms.test("0"+a))?f:(b.buffer[c]="0",c++,{pos:c})},cardinality:1}]},t:{validator:function(a,b,c,d,e){return e.regex.ampm.test(a+"m")},casing:"lower",cardinality:1}},insertMode:!1,autoUnmask:!1},datetime12:{mask:"1/2/y h:s t\\m",placeholder:"dd/mm/yyyy hh:mm xm",alias:"datetime",hourFormat:"12"},"mm/dd/yyyy hh:mm xm":{mask:"1/2/y h:s t\\m",placeholder:"mm/dd/yyyy hh:mm xm",alias:"datetime12",regex:{val2pre:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[13-9]|1[012])"+c+"[0-3])|(02"+c+"[0-2])")},val2:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|1[012])"+c+"(0[1-9]|[12][0-9]))|((0[13-9]|1[012])"+c+"30)|((0[13578]|1[02])"+c+"31)")},val1pre:new RegExp("[01]"),val1:new RegExp("0[1-9]|1[012]")},leapday:"02/29/",onKeyDown:function(c,d,e,f){var g=a(this);if(c.ctrlKey&&c.keyCode===b.keyCode.RIGHT){var h=new Date;g.val((h.getMonth()+1).toString()+h.getDate().toString()+h.getFullYear().toString()),g.trigger("setvalue")}}},"hh:mm t":{mask:"h:s t\\m",placeholder:"hh:mm xm",alias:"datetime",hourFormat:"12"},"h:s t":{mask:"h:s t\\m",placeholder:"hh:mm xm",alias:"datetime",hourFormat:"12"},"hh:mm:ss":{mask:"h:s:s",placeholder:"hh:mm:ss",alias:"datetime",autoUnmask:!1},"hh:mm":{mask:"h:s",placeholder:"hh:mm",alias:"datetime",autoUnmask:!1},date:{alias:"dd/mm/yyyy"},"mm/yyyy":{mask:"1/y",placeholder:"mm/yyyy",leapday:"donotuse",separator:"/",alias:"mm/dd/yyyy"},shamsi:{regex:{val2pre:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|1[012])"+c+"[0-3])")},val2:function(a){var c=b.escapeRegex.call(this,a);return new RegExp("((0[1-9]|1[012])"+c+"(0[1-9]|[12][0-9]))|((0[1-9]|1[012])"+c+"30)|((0[1-6])"+c+"31)")},val1pre:new RegExp("[01]"),val1:new RegExp("0[1-9]|1[012]")},yearrange:{minyear:1300,maxyear:1499},mask:"y/1/2",leapday:"/12/30",placeholder:"yyyy/mm/dd",alias:"mm/dd/yyyy",clearIncomplete:!0}}),b}(jQuery,Inputmask),function(a,b){return b.extendDefinitions({A:{validator:"[A-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]",cardinality:1,casing:"upper"},"&":{validator:"[0-9A-Za-z\u0410-\u044f\u0401\u0451\xc0-\xff\xb5]",cardinality:1,casing:"upper"},"#":{validator:"[0-9A-Fa-f]",cardinality:1,casing:"upper"}}),b.extendAliases({url:{definitions:{i:{validator:".",cardinality:1}},mask:"(\\http://)|(\\http\\s://)|(ftp://)|(ftp\\s://)i{+}",insertMode:!1,autoUnmask:!1,inputmode:"url"},ip:{mask:"i[i[i]].i[i[i]].i[i[i]].i[i[i]]",definitions:{i:{validator:function(a,b,c,d,e){return c-1>-1&&"."!==b.buffer[c-1]?(a=b.buffer[c-1]+a,a=c-2>
12084-1&&"."!==b.buffer[c-2]?b.buffer[c-2]+a:"0"+a):a="00"+a,new RegExp("25[0-5]|2[0-4][0-9]|[01][0-9][0-9]").test(a)},cardinality:1}},onUnMask:function(a,b,c){return a},inputmode:"numeric"},email:{mask:"*{1,64}[.*{1,64}][.*{1,64}][.*{1,63}]@-{1,63}.-{1,63}[.-{1,63}][.-{1,63}]",greedy:!1,onBeforePaste:function(a,b){return a=a.toLowerCase(),a.replace("mailto:","")},definitions:{"*":{validator:"[0-9A-Za-z!#$%&'*+/=?^_`{|}~-]",cardinality:1,casing:"lower"},"-":{validator:"[0-9A-Za-z-]",cardinality:1,casing:"lower"}},onUnMask:function(a,b,c){return a},inputmode:"email"},mac:{mask:"##:##:##:##:##:##"},vin:{mask:"V{13}9{4}",definitions:{V:{validator:"[A-HJ-NPR-Za-hj-npr-z\\d]",cardinality:1,casing:"upper"}},clearIncomplete:!0,autoUnmask:!0}}),b}(jQuery,Inputmask),function(a,b){return b.extendAliases({numeric:{mask:function(a){function c(b){for(var c="",d=0;d<b.length;d++)c+=a.definitions[b.charAt(d)]||a.optionalmarker.start===b.charAt(d)||a.optionalmarker.end===b.charAt(d)||a.quantifiermarker.start===b.charAt(d)||a.quantifiermarker.end===b.charAt(d)||a.groupmarker.start===b.charAt(d)||a.groupmarker.end===b.charAt(d)||a.alternatormarker===b.charAt(d)?"\\"+b.charAt(d):b.charAt(d);return c}if(0!==a.repeat&&isNaN(a.integerDigits)&&(a.integerDigits=a.repeat),a.repeat=0,a.groupSeparator===a.radixPoint&&("."===a.radixPoint?a.groupSeparator=",":","===a.radixPoint?a.groupSeparator=".":a.groupSeparator="")," "===a.groupSeparator&&(a.skipOptionalPartCharacter=void 0),a.autoGroup=a.autoGroup&&""!==a.groupSeparator,a.autoGroup&&("string"==typeof a.groupSize&&isFinite(a.groupSize)&&(a.groupSize=parseInt(a.groupSize)),isFinite(a.integerDigits))){var d=Math.floor(a.integerDigits/a.groupSize),e=a.integerDigits%a.groupSize;a.integerDigits=parseInt(a.integerDigits)+(0===e?d-1:d),a.integerDigits<1&&(a.integerDigits="*")}a.placeholder.length>1&&(a.placeholder=a.placeholder.charAt(0)),"radixFocus"===a.positionCaretOnClick&&""===a.placeholder&&a.integerOptional===!1&&(a.positionCaretOnClick="lvp"),a.definitions[";"]=a.definitions["~"],a.definitions[";"].definitionSymbol="~",a.numericInput===!0&&(a.positionCaretOnClick="radixFocus"===a.positionCaretOnClick?"lvp":a.positionCaretOnClick,a.digitsOptional=!1,isNaN(a.digits)&&(a.digits=2),a.decimalProtect=!1);var f="[+]";if(f+=c(a.prefix),f+=a.integerOptional===!0?"~{1,"+a.integerDigits+"}":"~{"+a.integerDigits+"}",void 0!==a.digits){a.decimalProtect&&(a.radixPointDefinitionSymbol=":");var g=a.digits.toString().split(",");isFinite(g[0]&&g[1]&&isFinite(g[1]))?f+=(a.decimalProtect?":":a.radixPoint)+";{"+a.digits+"}":(isNaN(a.digits)||parseInt(a.digits)>0)&&(f+=a.digitsOptional?"["+(a.decimalProtect?":":a.radixPoint)+";{1,"+a.digits+"}]":(a.decimalProtect?":":a.radixPoint)+";{"+a.digits+"}")}return f+=c(a.suffix),f+="[-]",a.greedy=!1,null!==a.min&&(a.min=a.min.toString().replace(new RegExp(b.escapeRegex(a.groupSeparator),"g"),""),","===a.radixPoint&&(a.min=a.min.replace(a.radixPoint,"."))),null!==a.max&&(a.max=a.max.toString().replace(new RegExp(b.escapeRegex(a.groupSeparator),"g"),""),","===a.radixPoint&&(a.max=a.max.replace(a.radixPoint,"."))),f},placeholder:"",greedy:!1,digits:"*",digitsOptional:!0,radixPoint:".",positionCaretOnClick:"radixFocus",groupSize:3,groupSeparator:"",autoGroup:!1,allowPlus:!0,allowMinus:!0,negationSymbol:{front:"-",back:""},integerDigits:"+",integerOptional:!0,prefix:"",suffix:"",rightAlign:!0,decimalProtect:!0,min:null,max:null,step:1,insertMode:!0,autoUnmask:!1,unmaskAsNumber:!1,inputmode:"numeric",postFormat:function(c,d,e){e.numericInput===!0&&(c=c.reverse(),isFinite(d)&&(d=c.join("").length-d-1));var f,g;d=d>=c.length?c.length-1:d<0?0:d;var h=c[d],i=c.slice();h===e.groupSeparator&&(i.splice(d--,1),h=i[d]);var j=i.join("").match(new RegExp("^"+b.escapeRegex(e.negationSymbol.front)));j=null!==j&&1===j.length,d>(j?e.negationSymbol.front.length:0)+e.prefix.length&&d<i.length-e.suffix.length&&(i[d]="!");var k=i.join(""),l=k;if(j&&(k=k.replace(new RegExp("^"+b.escapeRegex(e.negationSymbol.front)),""),k=k.replace(new RegExp(b.escapeRegex(e.negationSymbol.back)+"$"),"")),k=k.replace(new RegExp(b.escapeRegex(e.suffix)+"$"),""),k=k.replace(new RegExp("^"+b.escapeRegex(e.prefix)),""),k.length>0&&e.autoGroup||k.indexOf(e.groupSeparator)!==-1){var m=b.escapeRegex(e.groupSeparator);k=k.replace(new RegExp(m,"g"),"");var n=k.split(h===e.radixPoint?"!":e.radixPoint);if(k=""===e.radixPoint?k:n[0],h!==e.negationSymbol.front&&(k=k.replace("!","?")),k.length>e.groupSize)for(var o=new RegExp("([-+]?[\\d?]+)([\\d?]{"+e.groupSize+"})");o.test(k)&&""!==e.groupSeparator;)k=k.replace(o,"$1"+e.groupSeparator+"$2"),k=k.replace(e.groupSeparator+e.groupSeparator,e.groupSeparator);k=k.replace("?","!"),""!==e.radixPoint&&n.length>1&&(k+=(h===e.radixPoint?"!":e.radixPoint)+n[1])}k=e.prefix+k+e.suffix,j&&(k=e.negationSymbol.front+k+e.negationSymbol.back); 12085var p=l!==k;if(p)for(c.length=k.length,f=0,g=k.length;f<g;f++)c[f]=k.charAt(f);var q=a.inArray("!",k);return q===-1&&(q=d),c[q]=h,q=e.numericInput&&isFinite(d)?c.join("").length-q-1:q,e.numericInput&&(c=c.reverse(),a.inArray(e.radixPoint,c)<q&&c.join("").length-e.suffix.length!==q&&(q-=1)),{pos:q,refreshFromBuffer:p,buffer:c,isNegative:j}},onBeforeWrite:function(c,d,e,f){var g;
12085if(c&&("blur"===c.type||"checkval"===c.type||"keydown"===c.type)){var h=f.numericInput?d.slice().reverse().join(""):d.join(""),i=h.replace(f.prefix,"");i=i.replace(f.suffix,""),i=i.replace(new RegExp(b.escapeRegex(f.groupSeparator),"g"),""),","===f.radixPoint&&(i=i.replace(f.radixPoint,"."));var j=i.match(new RegExp("[-"+b.escapeRegex(f.negationSymbol.front)+"]","g"));if(j=null!==j&&1===j.length,i=i.replace(new RegExp("[-"+b.escapeRegex(f.negationSymbol.front)+"]","g"),""),i=i.replace(new RegExp(b.escapeRegex(f.negationSymbol.back)+"$"),""),isNaN(f.placeholder)&&(i=i.replace(new RegExp(b.escapeRegex(f.placeholder),"g"),"")),i=i===f.negationSymbol.front?i+"0":i,""!==i&&isFinite(i)){var k=parseFloat(i),l=j?k*-1:k;if(null!==f.min&&isFinite(f.min)&&l<parseFloat(f.min)?(k=Math.abs(f.min),j=f.min<0,h=void 0):null!==f.max&&isFinite(f.max)&&l>parseFloat(f.max)&&(k=Math.abs(f.max),j=f.max<0,h=void 0),i=k.toString().replace(".",f.radixPoint).split(""),isFinite(f.digits)){var m=a.inArray(f.radixPoint,i),n=a.inArray(f.radixPoint,h);m===-1&&(i.push(f.radixPoint),m=i.length-1);for(var o=1;o<=f.digits;o++)f.digitsOptional||void 0!==i[m+o]&&i[m+o]!==f.placeholder.charAt(0)?n!==-1&&void 0!==h[n+o]&&(i[m+o]=i[m+o]||h[n+o]):i[m+o]="0";i[i.length-1]===f.radixPoint&&delete i[i.length-1]}if(k.toString()!==i&&k.toString()+"."!==i||j)return i=(f.prefix+i.join("")).split(""),!j||0===k&&"blur"===c.type||(i.unshift(f.negationSymbol.front),i.push(f.negationSymbol.back)),f.numericInput&&(i=i.reverse()),g=f.postFormat(i,f.numericInput?e:e-1,f),g.buffer&&(g.refreshFromBuffer=g.buffer.join("")!==d.join("")),g}}if(f.autoGroup)return g=f.postFormat(d,f.numericInput?e:e-1,f),g.caret=e<(g.isNegative?f.negationSymbol.front.length:0)+f.prefix.length||e>g.buffer.length-(g.isNegative?f.negationSymbol.back.length:0)?g.pos:g.pos+1,g},regex:{integerPart:function(a){return new RegExp("["+b.escapeRegex(a.negationSymbol.front)+"+]?\\d+")},integerNPart:function(a){return new RegExp("[\\d"+b.escapeRegex(a.groupSeparator)+b.escapeRegex(a.placeholder.charAt(0))+"]+")}},signHandler:function(a,b,c,d,e){if(!d&&e.allowMinus&&"-"===a||e.allowPlus&&"+"===a){var f=b.buffer.join("").match(e.regex.integerPart(e));if(f&&f[0].length>0)return b.buffer[f.index]===("-"===a?"+":e.negationSymbol.front)?"-"===a?""!==e.negationSymbol.back?{pos:0,c:e.negationSymbol.front,remove:0,caret:c,insert:{pos:b.buffer.length-1,c:e.negationSymbol.back}}:{pos:0,c:e.negationSymbol.front,remove:0,caret:c}:""!==e.negationSymbol.back?{pos:0,c:"+",remove:[0,b.buffer.length-1],caret:c}:{pos:0,c:"+",remove:0,caret:c}:b.buffer[0]===("-"===a?e.negationSymbol.front:"+")?"-"===a&&""!==e.negationSymbol.back?{remove:[0,b.buffer.length-1],caret:c-1}:{remove:0,caret:c-1}:"-"===a?""!==e.negationSymbol.back?{pos:0,c:e.negationSymbol.front,caret:c+1,insert:{pos:b.buffer.length,c:e.negationSymbol.back}}:{pos:0,c:e.negationSymbol.front,caret:c+1}:{pos:0,c:a,caret:c+1}}return!1},radixHandler:function(b,c,d,e,f){if(!e&&f.numericInput!==!0&&b===f.radixPoint&&void 0!==f.digits&&(isNaN(f.digits)||parseInt(f.digits)>0)){var g=a.inArray(f.radixPoint,c.buffer),h=c.buffer.join("").match(f.regex.integerPart(f));if(g!==-1&&c.validPositions[g])return c.validPositions[g-1]?{caret:g+1}:{pos:h.index,c:h[0],caret:g+1};if(!h||"0"===h[0]&&h.index+1!==d)return c.buffer[h?h.index:d]="0",{pos:(h?h.index:d)+1,c:f.radixPoint}}return!1},leadingZeroHandler:function(b,c,d,e,f,g){if(!e){var h=c.buffer.slice("");if(h.splice(0,f.prefix.length),h.splice(h.length-f.suffix.length,f.suffix.length),f.numericInput===!0){var h=h.reverse(),i=h[0];if("0"===i&&void 0===c.validPositions[d-1])return{pos:d,remove:h.length-1}}else{d-=f.prefix.length;var j=a.inArray(f.radixPoint,h),k=h.slice(0,j!==-1?j:void 0).join("").match(f.regex.integerNPart(f));if(k&&(j===-1||d<=j)){var l=j===-1?0:parseInt(h.slice(j+1).join(""));if(0===k[0].indexOf(""!==f.placeholder?f.placeholder.charAt(0):"0")&&(k.index+1===d||g!==!0&&0===l))return c.buffer.splice(k.index+f.prefix.length,1),{pos:k.index+f.prefix.length,remove:k.index+f.prefix.length};if("0"===b&&d<=k.index&&k[0]!==f.groupSeparator)return!1}}}return!0},definitions:{"~":{validator:function(c,d,e,f,g,h){var i=g.signHandler(c,d,e,f,g);if(!i&&(i=g.radixHandler(c,d,e,f,g),!i&&(i=f?new RegExp("[0-9"+b.escapeRegex(g.groupSeparator)+"]").test(c):new RegExp("[0-9]").test(c),i===!0&&(i=g.leadingZeroHandler(c,d,e,f,g,h),i===!0)))){var j=a.inArray(g.radixPoint,d.buffer);i=j!==-1&&(g.digitsOptional===!1||d.validPositions[e])&&g.numericInput!==!0&&e>j&&!f?{pos:e,remove:e}:{pos:e}}return i},cardinality:1},"+":{validator:function(a,b,c,d,e){var f=e.signHandler(a,b,c,d,e);return!f&&(d&&e.allowMinus&&a===e.negationSymbol.front||e.allowMinus&&"-"===a||e.allowPlus&&"+"===a)&&(f=!(!d&&"-"===a)||(""!==e.negationSymbol.back?{pos:c,c:"-"===a?e.negationSymbol.front:"+",caret:c+1,insert:{pos:b.buffer.length,c:e.negationSymbol.back}}:{pos:c,c:"-"===a?e.negationSymbol.front:"+",caret:c+1})),f},cardinality:1,placeholder:""},"-":{validator:function(a,b,c,d,e){var f=e.signHandler(a,b,c,d,e);return!f&&d&&e.allowMinus&&a===e.negationSymbol.back&&(f=!0),f},cardinality:1,placeholder:""},":":{validator:function(a,c,d,e,f){var g=f.signHandler(a,c,d,e,f);if(!g){var h="["+b.escapeRegex(f.radixPoint)+"]";g=new RegExp(h).test(a),g&&c.validPositions[d]&&c.validPositions[d].match.placeholder===f.radixPoint&&(g={caret:d+1})}return g?{c:f.radixPoint}:g},cardinality:1,placeholder:function(a){return a.radixPoint}}},onUnMask:function(a,c,d){if(""===c&&d.nullable===!0)return c;var e=a.replace(d.prefix,"");return e=e.replace(d.suffix,""),e=e.replace(new RegExp(b.escapeRegex(d.groupSeparator),"g"),""),d.unmaskAsNumber?(""!==d.radixPoint&&e.indexOf(d.radixPoint)!==-1&&(e=e.replace(b.escapeRegex.call(this,d.radixPoint),".")),Number(e)):e},isComplete:function(a,c){var d=a.join(""),e=a.slice();if(c.postFormat(e,0,c),e.join("")!==d)return!1;var f=d.replace(c.prefix,"");return f=f.replace(c.suffix,""),f=f.replace(new RegExp(b.escapeRegex(c.groupSeparator),"g"),""),","===c.radixPoint&&(f=f.replace(b.escapeRegex(c.radixPoint),".")),isFinite(f)},onBeforeMask:function(a,c){if(c.numericInput===!0&&(a=a.split("").reverse().join("")),""!==c.radixPoint&&isFinite(a))a=a.toString().replace(".",c.radixPoint);else{var d=a.match(/,/g),e=a.match(/\./g);e&&d?e.length>d.length?(a=a.replace(/\./g,""),a=a.replace(",",c.radixPoint)):d.length>e.length?(a=a.replace(/,/g,""),a=a.replace(".",c.radixPoint)):a=a.indexOf(".")<a.indexOf(",")?a.replace(/\./g,""):a=a.replace(/,/g,""):a=a.replace(new RegExp(b.escapeRegex(c.groupSeparator),"g"),"")}if(0===c.digits&&(a.indexOf(".")!==-1?a=a.substring(0,a.indexOf(".")):a.indexOf(",")!==-1&&(a=a.substring(0,a.indexOf(",")))),""!==c.radixPoint&&isFinite(c.digits)&&a.indexOf(c.radixPoint)!==-1){var f=a.split(c.radixPoint),g=f[1].match(new RegExp("\\d*"))[0];if(parseInt(c.digits)<g.toString().length){var h=Math.pow(10,parseInt(c.digits));a=a.replace(b.escapeRegex(c.radixPoint),"."),a=Math.round(parseFloat(a)*h)/h,a=a.toString().replace(".",c.radixPoint)}}return c.numericInput===!0&&(a=a.split("").reverse().join("")),a.toString()},canClearPosition:function(a,b,c,d,e){var f=a.validPositions[b].input,g=f!==e.radixPoint||null!==a.validPositions[b].match.fn&&e.decimalProtect===!1||isFinite(f)||b===c||f===e.groupSeparator||f===e.negationSymbol.front||f===e.negationSymbol.back;return g},onKeyDown:function(c,d,e,f){var g=a(this);if(c.ctrlKey)switch(c.keyCode){case b.keyCode.UP:g.val(parseFloat(this.inputmask.unmaskedvalue())+parseInt(f.step)),g.trigger("setvalue");break;case b.keyCode.DOWN:g.val(parseFloat(this.inputmask.unmaskedvalue())-parseInt(f.step)),g.trigger("setvalue")}}},currency:{prefix:"$ ",groupSeparator:",",alias:"numeric",placeholder:"0",autoGroup:!0,digits:2,digitsOptional:!1,clearMaskOnLostFocus:!1}
12085,decimal:{alias:"numeric"},integer:{alias:"numeric",digits:0,radixPoint:""},percentage:{alias:"numeric",digits:2,radixPoint:".",placeholder:"0",autoGroup:!1,min:0,max:100,suffix:" %",allowPlus:!1,allowMinus:!1}}),b}(jQuery,Inputmask),function(a,b){return b.extendAliases({abstractphone:{countrycode:"",phoneCodes:[],mask:function(a){a.definitions={"#":a.definitions[9]};var b=a.phoneCodes.sort(function(a,b){var c=(a.mask||a).replace(/#/g,"9").replace(/[\+\(\)#-]/g,""),d=(b.mask||b).replace(/#/g,"9").replace(/[\+\(\)#-]/g,""),e=(a.mask||a).split("#")[0],f=(b.mask||b).split("#")[0];return 0===f.indexOf(e)?-1:0===e.indexOf(f)?1:c.localeCompare(d)});return b},keepStatic:!0,onBeforeMask:function(a,b){var c=a.replace(/^0{1,2}/,"").replace(/[\s]/g,"");return(c.indexOf(b.countrycode)>1||c.indexOf(b.countrycode)===-1)&&(c="+"+b.countrycode+c),c},onUnMask:function(a,b,c){return b},inputmode:"tel"}}),b}(jQuery,Inputmask),function(a,b){return b.extendAliases({Regex:{mask:"r",greedy:!1,repeat:"*",regex:null,regexTokens:null,tokenizer:/\[\^?]?(?:[^\\\]]+|\\[\S\s]?)*]?|\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\]+|./g,quantifierFilter:/[0-9]+[^,]/,isComplete:function(a,b){return new RegExp(b.regex).test(a.join(""))},definitions:{r:{validator:function(b,c,d,e,f){function g(a,b){this.matches=[],this.isGroup=a||!1,this.isQuantifier=b||!1,this.quantifier={min:1,max:1},this.repeaterPart=void 0}function h(){var a,b,c=new g,d=[];for(f.regexTokens=[];a=f.tokenizer.exec(f.regex);)switch(b=a[0],b.charAt(0)){case"(":d.push(new g((!0)));break;case")":k=d.pop(),d.length>0?d[d.length-1].matches.push(k):c.matches.push(k);break;case"{":case"+":case"*":var e=new g((!1),(!0));b=b.replace(/[{}]/g,"");var h=b.split(","),i=isNaN(h[0])?h[0]:parseInt(h[0]),j=1===h.length?i:isNaN(h[1])?h[1]:parseInt(h[1]);if(e.quantifier={min:i,max:j},d.length>0){var l=d[d.length-1].matches;a=l.pop(),a.isGroup||(k=new g((!0)),k.matches.push(a),a=k),l.push(a),l.push(e)}else a=c.matches.pop(),a.isGroup||(k=new g((!0)),k.matches.push(a),a=k),c.matches.push(a),c.matches.push(e);break;default:d.length>0?d[d.length-1].matches.push(b):c.matches.push(b)}c.matches.length>0&&f.regexTokens.push(c)}function i(b,c){var d=!1;c&&(m+="(",o++);for(var e=0;e<b.matches.length;e++){var f=b.matches[e];if(f.isGroup===!0)d=i(f,!0);else if(f.isQuantifier===!0){var g=a.inArray(f,b.matches),h=b.matches[g-1],k=m;if(isNaN(f.quantifier.max)){for(;f.repeaterPart&&f.repeaterPart!==m&&f.repeaterPart.length>m.length&&!(d=i(h,!0)););d=d||i(h,!0),d&&(f.repeaterPart=m),m=k+f.quantifier.max}else{for(var l=0,n=f.quantifier.max-1;l<n&&!(d=i(h,!0));l++);m=k+"{"+f.quantifier.min+","+f.quantifier.max+"}"}}else if(void 0!==f.matches)for(var p=0;p<f.length&&!(d=i(f[p],c));p++);else{var q;if("["==f.charAt(0)){q=m,q+=f;for(var r=0;r<o;r++)q+=")";var s=new RegExp("^("+q+")$");d=s.test(j)}else for(var t=0,u=f.length;t<u;t++)if("\\"!==f.charAt(t)){q=m,q+=f.substr(0,t+1),q=q.replace(/\|$/,"");for(var r=0;r<o;r++)q+=")";var s=new RegExp("^("+q+")$");if(d=s.test(j))break}m+=f}if(d)break}return c&&(m+=")",o--),d}var j,k,l=c.buffer.slice(),m="",n=!1,o=0;null===f.regexTokens&&h(),l.splice(d,0,b),j=l.join("");for(var p=0;p<f.regexTokens.length;p++){var q=f.regexTokens[p];if(n=i(q,q.isGroup))break}return n},cardinality:1}}}}),b}(jQuery,Inputmask); 12086window.tinymce = window.tinymce || { 12087 base: '/assets/tinymce', 12088 suffix: '' 12089}; 12090// 4.3.3 (2016-01-14) 12091 12092/** 12093 * Compiled inline version. (Library mode) 12094 */ 12095 12096/*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */ 12097/*globals $code */ 12098 12099(function(exports, undefined) { 12100 "use strict"; 12101 12102 var modules = {}; 12103 12104 function require(ids, callback) { 12105 var module, defs = []; 12106 12107 for (var i = 0; i < ids.length; ++i) { 12108 module = modules[ids[i]] || resolve(ids[i]); 12109 if (!module) { 12110 throw 'module definition dependecy not found: ' + ids[i]; 12111 } 12112 12113 defs.push(module); 12114 } 12115 12116 callback.apply(null, defs); 12117 } 12118 12119 function define(id, dependencies, definition) { 12120 if (typeof id !== 'string') {
12121 throw 'invalid module definition, module id must be defined and be a string'; 12122 } 12123 12124 if (dependencies === undefined) { 12125 throw 'invalid module definition, dependencies must be specified'; 12126 } 12127 12128 if (definition === undefined) { 12129 throw 'invalid module definition, definition function must be specified'; 12130 } 12131 12132 require(dependencies, function() { 12133 modules[id] = definition.apply(null, arguments); 12134 }); 12135 } 12136 12137 function defined(id) { 12138 return !!modules[id]; 12139 } 12140 12141 function resolve(id) { 12142 var target = exports; 12143 var fragments = id.split(/[.\/]/); 12144 12145 for (var fi = 0; fi < fragments.length; ++fi) { 12146 if (!target[fragments[fi]]) { 12147 return; 12148 } 12149 12150 target = target[fragments[fi]]; 12151 } 12152 12153 return target; 12154 } 12155 12156 function expose(ids) { 12157 var i, target, id, fragments, privateModules; 12158 12159 for (i = 0; i < ids.length; i++) { 12160 target = exports; 12161 id = ids[i]; 12162 fragments = id.split(/[.\/]/); 12163 12164 for (var fi = 0; fi < fragments.length - 1; ++fi) { 12165 if (target[fragments[fi]] === undefined) { 12166 target[fragments[fi]] = {}; 12167 } 12168 12169 target = target[fragments[fi]]; 12170 } 12171 12172 target[fragments[fragments.length - 1]] = modules[id]; 12173 } 12174 12175 // Expose private modules for unit tests 12176 if (exports.AMDLC_TESTS) { 12177 privateModules = exports.privateModules || {}; 12178 12179 for (id in modules) { 12180 privateModules[id] = modules[id]; 12181 } 12182 12183 for (i = 0; i < ids.length; i++) { 12184 delete privateModules[ids[i]]; 12185 } 12186 12187 exports.privateModules = privateModules; 12188 } 12189 } 12190 12191// Included from: js/tinymce/classes/geom/Rect.js 12192 12193/** 12194 * Rect.js 12195 * 12196 * Released under LGPL License. 12197 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 12198 * 12199 * License: http://www.tinymce.com/license 12200 * Contributing: http://www.tinymce.com/contributing 12201 */ 12202 12203/** 12204 * Contains various tools for rect/position calculation. 12205 * 12206 * @class tinymce.geom.Rect 12207 */ 12208define("tinymce/geom/Rect", [ 12209], function() { 12210 "use strict"; 12211 12212 var min = Math.min, max = Math.max, round = Math.round; 12213 12214 /** 12215 * Returns the rect positioned based on the relative position name 12216 * to the target rect. 12217 * 12218 * @method relativePosition 12219 * @param {Rect} rect Source rect to modify into a new rect. 12220 * @param {Rect} targetRect Rect to move relative to based on the rel option. 12221 * @param {String} rel Relative position. For example: tr-bl. 12222 */ 12223 function relativePosition(rect, targetRect, rel) { 12224 var x, y, w, h, targetW, targetH; 12225 12226 x = targetRect.x; 12227 y = targetRect.y; 12228 w = rect.w; 12229 h = rect.h; 12230 targetW = targetRect.w; 12231 targetH = targetRect.h; 12232 12233 rel = (rel || '').split(''); 12234 12235 if (rel[0] === 'b') { 12236 y += targetH; 12237 } 12238 12239 if (rel[1] === 'r') { 12240 x += targetW; 12241 } 12242 12243 if (rel[0] === 'c') { 12244 y += round(targetH / 2); 12245 } 12246 12247 if (rel[1] === 'c') { 12248 x += round(targetW / 2); 12249 } 12250 12251 if (rel[3] === 'b') { 12252 y -= h; 12253 } 12254 12255 if (rel[4] === 'r') { 12256 x -= w; 12257 } 12258 12259 if (rel[3] === 'c') { 12260 y -= round(h / 2); 12261 } 12262 12263 if (rel[4] === 'c') { 12264 x -= round(w / 2); 12265 } 12266 12267 return create(x, y, w, h); 12268 } 12269 12270 /** 12271 * Tests various positions to get the most suitable one. 12272 * 12273 * @method findBestRelativePosition 12274 * @param {Rect} rect Rect to use as source. 12275 * @param {Rect} targetRect Rect to move relative to. 12276 * @param {Rect} constrainRect Rect to constrain within. 12277 * @param {Array} rels Array of relative positions to test against. 12278 */ 12279 function findBestRelativePosition(rect, targetRect, constrainRect, rels) { 12280 var pos, i; 12281 12282 for (i = 0; i < rels.length; i++) { 12283 pos = relativePosition(rect, targetRect, rels[i]); 12284 12285 if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x && 12286 pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) { 12287 return rels[i]; 12288 } 12289 } 12290 12291 return null; 12292 } 12293 12294 /** 12295 * Inflates the rect in all directions. 12296 * 12297 * @method inflate 12298 * @param {Rect} rect Rect to expand. 12299 * @param {Number} w Relative width to expand by. 12300 * @param {Number} h Relative height to expand by. 12301 * @return {Rect} New expanded rect. 12302 */ 12303 function inflate(rect, w, h) { 12304 return create(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2); 12305 } 12306 12307 /**
12308 * Returns the intersection of the specified rectangles. 12309 * 12310 * @method intersect 12311 * @param {Rect} rect The first rectangle to compare. 12312 * @param {Rect} cropRect The second rectangle to compare. 12313 * @return {Rect} The intersection of the two rectangles or null if they don't intersect. 12314 */ 12315 function intersect(rect, cropRect) { 12316 var x1, y1, x2, y2; 12317 12318 x1 = max(rect.x, cropRect.x); 12319 y1 = max(rect.y, cropRect.y); 12320 x2 = min(rect.x + rect.w, cropRect.x + cropRect.w); 12321 y2 = min(rect.y + rect.h, cropRect.y + cropRect.h); 12322 12323 if (x2 - x1 < 0 || y2 - y1 < 0) { 12324 return null; 12325 } 12326 12327 return create(x1, y1, x2 - x1, y2 - y1); 12328 } 12329 12330 /** 12331 * Returns a rect clamped within the specified clamp rect. This forces the 12332 * rect to be inside the clamp rect. 12333 * 12334 * @method clamp 12335 * @param {Rect} rect Rectangle to force within clamp rect. 12336 * @param {Rect} clampRect Rectable to force within. 12337 * @param {Boolean} fixedSize True/false if size should be fixed. 12338 * @return {Rect} Clamped rect. 12339 */ 12340 function clamp(rect, clampRect, fixedSize) { 12341 var underflowX1, underflowY1, overflowX2, overflowY2, 12342 x1, y1, x2, y2, cx2, cy2; 12343 12344 x1 = rect.x; 12345 y1 = rect.y; 12346 x2 = rect.x + rect.w; 12347 y2 = rect.y + rect.h; 12348 cx2 = clampRect.x + clampRect.w; 12349 cy2 = clampRect.y + clampRect.h; 12350 12351 underflowX1 = max(0, clampRect.x - x1); 12352 underflowY1 = max(0, clampRect.y - y1); 12353 overflowX2 = max(0, x2 - cx2); 12354 overflowY2 = max(0, y2 - cy2); 12355 12356 x1 += underflowX1; 12357 y1 += underflowY1; 12358 12359 if (fixedSize) { 12360 x2 += underflowX1; 12361 y2 += underflowY1; 12362 x1 -= overflowX2; 12363 y1 -= overflowY2; 12364 } 12365 12366 x2 -= overflowX2; 12367 y2 -= overflowY2; 12368 12369 return create(x1, y1, x2 - x1, y2 - y1); 12370 } 12371 12372 /** 12373 * Creates a new rectangle object. 12374 * 12375 * @method create 12376 * @param {Number} x Rectangle x location. 12377 * @param {Number} y Rectangle y location. 12378 * @param {Number} w Rectangle width. 12379 * @param {Number} h Rectangle height. 12380 * @return {Rect} New rectangle object. 12381 */ 12382 function create(x, y, w, h) { 12383 return {x: x, y: y, w: w, h: h}; 12384 } 12385 12386 /** 12387 * Creates a new rectangle object form a clientRects object. 12388 * 12389 * @method fromClientRect 12390 * @param {ClientRect} clientRect DOM ClientRect object. 12391 * @return {Rect} New rectangle object. 12392 */ 12393 function fromClientRect(clientRect) { 12394 return create(clientRect.left, clientRect.top, clientRect.width, clientRect.height); 12395 } 12396 12397 return { 12398 inflate: inflate, 12399 relativePosition: relativePosition, 12400 findBestRelativePosition: findBestRelativePosition, 12401 intersect: intersect, 12402 clamp: clamp, 12403 create: create, 12404 fromClientRect: fromClientRect 12405 }; 12406}); 12407 12408// Included from: js/tinymce/classes/util/Promise.js 12409 12410/** 12411 * Promise.js 12412 * 12413 * Released under LGPL License. 12414 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 12415 * 12416 * Promise polyfill under MIT license: https://github.com/taylorhakes/promise-polyfill 12417 * 12418 * License: http://www.tinymce.com/license 12419 * Contributing: http://www.tinymce.com/contributing 12420 */ 12421 12422/* eslint-disable */ 12423/* jshint ignore:start */ 12424 12425/** 12426 * Modifed to be a feature fill and wrapped as tinymce module. 12427 */ 12428define("tinymce/util/Promise", [], function() { 12429 if (window.Promise) { 12430 return window.Promise; 12431 } 12432 12433 // Use polyfill for setImmediate for performance gains 12434 var asap = Promise.immediateFn || (typeof setImmediate === 'function' && setImmediate) || 12435 function(fn) { setTimeout(fn, 1); }; 12436 12437 // Polyfill for Function.prototype.bind 12438 function bind(fn, thisArg) { 12439 return function() { 12440 fn.apply(thisArg, arguments); 12441 }; 12442 } 12443 12444 var isArray = Array.isArray || function(value) { return Object.prototype.toString.call(value) === "[object Array]"; }; 12445 12446 function Promise(fn) { 12447 if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); 12448 if (typeof fn !== 'function') throw new TypeError('not a function'); 12449 this._state = null; 12450 this._value = null; 12451 this._deferreds = []; 12452 12453 doResolve(fn, bind(resolve, this), bind(reject, this)); 12454 } 12455 12456 function handle(deferred) { 12457 var me = this; 12458 if (this._state === null) { 12459 this._deferreds.push(deferred); 12460 return; 12461 } 12462 asap(function() { 12463 var cb = me._state ? deferred.onFulfilled : deferred.onRejected; 12464 if (cb === null) { 12465 (me._state ? deferred.resolve : deferred.reject)(me._value); 12466 return; 12467 } 12468 var ret; 12469 try { 12470 ret = cb(me._value); 12471 } 12472 catch (e) { 12473 deferred.reject(e); 12474 return; 12475 } 12476 deferred.resolve(ret); 12477 }); 12478 } 12479 12480 function resolve(newValue) { 12481 try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure 12482 if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.'); 12483 if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { 12484 var then = newValue.then; 12485 if (typeof then === 'function') { 12486 doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this)); 12487 return; 12488 } 12489 } 12490 this._state = true; 12491 this._value = newValue; 12492 finale.call(this); 12493 } catch (e) { reject.call(this, e); } 12494 } 12495 12496 function reject(newValue) { 12497 this._state = false;
12498 this._value = newValue; 12499 finale.call(this); 12500 } 12501 12502 function finale() { 12503 for (var i = 0, len = this._deferreds.length; i < len; i++) { 12504 handle.call(this, this._deferreds[i]); 12505 } 12506 this._deferreds = null; 12507 } 12508 12509 function Handler(onFulfilled, onRejected, resolve, reject){ 12510 this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; 12511 this.onRejected = typeof onRejected === 'function' ? onRejected : null; 12512 this.resolve = resolve; 12513 this.reject = reject; 12514 } 12515 12516 /** 12517 * Take a potentially misbehaving resolver function and make sure 12518 * onFulfilled and onRejected are only called once. 12519 * 12520 * Makes no guarantees about asynchrony. 12521 */ 12522 function doResolve(fn, onFulfilled, onRejected) { 12523 var done = false; 12524 try { 12525 fn(function (value) { 12526 if (done) return; 12527 done = true; 12528 onFulfilled(value); 12529 }, function (reason) { 12530 if (done) return; 12531 done = true; 12532 onRejected(reason); 12533 }); 12534 } catch (ex) { 12535 if (done) return; 12536 done = true; 12537 onRejected(ex); 12538 } 12539 } 12540 12541 Promise.prototype['catch'] = function (onRejected) { 12542 return this.then(null, onRejected); 12543 }; 12544 12545 Promise.prototype.then = function(onFulfilled, onRejected) { 12546 var me = this; 12547 return new Promise(function(resolve, reject) { 12548 handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject)); 12549 }); 12550 }; 12551 12552 Promise.all = function () { 12553 var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments); 12554 12555 return new Promise(function (resolve, reject) { 12556 if (args.length === 0) return resolve([]); 12557 var remaining = args.length; 12558 function res(i, val) { 12559 try { 12560 if (val && (typeof val === 'object' || typeof val === 'function')) { 12561 var then = val.then; 12562 if (typeof then === 'function') { 12563 then.call(val, function (val) { res(i, val); }, reject); 12564 return; 12565 } 12566 } 12567 args[i] = val; 12568 if (--remaining === 0) { 12569 resolve(args); 12570 } 12571 } catch (ex) { 12572 reject(ex); 12573 } 12574 } 12575 for (var i = 0; i < args.length; i++) { 12576 res(i, args[i]); 12577 } 12578 }); 12579 }; 12580 12581 Promise.resolve = function (value) { 12582 if (value && typeof value === 'object' && value.constructor === Promise) { 12583 return value; 12584 } 12585 12586 return new Promise(function (resolve) { 12587 resolve(value); 12588 }); 12589 }; 12590 12591 Promise.reject = function (value) { 12592 return new Promise(function (resolve, reject) { 12593 reject(value); 12594 }); 12595 }; 12596 12597 Promise.race = function (values) { 12598 return new Promise(function (resolve, reject) { 12599 for(var i = 0, len = values.length; i < len; i++) { 12600 values[i].then(resolve, reject); 12601 } 12602 }); 12603 }; 12604 12605 return Promise; 12606}); 12607 12608/* jshint ignore:end */ 12609/* eslint-enable */ 12610 12611// Included from: js/tinymce/classes/util/Delay.js 12612 12613/** 12614 * Delay.js 12615 * 12616 * Released under LGPL License. 12617 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 12618 * 12619 * License: http://www.tinymce.com/license 12620 * Contributing: http://www.tinymce.com/contributing 12621 */ 12622 12623/** 12624 * Utility class for working with delayed actions like setTimeout. 12625 * 12626 * @class tinymce.util.Delay 12627 */ 12628define("tinymce/util/Delay", [ 12629 "tinymce/util/Promise" 12630], function(Promise) { 12631 var requestAnimationFramePromise; 12632 12633 function requestAnimationFrame(callback, element) { 12634 var i, requestAnimationFrameFunc = window.requestAnimationFrame, vendors = ['ms', 'moz', 'webkit']; 12635 12636 function featurefill(callback) { 12637 window.setTimeout(callback, 0); 12638 } 12639 12640 for (i = 0; i < vendors.length && !requestAnimationFrameFunc; i++) { 12641 requestAnimationFrameFunc = window[vendors[i] + 'RequestAnimationFrame']; 12642 } 12643 12644 if (!requestAnimationFrameFunc) { 12645 requestAnimationFrameFunc = featurefill; 12646 } 12647 12648 requestAnimationFrameFunc(callback, element); 12649 } 12650 12651 function wrappedSetTimeout(callback, time) { 12652 if (typeof time != 'number') { 12653 time = 0; 12654 } 12655 12656 return setTimeout(callback, time); 12657 } 12658 12659 function wrappedSetInterval(callback, time) { 12660 if (typeof time != 'number') { 12661 time = 0; 12662 } 12663 12664 return setInterval(callback, time); 12665 } 12666 12667 function wrappedClearTimeout(id) { 12668 return clearTimeout(id); 12669 } 12670 12671 function wrappedClearInterval(id) { 12672 return clearInterval(id); 12673 } 12674 12675 return { 12676 /** 12677 * Requests an animation frame and fallbacks to a timeout on older browsers. 12678 * 12679 * @method requestAnimationFrame 12680 * @param {function} callback Callback to execute when a new frame is available. 12681 * @param {DOMElement} element Optional element to scope it to. 12682 */ 12683 requestAnimationFrame: function(callback, element) { 12684 if (requestAnimationFramePromise) { 12685 requestAnimationFramePromise.then(callback); 12686 return; 12687 } 12688 12689 requestAnimationFramePromise = new Promise(function(resolve) { 12690 if (!element) { 12691 element = document.body; 12692 } 12693 12694 requestAnimationFrame(resolve, element); 12695 }).then(callback); 12696 }, 12697 12698 /** 12699 * Sets a timer in ms and executes the specified callback when the timer runs out. 12700 * 12701 * @method setTimeout 12702 * @param {function} callback Callback to execute when timer runs out. 12703 * @param {Number} time Optional time to wait before the callback is executed, defaults to 0. 12704 * @return {Number} Timeout id number. 12705 */ 12706 setTimeout: wrappedSetTimeout, 12707 12708 /** 12709 * Sets an interval timer in ms and executes the specified callback at every interval of that time. 12710 * 12711 * @method setInterval 12712 * @param {function} callback Callback to execute when interval time runs out. 12713 * @param {Number} time Optional time to wait before the callback is executed, defaults to 0. 12714 * @return {Number} Timeout id number. 12715 */ 12716 setInterval: wrappedSetInterval, 12717 12718 /** 12719 * Sets an editor timeout it's similar to setTimeout except that it checks if the editor instance is 12720 * still alive when the callback gets executed. 12721 * 12722 * @method setEditorTimeout 12723 * @param {tinymce.Editor} editor Editor instance to check the removed state on. 12724 * @param {function} callback Callback to execute when timer runs out. 12725 * @param {Number} time Optional time to wait before the callback is executed, defaults to 0. 12726 * @return {Number} Timeout id number. 12727 */ 12728 setEditorTimeout: function(editor, callback, time) { 12729 return wrappedSetTimeout(function() { 12730 if (!editor.removed) { 12731 callback(); 12732 } 12733 }, time); 12734 }, 12735 12736 /** 12737 * Sets an interval timer it's similar to setInterval except that it checks if the editor instance is 12738 * still alive when the callback gets executed. 12739 * 12740 * @method setEditorInterval 12741 * @param {function} callback Callback to execute when interval time runs out. 12742 * @param {Number} time Optional time to wait before the callback is executed, defaults to 0. 12743 * @return {Number} Timeout id number. 12744 */ 12745 setEditorInterval: function(editor, callback, time) { 12746 var timer; 12747 12748 timer = wrappedSetInterval(function() { 12749 if (!editor.removed) { 12750 callback(); 12751 } else { 12752 clearInterval(timer); 12753 } 12754 }, time); 12755 12756 return timer; 12757 }, 12758 12759 /** 12760 * Creates throttled callback function that only gets executed once within the specified time. 12761 * 12762 * @method throttle 12763 * @param {function} callback Callback to execute when timer finishes. 12764 * @param {Number} time Optional time to wait before the callback is executed, defaults to 0. 12765 * @return {Function} Throttled function callback. 12766 */ 12767 throttle: function(callback, time) { 12768 var timer; 12769 12770 return function() { 12771 var args = arguments; 12772 12773 clearTimeout(timer); 12774 12775 timer = wrappedSetTimeout(function() { 12776 callback.apply(this, args); 12777 }, time); 12778 }; 12779 }, 12780 12781 /** 12782 * Clears an interval timer so it won't execute. 12783 * 12784 * @method clearInterval 12785 * @param {Number} Interval timer id number. 12786 */ 12787 clearInterval: wrappedClearInterval, 12788 12789 /** 12790 * Clears an timeout timer so it won't execute. 12791 * 12792 * @method clearTimeout 12793 * @param {Number} Timeout timer id number. 12794 */ 12795 clearTimeout: wrappedClearTimeout 12796 }; 12797}); 12798 12799// Included from: js/tinymce/classes/dom/EventUtils.js 12800 12801/** 12802 * EventUtils.js 12803 * 12804 * Released under LGPL License. 12805 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 12806 * 12807 * License: http://www.tinymce.com/license 12808 * Contributing: http://www.tinymce.com/contributing 12809 */ 12810 12811/*jshint loopfunc:true*/ 12812/*eslint no-loop-func:0 */ 12813 12814/** 12815 * This class wraps the browsers native event logic with more convenient methods. 12816 * 12817 * @class tinymce.dom.EventUtils 12818 */ 12819define("tinymce/dom/EventUtils", [ 12820 "tinymce/util/Delay" 12821], function(Delay) { 12822 "use strict"; 12823 12824 var eventExpandoPrefix = "mce-data-"; 12825 var mouseEventRe = /^(?:mouse|contextmenu)|click/; 12826 var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1, webkitMovementX: 1, webkitMovementY: 1}; 12827 12828 /** 12829 * Binds a native event to a callback on the speified target. 12830 */ 12831 function addEvent(target, name, callback, capture) { 12832 if (target.addEventListener) { 12833 target.addEventListener(name, callback, capture || false); 12834 } else if (target.attachEvent) { 12835 target.attachEvent('on' + name, callback); 12836 } 12837 } 12838
12839 /** 12840 * Unbinds a native event callback on the specified target. 12841 */ 12842 function removeEvent(target, name, callback, capture) { 12843 if (target.removeEventListener) { 12844 target.removeEventListener(name, callback, capture || false); 12845 } else if (target.detachEvent) { 12846 target.detachEvent('on' + name, callback); 12847 } 12848 } 12849 12850 /** 12851 * Normalizes a native event object or just adds the event specific methods on a custom event. 12852 */ 12853 function fix(originalEvent, data) { 12854 var name, event = data || {}, undef; 12855 12856 // Dummy function that gets replaced on the delegation state functions 12857 function returnFalse() { 12858 return false; 12859 } 12860 12861 // Dummy function that gets replaced on the delegation state functions 12862 function returnTrue() { 12863 return true; 12864 } 12865 12866 // Copy all properties from the original event 12867 for (name in originalEvent) { 12868 // layerX/layerY is deprecated in Chrome and produces a warning 12869 if (!deprecated[name]) { 12870 event[name] = originalEvent[name]; 12871 } 12872 } 12873 12874 // Normalize target IE uses srcElement 12875 if (!event.target) { 12876 event.target = event.srcElement || document; 12877 } 12878 12879 // Calculate pageX/Y if missing and clientX/Y available 12880 if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) { 12881 var eventDoc = event.target.ownerDocument || document; 12882 var doc = eventDoc.documentElement; 12883 var body = eventDoc.body; 12884 12885 event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - 12886 (doc && doc.clientLeft || body && body.clientLeft || 0); 12887 12888 event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - 12889 (doc && doc.clientTop || body && body.clientTop || 0); 12890 } 12891 12892 // Add preventDefault method 12893 event.preventDefault = function() { 12894 event.isDefaultPrevented = returnTrue; 12895 12896 // Execute preventDefault on the original event object 12897 if (originalEvent) { 12898 if (originalEvent.preventDefault) { 12899 originalEvent.preventDefault(); 12900 } else { 12901 originalEvent.returnValue = false; // IE 12902 } 12903 } 12904 }; 12905 12906 // Add stopPropagation 12907 event.stopPropagation = function() { 12908 event.isPropagationStopped = returnTrue; 12909 12910 // Execute stopPropagation on the original event object 12911 if (originalEvent) { 12912 if (originalEvent.stopPropagation) { 12913 originalEvent.stopPropagation(); 12914 } else { 12915 originalEvent.cancelBubble = true; // IE 12916 } 12917 } 12918 }; 12919 12920 // Add stopImmediatePropagation 12921 event.stopImmediatePropagation = function() { 12922 event.isImmediatePropagationStopped = returnTrue; 12923 event.stopPropagation(); 12924 }; 12925 12926 // Add event delegation states 12927 if (!event.isDefaultPrevented) { 12928 event.isDefaultPrevented = returnFalse; 12929 event.isPropagationStopped = returnFalse; 12930 event.isImmediatePropagationStopped = returnFalse; 12931 } 12932 12933 // Add missing metaKey for IE 8 12934 if (typeof event.metaKey == 'undefined') { 12935 event.metaKey = false; 12936 } 12937 12938 return event; 12939 } 12940 12941 /** 12942 * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized. 12943 * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times. 12944 */ 12945 function bindOnReady(win, callback, eventUtils) { 12946 var doc = win.document, event = {type: 'ready'}; 12947 12948 if (eventUtils.domLoaded) { 12949 callback(event); 12950 return; 12951 } 12952 12953 // Gets called when the DOM is ready 12954 function readyHandler() { 12955 if (!eventUtils.domLoaded) { 12956 eventUtils.domLoaded = true; 12957 callback(event); 12958 } 12959 } 12960 12961 function waitForDomLoaded() { 12962 // Check complete or interactive state if there is a body 12963 // element on some iframes IE 8 will produce a null body 12964 if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) { 12965 removeEvent(doc, "readystatechange", waitForDomLoaded); 12966 readyHandler(); 12967 } 12968 } 12969 12970 function tryScroll() { 12971 try { 12972 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. 12973 // http://javascript.nwbox.com/IEContentLoaded/ 12974 doc.documentElement.doScroll("left"); 12975 } catch (ex) { 12976 Delay.setTimeout(tryScroll); 12977 return; 12978 } 12979 12980 readyHandler(); 12981 } 12982 12983 // Use W3C method 12984 if (doc.addEventListener) { 12985 if (doc.readyState === "complete") { 12986 readyHandler(); 12987 } else { 12988 addEvent(win, 'DOMContentLoaded', readyHandler); 12989 } 12990 } else { 12991 // Use IE method 12992 addEvent(doc, "readystatechange", waitForDomLoaded); 12993 12994 // Wait until we can scroll, when we can the DOM is initialized 12995 if (doc.documentElement.doScroll && win.self === win.top) { 12996 tryScroll(); 12997 } 12998 } 12999 13000 // Fallback if any of the above methods should fail for some odd reason 13001 addEvent(win, 'load', readyHandler); 13002 } 13003 13004 /** 13005 * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers. 13006 */ 13007 function EventUtils() { 13008 var self = this, events = {}
13008, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave; 13009 13010 expando = eventExpandoPrefix + (+new Date()).toString(32); 13011 hasMouseEnterLeave = "onmouseenter" in document.documentElement; 13012 hasFocusIn = "onfocusin" in document.documentElement; 13013 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'}; 13014 count = 1; 13015 13016 // State if the DOMContentLoaded was executed or not 13017 self.domLoaded = false; 13018 self.events = events; 13019 13020 /** 13021 * Executes all event handler callbacks for a specific event. 13022 * 13023 * @private 13024 * @param {Event} evt Event object. 13025 * @param {String} id Expando id value to look for. 13026 */ 13027 function executeHandlers(evt, id) { 13028 var callbackList, i, l, callback, container = events[id]; 13029 13030 callbackList = container && container[evt.type]; 13031 if (callbackList) { 13032 for (i = 0, l = callbackList.length; i < l; i++) { 13033 callback = callbackList[i]; 13034 13035 // Check if callback exists might be removed if a unbind is called inside the callback 13036 if (callback && callback.func.call(callback.scope, evt) === false) { 13037 evt.preventDefault(); 13038 } 13039 13040 // Should we stop propagation to immediate listeners 13041 if (evt.isImmediatePropagationStopped()) { 13042 return; 13043 } 13044 } 13045 } 13046 } 13047 13048 /** 13049 * Binds a callback to an event on the specified target. 13050 * 13051 * @method bind 13052 * @param {Object} target Target node/window or custom object. 13053 * @param {String} names Name of the event to bind. 13054 * @param {function} callback Callback function to execute when the event occurs. 13055 * @param {Object} scope Scope to call the callback function on, defaults to target. 13056 * @return {function} Callback function that got bound. 13057 */ 13058 self.bind = function(target, names, callback, scope) { 13059 var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window; 13060 13061 // Native event handler function patches the event and executes the callbacks for the expando 13062 function defaultNativeHandler(evt) { 13063 executeHandlers(fix(evt || win.event), id); 13064 } 13065 13066 // Don't bind to text nodes or comments 13067 if (!target || target.nodeType === 3 || target.nodeType === 8) { 13068 return; 13069 } 13070 13071 // Create or get events id for the target 13072 if (!target[expando]) { 13073 id = count++; 13074 target[expando] = id; 13075 events[id] = {}; 13076 } else { 13077 id = target[expando]; 13078 } 13079 13080 // Setup the specified scope or use the target as a default 13081 scope = scope || target; 13082 13083 // Split names and bind each event, enables you to bind multiple events with one call 13084 names = names.split(' '); 13085 i = names.length; 13086 while (i--) { 13087 name = names[i]; 13088 nativeHandler = defaultNativeHandler; 13089 fakeName = capture = false; 13090 13091 // Use ready instead of DOMContentLoaded 13092 if (name === "DOMContentLoaded") { 13093 name = "ready"; 13094 } 13095 13096 // DOM is already ready 13097 if (self.domLoaded && name === "ready" && target.readyState == 'complete') { 13098 callback.call(scope, fix({type: name})); 13099 continue; 13100 } 13101 13102 // Handle mouseenter/mouseleaver 13103 if (!hasMouseEnterLeave) { 13104 fakeName = mouseEnterLeave[name]; 13105 13106 if (fakeName) { 13107 nativeHandler = function(evt) { 13108 var current, related; 13109 13110 current = evt.currentTarget; 13111 related = evt.relatedTarget; 13112 13113 // Check if related is inside the current target if it's not then the event should 13114 // be ignored since it's a mouseover/mouseout inside the element 13115 if (related && current.contains) { 13116 // Use contains for performance 13117 related = current.contains(related); 13118 } else { 13119 while (related && related !== current) { 13120 related = related.parentNode; 13121 } 13122 } 13123 13124 // Fire fake event 13125 if (!related) { 13126 evt = fix(evt || win.event); 13127 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter'; 13128 evt.target = current; 13129 executeHandlers(evt, id); 13130 } 13131 }; 13132 } 13133 } 13134 13135 // Fake bubbling of focusin/focusout 13136 if (!hasFocusIn && (name === "focusin" || name === "focusout")) { 13137 capture = true; 13138 fakeName = name === "focusin" ? "focus" : "blur"; 13139 nativeHandler = function(evt) { 13140 evt = fix(evt || win.event);
13141 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout'; 13142 executeHandlers(evt, id); 13143 }; 13144 } 13145 13146 // Setup callback list and bind native event 13147 callbackList = events[id][name]; 13148 if (!callbackList) { 13149 events[id][name] = callbackList = [{func: callback, scope: scope}]; 13150 callbackList.fakeName = fakeName; 13151 callbackList.capture = capture; 13152 //callbackList.callback = callback; 13153 13154 // Add the nativeHandler to the callback list so that we can later unbind it 13155 callbackList.nativeHandler = nativeHandler; 13156 13157 // Check if the target has native events support 13158 13159 if (name === "ready") { 13160 bindOnReady(target, nativeHandler, self); 13161 } else { 13162 addEvent(target, fakeName || name, nativeHandler, capture); 13163 } 13164 } else { 13165 if (name === "ready" && self.domLoaded) { 13166 callback({type: name}); 13167 } else { 13168 // If it already has an native handler then just push the callback 13169 callbackList.push({func: callback, scope: scope}); 13170 } 13171 } 13172 } 13173 13174 target = callbackList = 0; // Clean memory for IE 13175 13176 return callback; 13177 }; 13178 13179 /** 13180 * Unbinds the specified event by name, name and callback or all events on the target. 13181 * 13182 * @method unbind 13183 * @param {Object} target Target node/window or custom object. 13184 * @param {String} names Optional event name to unbind. 13185 * @param {function} callback Optional callback function to unbind. 13186 * @return {EventUtils} Event utils instance. 13187 */ 13188 self.unbind = function(target, names, callback) { 13189 var id, callbackList, i, ci, name, eventMap; 13190 13191 // Don't bind to text nodes or comments 13192 if (!target || target.nodeType === 3 || target.nodeType === 8) { 13193 return self; 13194 } 13195 13196 // Unbind event or events if the target has the expando 13197 id = target[expando]; 13198 if (id) { 13199 eventMap = events[id]; 13200 13201 // Specific callback 13202 if (names) { 13203 names = names.split(' '); 13204 i = names.length; 13205 while (i--) { 13206 name = names[i]; 13207 callbackList = eventMap[name]; 13208 13209 // Unbind the event if it exists in the map 13210 if (callbackList) { 13211 // Remove specified callback 13212 if (callback) { 13213 ci = callbackList.length; 13214 while (ci--) { 13215 if (callbackList[ci].func === callback) { 13216 var nativeHandler = callbackList.nativeHandler; 13217 var fakeName = callbackList.fakeName, capture = callbackList.capture; 13218 13219 // Clone callbackList since unbind inside a callback would otherwise break the handlers loop 13220 callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1)); 13221 callbackList.nativeHandler = nativeHandler; 13222 callbackList.fakeName = fakeName; 13223 callbackList.capture = capture; 13224 13225 eventMap[name] = callbackList; 13226 } 13227 } 13228 } 13229 13230 // Remove all callbacks if there isn't a specified callback or there is no callbacks left 13231 if (!callback || callbackList.length === 0) { 13232 delete eventMap[name]; 13233 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 13234 } 13235 } 13236 } 13237 } else { 13238 // All events for a specific element 13239 for (name in eventMap) { 13240 callbackList = eventMap[name]; 13241 removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture); 13242 } 13243 13244 eventMap = {}; 13245 } 13246 13247 // Check if object is empty, if it isn't then we won't remove the expando map 13248 for (name in eventMap) { 13249 return self; 13250 } 13251 13252 // Delete event object 13253 delete events[id]; 13254 13255 // Remove expando from target 13256 try { 13257 // IE will fail here since it can't delete properties from window 13258 delete target[expando]; 13259 } catch (ex) { 13260 // IE will set it to null 13261 target[expando] = null; 13262 } 13263 } 13264 13265 return self; 13266 }; 13267 13268 /** 13269 * Fires the specified event on the specified target. 13270 * 13271 * @method fire 13272 * @param {Object} target Target node/window or custom object. 13273 * @param {String} name Event name to fire. 13274 * @param {Object} args Optional arguments to send to the observers. 13275 * @return {EventUtils} Event utils instance. 13276 */ 13277 self.fire = function(target, name, args) { 13278 var id; 13279 13280 // Don't bind to text nodes or comments 13281 if (!target || target.nodeType === 3 || target.nodeType === 8) { 13282 return self; 13283 } 13284 13285 // Build event object by patching the args
13286 args = fix(null, args); 13287 args.type = name; 13288 args.target = target; 13289 13290 do { 13291 // Found an expando that means there is listeners to execute 13292 id = target[expando]; 13293 if (id) { 13294 executeHandlers(args, id); 13295 } 13296 13297 // Walk up the DOM 13298 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow; 13299 } while (target && !args.isPropagationStopped()); 13300 13301 return self; 13302 }; 13303 13304 /** 13305 * Removes all bound event listeners for the specified target. This will also remove any bound 13306 * listeners to child nodes within that target. 13307 * 13308 * @method clean 13309 * @param {Object} target Target node/window object. 13310 * @return {EventUtils} Event utils instance. 13311 */ 13312 self.clean = function(target) { 13313 var i, children, unbind = self.unbind; 13314 13315 // Don't bind to text nodes or comments 13316 if (!target || target.nodeType === 3 || target.nodeType === 8) { 13317 return self; 13318 } 13319 13320 // Unbind any element on the specified target 13321 if (target[expando]) { 13322 unbind(target); 13323 } 13324 13325 // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children 13326 if (!target.getElementsByTagName) { 13327 target = target.document; 13328 } 13329 13330 // Remove events from each child element 13331 if (target && target.getElementsByTagName) { 13332 unbind(target); 13333 13334 children = target.getElementsByTagName('*'); 13335 i = children.length; 13336 while (i--) { 13337 target = children[i]; 13338 13339 if (target[expando]) { 13340 unbind(target); 13341 } 13342 } 13343 } 13344 13345 return self; 13346 }; 13347 13348 /** 13349 * Destroys the event object. Call this on IE to remove memory leaks. 13350 */ 13351 self.destroy = function() { 13352 events = {}; 13353 }; 13354 13355 // Legacy function for canceling events 13356 self.cancel = function(e) { 13357 if (e) { 13358 e.preventDefault(); 13359 e.stopImmediatePropagation(); 13360 } 13361 13362 return false; 13363 }; 13364 } 13365 13366 EventUtils.Event = new EventUtils(); 13367 EventUtils.Event.bind(window, 'ready', function() {}); 13368 13369 return EventUtils; 13370}); 13371 13372// Included from: js/tinymce/classes/dom/Sizzle.jQuery.js 13373 13374/** 13375 * Sizzle.jQuery.js 13376 * 13377 * Released under LGPL License. 13378 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 13379 * 13380 * License: http://www.tinymce.com/license 13381 * Contributing: http://www.tinymce.com/contributing 13382 */ 13383 13384/*global jQuery:true */ 13385 13386/* 13387 * Fake Sizzle using jQuery. 13388 */ 13389define("tinymce/dom/Sizzle", [], function() { 13390 // Detect if jQuery is loaded 13391 if (!window.jQuery) { 13392 throw new Error("Load jQuery first"); 13393 } 13394 13395 return jQuery.find; 13396}); 13397 13398// Included from: js/tinymce/classes/Env.js 13399 13400/** 13401 * Env.js 13402 * 13403 * Released under LGPL License. 13404 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 13405 * 13406 * License: http://www.tinymce.com/license 13407 * Contributing: http://www.tinymce.com/contributing 13408 */ 13409 13410/** 13411 * This class contains various environment constants like browser versions etc. 13412 * Normally you don't want to sniff specific browser versions but sometimes you have 13413 * to when it's impossible to feature detect. So use this with care. 13414 * 13415 * @class tinymce.Env 13416 * @static 13417 */ 13418define("tinymce/Env", [], function() { 13419 var nav = navigator, userAgent = nav.userAgent; 13420 var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet; 13421 13422 function matchMediaQuery(query) {
13423 return "matchMedia" in window ? matchMedia(query).matches : false; 13424 } 13425 13426 opera = window.opera && window.opera.buildNumber; 13427 android = /Android/.test(userAgent); 13428 webkit = /WebKit/.test(userAgent); 13429 ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName); 13430 ie = ie && /MSIE (\w+)\./.exec(userAgent)[1]; 13431 ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false; 13432 ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false; 13433 ie = ie || ie11 || ie12; 13434 gecko = !webkit && !ie11 && /Gecko/.test(userAgent); 13435 mac = userAgent.indexOf('Mac') != -1; 13436 iDevice = /(iPad|iPhone)/.test(userAgent); 13437 fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL; 13438 phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice); 13439 tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice); 13440 13441 if (ie12) { 13442 webkit = false; 13443 } 13444 13445 // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions 13446 // says it has contentEditable support but there is no visible caret. 13447 var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534; 13448 13449 return { 13450 /** 13451 * Constant that is true if the browser is Opera. 13452 * 13453 * @property opera 13454 * @type Boolean 13455 * @final 13456 */ 13457 opera: opera, 13458 13459 /** 13460 * Constant that is true if the browser is WebKit (Safari/Chrome). 13461 * 13462 * @property webKit 13463 * @type Boolean 13464 * @final 13465 */ 13466 webkit: webkit, 13467 13468 /** 13469 * Constant that is more than zero if the browser is IE. 13470 * 13471 * @property ie 13472 * @type Boolean 13473 * @final 13474 */ 13475 ie: ie, 13476 13477 /** 13478 * Constant that is true if the browser is Gecko. 13479 * 13480 * @property gecko 13481 * @type Boolean 13482 * @final 13483 */ 13484 gecko: gecko, 13485 13486 /** 13487 * Constant that is true if the os is Mac OS. 13488 * 13489 * @property mac 13490 * @type Boolean 13491 * @final 13492 */ 13493 mac: mac, 13494 13495 /** 13496 * Constant that is true if the os is iOS. 13497 * 13498 * @property iOS 13499 * @type Boolean 13500 * @final 13501 */ 13502 iOS: iDevice, 13503 13504 /** 13505 * Constant that is true if the os is android. 13506 * 13507 * @property android 13508 * @type Boolean 13509 * @final 13510 */ 13511 android: android, 13512 13513 /** 13514 * Constant that is true if the browser supports editing. 13515 * 13516 * @property contentEditable 13517 * @type Boolean 13518 * @final 13519 */ 13520 contentEditable: contentEditable, 13521 13522 /** 13523 * Transparent image data url. 13524 * 13525 * @property transparentSrc 13526 * @type Boolean 13527 * @final 13528 */ 13529 transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", 13530 13531 /** 13532 * Returns true/false if the browser can or can't place the caret after a inline block like an image. 13533 * 13534 * @property noCaretAfter 13535 * @type Boolean 13536 * @final 13537 */ 13538 caretAfter: ie != 8, 13539 13540 /** 13541 * Constant that is true if the browser supports native DOM Ranges. IE 9+. 13542 * 13543 * @property range 13544 * @type Boolean 13545 */ 13546 range: window.getSelection && "Range" in window, 13547 13548 /** 13549 * Returns the IE document mode for non IE browsers this will fake IE 10. 13550 * 13551 * @property documentMode 13552 * @type Number 13553 */ 13554 documentMode: ie && !ie12 ? (document.documentMode || 7) : 10, 13555 13556 /** 13557 * Constant that is true if the browser has a modern file api. 13558 * 13559 * @property fileApi 13560 * @type Boolean 13561 */ 13562 fileApi: fileApi, 13563 13564 /** 13565 * Constant that is true if the browser supports contentEditable=false regions. 13566 * 13567 * @property ceFalse 13568 * @type Boolean 13569 */ 13570 ceFalse: (ie === false || ie > 8), 13571 13572 desktop: !phone && !tablet 13573 }; 13574}); 13575 13576// Included from: js/tinymce/classes/util/Arr.js 13577 13578/** 13579 * Arr.js 13580 * 13581 * Released under LGPL License. 13582 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 13583 * 13584 * License: http://www.tinymce.com/license 13585 * Contributing: http://www.tinymce.com/contributing 13586 */ 13587 13588/** 13589 * Array utility class. 13590 * 13591 * @private 13592 * @class tinymce.util.Arr 13593 */ 13594define("tinymce/util/Arr", [], function() { 13595 var isArray = Array.isArray || function(obj) { 13596 return Object.prototype.toString.call(obj) === "[object Array]"; 13597 }; 13598 13599 function toArray(obj) { 13600 var array = obj, i, l; 13601 13602 if (!isArray(obj)) { 13603 array = []; 13604 for (i = 0, l = obj.length; i < l; i++) { 13605 array[i] = obj[i]; 13606 } 13607 } 13608 13609 return array; 13610 } 13611 13612 function each(o, cb, s) { 13613 var n, l; 13614 13615 if (!o) { 13616 return 0; 13617 } 13618 13619 s = s || o; 13620 13621 if (o.length !== undefined) { 13622 // Indexed arrays, needed for Safari 13623 for (n = 0, l = o.length; n < l; n++) { 13624 if (cb.call(s, o[n], n, o) === false) { 13625 return 0; 13626 } 13627 } 13628 } else { 13629 // Hashtables 13630 for (n in o) { 13631 if (o.hasOwnProperty(n)) { 13632 if (cb.call(s, o[n], n, o) === false) { 13633 return 0; 13634 } 13635 } 13636 } 13637 } 13638 13639 return 1; 13640 } 13641 13642 function map(array, callback) { 13643 var out = []; 13644 13645 each(array, function(item, index) { 13646 out.push(callback(item, index, array)); 13647 }); 13648 13649 return out; 13650 } 13651 13652 function filter(a, f) { 13653 var o = []; 13654 13655 each(a, function(v, index) { 13656 if (!f || f(v, index, a)) { 13657 o.push(v); 13658 } 13659 }); 13660 13661 return o; 13662 } 13663 13664 function indexOf(a, v) { 13665 var i, l; 13666 13667 if (a) { 13668 for (i = 0, l = a.length; i < l; i++) { 13669 if (a[i] === v) { 13670 return i; 13671 } 13672 } 13673 } 13674 13675 return -1; 13676 } 13677 13678 function reduce(collection, iteratee, accumulator, thisArg) { 13679 var i = 0; 13680 13681 if (arguments.length < 3) { 13682 accumulator = collection[0]; 13683 } 13684 13685 for (; i < collection.length; i++) { 13686 accumulator = iteratee.call(thisArg, accumulator, collection[i], i); 13687 } 13688 13689 return accumulator; 13690 } 13691 13692 function findIndex(array, predicate, thisArg) { 13693 var i, l; 13694 13695 for (i = 0, l = array.length; i < l; i++) { 13696 if (predicate.call(thisArg, array[i], i, array)) { 13697 return i; 13698 } 13699 } 13700 13701 return -1; 13702 } 13703 13704 function find(array, predicate, thisArg) { 13705 var idx = findIndex(array, predicate, thisArg); 13706 13707 if (idx !== -1) { 13708 return array[idx]; 13709 } 13710 13711 return undefined; 13712 } 13713 13714 function last(collection) { 13715 return collection[collection.length - 1]; 13716 } 13717 13718 return { 13719 isArray: isArray, 13720 toArray: toArray, 13721 each: each, 13722 map: map, 13723 filter: filter, 13724 indexOf: indexOf, 13725 reduce: reduce, 13726 findIndex: findIndex, 13727 find: find, 13728 last: last 13729 }; 13730}); 13731 13732// Included from: js/tinymce/classes/util/Tools.js 13733 13734/** 13735 * Tools.js 13736 * 13737 * Released under LGPL License. 13738 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 13739 * 13740 * License: http://www.tinymce.com/license 13741 * Contributing: http://www.tinymce.com/contributing 13742 */ 13743 13744/** 13745 * This class contains various utlity functions. These are also exposed 13746 * directly on the tinymce namespace. 13747 * 13748 * @class tinymce.util.Tools 13749 */ 13750define("tinymce/util/Tools", [ 13751 "tinymce/Env", 13752 "tinymce/util/Arr" 13753], function(Env, Arr) { 13754 /** 13755 * Removes whitespace from the beginning and end of a string. 13756 * 13757 * @method trim 13758 * @param {String} s String to remove whitespace from. 13759 * @return {String} New string with removed whitespace. 13760 */ 13761 var whiteSpaceRegExp = /^\s*|\s*$/g; 13762 13763 function trim(str) { 13764 return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 13765 } 13766 13767 /** 13768 * Checks if a object is of a specific type for example an array.
13769 * 13770 * @method is 13771 * @param {Object} obj Object to check type of. 13772 * @param {string} type Optional type to check for. 13773 * @return {Boolean} true/false if the object is of the specified type. 13774 */ 13775 function is(obj, type) { 13776 if (!type) { 13777 return obj !== undefined; 13778 } 13779 13780 if (type == 'array' && Arr.isArray(obj)) { 13781 return true; 13782 } 13783 13784 return typeof obj == type; 13785 } 13786 13787 /** 13788 * Makes a name/object map out of an array with names. 13789 * 13790 * @method makeMap 13791 * @param {Array/String} items Items to make map out of. 13792 * @param {String} delim Optional delimiter to split string by. 13793 * @param {Object} map Optional map to add items to. 13794 * @return {Object} Name/value map of items. 13795 */ 13796 function makeMap(items, delim, map) { 13797 var i; 13798 13799 items = items || []; 13800 delim = delim || ','; 13801 13802 if (typeof items == "string") { 13803 items = items.split(delim); 13804 } 13805 13806 map = map || {}; 13807 13808 i = items.length; 13809 while (i--) { 13810 map[items[i]] = {}; 13811 } 13812 13813 return map; 13814 } 13815 13816 /** 13817 * Creates a class, subclass or static singleton. 13818 * More details on this method can be found in the Wiki. 13819 * 13820 * @method create 13821 * @param {String} s Class name, inheritance and prefix. 13822 * @param {Object} p Collection of methods to add to the class. 13823 * @param {Object} root Optional root object defaults to the global window object. 13824 * @example 13825 * // Creates a basic class 13826 * tinymce.create('tinymce.somepackage.SomeClass', { 13827 * SomeClass: function() { 13828 * // Class constructor 13829 * }, 13830 * 13831 * method: function() { 13832 * // Some method 13833 * } 13834 * }); 13835 * 13836 * // Creates a basic subclass class 13837 * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', { 13838 * SomeSubClass: function() { 13839 * // Class constructor 13840 * this.parent(); // Call parent constructor 13841 * }, 13842 * 13843 * method: function() { 13844 * // Some method 13845 * this.parent(); // Call parent method 13846 * }, 13847 * 13848 * 'static': { 13849 * staticMethod: function() { 13850 * // Static method 13851 * } 13852 * } 13853 * }); 13854 * 13855 * // Creates a singleton/static class 13856 * tinymce.create('static tinymce.somepackage.SomeSingletonClass', { 13857 * method: function() { 13858 * // Some method 13859 * } 13860 * }); 13861 */ 13862 function create(s, p, root) { 13863 var self = this, sp, ns, cn, scn, c, de = 0; 13864 13865 // Parse : <prefix> <class>:<super class> 13866 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s); 13867 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name 13868 13869 // Create namespace for new class 13870 ns = self.createNS(s[3].replace(/\.\w+$/, ''), root); 13871 13872 // Class already exists 13873 if (ns[cn]) { 13874 return; 13875 } 13876 13877 // Make pure static class 13878 if (s[2] == 'static') { 13879 ns[cn] = p; 13880 13881 if (this.onCreate) { 13882 this.onCreate(s[2], s[3], ns[cn]); 13883 } 13884 13885 return; 13886 } 13887 13888 // Create default constructor 13889 if (!p[cn]) { 13890 p[cn] = function() {}; 13891 de = 1; 13892 } 13893 13894 // Add constructor and methods 13895 ns[cn] = p[cn]; 13896 self.extend(ns[cn].prototype, p); 13897 13898 // Extend 13899 if (s[5]) { 13900 sp = self.resolve(s[5]).prototype; 13901 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name 13902 13903 // Extend constructor 13904 c = ns[cn]; 13905 if (de) { 13906 // Add passthrough constructor 13907 ns[cn] = function() { 13908 return sp[scn].apply(this, arguments); 13909 }; 13910 } else { 13911 // Add inherit constructor 13912 ns[cn] = function() { 13913 this.parent = sp[scn]; 13914 return c.apply(this, arguments); 13915 }; 13916 } 13917 ns[cn].prototype[cn] = ns[cn]; 13918 13919 // Add super methods 13920 self.each(sp, function(f, n) { 13921 ns[cn].prototype[n] = sp[n]; 13922 }); 13923 13924 // Add overridden methods 13925 self.each(p, function(f, n) { 13926 // Extend methods if needed 13927 if (sp[n]) { 13928 ns[cn].prototype[n] = function() { 13929 this.parent = sp[n]; 13930 return f.apply(this, arguments); 13931 }; 13932 } else { 13933 if (n != cn) { 13934 ns[cn].prototype[n] = f; 13935 } 13936 } 13937 }); 13938 } 13939 13940 // Add static methods 13941 /*jshint sub:true*/ 13942 /*eslint dot-notation:0*/ 13943 self.each(p['static'], function(f, n) { 13944 ns[cn][n] = f; 13945 }); 13946 } 13947 13948 function extend(obj, ext) { 13949 var i, l, name, args = arguments, value; 13950 13951 for (i = 1, l = args.length; i < l; i++) { 13952 ext = args[i]; 13953 for (name in ext) { 13954 if (ext.hasOwnProperty(name)) { 13955 value = ext[name]; 13956 13957 if (value !== undefined) { 13958 obj[name] = value; 13959 } 13960 } 13961 } 13962 } 13963 13964 return obj; 13965 } 13966 13967 /** 13968 * Executed the specified function for each item in a object tree. 13969 * 13970 * @method walk 13971 * @param {Object} o Object tree to walk though. 13972 * @param {function} f Function to call for each item. 13973 * @param {String} n Optional name of collection inside the objects to walk for example childNodes. 13974 * @param {String} s Optional scope to execute the function in. 13975 */ 13976 function walk(o, f, n, s) { 13977 s = s || this; 13978 13979 if (o) { 13980 if (n) { 13981 o = o[n]; 13982 } 13983 13984 Arr.each(o, function(o, i) { 13985 if (f.call(s, o, i, n) === false) { 13986 return false;
13987 } 13988 13989 walk(o, f, n, s); 13990 }); 13991 } 13992 } 13993 13994 /** 13995 * Creates a namespace on a specific object. 13996 * 13997 * @method createNS 13998 * @param {String} n Namespace to create for example a.b.c.d. 13999 * @param {Object} o Optional object to add namespace to, defaults to window. 14000 * @return {Object} New namespace object the last item in path. 14001 * @example 14002 * // Create some namespace 14003 * tinymce.createNS('tinymce.somepackage.subpackage'); 14004 * 14005 * // Add a singleton 14006 * var tinymce.somepackage.subpackage.SomeSingleton = { 14007 * method: function() { 14008 * // Some method 14009 * } 14010 * }; 14011 */ 14012 function createNS(n, o) { 14013 var i, v; 14014 14015 o = o || window; 14016 14017 n = n.split('.'); 14018 for (i = 0; i < n.length; i++) { 14019 v = n[i]; 14020 14021 if (!o[v]) { 14022 o[v] = {}; 14023 } 14024 14025 o = o[v]; 14026 } 14027 14028 return o; 14029 } 14030 14031 /** 14032 * Resolves a string and returns the object from a specific structure. 14033 * 14034 * @method resolve 14035 * @param {String} n Path to resolve for example a.b.c.d. 14036 * @param {Object} o Optional object to search though, defaults to window. 14037 * @return {Object} Last object in path or null if it couldn't be resolved. 14038 * @example 14039 * // Resolve a path into an object reference 14040 * var obj = tinymce.resolve('a.b.c.d'); 14041 */ 14042 function resolve(n, o) { 14043 var i, l; 14044 14045 o = o || window; 14046 14047 n = n.split('.'); 14048 for (i = 0, l = n.length; i < l; i++) { 14049 o = o[n[i]]; 14050 14051 if (!o) { 14052 break; 14053 } 14054 } 14055 14056 return o; 14057 } 14058 14059 /** 14060 * Splits a string but removes the whitespace before and after each value. 14061 * 14062 * @method explode 14063 * @param {string} s String to split. 14064 * @param {string} d Delimiter to split by. 14065 * @example 14066 * // Split a string into an array with a,b,c 14067 * var arr = tinymce.explode('a, b, c'); 14068 */ 14069 function explode(s, d) { 14070 if (!s || is(s, 'array')) { 14071 return s; 14072 } 14073 14074 return Arr.map(s.split(d || ','), trim); 14075 } 14076 14077 function _addCacheSuffix(url) { 14078 var cacheSuffix = Env.cacheSuffix; 14079 14080 if (cacheSuffix) { 14081 url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix; 14082 } 14083 14084 return url; 14085 } 14086 14087 return { 14088 trim: trim, 14089 14090 /** 14091 * Returns true/false if the object is an array or not. 14092 * 14093 * @method isArray 14094 * @param {Object} obj Object to check. 14095 * @return {boolean} true/false state if the object is an array or not. 14096 */ 14097 isArray: Arr.isArray, 14098 14099 is: is, 14100 14101 /** 14102 * Converts the specified object into a real JavaScript array. 14103 * 14104 * @method toArray 14105 * @param {Object} obj Object to convert into array. 14106 * @return {Array} Array object based in input. 14107 */ 14108 toArray: Arr.toArray, 14109 makeMap: makeMap, 14110 14111 /** 14112 * Performs an iteration of all items in a collection such as an object or array. This method will execure the 14113 * callback function for each item in the collection, if the callback returns false the iteration will terminate. 14114 * The callback has the following format: cb(value, key_or_index). 14115 * 14116 * @method each 14117 * @param {Object} o Collection to iterate. 14118 * @param {function} cb Callback function to execute for each item. 14119 * @param {Object} s Optional scope to execute the callback in. 14120 * @example 14121 * // Iterate an array 14122 * tinymce.each([1,2,3], function(v, i) { 14123 * console.debug("Value: " + v + ", Index: " + i); 14124 * }); 14125 * 14126 * // Iterate an object 14127 * tinymce.each({a: 1, b: 2, c: 3], function(v, k) { 14128 * console.debug("Value: " + v + ", Key: " + k); 14129 * }); 14130 */ 14131 each: Arr.each, 14132 14133 /** 14134 * Creates a new array by the return value of each iteration function call. This enables you to convert 14135 * one array list into another. 14136 * 14137 * @method map 14138 * @param {Array} array Array of items to iterate. 14139 * @param {function} callback Function to call for each item. It's return value will be the new value. 14140 * @return {Array} Array with new values based on function return values. 14141 */ 14142 map: Arr.map, 14143 14144 /** 14145 * Filters out items from the input array by calling the specified function for each item. 14146 * If the function returns false the item will be excluded if it returns true it will be included. 14147 * 14148 * @method grep 14149 * @param {Array} a Array of items to loop though. 14150 * @param {function} f Function to call for each item. Include/exclude depends on it's return value. 14151 * @return {Array} New array with values imported and filtered based in input. 14152 * @example 14153 * // Filter out some items, this will return an array with 4 and 5 14154 * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;}); 14155 */ 14156 grep: Arr.filter, 14157 14158 /** 14159 * Returns true/false if the object is an array or not. 14160 * 14161 * @method isArray 14162 * @param {Object} obj Object to check. 14163 * @return {boolean} true/false state if the object is an array or not. 14164 */ 14165 inArray: Arr.indexOf, 14166 14167 extend: extend, 14168 create: create, 14169 walk: walk, 14170 createNS: createNS, 14171 resolve: resolve, 14172 explode: explode, 14173 _addCacheSuffix: _addCacheSuffix 14174 }; 14175}); 14176 14177// Included from: js/tinymce/classes/dom/DomQuery.js 14178 14179/** 14180 * DomQuery.js 14181 * 14182 * Released under LGPL License. 14183 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 14184 * 14185 * License: http://www.tinymce.com/license 14186 * Contributing: http://www.tinymce.com/contributing 14187 */ 14188 14189/** 14190 * This class mimics most of the jQuery API: 14191 * 14192 * This is whats currently implemented: 14193 * - Utility functions 14194 * - DOM traversial 14195 * - DOM manipulation 14196 * - Event binding 14197 * 14198 * This is not currently implemented: 14199 * - Dimension 14200 * - Ajax 14201 * - Animation 14202 * - Advanced chaining 14203 * 14204 * @example 14205 * var $ = tinymce.dom.DomQuery; 14206 * $('p').attr('attr', 'value').addClass('class'); 14207 * 14208 * @class tinymce.dom.DomQuery 14209 */ 14210define("tinymce/dom/DomQuery", [ 14211 "tinymce/dom/EventUtils", 14212 "tinymce/dom/Sizzle", 14213 "tinymce/util/Tools", 14214 "tinymce/Env" 14215], function(EventUtils, Sizzle, Tools, Env) { 14216 var doc = document, push = Array.prototype.push, slice = Array.prototype.slice; 14217 var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/; 14218 var Event = EventUtils.Event, undef; 14219 var skipUniques = Tools.makeMap('children,contents,next,prev'); 14220 14221 function isDefined(obj) { 14222 return typeof obj !== 'undefined'; 14223 } 14224 14225 function isString(obj) { 14226 return typeof obj === 'string'; 14227 } 14228 14229 function isWindow(obj) { 14230 return obj && obj == obj.window; 14231 } 14232 14233 function createFragment(html, fragDoc) { 14234 var frag, node, container; 14235 14236 fragDoc = fragDoc || doc; 14237 container = fragDoc.createElement('div'); 14238 frag = fragDoc.createDocumentFragment(); 14239 container.innerHTML = html; 14240 14241 while ((node = container.firstChild)) { 14242 frag.appendChild(node); 14243 } 14244 14245 return frag; 14246 } 14247 14248 function domManipulate(targetNodes, sourceItem, callback, reverse) { 14249 var i; 14250 14251 if (isString(sourceItem)) { 14252 sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0])); 14253 } else if (sourceItem.length && !sourceItem.nodeType) { 14254 sourceItem = DomQuery.makeArray(sourceItem); 14255 14256 if (reverse) { 14257 for (i = sourceItem.length - 1; i >= 0; i--) { 14258 domManipulate(targetNodes, sourceItem[i], callback, reverse); 14259 } 14260 } else { 14261 for (i = 0; i < sourceItem.length; i++) { 14262 domManipulate(targetNodes, sourceItem[i], callback, reverse); 14263 } 14264 } 14265 14266 return targetNodes; 14267 } 14268 14269 if (sourceItem.nodeType) { 14270 i = targetNodes.length; 14271 while (i--) { 14272 callback.call(targetNodes[i], sourceItem); 14273 } 14274 } 14275 14276 return targetNodes; 14277 } 14278 14279 function hasClass(node, className) { 14280 return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1; 14281 } 14282 14283 function wrap(elements, wrapper, all) { 14284 var lastParent, newWrapper; 14285 14286 wrapper = DomQuery(wrapper)[0]; 14287 14288 elements.each(function() { 14289 var self = this; 14290 14291 if (!all || lastParent != self.parentNode) { 14292 lastParent = self.parentNode; 14293 newWrapper = wrapper.cloneNode(false); 14294 self.parentNode.insertBefore(newWrapper, self); 14295 newWrapper.appendChild(self); 14296 } else { 14297 newWrapper.appendChild(self); 14298 } 14299 }); 14300 14301 return elements; 14302 } 14303 14304 var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' '); 14305 var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref nosh
14305ade nowrap readonly selected', ' '); 14306 var propFix = { 14307 'for': 'htmlFor', 14308 'class': 'className', 14309 'readonly': 'readOnly' 14310 }; 14311 var cssFix = { 14312 'float': 'cssFloat' 14313 }; 14314 14315 var attrHooks = {}, cssHooks = {}; 14316 14317 function DomQuery(selector, context) { 14318 /*eslint new-cap:0 */ 14319 return new DomQuery.fn.init(selector, context); 14320 } 14321 14322 function inArray(item, array) { 14323 var i; 14324 14325 if (array.indexOf) { 14326 return array.indexOf(item); 14327 } 14328 14329 i = array.length; 14330 while (i--) { 14331 if (array[i] === item) { 14332 return i; 14333 } 14334 } 14335 14336 return -1; 14337 } 14338 14339 var whiteSpaceRegExp = /^\s*|\s*$/g; 14340 14341 function trim(str) { 14342 return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, ''); 14343 } 14344 14345 function each(obj, callback) { 14346 var length, key, i, undef, value; 14347 14348 if (obj) { 14349 length = obj.length; 14350 14351 if (length === undef) { 14352 // Loop object items 14353 for (key in obj) { 14354 if (obj.hasOwnProperty(key)) { 14355 value = obj[key]; 14356 if (callback.call(value, key, value) === false) { 14357 break; 14358 } 14359 } 14360 } 14361 } else { 14362 // Loop array items 14363 for (i = 0; i < length; i++) { 14364 value = obj[i]; 14365 if (callback.call(value, i, value) === false) { 14366 break; 14367 } 14368 } 14369 } 14370 } 14371 14372 return obj; 14373 } 14374 14375 function grep(array, callback) { 14376 var out = []; 14377 14378 each(array, function(i, item) { 14379 if (callback(item, i)) { 14380 out.push(item); 14381 } 14382 }); 14383 14384 return out; 14385 } 14386 14387 function getElementDocument(element) { 14388 if (!element) { 14389 return doc; 14390 } 14391 14392 if (element.nodeType == 9) { 14393 return element; 14394 } 14395 14396 return element.ownerDocument; 14397 } 14398 14399 DomQuery.fn = DomQuery.prototype = { 14400 constructor: DomQuery, 14401 14402 /** 14403 * Selector for the current set. 14404 * 14405 * @property selector
14406 * @type String 14407 */ 14408 selector: "", 14409 14410 /** 14411 * Context used to create the set. 14412 * 14413 * @property context 14414 * @type Element 14415 */ 14416 context: null, 14417 14418 /** 14419 * Number of items in the current set. 14420 * 14421 * @property length 14422 * @type Number 14423 */ 14424 length: 0, 14425 14426 /** 14427 * Constructs a new DomQuery instance with the specified selector or context. 14428 * 14429 * @constructor 14430 * @method init 14431 * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string. 14432 * @param {Document/Element} context Optional context to search in. 14433 */ 14434 init: function(selector, context) { 14435 var self = this, match, node; 14436 14437 if (!selector) { 14438 return self; 14439 } 14440 14441 if (selector.nodeType) { 14442 self.context = self[0] = selector; 14443 self.length = 1; 14444 14445 return self; 14446 } 14447 14448 if (context && context.nodeType) { 14449 self.context = context; 14450 } else { 14451 if (context) { 14452 return DomQuery(selector).attr(context); 14453 } 14454 14455 self.context = context = document; 14456 } 14457 14458 if (isString(selector)) { 14459 self.selector = selector; 14460 14461 if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) { 14462 match = [null, selector, null]; 14463 } else { 14464 match = rquickExpr.exec(selector); 14465 } 14466 14467 if (match) { 14468 if (match[1]) { 14469 node = createFragment(selector, getElementDocument(context)).firstChild; 14470 14471 while (node) { 14472 push.call(self, node); 14473 node = node.nextSibling; 14474 } 14475 } else { 14476 node = getElementDocument(context).getElementById(match[2]); 14477 14478 if (!node) { 14479 return self; 14480 } 14481 14482 if (node.id !== match[2]) { 14483 return self.find(selector); 14484 } 14485 14486 self.length = 1; 14487 self[0] = node; 14488 } 14489 } else { 14490 return DomQuery(context).find(selector); 14491 } 14492 } else { 14493 this.add(selector, false); 14494 } 14495 14496 return self; 14497 }, 14498 14499 /** 14500 * Converts the current set to an array. 14501 * 14502 * @method toArray 14503 * @return {Array} Array of all nodes in set. 14504 */ 14505 toArray: function() { 14506 return Tools.toArray(this); 14507 }, 14508 14509 /** 14510 * Adds new nodes to the set. 14511 * 14512 * @method add 14513 * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set. 14514 * @param {Boolean} sort Optional sort flag that enables sorting of elements. 14515 * @return {tinymce.dom.DomQuery} New instance with nodes added. 14516 */ 14517 add: function(items, sort) { 14518 var self = this, nodes, i; 14519 14520 if (isString(items)) { 14521 return self.add(DomQuery(items)); 14522 } 14523 14524 if (sort !== false) { 14525 nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items))); 14526 self.length = nodes.length; 14527 for (i = 0; i < nodes.length; i++) { 14528 self[i] = nodes[i]; 14529 } 14530 } else { 14531 push.apply(self, DomQuery.makeArray(items)); 14532 } 14533 14534 return self; 14535 }, 14536 14537 /** 14538 * Sets/gets attributes on the elements in the current set. 14539 * 14540 * @method attr 14541 * @param {String/Object} name Name of attribute to get or an object with attributes to set. 14542 * @param {String} value Optional value to set. 14543 * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified. 14544 */ 14545 attr: function(name, value) { 14546 var self = this, hook; 14547 14548 if (typeof name === "object") { 14549 each(name, function(name, value) { 14550 self.attr(name, value); 14551 }); 14552 } else if (isDefined(value)) { 14553 this.each(function() { 14554 var hook; 14555 14556 if (this.nodeType === 1) { 14557 hook = attrHooks[name]; 14558 if (hook && hook.set) { 14559 hook.set(this, value); 14560 return; 14561 } 14562 14563 if (value === null) { 14564 this.removeAttribute(name, 2); 14565 } else { 14566 this.setAttribute(name, value, 2); 14567 } 14568 } 14569 }); 14570 } else { 14571 if (self[0] && self[0].nodeType === 1) { 14572 hook = attrHooks[name]; 14573 if (hook && hook.get) { 14574 return hook.get(self[0], name); 14575 } 14576 14577 if (booleanMap[name]) { 14578 return self.prop(name) ? name : undef; 14579 } 14580 14581 value = self[0].getAttribute(name, 2); 14582 14583 if (value === null) { 14584 value = undef; 14585 } 14586 } 14587 14588 return value; 14589 } 14590 14591 return self; 14592 }, 14593 14594 /** 14595 * Removes attributse on the elements in the current set. 14596 * 14597 * @method removeAttr 14598 * @param {String/Object}
14598 name Name of attribute to remove. 14599 * @return {tinymce.dom.DomQuery/String} Current set. 14600 */ 14601 removeAttr: function(name) { 14602 return this.attr(name, null); 14603 }, 14604 14605 /** 14606 * Sets/gets properties on the elements in the current set. 14607 * 14608 * @method attr 14609 * @param {String/Object} name Name of property to get or an object with properties to set. 14610 * @param {String} value Optional value to set. 14611 * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified. 14612 */ 14613 prop: function(name, value) { 14614 var self = this; 14615 14616 name = propFix[name] || name; 14617 14618 if (typeof name === "object") { 14619 each(name, function(name, value) { 14620 self.prop(name, value); 14621 }); 14622 } else if (isDefined(value)) { 14623 this.each(function() { 14624 if (this.nodeType == 1) { 14625 this[name] = value; 14626 } 14627 }); 14628 } else { 14629 if (self[0] && self[0].nodeType && name in self[0]) { 14630 return self[0][name]; 14631 } 14632 14633 return value; 14634 } 14635 14636 return self; 14637 }, 14638 14639 /** 14640 * Sets/gets styles on the elements in the current set. 14641 * 14642 * @method css 14643 * @param {String/Object} name Name of style to get or an object with styles to set. 14644 * @param {String} value Optional value to set. 14645 * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified. 14646 */ 14647 css: function(name, value) { 14648 var self = this, elm, hook; 14649 14650 function camel(name) { 14651 return name.replace(/-(\D)/g, function(a, b) { 14652 return b.toUpperCase(); 14653 }); 14654 } 14655 14656 function dashed(name) { 14657 return name.replace(/[A-Z]/g, function(a) { 14658 return '-' + a; 14659 }); 14660 } 14661 14662 if (typeof name === "object") { 14663 each(name, function(name, value) { 14664 self.css(name, value); 14665 }); 14666 } else { 14667 if (isDefined(value)) { 14668 name = camel(name); 14669 14670 // Default px suffix on these 14671 if (typeof value === 'number' && !numericCssMap[name]) { 14672 value += 'px'; 14673 } 14674 14675 self.each(function() { 14676 var style = this.style; 14677 14678 hook = cssHooks[name]; 14679 if (hook && hook.set) { 14680 hook.set(this, value); 14681 return; 14682 } 14683 14684 try { 14685 this.style[cssFix[name] || name] = value; 14686 } catch (ex) { 14687 // Ignore 14688 } 14689 14690 if (value === null || value === '') { 14691 if (style.removeProperty) { 14692 style.removeProperty(dashed(name)); 14693 } else { 14694 style.removeAttribute(name); 14695 } 14696 } 14697 }); 14698 } else { 14699 elm = self[0]; 14700 14701 hook = cssHooks[name]; 14702 if (hook && hook.get) { 14703 return hook.get(elm); 14704 } 14705 14706 if (elm.ownerDocument.defaultView) { 14707 try { 14708 return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name)); 14709 } catch (ex) { 14710 return undef; 14711 } 14712 } else if (elm.currentStyle) { 14713 return elm.currentStyle[camel(name)]; 14714 } 14715 } 14716 } 14717 14718 return self; 14719 }, 14720 14721 /** 14722 * Removes all nodes in set from the document. 14723 * 14724 * @method remove 14725 * @return {tinymce.dom.DomQuery} Current set with the removed nodes. 14726 */ 14727 remove: function() { 14728 var self = this, node, i = this.length; 14729 14730 while (i--) { 14731 node = self[i]; 14732 Event.clean(node); 14733 14734 if (node.parentNode) { 14735 node.parentNode.removeChild(node); 14736 } 14737 } 14738 14739 return this; 14740 }, 14741 14742 /** 14743 * Empties all elements in set. 14744 * 14745 * @method empty 14746 * @return {tinymce.dom.DomQuery} Current set with the empty nodes. 14747 */ 14748 empty: function() { 14749 var self = this, node, i = this.length; 14750 14751 while (i--) { 14752 node = self[i]; 14753 while (node.firstChild) { 14754 node.removeChild(node.firstChild); 14755 } 14756 } 14757 14758 return this; 14759 }, 14760 14761 /** 14762 * Sets or gets the HTML of the current set or first set node. 14763 * 14764 * @method html 14765 * @param {String} value Optional innerHTML value to set on each element. 14766 * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element. 14767 */ 14768 html: function(value) { 14769 var self = this, i; 14770 14771 if (isDefined(value)) { 14772 i = self.length; 14773 14774 try { 14775 while (i--) { 14776 self[i].innerHTML = value; 14777 } 14778 } catch (ex) { 14779 // Workaround for "Unknown runtime error" when DIV is added to P on IE 14780 DomQuery(self[i]).empty().append(value); 14781 } 14782 14783 return self; 14784 } 14785 14786 return self[0] ? self[0].innerHTML : ''; 14787 }, 14788 14789 /** 14790 * Sets or gets the text of the current set or first set node. 14791 * 14792 * @method text 14793 * @param {String} value Optional innerText value to set on each element. 14794 * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element. 14795 */ 14796 text: function(value) { 14797 var self = this, i; 14798 14799 if (isDefined(value)) { 14800 i = self.length; 14801 while (i--) { 14802 if ("innerText" in self[i]) { 14803 self[i].innerText = value; 14804 } else { 14805 self[0].textContent = value; 14806 } 14807 } 14808 14809 return self; 14810 } 14811 14812 return self[0] ? (self[0].innerText || self[0].textContent) : ''; 14813 }, 14814 14815 /** 14816 * Appends the specified node/html or node set to the current set nodes. 14817 * 14818 * @method append 14819 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set. 14820 * @return {tinymce.dom.DomQuery} Current set. 14821 */ 14822 append: function() { 14823 return domManipulate(this, arguments, function(node) { 14824 if (this.nodeType === 1) { 14825 this.appendChild(node); 14826 } 14827 }); 14828 }, 14829 14830 /** 14831 * Prepends the specified node/html or node set to the current set nodes. 14832 * 14833 * @method prepend 14834 * @param {String/Element/Array/tinymce.dom.DomQuery}
14834 content Content to prepend to each element in set. 14835 * @return {tinymce.dom.DomQuery} Current set. 14836 */ 14837 prepend: function() { 14838 return domManipulate(this, arguments, function(node) { 14839 if (this.nodeType === 1) { 14840 this.insertBefore(node, this.firstChild); 14841 } 14842 }, true); 14843 }, 14844 14845 /** 14846 * Adds the specified elements before current set nodes. 14847 * 14848 * @method before 14849 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set. 14850 * @return {tinymce.dom.DomQuery} Current set. 14851 */ 14852 before: function() { 14853 var self = this; 14854 14855 if (self[0] && self[0].parentNode) { 14856 return domManipulate(self, arguments, function(node) { 14857 this.parentNode.insertBefore(node, this); 14858 }); 14859 } 14860 14861 return self; 14862 }, 14863 14864 /** 14865 * Adds the specified elements after current set nodes. 14866 * 14867 * @method after 14868 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set. 14869 * @return {tinymce.dom.DomQuery} Current set. 14870 */ 14871 after: function() { 14872 var self = this; 14873 14874 if (self[0] && self[0].parentNode) { 14875 return domManipulate(self, arguments, function(node) { 14876 this.parentNode.insertBefore(node, this.nextSibling); 14877 }, true); 14878 } 14879 14880 return self; 14881 }, 14882 14883 /** 14884 * Appends the specified set nodes to the specified selector/instance. 14885 * 14886 * @method appendTo 14887 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to. 14888 * @return {tinymce.dom.DomQuery} Current set with the appended nodes. 14889 */ 14890 appendTo: function(val) { 14891 DomQuery(val).append(this); 14892 14893 return this; 14894 }, 14895 14896 /** 14897 * Prepends the specified set nodes to the specified selector/instance. 14898 * 14899 * @method prependTo 14900 * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to. 14901 * @return {tinymce.dom.DomQuery} Current set with the prepended nodes. 14902 */ 14903 prependTo: function(val) { 14904 DomQuery(val).prepend(this); 14905 14906 return this; 14907 }, 14908 14909 /** 14910 * Replaces the nodes in set with the specified content. 14911 * 14912 * @method replaceWith 14913 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with. 14914 * @return {tinymce.dom.DomQuery} Set with replaced nodes. 14915 */ 14916 replaceWith: function(content) { 14917 return this.before(content).remove(); 14918 }, 14919 14920 /** 14921 * Wraps all elements in set with the specified wrapper. 14922 * 14923 * @method wrap 14924 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 14925 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 14926 */ 14927 wrap: function(content) { 14928 return wrap(this, content); 14929 }, 14930 14931 /** 14932 * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them 14933 * will be wrapped in the same wrapper. 14934 * 14935 * @method wrapAll 14936 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 14937 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 14938 */ 14939 wrapAll: function(content) { 14940 return wrap(this, content, true); 14941 }, 14942 14943 /** 14944 * Wraps all elements inner contents in set with the specified wrapper. 14945 * 14946 * @method wrapInner 14947 * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with. 14948 * @return {tinymce.dom.DomQuery} Set with wrapped nodes. 14949 */ 14950 wrapInner: function(content) { 14951 this.each(function() { 14952 DomQuery(this).contents().wrapAll(content); 14953 }); 14954 14955 return this; 14956 }, 14957 14958 /** 14959 * Unwraps all elements by removing the parent element of each item in set. 14960 * 14961 * @method unwrap 14962 * @return {tinymce.dom.DomQuery} Set with unwrapped nodes. 14963 */ 14964 unwrap: function() { 14965 return this.parent().each(function() { 14966 DomQuery(this).replaceWith(this.childNodes); 14967 }); 14968 }, 14969 14970 /** 14971 * Clones all nodes in set. 14972 * 14973 * @method clone 14974 * @return {tinymce.dom.DomQuery} Set with cloned nodes. 14975 */ 14976 clone: function() { 14977 var result = []; 14978 14979 this.each(function() { 14980 result.push(this.cloneNode(true)); 14981 }); 14982 14983 return DomQuery(result); 14984 }, 14985 14986 /** 14987 * Adds the specified class name to the current set elements. 14988 * 14989 * @method addClass 14990 * @param {String} className Class name to add. 14991 * @return {tinymce.dom.DomQuery} Current set. 14992 */ 14993 addClass: function(className) { 14994 return this.toggleClass(className, true); 14995 }, 14996 14997 /** 14998 * Removes the specified class name to the current set elements. 14999 * 15000 * @method removeClass 15001 * @param {String} className Class name to remove. 15002 * @return {tinymce.dom.DomQuery} Current set. 15003 */ 15004 removeClass: function(className) { 15005 return this.toggleClass(className, false); 15006 }, 15007 15008 /** 15009 * Toggles the specified class name on the current set elements. 15010 * 15011 * @method toggleClass 15012 * @param {String} className Class name to add/remove. 15013 * @param {Boolean} state Optional state to toggle on/off. 15014 * @return {tinymce.dom.DomQuery} Current set. 15015 */ 15016 toggleClass: function(className, state) { 15017 var self = this; 15018 15019 // Functions are not supported 15020 if (typeof className != 'string') { 15021 return self; 15022 } 15023 15024 if (className.indexOf(' ') !== -1) { 15025 each(className.split(' '), function() { 15026 self.toggleClass(this, state); 15027 }); 15028 } else { 15029 self.each(function(index, node) { 15030 var existingClassName, classState; 15031 15032 classState = hasClass(node, className); 15033 if (classState !== state) {
15034 existingClassName = node.className; 15035 15036 if (classState) { 15037 node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' ')); 15038 } else { 15039 node.className += existingClassName ? ' ' + className : className; 15040 } 15041 } 15042 }); 15043 } 15044 15045 return self; 15046 }, 15047 15048 /** 15049 * Returns true/false if the first item in set has the specified class. 15050 * 15051 * @method hasClass 15052 * @param {String} className Class name to check for. 15053 * @return {Boolean} True/false if the set has the specified class. 15054 */ 15055 hasClass: function(className) { 15056 return hasClass(this[0], className); 15057 }, 15058 15059 /** 15060 * Executes the callback function for each item DomQuery collection. If you return false in the 15061 * callback it will break the loop. 15062 * 15063 * @method each 15064 * @param {function} callback Callback function to execute for each item. 15065 * @return {tinymce.dom.DomQuery} Current set. 15066 */ 15067 each: function(callback) { 15068 return each(this, callback); 15069 }, 15070 15071 /** 15072 * Binds an event with callback function to the elements in set. 15073 * 15074 * @method on 15075 * @param {String} name Name of the event to bind. 15076 * @param {function} callback Callback function to execute when the event occurs. 15077 * @return {tinymce.dom.DomQuery} Current set. 15078 */ 15079 on: function(name, callback) { 15080 return this.each(function() { 15081 Event.bind(this, name, callback); 15082 }); 15083 }, 15084 15085 /** 15086 * Unbinds an event with callback function to the elements in set. 15087 * 15088 * @method off 15089 * @param {String} name Optional name of the event to bind. 15090 * @param {function} callback Optional callback function to execute when the event occurs. 15091 * @return {tinymce.dom.DomQuery} Current set. 15092 */ 15093 off: function(name, callback) { 15094 return this.each(function() { 15095 Event.unbind(this, name, callback); 15096 }); 15097 }, 15098 15099 /** 15100 * Triggers the specified event by name or event object. 15101 * 15102 * @method trigger 15103 * @param {String/Object} name Name of the event to trigger or event object. 15104 * @return {tinymce.dom.DomQuery} Current set. 15105 */ 15106 trigger: function(name) { 15107 return this.each(function() { 15108 if (typeof name == 'object') { 15109 Event.fire(this, name.type, name); 15110 } else { 15111 Event.fire(this, name); 15112 } 15113 }); 15114 }, 15115 15116 /** 15117 * Shows all elements in set. 15118 * 15119 * @method show 15120 * @return {tinymce.dom.DomQuery} Current set. 15121 */ 15122 show: function() { 15123 return this.css('display', ''); 15124 }, 15125 15126 /** 15127 * Hides all elements in set. 15128 * 15129 * @method hide 15130 * @return {tinymce.dom.DomQuery} Current set. 15131 */ 15132 hide: function() { 15133 return this.css('display', 'none'); 15134 }, 15135 15136 /** 15137 * Slices the current set. 15138 * 15139 * @method slice 15140 * @param {Number} start Start index to slice at. 15141 * @param {Number} end Optional end index to end slice at. 15142 * @return {tinymce.dom.DomQuery} Sliced set. 15143 */ 15144 slice: function() { 15145 return new DomQuery(slice.apply(this, arguments)); 15146 }, 15147 15148 /** 15149 * Makes the set equal to the specified index. 15150 * 15151 * @method eq 15152 * @param {Number} index Index to set it equal to. 15153 * @return {tinymce.dom.DomQuery} Single item set. 15154 */ 15155 eq: function(index) { 15156 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 15157 }, 15158 15159 /** 15160 * Makes the set equal to first element in set. 15161 * 15162 * @method first 15163 * @return {tinymce.dom.DomQuery} Single item set. 15164 */ 15165 first: function() { 15166 return this.eq(0); 15167 }, 15168 15169 /** 15170 * Makes the set equal to last element in set. 15171 * 15172 * @method last 15173 * @return {tinymce.dom.DomQuery} Single item set. 15174 */ 15175 last: function() { 15176 return this.eq(-1); 15177 }, 15178 15179 /** 15180 * Finds elements by the specified selector for each element in set. 15181 * 15182 * @method find 15183 * @param {String} selector Selector to find elements by. 15184 * @return {tinymce.dom.DomQuery} Set with matches elements. 15185 */ 15186 find: function(selector) { 15187 var i, l, ret = []; 15188 15189 for (i = 0, l = this.length; i < l; i++) { 15190 DomQuery.find(selector, this[i], ret); 15191 } 15192 15193 return DomQuery(ret); 15194 }, 15195 15196 /** 15197 * Filters the current set with the specified selector. 15198 * 15199 * @method filter 15200 * @param {String/function} selector Selector to filter elements by. 15201 * @return {tinymce.dom.DomQuery} Set with filtered elements. 15202 */ 15203 filter: function(selector) { 15204 if (typeof selector == 'function') { 15205 return DomQuery(grep(this.toArray(), function(item, i) { 15206 return selector(i, item); 15207 })); 15208 } 15209 15210 return DomQuery(DomQuery.filter(selector, this.toArray())); 15211 }, 15212 15213 /** 15214 * Gets the current node or any parent matching the specified selector. 15215 * 15216 * @method closest 15217 * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find. 15218 * @return {tinymce.dom.DomQuery} Set with closest elements. 15219 */ 15220 closest: function(selector) { 15221 var result = []; 15222 15223 if (selector instanceof DomQuery) { 15224 selector = selector[0]; 15225 } 15226 15227 this.each(function(i, node) { 15228 while (node) { 15229 if (typeof selector == 'string' && DomQuery(node).is(selector)) { 15230 result.push(node); 15231 break; 15232 } else if (node == selector) { 15233 result.push(node); 15234 break; 15235 } 15236 15237 node = node.parentNode; 15238 } 15239 }); 15240 15241 return DomQuery(result); 15242 }, 15243 15244 /** 15245 * Returns the offset of the first element in set or sets the top/left css properties of all elements in set. 15246 * 15247 * @method offset 15248 * @param {Object} offset Optional offset object to set on each item. 15249 * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset. 15250 */ 15251 offset: function(offset) { 15252 var elm, doc, docElm; 15253 var x = 0, y = 0, pos; 15254 15255 if (!offset) { 15256 elm = this[0]; 15257 15258 if (elm) { 15259 doc = elm.ownerDocument; 15260 docElm = doc.documentElement; 15261 15262 if (elm.getBoundingClientRect) { 15263 pos = elm.getBoundingClientRect(); 15264 x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft; 15265 y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop; 15266 } 15267 } 15268 15269 return { 15270 left: x, 15271 top: y 15272 }; 15273 } 15274 15275 return this.css(offset); 15276 }, 15277 15278 push: push, 15279 sort: [].sort, 15280 splice: [].splice 15281 }; 15282 15283 // Static members 15284 Tools.extend(DomQuery, { 15285 /** 15286 * Extends the specified object with one or more objects. 15287 * 15288 * @static 15289 * @method extend 15290 * @param {Object} target Target object to extend with new items. 15291 * @param {Object..} object Object to extend the target with. 15292 * @return {Object} Extended input object. 15293 */ 15294 extend: Tools.extend, 15295 15296 /** 15297 * Creates an array out of an array like object. 15298 * 15299 * @static 15300 * @method makeArray 15301 * @param {Object} object Object to convert to array.
15302 * @return {Array} Array produced from object. 15303 */ 15304 makeArray: function(object) { 15305 if (isWindow(object) || object.nodeType) { 15306 return [object]; 15307 } 15308 15309 return Tools.toArray(object); 15310 }, 15311 15312 /** 15313 * Returns the index of the specified item inside the array. 15314 * 15315 * @static 15316 * @method inArray 15317 * @param {Object} item Item to look for. 15318 * @param {Array} array Array to look for item in. 15319 * @return {Number} Index of the item or -1. 15320 */ 15321 inArray: inArray, 15322 15323 /** 15324 * Returns true/false if the specified object is an array or not. 15325 * 15326 * @static 15327 * @method isArray 15328 * @param {Object} array Object to check if it's an array or not. 15329 * @return {Boolean} True/false if the object is an array. 15330 */ 15331 isArray: Tools.isArray, 15332 15333 /** 15334 * Executes the callback function for each item in array/object. If you return false in the 15335 * callback it will break the loop. 15336 * 15337 * @static 15338 * @method each 15339 * @param {Object} obj Object to iterate. 15340 * @param {function} callback Callback function to execute for each item. 15341 */ 15342 each: each, 15343 15344 /** 15345 * Removes whitespace from the beginning and end of a string. 15346 * 15347 * @static 15348 * @method trim 15349 * @param {String} str String to remove whitespace from. 15350 * @return {String} New string with removed whitespace. 15351 */ 15352 trim: trim, 15353 15354 /** 15355 * Filters out items from the input array by calling the specified function for each item. 15356 * If the function returns false the item will be excluded if it returns true it will be included. 15357 * 15358 * @static 15359 * @method grep 15360 * @param {Array} array Array of items to loop though. 15361 * @param {function} callback Function to call for each item. Include/exclude depends on it's return value. 15362 * @return {Array} New array with values imported and filtered based in input. 15363 * @example 15364 * // Filter out some items, this will return an array with 4 and 5 15365 * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;}); 15366 */ 15367 grep: grep, 15368 15369 // Sizzle 15370 find: Sizzle, 15371 expr: Sizzle.selectors, 15372 unique: Sizzle.uniqueSort, 15373 text: Sizzle.getText, 15374 contains: Sizzle.contains, 15375 filter: function(expr, elems, not) { 15376 var i = elems.length; 15377 15378 if (not) { 15379 expr = ":not(" + expr + ")"; 15380 } 15381 15382 while (i--) { 15383 if (elems[i].nodeType != 1) { 15384 elems.splice(i, 1); 15385 } 15386 } 15387 15388 if (elems.length === 1) { 15389 elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : []; 15390 } else { 15391 elems = DomQuery.find.matches(expr, elems); 15392 } 15393 15394 return elems; 15395 } 15396 }); 15397 15398 function dir(el, prop, until) { 15399 var matched = [], cur = el[prop]; 15400 15401 if (typeof until != 'string' && until instanceof DomQuery) { 15402 until = until[0]; 15403 } 15404 15405 while (cur && cur.nodeType !== 9) { 15406 if (until !== undefined) { 15407 if (cur === until) { 15408 break; 15409 } 15410 15411 if (typeof until == 'string' && DomQuery(cur).is(until)) { 15412 break; 15413 } 15414 } 15415 15416 if (cur.nodeType === 1) { 15417 matched.push(cur); 15418 } 15419 15420 cur = cur[prop]; 15421 } 15422 15423 return matched; 15424 } 15425 15426 function sibling(node, siblingName, nodeType, until) { 15427 var result = []; 15428 15429 if (until instanceof DomQuery) { 15430 until = until[0]; 15431 } 15432 15433 for (; node; node = node[siblingName]) { 15434 if (nodeType && node.nodeType !== nodeType) { 15435 continue; 15436 } 15437 15438 if (until !== undefined) { 15439 if (node === until) { 15440 break; 15441 } 15442 15443 if (typeof until == 'string' && DomQuery(node).is(until)) { 15444 break; 15445 } 15446 } 15447 15448 result.push(node); 15449 } 15450 15451 return result; 15452 } 15453 15454 function firstSibling(node, siblingName, nodeType) { 15455 for (node = node[siblingName]; node; node = node[siblingName]) { 15456 if (node.nodeType == nodeType) { 15457 return node; 15458 } 15459 } 15460 15461 return null; 15462 } 15463 15464 each({ 15465 /** 15466 * Returns a new collection with the parent of each item in current collection matching the optional selector. 15467 * 15468 * @method parent 15469 * @param {Element/tinymce.dom.DomQuery} node Node to match parents against. 15470 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 15471 */ 15472 parent: function(node) { 15473 var parent = node.parentNode; 15474 15475 return parent && parent.nodeType !== 11 ? parent : null; 15476 }, 15477 15478 /** 15479 * Returns a new collection with the all the parents of each item in current collection matching the optional selector. 15480 * 15481 * @method parents 15482 * @param {Element/tinymce.dom.DomQuery} node Node to match parents against. 15483 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 15484 */ 15485 parents: function(node) { 15486 return dir(node, "parentNode"); 15487 }, 15488 15489 /** 15490 * Returns a new collection with next sibling of each item in current collection matching the optional selector. 15491 * 15492 * @method next 15493 * @param {Element/tinymce.dom.DomQuery} node Node to match the next element against. 15494 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15495 */ 15496 next: function(node) { 15497 return firstSibling(node, 'nextSibling', 1); 15498 }, 15499 15500 /** 15501 * Returns a new collection with previous sibling of each item in current collection matching the optional selector. 15502 * 15503 * @method prev 15504 * @param {Element/tinymce.dom.DomQuery} node Node to match the previous element against. 15505 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15506 */ 15507 prev: function(node) { 15508 return firstSibling(node, 'previousSibling', 1); 15509 }, 15510 15511 /** 15512 * Returns all child elements matching the optional selector. 15513 * 15514 * @method children 15515 * @param {Element/tinymce.dom.DomQuery} node Node to match the elements against. 15516 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15517 */ 15518 children: function(node) { 15519 return sibling(node.firstChild, 'nextSibling', 1); 15520 }, 15521 15522 /** 15523 * Returns all child nodes matching the optional selector. 15524 * 15525 * @method contents 15526 * @param {Element/tinymce.dom.DomQuery} node Node to get the contents of. 15527 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15528 */ 15529 contents: function(node) { 15530 return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes); 15531 } 15532 }, function(name, fn) { 15533 DomQuery.fn[name] = function(selector) { 15534 var self = this, result = []; 15535 15536 self.each(function() { 15537 var nodes = fn.call(result, this, selector, result); 15538 15539 if (nodes) { 15540 if (DomQuery.isArray(nodes)) { 15541 result.push.apply(result, nodes); 15542 } else { 15543 result.push(nodes); 15544 } 15545 } 15546 }); 15547 15548 // If traversing on multiple elements we might get the same elements twice 15549 if (this.length > 1) { 15550 if (!skipUniques[name]) { 15551 result = DomQuery.unique(result); 15552 } 15553 15554 if (name.indexOf('parents') === 0) { 15555 result = result.reverse(); 15556 } 15557 } 15558 15559 result = DomQuery(result); 15560 15561 if (selector) { 15562 return result.filter(selector); 15563 } 15564 15565 return result; 15566 }; 15567 }); 15568 15569 each({ 15570 /** 15571 * Returns a new collection with the all the parents until the matching selector/element 15572 * of each item in current collection matching the optional selector. 15573 * 15574 * @method parentsUntil 15575 * @param {Element/tinymce.dom.DomQuery} node Node to find parent of. 15576 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 15577 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents. 15578 */ 15579 parentsUntil: function(node, until) { 15580 return dir(node, "parentNode", until); 15581 }, 15582 15583 /** 15584 * Returns a new collection with all next siblings of each item in current collection matching the optional selector. 15585 * 15586 * @method nextUntil 15587 * @param {Element/tinymce.dom.DomQuery} node Node to find next siblings on. 15588 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 15589 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15590 */ 15591 nextUntil: function(node, until) { 15592 return sibling(node, 'nextSibling', 1, until).slice(1); 15593 }, 15594 15595 /** 15596 * Returns a new collection with all previous siblings of each item in current collection matching the optional selector. 15597 * 15598 * @method prevUntil 15599 * @param {Element/tinymce.dom.DomQuery} node Node to find previous siblings on. 15600 * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element. 15601 * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements. 15602 */ 15603 prevUntil: function(node, until) { 15604 return sibling(node, 'previousSibling', 1, until).slice(1); 15605 } 15606 }, function(name, fn) { 15607 DomQuery.fn[name] = function(selector, filter) { 15608 var self = this, result = []; 15609 15610 self.each(function() { 15611 var nodes = fn.call(result, this, selector, result); 15612 15613 if (nodes) { 15614 if (DomQuery.isArray(nodes)) { 15615 result.push.apply(result, nodes); 15616 } else { 15617 result.push(nodes); 15618 } 15619 } 15620 }); 15621 15622 // If traversing on multiple elements we might get the same elements twice 15623 if (this.length > 1) { 15624 result = DomQuery.unique(result); 15625 15626 if (name.indexOf('parents') === 0 || name === 'prevUntil') { 15627 result = result.reverse(); 15628 } 15629 } 15630 15631 result = DomQuery(result); 15632 15633 if (filter) { 15634 return result.filter(filter); 15635 } 15636 15637 return result; 15638 }; 15639 }); 15640 15641 /** 15642 * Returns true/false if the current set items matches the selector. 15643 * 15644 * @method is 15645 * @param {String} selector Selector to match the elements against. 15646 * @return {Boolean} True/false if the current set matches the selector. 15647 */ 15648 DomQuery.fn.is = function(selector) { 15649 return !!selector && this.filter(selector).length > 0; 15650 }; 15651 15652 DomQuery.fn.init.prototype = DomQuery.fn; 15653 15654 DomQuery.overrideDefaults = function(callback) { 15655 var defaults; 15656 15657 function sub(selector, context) { 15658 defaults = defaults || callback(); 15659
15660 if (arguments.length === 0) { 15661 selector = defaults.element; 15662 } 15663 15664 if (!context) { 15665 context = defaults.context; 15666 } 15667 15668 return new sub.fn.init(selector, context); 15669 } 15670 15671 DomQuery.extend(sub, this); 15672 15673 return sub; 15674 }; 15675 15676 function appendHooks(targetHooks, prop, hooks) { 15677 each(hooks, function(name, func) { 15678 targetHooks[name] = targetHooks[name] || {}; 15679 targetHooks[name][prop] = func; 15680 }); 15681 } 15682 15683 if (Env.ie && Env.ie < 8) { 15684 appendHooks(attrHooks, 'get', { 15685 maxlength: function(elm) { 15686 var value = elm.maxLength; 15687 15688 if (value === 0x7fffffff) { 15689 return undef; 15690 } 15691 15692 return value; 15693 }, 15694 15695 size: function(elm) { 15696 var value = elm.size; 15697 15698 if (value === 20) { 15699 return undef; 15700 } 15701 15702 return value; 15703 }, 15704 15705 'class': function(elm) { 15706 return elm.className; 15707 }, 15708 15709 style: function(elm) { 15710 var value = elm.style.cssText; 15711 15712 if (value.length === 0) { 15713 return undef; 15714 } 15715 15716 return value; 15717 } 15718 }); 15719 15720 appendHooks(attrHooks, 'set', { 15721 'class': function(elm, value) { 15722 elm.className = value; 15723 }, 15724 15725 style: function(elm, value) { 15726 elm.style.cssText = value; 15727 } 15728 }); 15729 } 15730 15731 if (Env.ie && Env.ie < 9) { 15732 /*jshint sub:true */ 15733 /*eslint dot-notation: 0*/ 15734 cssFix['float'] = 'styleFloat'; 15735 15736 appendHooks(cssHooks, 'set', { 15737 opacity: function(elm, value) { 15738 var style = elm.style; 15739 15740 if (value === null || value === '') { 15741 style.removeAttribute('filter'); 15742 } else { 15743 style.zoom = 1; 15744 style.filter = 'alpha(opacity=' + (value * 100) + ')'; 15745 } 15746 } 15747 }); 15748 } 15749 15750 DomQuery.attrHooks = attrHooks; 15751 DomQuery.cssHooks = cssHooks; 15752 15753 return DomQuery; 15754}); 15755 15756// Included from: js/tinymce/classes/html/Styles.js 15757 15758/** 15759 * Styles.js 15760 * 15761 * Released under LGPL License. 15762 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 15763 * 15764 * License: http://www.tinymce.com/license 15765 * Contributing: http://www.tinymce.com/contributing 15766 */ 15767 15768/** 15769 * This class is used to parse CSS styles it also compresses styles to reduce the output size. 15770 * 15771 * @example 15772 * var Styles = new tinymce.html.Styles({ 15773 * url_converter: function(url) { 15774 * return url; 15775 * } 15776 * }); 15777 * 15778 * styles = Styles.parse('border: 1px solid red'); 15779 * styles.color = 'red'; 15780 * 15781 * console.log(new tinymce.html.StyleSerializer().serialize(styles)); 15782 * 15783 * @class tinymce.html.Styles 15784 * @version 3.4 15785 */ 15786define("tinymce/html/Styles", [], function() { 15787 return function(settings, schema) { 15788 /*jshint maxlen:255 */ 15789 /*eslint max-len:0 */ 15790 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, 15791 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, 15792 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, 15793 trimRightRegExp = /\s+$/, 15794 undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF'; 15795 15796 settings = settings || {}; 15797 15798 if (schema) { 15799 validStyles = schema.getValidStyles(); 15800 invalidStyles = schema.getInvalidStyles(); 15801 } 15802 15803 encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' '); 15804 for (i = 0; i < encodingItems.length; i++) { 15805 encodingLookup[encodingItems[i]] = invisibleChar + i; 15806 encodingLookup[invisibleChar + i] = encodingItems[i]; 15807 } 15808 15809 function toHex(match, r, g, b) { 15810 function hex(val) { 15811 val = parseInt(val, 10).toString(16); 15812 15813 return val.length > 1 ? val : '0' + val; // 0 -> 00 15814 } 15815 15816 return '#' + hex(r) + hex(g) + hex(b); 15817 } 15818 15819 return { 15820 /** 15821 * Parses the specified RGB color value and returns a hex version of that color. 15822 * 15823 * @method toHex 15824 * @param {String} color RGB string value like rgb(1,2,3) 15825 * @return {String} Hex version of that RGB value like #FF00FF. 15826 */ 15827 toHex: function(color) { 15828 return color.replace(rgbRegExp, toHex); 15829 }, 15830 15831 /**
15832 * Parses the specified style value into an object collection. This parser will also 15833 * merge and remove any redundant items that browsers might have added. It will also convert non hex 15834 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 15835 * 15836 * @method parse 15837 * @param {String} css Style value to parse for example: border:1px solid red;. 15838 * @return {Object} Object representation of that style like {border: '1px solid red'} 15839 */ 15840 parse: function(css) { 15841 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter; 15842 var urlConverterScope = settings.url_converter_scope || this; 15843 15844 function compress(prefix, suffix, noJoin) { 15845 var top, right, bottom, left; 15846 15847 top = styles[prefix + '-top' + suffix]; 15848 if (!top) { 15849 return; 15850 } 15851 15852 right = styles[prefix + '-right' + suffix]; 15853 if (!right) { 15854 return; 15855 } 15856 15857 bottom = styles[prefix + '-bottom' + suffix]; 15858 if (!bottom) { 15859 return; 15860 } 15861 15862 left = styles[prefix + '-left' + suffix]; 15863 if (!left) { 15864 return; 15865 } 15866 15867 var box = [top, right, bottom, left]; 15868 i = box.length - 1; 15869 while (i--) { 15870 if (box[i] !== box[i + 1]) { 15871 break; 15872 } 15873 } 15874 15875 if (i > -1 && noJoin) { 15876 return; 15877 } 15878 15879 styles[prefix + suffix] = i == -1 ? box[0] : box.join(' '); 15880 delete styles[prefix + '-top' + suffix]; 15881 delete styles[prefix + '-right' + suffix]; 15882 delete styles[prefix + '-bottom' + suffix]; 15883 delete styles[prefix + '-left' + suffix]; 15884 } 15885 15886 /** 15887 * Checks if the specific style can be compressed in other words if all border-width are equal. 15888 */ 15889 function canCompress(key) { 15890 var value = styles[key], i; 15891 15892 if (!value) { 15893 return; 15894 } 15895 15896 value = value.split(' '); 15897 i = value.length; 15898 while (i--) { 15899 if (value[i] !== value[0]) { 15900 return false; 15901 } 15902 } 15903 15904 styles[key] = value[0]; 15905 15906 return true; 15907 } 15908 15909 /** 15910 * Compresses multiple styles into one style. 15911 */ 15912 function compress2(target, a, b, c) { 15913 if (!canCompress(a)) { 15914 return; 15915 } 15916 15917 if (!canCompress(b)) { 15918 return; 15919 } 15920 15921 if (!canCompress(c)) { 15922 return; 15923 } 15924 15925 // Compress 15926 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; 15927 delete styles[a]; 15928 delete styles[b]; 15929 delete styles[c]; 15930 } 15931 15932 // Encodes the specified string by replacing all \" \' ; : with _<num> 15933 function encode(str) { 15934 isEncoded = true; 15935 15936 return encodingLookup[str]; 15937 } 15938 15939 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc 15940 // It will also decode the \" \' if keep_slashes is set to fale or omitted 15941 function decode(str, keep_slashes) { 15942 if (isEncoded) { 15943 str = str.replace(/\uFEFF[0-9]/g, function(str) { 15944 return encodingLookup[str]; 15945 }); 15946 } 15947 15948 if (!keep_slashes) { 15949 str = str.replace(/\\([\'\";:])/g, "$1"); 15950 } 15951 15952 return str; 15953 } 15954 15955 function processUrl(match, url, url2, url3, str, str2) { 15956 str = str || str2; 15957 15958 if (str) { 15959 str = decode(str); 15960 15961 // Force strings into single quote format 15962 return "'" + str.replace(/\'/g, "\\'") + "'"; 15963 } 15964 15965 url = decode(url || url2 || url3); 15966 15967 if (!settings.allow_script_urls) { 15968 var scriptUrl = url.replace(/[\s\r\n]+/, ''); 15969 15970 if (/(java|vb)script:/i.test(scriptUrl)) { 15971 return ""; 15972 } 15973 15974 if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) { 15975 return ""; 15976 } 15977 } 15978 15979 // Convert the URL to relative/absolute depending on config 15980 if (urlConverter) { 15981 url = urlConverter.call(urlConverterScope, url, 'style'); 15982 } 15983 15984 // Output new URL format 15985 return "url('" + url.replace(/\'/g, "\\'") + "')"; 15986 } 15987 15988 if (css) { 15989 css = css.replace(/[\u0000-\u001F]/g, ''); 15990 15991 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing 15992 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { 15993 return str.replace(/[;:]/g, encode); 15994 }); 15995 15996 // Parse styles 15997 while ((matches = styleRegExp.exec(css))) { 15998 name = matches[1].replace(trimRightRegExp, '').toLowerCase(); 15999 value = matches[2].replace(trimRightRegExp, ''); 16000 16001 // Decode escaped sequences like \65 -> e 16002 /*jshint loopfunc:true*/ 16003 /*eslint no-loop-func:0 */ 16004 value = value.replace(/\\[0-9a-f]+/g, function(e) { 16005 return String.fromCharCode(parseInt(e.substr(1), 16)); 16006 }); 16007 16008 if (name && value.length > 0) { 16009 // Don't allow behavior name or expression/comments within the values 16010 if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) { 16011 continue; 16012 } 16013 16014 // Opera will produce 700 instead of bold in their style values 16015 if (name === 'font-weight' && value === '700') { 16016 value = 'bold'; 16017 } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED 16018 value = value.toLowerCase(); 16019 } 16020 16021 // Convert RGB colors to HEX 16022 value = value.replace(rgbRegExp, toHex); 16023 16024 // Convert URLs and force them into url('value') format 16025 value = value.replace(urlOrStrRegExp, processUrl); 16026 styles[name] = isEncoded ? decode(value, true) : value; 16027 } 16028 16029 styleRegExp.lastIndex = matches.index + matches[0].length; 16030 } 16031 // Compress the styles to reduce it's size for example IE will expand styles 16032 compress("border", "", true); 16033 compress("border", "-width"); 16034 compress("border", "-color"); 16035 compress("border", "-style"); 16036 compress("padding", ""); 16037 compress("margin", ""); 16038 compress2('border', 'border-width', 'border-style', 'border-color'); 16039 16040 // Remove pointless border, IE produces these 16041 if (styles.border === 'medium none') { 16042 delete styles.border; 16043 } 16044 16045 // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p> 16046 // So let us assume it shouldn't be there 16047 if (styles['border-image'] === 'none') { 16048 delete styles['border-image']; 16049 } 16050 } 16051 16052 return styles; 16053 }, 16054 16055 /** 16056 * Serializes the specified style object into a string. 16057 * 16058 * @method serialize 16059 * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'} 16060 * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized. 16061 * @return {String} String representation of the style object for example: border: 1px solid red. 16062 */ 16063 serialize: function(styles, elementName) { 16064 var css = '', name, value; 16065 16066 function serializeStyles(name) { 16067 var styleList, i, l, value; 16068 16069 styleList = validStyles[name]; 16070 if (styleList) { 16071 for (i = 0, l = styleList.length; i < l; i++) { 16072 name = styleList[i]; 16073 value = styles[name]; 16074 16075 if (value !== undef && value.length > 0) { 16076 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 16077 } 16078 } 16079 } 16080 } 16081 16082 function isValid(name, elementName) { 16083 var styleMap; 16084 16085 styleMap = invalidStyles['*']; 16086 if (styleMap && styleMap[name]) { 16087 return false;
16088 } 16089 16090 styleMap = invalidStyles[elementName]; 16091 if (styleMap && styleMap[name]) { 16092 return false; 16093 } 16094 16095 return true; 16096 } 16097 16098 // Serialize styles according to schema 16099 if (elementName && validStyles) { 16100 // Serialize global styles and element specific styles 16101 serializeStyles('*'); 16102 serializeStyles(elementName); 16103 } else { 16104 // Output the styles in the order they are inside the object 16105 for (name in styles) { 16106 value = styles[name]; 16107 16108 if (value !== undef && value.length > 0) { 16109 if (!invalidStyles || isValid(name, elementName)) { 16110 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; 16111 } 16112 } 16113 } 16114 } 16115 16116 return css; 16117 } 16118 }; 16119 }; 16120}); 16121 16122// Included from: js/tinymce/classes/dom/TreeWalker.js 16123 16124/** 16125 * TreeWalker.js 16126 * 16127 * Released under LGPL License. 16128 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 16129 * 16130 * License: http://www.tinymce.com/license 16131 * Contributing: http://www.tinymce.com/contributing 16132 */ 16133 16134/** 16135 * TreeWalker class enables you to walk the DOM in a linear manner. 16136 * 16137 * @class tinymce.dom.TreeWalker 16138 * @example 16139 * var walker = new tinymce.dom.TreeWalker(startNode); 16140 * 16141 * do { 16142 * console.log(walker.current()); 16143 * } while (walker.next()); 16144 */ 16145define("tinymce/dom/TreeWalker", [], function() { 16146 /** 16147 * Constructs a new TreeWalker instance. 16148 * 16149 * @constructor 16150 * @method TreeWalker 16151 * @param {Node} startNode Node to start walking from. 16152 * @param {node} rootNode Optional root node to never walk out of. 16153 */ 16154 return function(startNode, rootNode) { 16155 var node = startNode; 16156 16157 function findSibling(node, startName, siblingName, shallow) { 16158 var sibling, parent; 16159 16160 if (node) { 16161 // Walk into nodes if it has a start 16162 if (!shallow && node[startName]) { 16163 return node[startName]; 16164 } 16165 16166 // Return the sibling if it has one 16167 if (node != rootNode) { 16168 sibling = node[siblingName]; 16169 if (sibling) { 16170 return sibling; 16171 } 16172 16173 // Walk up the parents to look for siblings 16174 for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) { 16175 sibling = parent[siblingName]; 16176 if (sibling) { 16177 return sibling; 16178 } 16179 } 16180 } 16181 } 16182 } 16183 16184 /** 16185 * Returns the current node. 16186 * 16187 * @method current 16188 * @return {Node} Current node where the walker is. 16189 */ 16190 this.current = function() { 16191 return node; 16192 }; 16193 16194 /** 16195 * Walks to the next node in tree. 16196 * 16197 * @method next 16198 * @return {Node} Current node where the walker is after moving to the next node. 16199 */ 16200 this.next = function(shallow) { 16201 node = findSibling(node, 'firstChild', 'nextSibling', shallow); 16202 return node; 16203 }; 16204 16205 /** 16206 * Walks to the previous node in tree. 16207 * 16208 * @method prev 16209 * @return {Node} Current node where the walker is after moving to the previous node. 16210 */ 16211 this.prev = function(shallow) { 16212 node = findSibling(node, 'lastChild', 'previousSibling', shallow); 16213 return node; 16214 }; 16215 }; 16216}); 16217 16218// Included from: js/tinymce/classes/dom/Range.js 16219 16220/** 16221 * Range.js 16222 * 16223 * Released under LGPL License. 16224 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 16225 * 16226 * License: http://www.tinymce.com/license 16227 * Contributing: http://www.tinymce.com/contributing 16228 */ 16229 16230/** 16231 * Old IE Range. 16232 * 16233 * @private 16234 * @class tinymce.dom.Range 16235 */ 16236define("tinymce/dom/Range", [ 16237 "tinymce/util/Tools" 16238], function(Tools) { 16239 // Range constructor 16240 function Range(dom) { 16241 var self = this, 16242 doc = dom.doc, 16243 EXTRACT = 0, 16244 CLONE = 1, 16245 DELETE = 2, 16246 TRUE = true, 16247 FALSE = false, 16248 START_OFFSET = 'startOffset', 16249 START_CONTAINER = 'startContainer', 16250 END_CONTAINER = 'endContainer', 16251 END_OFFSET = 'endOffset', 16252 extend = Tools.extend, 16253 nodeIndex = dom.nodeIndex; 16254 16255 function createDocumentFragment() { 16256 return doc.createDocumentFragment(); 16257 } 16258 16259 function setStart(n, o) { 16260 _setEndPoint(TRUE, n, o); 16261 } 16262 16263 function setEnd(n, o) { 16264 _setEndPoint(FALSE, n, o); 16265 } 16266 16267 function setStartBefore(n) { 16268 setStart(n.parentNode, nodeIndex(n)); 16269 } 16270 16271 function setStartAfter(n) { 16272 setStart(n.parentNode, nodeIndex(n) + 1); 16273 } 16274 16275 function setEndBefore(n) { 16276 setEnd(n.parentNode, nodeIndex(n)); 16277 } 16278 16279 function setEndAfter(n) { 16280 setEnd(n.parentNode, nodeIndex(n) + 1); 16281 } 16282 16283 function collapse(ts) { 16284 if (ts) { 16285 self[END_CONTAINER] = self[START_CONTAINER]; 16286 self[END_OFFSET] = self[START_OFFSET]; 16287 } else { 16288 self[START_CONTAINER] = self[END_CONTAINER]; 16289 self[START_OFFSET] = self[END_OFFSET]; 16290 } 16291 16292 self.collapsed = TRUE; 16293 } 16294 16295 function selectNode(n) { 16296 setStartBefore(n); 16297 setEndAfter(n); 16298 } 16299 16300 function selectNodeContents(n) { 16301 setStart(n, 0); 16302 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); 16303 } 16304 16305 function compareBoundaryPoints(h, r) { 16306 var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET], 16307 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; 16308 16309 // Check START_TO_START 16310 if (h === 0) { 16311 return _compareBoundaryPoints(sc, so, rsc, rso); 16312 } 16313 16314 // Check START_TO_END 16315 if (h === 1) { 16316 return _compareBoundaryPoints(ec, eo, rsc, rso); 16317 } 16318 16319 // Check END_TO_END 16320 if (h === 2) { 16321 return _compareBoundaryPoints(ec, eo, rec, reo); 16322 } 16323 16324 // Check END_TO_START 16325 if (h === 3) { 16326 return _compareBoundaryPoints(sc, so, rec, reo); 16327 } 16328 } 16329 16330 function deleteContents() { 16331 _traverse(DELETE); 16332 } 16333 16334 function extractContents() { 16335 return _traverse(EXTRACT); 16336 } 16337 16338 function cloneContents() { 16339 return _traverse(CLONE); 16340 } 16341
16342 function insertNode(n) { 16343 var startContainer = this[START_CONTAINER], 16344 startOffset = this[START_OFFSET], nn, o; 16345 16346 // Node is TEXT_NODE or CDATA 16347 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { 16348 if (!startOffset) { 16349 // At the start of text 16350 startContainer.parentNode.insertBefore(n, startContainer); 16351 } else if (startOffset >= startContainer.nodeValue.length) { 16352 // At the end of text 16353 dom.insertAfter(n, startContainer); 16354 } else { 16355 // Middle, need to split 16356 nn = startContainer.splitText(startOffset); 16357 startContainer.parentNode.insertBefore(n, nn); 16358 } 16359 } else { 16360 // Insert element node 16361 if (startContainer.childNodes.length > 0) { 16362 o = startContainer.childNodes[startOffset]; 16363 } 16364 16365 if (o) { 16366 startContainer.insertBefore(n, o); 16367 } else { 16368 if (startContainer.nodeType == 3) { 16369 dom.insertAfter(n, startContainer); 16370 } else { 16371 startContainer.appendChild(n); 16372 } 16373 } 16374 } 16375 } 16376 16377 function surroundContents(n) { 16378 var f = self.extractContents(); 16379 16380 self.insertNode(n); 16381 n.appendChild(f); 16382 self.selectNode(n); 16383 } 16384 16385 function cloneRange() { 16386 return extend(new Range(dom), { 16387 startContainer: self[START_CONTAINER], 16388 startOffset: self[START_OFFSET], 16389 endContainer: self[END_CONTAINER], 16390 endOffset: self[END_OFFSET], 16391 collapsed: self.collapsed, 16392 commonAncestorContainer: self.commonAncestorContainer 16393 }); 16394 } 16395 16396 // Private methods 16397 16398 function _getSelectedNode(container, offset) { 16399 var child; 16400 16401 // TEXT_NODE 16402 if (container.nodeType == 3) { 16403 return container; 16404 } 16405 16406 if (offset < 0) { 16407 return container; 16408 } 16409 16410 child = container.firstChild; 16411 while (child && offset > 0) { 16412 --offset; 16413 child = child.nextSibling; 16414 } 16415 16416 if (child) { 16417 return child; 16418 } 16419 16420 return container; 16421 } 16422 16423 function _isCollapsed() { 16424 return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]); 16425 } 16426 16427 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
16428 var c, offsetC, n, cmnRoot, childA, childB; 16429 16430 // In the first case the boundary-points have the same container. A is before B 16431 // if its offset is less than the offset of B, A is equal to B if its offset is 16432 // equal to the offset of B, and A is after B if its offset is greater than the 16433 // offset of B. 16434 if (containerA == containerB) { 16435 if (offsetA == offsetB) { 16436 return 0; // equal 16437 } 16438 16439 if (offsetA < offsetB) { 16440 return -1; // before 16441 } 16442 16443 return 1; // after 16444 } 16445 16446 // In the second case a child node C of the container of A is an ancestor 16447 // container of B. In this case, A is before B if the offset of A is less than or 16448 // equal to the index of the child node C and A is after B otherwise. 16449 c = containerB; 16450 while (c && c.parentNode != containerA) { 16451 c = c.parentNode; 16452 } 16453 16454 if (c) { 16455 offsetC = 0; 16456 n = containerA.firstChild; 16457 16458 while (n != c && offsetC < offsetA) { 16459 offsetC++; 16460 n = n.nextSibling; 16461 } 16462 16463 if (offsetA <= offsetC) { 16464 return -1; // before 16465 } 16466 16467 return 1; // after 16468 } 16469 16470 // In the third case a child node C of the container of B is an ancestor container 16471 // of A. In this case, A is before B if the index of the child node C is less than 16472 // the offset of B and A is after B otherwise. 16473 c = containerA; 16474 while (c && c.parentNode != containerB) { 16475 c = c.parentNode; 16476 } 16477 16478 if (c) { 16479 offsetC = 0; 16480 n = containerB.firstChild; 16481 16482 while (n != c && offsetC < offsetB) { 16483 offsetC++; 16484 n = n.nextSibling; 16485 } 16486 16487 if (offsetC < offsetB) { 16488 return -1; // before 16489 } 16490 16491 return 1; // after 16492 } 16493 16494 // In the fourth case, none of three other cases hold: the containers of A and B 16495 // are siblings or descendants of sibling nodes. In this case, A is before B if 16496 // the container of A is before the container of B in a pre-order traversal of the 16497 // Ranges' context tree and A is after B otherwise. 16498 cmnRoot = dom.findCommonAncestor(containerA, containerB); 16499 childA = containerA; 16500 16501 while (childA && childA.parentNode != cmnRoot) { 16502 childA = childA.parentNode; 16503 } 16504 16505 if (!childA) { 16506 childA = cmnRoot; 16507 } 16508 16509 childB = containerB; 16510 while (childB && childB.parentNode != cmnRoot) { 16511 childB = childB.parentNode; 16512 } 16513 16514 if (!childB) { 16515 childB = cmnRoot; 16516 } 16517 16518 if (childA == childB) { 16519 return 0; // equal 16520 } 16521 16522 n = cmnRoot.firstChild; 16523 while (n) { 16524 if (n == childA) { 16525 return -1; // before 16526 } 16527 16528 if (n == childB) { 16529 return 1; // after 16530 } 16531 16532 n = n.nextSibling; 16533 } 16534 } 16535 16536 function _setEndPoint(st, n, o) { 16537 var ec, sc; 16538 16539 if (st) { 16540 self[START_CONTAINER] = n; 16541 self[START_OFFSET] = o; 16542 } else { 16543 self[END_CONTAINER] = n; 16544 self[END_OFFSET] = o; 16545 } 16546 16547 // If one boundary-point of a Range is set to have a root container 16548 // other than the current one for the Range, the Range is collapsed to 16549 // the new position. This enforces the restriction that both boundary- 16550 // points of a Range must have the same root container. 16551 ec = self[END_CONTAINER]; 16552 while (ec.parentNode) { 16553 ec = ec.parentNode; 16554 } 16555 16556 sc = self[START_CONTAINER]; 16557 while (sc.parentNode) { 16558 sc = sc.parentNode; 16559 } 16560 16561 if (sc == ec) { 16562 // The start position of a Range is guaranteed to never be after the 16563 // end position. To enforce this restriction, if the start is set to 16564 // be at a position after the end, the Range is collapsed to that 16565 // position. 16566 if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) { 16567 self.collapse(st); 16568 } 16569 } else { 16570 self.collapse(st); 16571 } 16572 16573 self.collapsed = _isCollapsed(); 16574 self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]); 16575 } 16576 16577 function _traverse(how) { 16578 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; 16579 16580 if (self[START_CONTAINER] == self[END_CONTAINER]) { 16581 return _traverseSameContainer(how); 16582 } 16583 16584 for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 16585 if (p == self[START_CONTAINER]) { 16586 return _traverseCommonStartContainer(c, how); 16587 } 16588 16589 ++endContainerDepth; 16590 } 16591 16592 for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { 16593 if (p == self[END_CONTAINER]) { 16594 return _traverseCommonEndContainer(c, how); 16595 } 16596 16597 ++startContainerDepth; 16598 } 16599 16600 depthDiff = startContainerDepth - endContainerDepth; 16601 16602 startNode = self[START_CONTAINER]; 16603 while (depthDiff > 0) { 16604 startNode = startNode.parentNode; 16605 depthDiff--; 16606 } 16607 16608 endNode = self[END_CONTAINER]; 16609 while (depthDiff < 0) { 16610 endNode = endNode.parentNode; 16611 depthDiff++; 16612 } 16613 16614 // ascend the ancestor hierarchy until we have a common parent. 16615 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { 16616 startNode = sp; 16617 endNode = ep; 16618 } 16619 16620 return _traverseCommonAncestors(startNode, endNode, how); 16621 } 16622 16623 function _traverseSameContainer(how) { 16624 var frag, s, sub, n, cnt, sibling, xferNode, start, len; 16625 16626 if (how != DELETE) { 16627 frag = createDocumentFragment(); 16628 } 16629 16630 // If selection is empty, just return the fragment 16631 if (self[START_OFFSET] == self[END_OFFSET]) { 16632 return frag; 16633 } 16634 16635 // Text node needs special case handling 16636 if (self[START_CONTAINER].nodeType == 3) { // TEXT_NODE 16637 // get the substring 16638 s = self[START_CONTAINER].nodeValue; 16639 sub = s.substring(self[START_OFFSET], self[END_OFFSET]); 16640 16641 // set the original text node to its new value 16642 if (how != CLONE) { 16643 n = self[START_CONTAINER]; 16644 start = self[START_OFFSET]; 16645 len = self[END_OFFSET] - self[START_OFFSET]; 16646 16647 if (start === 0 && len >= n.nodeValue.length - 1) { 16648 n.parentNode.removeChild(n); 16649 } else { 16650 n.deleteData(start, len); 16651 } 16652 16653 // Nothing is partially selected, so collapse to start point 16654 self.collapse(TRUE); 16655 } 16656 16657 if (how == DELETE) { 16658 return; 16659 } 16660 16661 if (sub.length > 0) { 16662 frag.appendChild(doc.createTextNode(sub)); 16663 } 16664 16665 return frag; 16666 } 16667 16668 // Copy nodes between the start/end offsets. 16669 n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]); 16670 cnt = self[END_OFFSET] - self[START_OFFSET]; 16671 16672 while (n && cnt > 0) { 16673 sibling = n.nextSibling; 16674 xferNode = _traverseFullySelected(n, how); 16675 16676 if (frag) { 16677 frag.appendChild(xferNode); 16678 } 16679 16680 --cnt; 16681 n = sibling; 16682 } 16683 16684 // Nothing is partially selected, so collapse to start point 16685 if (how != CLONE) { 16686 self.collapse(TRUE); 16687 } 16688 16689 return frag; 16690 } 16691 16692 function _traverseCommonStartContainer(endAncestor, how) { 16693 var frag, n, endIdx, cnt, sibling, xferNode; 16694 16695 if (how != DELETE) { 16696 frag = createDocumentFragment(); 16697 } 16698 16699 n = _traverseRightBoundary(endAncestor, how); 16700 16701 if (frag) { 16702 frag.appendChild(n); 16703 } 16704 16705 endIdx = nodeIndex(endAncestor); 16706 cnt = endIdx - self[START_OFFSET]; 16707 16708 if (cnt <= 0) { 16709 // Collapse to just before the endAncestor, which 16710 // is partially selected. 16711 if (how != CLONE) { 16712 self.setEndBefore(endAncestor); 16713 self.collapse(FALSE); 16714 } 16715 16716 return frag; 16717 } 16718 16719 n = endAncestor.previousSibling; 16720 while (cnt > 0) { 16721 sibling = n.previousSibling; 16722 xferNode = _traverseFullySelected(n, how); 16723 16724 if (frag) { 16725 frag.insertBefore(xferNode, frag.firstChild); 16726 } 16727 16728 --cnt; 16729 n = sibling; 16730 } 16731 16732 // Collapse to just before the endAncestor, which 16733 // is partially selected. 16734 if (how != CLONE) { 16735 self.setEndBefore(endAncestor); 16736 self.collapse(FALSE); 16737 } 16738 16739 return frag; 16740 } 16741 16742 function _traverseCommonEndContainer(startAncestor, how) { 16743 var frag, startIdx, n, cnt, sibling, xferNode; 16744 16745 if (how != DELETE) { 16746 frag = createDocumentFragment(); 16747 } 16748 16749 n = _traverseLeftBoundary(startAncestor, how); 16750 if (frag) { 16751 frag.appendChild(n); 16752 } 16753 16754 startIdx = nodeIndex(startAncestor); 16755 ++startIdx; // Because we already traversed it 16756 16757 cnt = self[END_OFFSET] - startIdx; 16758 n = startAncestor.nextSibling; 16759 while (n && cnt > 0) { 16760 sibling = n.nextSibling; 16761 xferNode = _traverseFullySelected(n, how); 16762 16763 if (frag) { 16764 frag.appendChild(xferNode); 16765 } 16766 16767 --cnt; 16768 n = sibling; 16769 } 16770 16771 if (how != CLONE) { 16772 self.setStartAfter(startAncestor); 16773 self.collapse(TRUE); 16774 } 16775 16776 return frag; 16777 } 16778 16779 function _traverseCommonAncestors(startAncestor, endAncestor, how) { 16780 var n, frag, startOffset, endOffset, cnt, sibling, nextSibling; 16781 16782 if (how != DELETE) { 16783 frag = createDocumentFragment(); 16784 } 16785 16786 n = _traverseLeftBoundary(startAncestor, how); 16787 if (frag) { 16788 frag.appendChild(n); 16789 } 16790 16791 startOffset = nodeIndex(startAncestor); 16792 endOffset = nodeIndex(endAncestor); 16793 ++startOffset; 16794 16795 cnt = endOffset - startOffset; 16796 sibling = startAncestor.nextSibling; 16797 16798 while (cnt > 0) { 16799 nextSibling = sibling.nextSibling; 16800 n = _traverseFullySelected(sibling, how); 16801 16802 if (frag) { 16803 frag.appendChild(n); 16804 } 16805 16806 sibling = nextSibling; 16807 --cnt; 16808 } 16809 16810 n = _traverseRightBoundary(endAncestor, how); 16811 16812 if (frag) { 16813 frag.appendChild(n); 16814 } 16815 16816 if (how != CLONE) { 16817 self.setStartAfter(startAncestor); 16818 self.collapse(TRUE); 16819 } 16820 16821 return frag; 16822 } 16823 16824 function _traverseRightBoundary(root, how) {
16825 var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent; 16826 var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER]; 16827 16828 if (next == root) { 16829 return _traverseNode(next, isFullySelected, FALSE, how); 16830 } 16831 16832 parent = next.parentNode; 16833 clonedParent = _traverseNode(parent, FALSE, FALSE, how); 16834 16835 while (parent) { 16836 while (next) { 16837 prevSibling = next.previousSibling; 16838 clonedChild = _traverseNode(next, isFullySelected, FALSE, how); 16839 16840 if (how != DELETE) { 16841 clonedParent.insertBefore(clonedChild, clonedParent.firstChild); 16842 } 16843 16844 isFullySelected = TRUE; 16845 next = prevSibling; 16846 } 16847 16848 if (parent == root) { 16849 return clonedParent; 16850 } 16851 16852 next = parent.previousSibling; 16853 parent = parent.parentNode; 16854 16855 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); 16856 16857 if (how != DELETE) { 16858 clonedGrandParent.appendChild(clonedParent); 16859 } 16860 16861 clonedParent = clonedGrandParent; 16862 } 16863 } 16864 16865 function _traverseLeftBoundary(root, how) { 16866 var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER]; 16867 var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; 16868 16869 if (next == root) { 16870 return _traverseNode(next, isFullySelected, TRUE, how); 16871 } 16872 16873 parent = next.parentNode; 16874 clonedParent = _traverseNode(parent, FALSE, TRUE, how); 16875 16876 while (parent) { 16877 while (next) { 16878 nextSibling = next.nextSibling; 16879 clonedChild = _traverseNode(next, isFullySelected, TRUE, how); 16880 16881 if (how != DELETE) { 16882 clonedParent.appendChild(clonedChild); 16883 } 16884 16885 isFullySelected = TRUE; 16886 next = nextSibling; 16887 } 16888 16889 if (parent == root) { 16890 return clonedParent; 16891 } 16892 16893 next = parent.nextSibling; 16894 parent = parent.parentNode; 16895 16896 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); 16897 16898 if (how != DELETE) { 16899 clonedGrandParent.appendChild(clonedParent); 16900 } 16901 16902 clonedParent = clonedGrandParent; 16903 } 16904 } 16905 16906 function _traverseNode(n, isFullySelected, isLeft, how) { 16907 var txtValue, newNodeValue, oldNodeValue, offset, newNode; 16908 16909 if (isFullySelected) { 16910 return _traverseFullySelected(n, how); 16911 } 16912 16913 // TEXT_NODE 16914 if (n.nodeType == 3) { 16915 txtValue = n.nodeValue; 16916 16917 if (isLeft) { 16918 offset = self[START_OFFSET]; 16919 newNodeValue = txtValue.substring(offset); 16920 oldNodeValue = txtValue.substring(0, offset); 16921 } else { 16922 offset = self[END_OFFSET]; 16923 newNodeValue = txtValue.substring(0, offset); 16924 oldNodeValue = txtValue.substring(offset); 16925 } 16926 16927 if (how != CLONE) { 16928 n.nodeValue = oldNodeValue; 16929 } 16930 16931 if (how == DELETE) { 16932 return; 16933 } 16934 16935 newNode = dom.clone(n, FALSE); 16936 newNode.nodeValue = newNodeValue; 16937 16938 return newNode; 16939 } 16940 16941 if (how == DELETE) { 16942 return; 16943 } 16944 16945 return dom.clone(n, FALSE); 16946 } 16947 16948 function _traverseFullySelected(n, how) { 16949 if (how != DELETE) { 16950 return how == CLONE ? dom.clone(n, TRUE) : n; 16951 } 16952 16953 n.parentNode.removeChild(n); 16954 } 16955 16956 function toStringIE() { 16957 return dom.create('body', null, cloneContents()).outerText; 16958 } 16959 16960 extend(self, { 16961 // Initial states 16962 startContainer: doc, 16963 startOffset: 0, 16964 endContainer: doc, 16965 endOffset: 0, 16966 collapsed: TRUE, 16967 commonAncestorContainer: doc, 16968 16969 // Range constants 16970 START_TO_START: 0, 16971 START_TO_END: 1, 16972 END_TO_END: 2, 16973 END_TO_START: 3, 16974 16975 // Public methods 16976 setStart: setStart, 16977 setEnd: setEnd, 16978 setStartBefore: setStartBefore, 16979 setStartAfter: setStartAfter, 16980 setEndBefore: setEndBefore, 16981 setEndAfter: setEndAfter, 16982 collapse: collapse, 16983 selectNode: selectNode, 16984 selectNodeContents: selectNodeContents, 16985 compareBoundaryPoints: compareBoundaryPoints, 16986 deleteContents: deleteContents, 16987 extractContents: extractContents, 16988 cloneContents: cloneContents, 16989 insertNode: insertNode, 16990 surroundContents: surroundContents, 16991 cloneRange: cloneRange, 16992 toStringIE: toStringIE 16993 }); 16994 16995 return self; 16996 } 16997 16998 // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype 16999 Range.prototype.toString = function() { 17000 return this.toStringIE(); 17001 }; 17002 17003 return Range; 17004}); 17005 17006// Included from: js/tinymce/classes/html/Entities.js 17007 17008/** 17009 * Entities.js 17010 * 17011 * Released under LGPL License. 17012 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 17013 * 17014 * License: http://www.tinymce.com/license 17015 * Contributing: http://www.tinymce.com/contributing 17016 */ 17017 17018/*jshint bitwise:false */ 17019/*eslint no-bitwise:0 */ 17020
17021/** 17022 * Entity encoder class. 17023 * 17024 * @class tinymce.html.Entities 17025 * @static 17026 * @version 3.4 17027 */ 17028define("tinymce/html/Entities", [ 17029 "tinymce/util/Tools" 17030], function(Tools) { 17031 var makeMap = Tools.makeMap; 17032 17033 var namedEntities, baseEntities, reverseEntities, 17034 attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 17035 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 17036 rawCharsRegExp = /[<>&\"\']/g, 17037 entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi, 17038 asciiMap = { 17039 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020", 17040 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152", 17041 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022", 17042 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A", 17043 156: "\u0153", 158: "\u017E", 159: "\u0178" 17044 }; 17045 17046 // Raw entities 17047 baseEntities = { 17048 '\"': '"', // Needs to be escaped since the YUI compressor would otherwise break the code 17049 "'": ''', 17050 '<': '<', 17051 '>': '>', 17052 '&': '&', 17053 '\u0060': '`' 17054 }; 17055 17056 // Reverse lookup table for raw entities 17057 reverseEntities = { 17058 '<': '<', 17059 '>': '>', 17060 '&': '&', 17061 '"': '"', 17062 ''': "'" 17063 }; 17064 17065 // Decodes text by using the browser 17066 function nativeDecode(text) { 17067 var elm; 17068 17069 elm = document.createElement("div"); 17070 elm.innerHTML = text; 17071 17072 return elm.textContent || elm.innerText || text; 17073 } 17074 17075 // Build a two way lookup table for the entities 17076 function buildEntitiesLookup(items, radix) { 17077 var i, chr, entity, lookup = {}; 17078 17079 if (items) { 17080 items = items.split(','); 17081 radix = radix || 10; 17082 17083 // Build entities lookup table 17084 for (i = 0; i < items.length; i += 2) { 17085 chr = String.fromCharCode(parseInt(items[i], radix)); 17086 17087 // Only add non base entities 17088 if (!baseEntities[chr]) { 17089 entity = '&' + items[i + 1] + ';'; 17090 lookup[chr] = entity; 17091 lookup[entity] = chr; 17092 } 17093 } 17094 17095 return lookup; 17096 } 17097 } 17098 17099 // Unpack entities lookup where the numbers are in radix 32 to reduce the size 17100 namedEntities = buildEntitiesLookup( 17101 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + 17102 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + 17103 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + 17104 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + 17105 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + 17106 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + 17107 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + 17108 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + 17109 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + 17110 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 17111 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 17112 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 17113 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 17114 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 17115 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + 17116 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + 17117 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + 17118 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + 17119 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + 17120 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8
17120k4,' + 17121 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 17122 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 17123 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + 17124 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + 17125 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32); 17126 17127 var Entities = { 17128 /** 17129 * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded. 17130 * 17131 * @method encodeRaw 17132 * @param {String} text Text to encode. 17133 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 17134 * @return {String} Entity encoded text. 17135 */ 17136 encodeRaw: function(text, attr) { 17137 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 17138 return baseEntities[chr] || chr; 17139 }); 17140 }, 17141 17142 /** 17143 * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents 17144 * since it doesn't know if the context is within a attribute or text node. This was added for compatibility 17145 * and is exposed as the DOMUtils.encode function. 17146 * 17147 * @method encodeAllRaw 17148 * @param {String} text Text to encode. 17149 * @return {String} Entity encoded text. 17150 */ 17151 encodeAllRaw: function(text) { 17152 return ('' + text).replace(rawCharsRegExp, function(chr) { 17153 return baseEntities[chr] || chr; 17154 }); 17155 }, 17156 17157 /** 17158 * Encodes the specified string using numeric entities. The core entities will be 17159 * encoded as named ones but all non lower ascii characters will be encoded into numeric entities. 17160 * 17161 * @method encodeNumeric 17162 * @param {String} text Text to encode. 17163 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 17164 * @return {String} Entity encoded text. 17165 */ 17166 encodeNumeric: function(text, attr) { 17167 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 17168 // Multi byte sequence convert it to a single entity 17169 if (chr.length > 1) { 17170 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; 17171 } 17172 17173 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; 17174 }); 17175 }, 17176 17177 /** 17178 * Encodes the specified string using named entities. The core entities will be encoded 17179 * as named ones but all non lower ascii characters will be encoded into named entities. 17180 * 17181 * @method encodeNamed 17182 * @param {String} text Text to encode. 17183 * @param {Boolean} attr Optional flag to specify if the text is attribute contents. 17184 * @param {Object} entities Optional parameter with entities to use. 17185 * @return {String} Entity encoded text. 17186 */ 17187 encodeNamed: function(text, attr, entities) { 17188 entities = entities || namedEntities; 17189 17190 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 17191 return baseEntities[chr] || entities[chr] || chr; 17192 }); 17193 }, 17194 17195 /** 17196 * Returns an encode function based on the name(s) and it's optional entities. 17197 * 17198 * @method getEncodeFunc 17199 * @param {String} name Comma separated list of encoders for example named,numeric. 17200 * @param {String} entities Optional parameter with entities to use instead of the built in set. 17201 * @return {function} Encode function to be used. 17202 */ 17203 getEncodeFunc: function(name, entities) { 17204 entities = buildEntitiesLookup(entities) || namedEntities; 17205 17206 function encodeNamedAndNumeric(text, attr) { 17207 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { 17208 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; 17209 }); 17210 } 17211 17212 function encodeCustomNamed(text, attr) { 17213 return Entities.encodeNamed(text, attr, entities); 17214 } 17215 17216 // Replace + with , to be compatible with previous TinyMCE versions 17217 name = makeMap(name.replace(/\+/g, ',')); 17218 17219 // Named and numeric encoder 17220 if (name.named && name.numeric) { 17221 return encodeNamedAndNumeric; 17222 } 17223 17224 // Named encoder 17225 if (name.named) { 17226 // Custom names 17227 if (entities) { 17228 return encodeCustomNamed; 17229 } 17230 17231 return Entities.encodeNamed; 17232 } 17233 17234 // Numeric 17235 if (name.numeric) { 17236 return Entities.encodeNumeric; 17237 } 17238 17239 // Raw encoder 17240 return Entities.encodeRaw; 17241 }, 17242 17243 /** 17244 * Decodes the specified string, this will replace entities with raw UTF characters. 17245 * 17246 * @method decode 17247 * @param {String} text Text to entity decode. 17248 * @return {String} Entity decoded string. 17249 */ 17250 decode: function(text) { 17251 return text.replace(entityRegExp, function(all, numeric) { 17252 if (numeric) { 17253 if (numeric.charAt(0).toLowerCase() === 'x') { 17254 numeric = parseInt(numeric.substr(1), 16); 17255 } else { 17256 numeric = parseInt(numeric, 10); 17257 } 17258
17259 // Support upper UTF 17260 if (numeric > 0xFFFF) { 17261 numeric -= 0x10000; 17262 17263 return String.fromCharCode(0xD800 + (numeric >> 10), 0xDC00 + (numeric & 0x3FF)); 17264 } 17265 17266 return asciiMap[numeric] || String.fromCharCode(numeric); 17267 } 17268 17269 return reverseEntities[all] || namedEntities[all] || nativeDecode(all); 17270 }); 17271 } 17272 }; 17273 17274 return Entities; 17275}); 17276 17277// Included from: js/tinymce/classes/dom/StyleSheetLoader.js 17278 17279/** 17280 * StyleSheetLoader.js 17281 * 17282 * Released under LGPL License. 17283 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 17284 * 17285 * License: http://www.tinymce.com/license 17286 * Contributing: http://www.tinymce.com/contributing 17287 */ 17288 17289/** 17290 * This class handles loading of external stylesheets and fires events when these are loaded. 17291 * 17292 * @class tinymce.dom.StyleSheetLoader 17293 * @private 17294 */ 17295define("tinymce/dom/StyleSheetLoader", [ 17296 "tinymce/util/Tools", 17297 "tinymce/util/Delay" 17298], function(Tools, Delay) { 17299 "use strict"; 17300 17301 return function(document, settings) { 17302 var idCount = 0, loadedStates = {}, maxLoadTime; 17303 17304 settings = settings || {}; 17305 maxLoadTime = settings.maxLoadTime || 5000; 17306 17307 function appendToHead(node) { 17308 document.getElementsByTagName('head')[0].appendChild(node); 17309 } 17310 17311 /** 17312 * Loads the specified css style sheet file and call the loadedCallback once it's finished loading. 17313 * 17314 * @method load 17315 * @param {String} url Url to be loaded. 17316 * @param {Function} loadedCallback Callback to be executed when loaded. 17317 * @param {Function} errorCallback Callback to be executed when failed loading. 17318 */ 17319 function load(url, loadedCallback, errorCallback) { 17320 var link, style, startTime, state; 17321 17322 function passed() { 17323 var callbacks = state.passed, i = callbacks.length; 17324 17325 while (i--) { 17326 callbacks[i](); 17327 } 17328 17329 state.status = 2; 17330 state.passed = []; 17331 state.failed = []; 17332 } 17333 17334 function failed() { 17335 var callbacks = state.failed, i = callbacks.length; 17336 17337 while (i--) { 17338 callbacks[i](); 17339 } 17340 17341 state.status = 3; 17342 state.passed = []; 17343 state.failed = []; 17344 } 17345 17346 // Sniffs for older WebKit versions that have the link.onload but a broken one 17347 function isOldWebKit() { 17348 var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/); 17349 return !!(webKitChunks && webKitChunks[1] < 536); 17350 } 17351 17352 // Calls the waitCallback until the test returns true or the timeout occurs 17353 function wait(testCallback, waitCallback) { 17354 if (!testCallback()) { 17355 // Wait for timeout 17356 if ((new Date().getTime()) - startTime < maxLoadTime) { 17357 Delay.setTimeout(waitCallback); 17358 } else { 17359 failed(); 17360 } 17361 } 17362 } 17363 17364 // Workaround for WebKit that doesn't properly support the onload event for link elements 17365 // Or WebKit that fires the onload event before the StyleSheet is added to the document 17366 function waitForWebKitLinkLoaded() { 17367 wait(function() { 17368 var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner; 17369 17370 while (i--) { 17371 styleSheet = styleSheets[i]; 17372 owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement; 17373 if (owner && owner.id === link.id) { 17374 passed(); 17375 return true; 17376 } 17377 } 17378 }, waitForWebKitLinkLoaded); 17379 } 17380 17381 // Workaround for older Geckos that doesn't have any onload event for StyleSheets 17382 function waitForGeckoLinkLoaded() { 17383 wait(function() { 17384 try { 17385 // Accessing the cssRules will throw an exception until the CSS file is loaded 17386 var cssRules = style.sheet.cssRules; 17387 passed(); 17388 return !!cssRules; 17389 } catch (ex) { 17390 // Ignore 17391 } 17392 }, waitForGeckoLinkLoaded); 17393 } 17394 17395 url = Tools._addCacheSuffix(url); 17396 17397 if (!loadedStates[url]) { 17398 state = { 17399 passed: [], 17400 failed: [] 17401 }; 17402 17403 loadedStates[url] = state; 17404 } else { 17405 state = loadedStates[url]; 17406 } 17407 17408 if (loadedCallback) { 17409 state.passed.push(loadedCallback); 17410 } 17411 17412 if (errorCallback) { 17413 state.failed.push(errorCallback); 17414 } 17415 17416 // Is loading wait for it to pass 17417 if (state.status == 1) { 17418 return; 17419 } 17420 17421 // Has finished loading and was success 17422 if (state.status == 2) { 17423 passed(); 17424 return; 17425 } 17426 17427 // Has finished loading and was a failure 17428 if (state.status == 3) { 17429 failed(); 17430 return; 17431 } 17432 17433 // Start loading 17434 state.status = 1; 17435 link = document.createElement('link'); 17436 link.rel = 'stylesheet'; 17437 link.type = 'text/css'; 17438 link.id = 'u' + (idCount++); 17439 link.async = false;
17440 link.defer = false; 17441 startTime = new Date().getTime(); 17442 17443 // Feature detect onload on link element and sniff older webkits since it has an broken onload event 17444 if ("onload" in link && !isOldWebKit()) { 17445 link.onload = waitForWebKitLinkLoaded; 17446 link.onerror = failed; 17447 } else { 17448 // Sniff for old Firefox that doesn't support the onload event on link elements 17449 // TODO: Remove this in the future when everyone uses modern browsers 17450 if (navigator.userAgent.indexOf("Firefox") > 0) { 17451 style = document.createElement('style'); 17452 style.textContent = '@import "' + url + '"'; 17453 waitForGeckoLinkLoaded(); 17454 appendToHead(style); 17455 return; 17456 } 17457 17458 // Use the id owner on older webkits 17459 waitForWebKitLinkLoaded(); 17460 } 17461 17462 appendToHead(link); 17463 link.href = url; 17464 } 17465 17466 this.load = load; 17467 }; 17468}); 17469 17470// Included from: js/tinymce/classes/dom/DOMUtils.js 17471 17472/** 17473 * DOMUtils.js 17474 * 17475 * Released under LGPL License. 17476 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 17477 * 17478 * License: http://www.tinymce.com/license 17479 * Contributing: http://www.tinymce.com/contributing 17480 */ 17481 17482/** 17483 * Utility class for various DOM manipulation and retrieval functions. 17484 * 17485 * @class tinymce.dom.DOMUtils 17486 * @example 17487 * // Add a class to an element by id in the page 17488 * tinymce.DOM.addClass('someid', 'someclass'); 17489 * 17490 * // Add a class to an element by id inside the editor 17491 * tinymce.activeEditor.dom.addClass('someid', 'someclass'); 17492 */ 17493define("tinymce/dom/DOMUtils", [ 17494 "tinymce/dom/Sizzle", 17495 "tinymce/dom/DomQuery", 17496 "tinymce/html/Styles", 17497 "tinymce/dom/EventUtils", 17498 "tinymce/dom/TreeWalker", 17499 "tinymce/dom/Range", 17500 "tinymce/html/Entities", 17501 "tinymce/Env", 17502 "tinymce/util/Tools", 17503 "tinymce/dom/StyleSheetLoader" 17504], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) { 17505 // Shorten names 17506 var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim; 17507 var isIE = Env.ie; 17508 var simpleSelectorRe = /^([a-z0-9],?)+$/i; 17509 var whiteSpaceRegExp = /^[ \t\r\n]*$/; 17510 17511 function setupAttrHooks(domUtils, settings) { 17512 var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook; 17513 17514 keepUrlHook = { 17515 set: function($elm, value, name) { 17516 if (settings.url_converter) { 17517 value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]); 17518 } 17519 17520 $elm.attr('data-mce-' + name, value).attr(name, value); 17521 }, 17522 17523 get: function($elm, name) { 17524 return $elm.attr('data-mce-' + name) || $elm.attr(name); 17525 } 17526 }; 17527 17528 attrHooks = { 17529 style: { 17530 set: function($elm, value) { 17531 if (value !== null && typeof value === 'object') { 17532 $elm.css(value); 17533 return; 17534 } 17535 17536 if (keepValues) { 17537 $elm.attr('data-mce-style', value); 17538 } 17539 17540 $elm.attr('style', value); 17541 }, 17542 17543 get: function($elm) { 17544 var value = $elm.attr('data-mce-style') || $elm.attr('style'); 17545 17546 value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName); 17547 17548 return value; 17549 } 17550 } 17551 }; 17552 17553 if (keepValues) { 17554 attrHooks.href = attrHooks.src = keepUrlHook; 17555 } 17556 17557 return attrHooks; 17558 } 17559 17560 function updateInternalStyleAttr(domUtils, $elm) { 17561 var value = $elm.attr('style'); 17562 17563 value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName); 17564 17565 if (!value) { 17566 value = null; 17567 } 17568 17569 $elm.attr('data-mce-style', value); 17570 } 17571 17572 function nodeIndex(node, normalized) { 17573 var idx = 0, lastNodeType, nodeType; 17574 17575 if (node) { 17576 for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) { 17577 nodeType = node.nodeType; 17578 17579 // Normalize text nodes 17580 if (normalized && nodeType == 3) { 17581 if (nodeType == lastNodeType || !node.nodeValue.length) { 17582 continue; 17583 } 17584 } 17585 idx++; 17586 lastNodeType = nodeType; 17587 } 17588 } 17589 17590 return idx; 17591 } 17592 17593 /** 17594 * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class. 17595 * 17596 * @constructor 17597 * @method DOMUtils 17598 * @param {Document} doc Document reference to bind the utility class to. 17599 * @param {settings} settings Optional settings collection. 17600 */ 17601 function DOMUtils(doc, settings) { 17602 var self = this, blockElementsMap; 17603 17604 self.doc = doc; 17605 self.win = window; 17606 self.files = {}; 17607 self.counter = 0; 17608 self.stdMode = !isIE || doc.documentMode >= 8; 17609 self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode; 17610 self.styleSheetLoader = new StyleSheetLoader(doc); 17611 self.boundEvents = []; 17612 self.settings = settings = settings || {}; 17613 self.schema = settings.schema; 17614 self.styles = new Styles({ 17615 url_converter: settings.url_converter, 17616 url_converter_scope: settings.url_converter_scope 17617 }, settings.schema); 17618 17619 self.fixDoc(doc); 17620 self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
17621 self.attrHooks = setupAttrHooks(self, settings); 17622 blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {}; 17623 self.$ = $.overrideDefaults(function() { 17624 return { 17625 context: doc, 17626 element: self.getRoot() 17627 }; 17628 }); 17629 17630 /** 17631 * Returns true/false if the specified element is a block element or not. 17632 * 17633 * @method isBlock 17634 * @param {Node/String} node Element/Node to check. 17635 * @return {Boolean} True/False state if the node is a block element or not. 17636 */ 17637 self.isBlock = function(node) { 17638 // Fix for #5446 17639 if (!node) { 17640 return false; 17641 } 17642 17643 // This function is called in module pattern style since it might be executed with the wrong this scope 17644 var type = node.nodeType; 17645 17646 // If it's a node then check the type and use the nodeName 17647 if (type) { 17648 return !!(type === 1 && blockElementsMap[node.nodeName]); 17649 } 17650 17651 return !!blockElementsMap[node]; 17652 }; 17653 } 17654 17655 DOMUtils.prototype = { 17656 $$: function(elm) { 17657 if (typeof elm == 'string') { 17658 elm = this.get(elm); 17659 } 17660 17661 return this.$(elm); 17662 }, 17663 17664 root: null, 17665 17666 fixDoc: function(doc) { 17667 var settings = this.settings, name; 17668 17669 if (isIE && settings.schema) { 17670 // Add missing HTML 4/5 elements to IE 17671 ('abbr article aside audio canvas ' + 17672 'details figcaption figure footer ' + 17673 'header hgroup mark menu meter nav ' + 17674 'output progress section summary ' + 17675 'time video').replace(/\w+/g, function(name) { 17676 doc.createElement(name); 17677 }); 17678 17679 // Create all custom elements 17680 for (name in settings.schema.getCustomElements()) { 17681 doc.createElement(name); 17682 } 17683 } 17684 }, 17685 17686 clone: function(node, deep) { 17687 var self = this, clone, doc; 17688 17689 // TODO: Add feature detection here in the future 17690 if (!isIE || node.nodeType !== 1 || deep) { 17691 return node.cloneNode(deep); 17692 } 17693 17694 doc = self.doc; 17695 17696 // Make a HTML5 safe shallow copy 17697 if (!deep) { 17698 clone = doc.createElement(node.nodeName); 17699 17700 // Copy attribs 17701 each(self.getAttribs(node), function(attr) { 17702 self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName)); 17703 }); 17704 17705 return clone; 17706 } 17707 17708 return clone.firstChild; 17709 }, 17710 17711 /** 17712 * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not 17713 * go above the point of this root node. 17714 * 17715 * @method getRoot 17716 * @return {Element} Root element for the utility class. 17717 */ 17718 getRoot: function() { 17719 var self = this; 17720 17721 return self.settings.root_element || self.doc.body; 17722 }, 17723 17724 /** 17725 * Returns the viewport of the window. 17726 * 17727 * @method getViewPort 17728 * @param {Window} win Optional window to get viewport of. 17729 * @return {Object} Viewport object with fields x, y, w and h. 17730 */ 17731 getViewPort: function(win) { 17732 var doc, rootElm; 17733 17734 win = !win ? this.win : win; 17735 doc = win.document; 17736 rootElm = this.boxModel ? doc.documentElement : doc.body; 17737 17738 // Returns viewport size excluding scrollbars 17739 return { 17740 x: win.pageXOffset || rootElm.scrollLeft, 17741 y: win.pageYOffset || rootElm.scrollTop, 17742 w: win.innerWidth || rootElm.clientWidth, 17743 h: win.innerHeight || rootElm.clientHeight 17744 }; 17745 }, 17746 17747 /** 17748 * Returns the rectangle for a specific element. 17749 * 17750 * @method getRect 17751 * @param {Element/String} elm Element object or element ID to get rectangle from. 17752 * @return {object} Rectangle for specified element object with x, y, w, h fields. 17753 */ 17754 getRect: function(elm) { 17755 var self = this, pos, size; 17756 17757 elm = self.get(elm); 17758 pos = self.getPos(elm); 17759 size = self.getSize(elm); 17760 17761 return { 17762 x: pos.x, y: pos.y, 17763 w: size.w, h: size.h 17764 }; 17765 }, 17766 17767 /** 17768 * Returns the size dimensions of the specified element. 17769 * 17770 * @method getSize 17771 * @param {Element/String} elm Element object or element ID to get rectangle from. 17772 * @return {object} Rectangle for specified element object with w, h fields. 17773 */ 17774 getSize: function(elm) { 17775 var self = this, w, h; 17776 17777 elm = self.get(elm); 17778 w = self.getStyle(elm, 'width'); 17779 h = self.getStyle(elm, 'height'); 17780 17781 // Non pixel value, then force offset/clientWidth 17782 if (w.indexOf('px') === -1) { 17783 w = 0; 17784 } 17785 17786 // Non pixel value, then force offset/clientWidth 17787 if (h.indexOf('px') === -1) { 17788 h = 0; 17789 } 17790 17791 return { 17792 w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth, 17793 h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight 17794 }; 17795 }, 17796 17797 /** 17798 * Returns a node by the specified selector function. This function will 17799 * loop through all parent nodes and call the specified function for each node. 17800 * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end 17801 * and the node it found will be returned. 17802 * 17803 * @method getParent 17804 * @param {Node/String}
17804 node DOM node to search parents on or ID string. 17805 * @param {function} selector Selection function or CSS selector to execute on each node. 17806 * @param {Node} root Optional root element, never go below this point. 17807 * @return {Node} DOM Node or null if it wasn't found. 17808 */ 17809 getParent: function(node, selector, root) { 17810 return this.getParents(node, selector, root, false); 17811 }, 17812 17813 /** 17814 * Returns a node list of all parents matching the specified selector function or pattern. 17815 * If the function then returns true indicating that it has found what it was looking for and that node will be collected. 17816 * 17817 * @method getParents 17818 * @param {Node/String} node DOM node to search parents on or ID string. 17819 * @param {function} selector Selection function to execute on each node or CSS pattern. 17820 * @param {Node} root Optional root element, never go below this point. 17821 * @return {Array} Array of nodes or null if it wasn't found. 17822 */ 17823 getParents: function(node, selector, root, collect) { 17824 var self = this, selectorVal, result = []; 17825 17826 node = self.get(node); 17827 collect = collect === undefined; 17828 17829 // Default root on inline mode 17830 root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null); 17831 17832 // Wrap node name as func 17833 if (is(selector, 'string')) { 17834 selectorVal = selector; 17835 17836 if (selector === '*') { 17837 selector = function(node) { 17838 return node.nodeType == 1; 17839 }; 17840 } else { 17841 selector = function(node) { 17842 return self.is(node, selectorVal); 17843 }; 17844 } 17845 } 17846 17847 while (node) { 17848 if (node == root || !node.nodeType || node.nodeType === 9) { 17849 break; 17850 } 17851 17852 if (!selector || selector(node)) { 17853 if (collect) { 17854 result.push(node); 17855 } else { 17856 return node; 17857 } 17858 } 17859 17860 node = node.parentNode; 17861 } 17862 17863 return collect ? result : null; 17864 }, 17865 17866 /** 17867 * Returns the specified element by ID or the input element if it isn't a string. 17868 * 17869 * @method get 17870 * @param {String/Element} n Element id to look for or element to just pass though. 17871 * @return {Element} Element matching the specified id or null if it wasn't found. 17872 */ 17873 get: function(elm) { 17874 var name; 17875 17876 if (elm && this.doc && typeof elm == 'string') { 17877 name = elm; 17878 elm = this.doc.getElementById(elm); 17879 17880 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick 17881 if (elm && elm.id !== name) { 17882 return this.doc.getElementsByName(name)[1]; 17883 } 17884 } 17885 17886 return elm; 17887 }, 17888 17889 /** 17890 * Returns the next node that matches selector or function 17891 * 17892 * @method getNext 17893 * @param {Node} node Node to find siblings from. 17894 * @param {String/function} selector Selector CSS expression or function. 17895 * @return {Node} Next node item matching the selector or null if it wasn't found. 17896 */ 17897 getNext: function(node, selector) { 17898 return this._findSib(node, selector, 'nextSibling'); 17899 }, 17900 17901 /** 17902 * Returns the previous node that matches selector or function 17903 * 17904 * @method getPrev 17905 * @param {Node} node Node to find siblings from. 17906 * @param {String/function} selector Selector CSS expression or function. 17907 * @return {Node} Previous node item matching the selector or null if it wasn't found. 17908 */ 17909 getPrev: function(node, selector) { 17910 return this._findSib(node, selector, 'previousSibling'); 17911 }, 17912 17913 // #ifndef jquery 17914 17915 /** 17916 * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test". 17917 * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough 17918 * on more complex patterns. 17919 * 17920 * @method select 17921 * @param {String} selector CSS level 3 pattern to select/find elements by. 17922 * @param {Object} scope Optional root element/scope element to search in. 17923 * @return {Array} Array with all matched elements. 17924 * @example 17925 * // Adds a class to all paragraphs in the currently active editor 17926 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 17927 * 17928 * // Adds a class to all spans that have the test class in the currently active editor 17929 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass') 17930 */ 17931 select: function(selector, scope) { 17932 var self = this; 17933 17934 /*eslint new-cap:0 */ 17935 return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []); 17936 }, 17937 17938 /** 17939 * Returns true/false if the specified element matches the specified css pattern. 17940 * 17941 * @method is 17942 * @param {Node/NodeList} elm DOM node to match or an array of nodes to match. 17943 * @param {String} selector CSS pattern to match the element against. 17944 */ 17945 is: function(elm, selector) { 17946 var i; 17947 17948 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance 17949 if (elm.length === undefined) { 17950 // Simple all selector 17951 if (selector === '*') { 17952 return elm.nodeType == 1; 17953 } 17954 17955 // Simple selector just elements 17956 if (simpleSelectorRe.test(selector)) { 17957 selector = selector.toLowerCase().split(/,/); 17958 elm = elm.nodeName.toLowerCase(); 17959 17960 for (i = selector.length - 1; i >= 0; i--) { 17961 if (selector[i] == elm) { 17962 return true; 17963 } 17964 } 17965 17966 return false;
17967 } 17968 } 17969 17970 // Is non element 17971 if (elm.nodeType && elm.nodeType != 1) { 17972 return false; 17973 } 17974 17975 var elms = elm.nodeType ? [elm] : elm; 17976 17977 /*eslint new-cap:0 */ 17978 return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0; 17979 }, 17980 17981 // #endif 17982 17983 /** 17984 * Adds the specified element to another element or elements. 17985 * 17986 * @method add 17987 * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to. 17988 * @param {String/Element} name Name of new element to add or existing element to add. 17989 * @param {Object} attrs Optional object collection with arguments to add to the new element(s). 17990 * @param {String} html Optional inner HTML contents to add for each element. 17991 * @param {Boolean} create Optional flag if the element should be created or added. 17992 * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements 17993 * were passed in. 17994 * @example 17995 * // Adds a new paragraph to the end of the active editor 17996 * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content'); 17997 */ 17998 add: function(parentElm, name, attrs, html, create) { 17999 var self = this; 18000 18001 return this.run(parentElm, function(parentElm) { 18002 var newElm; 18003 18004 newElm = is(name, 'string') ? self.doc.createElement(name) : name; 18005 self.setAttribs(newElm, attrs); 18006 18007 if (html) { 18008 if (html.nodeType) { 18009 newElm.appendChild(html); 18010 } else { 18011 self.setHTML(newElm, html); 18012 } 18013 } 18014 18015 return !create ? parentElm.appendChild(newElm) : newElm; 18016 }); 18017 }, 18018 18019 /** 18020 * Creates a new element. 18021 * 18022 * @method create 18023 * @param {String} name Name of new element. 18024 * @param {Object} attrs Optional object name/value collection with element attributes. 18025 * @param {String} html Optional HTML string to set as inner HTML of the element. 18026 * @return {Element} HTML DOM node element that got created. 18027 * @example 18028 * // Adds an element where the caret/selection is in the active editor 18029 * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content'); 18030 * tinymce.activeEditor.selection.setNode(el); 18031 */ 18032 create: function(name, attrs, html) { 18033 return this.add(this.doc.createElement(name), name, attrs, html, 1); 18034 }, 18035 18036 /** 18037 * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in. 18038 * 18039 * @method createHTML 18040 * @param {String} name Name of new element. 18041 * @param {Object} attrs Optional object name/value collection with element attributes. 18042 * @param {String} html Optional HTML string to set as inner HTML of the element. 18043 * @return {String} String with new HTML element, for example: <a href="#">test</a>. 18044 * @example 18045 * // Creates a html chunk and inserts it at the current selection/caret location 18046 * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line')); 18047 */ 18048 createHTML: function(name, attrs, html) { 18049 var outHtml = '', key; 18050 18051 outHtml += '<' + name; 18052 18053 for (key in attrs) { 18054 if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') { 18055 outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"'; 18056 } 18057 } 18058 18059 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime 18060 if (typeof html != "undefined") { 18061 return outHtml + '>' + html + '</' + name + '>'; 18062 } 18063 18064 return outHtml + ' />'; 18065 }, 18066 18067 /** 18068 * Creates a document fragment out of the specified HTML string. 18069 * 18070 * @method createFragment 18071 * @param {String} html Html string to create fragment from. 18072 * @return {DocumentFragment} Document fragment node. 18073 */ 18074 createFragment: function(html) { 18075 var frag, node, doc = this.doc, container; 18076 18077 container = doc.createElement("div"); 18078 frag = doc.createDocumentFragment(); 18079 18080 if (html) { 18081 container.innerHTML = html; 18082 } 18083 18084 while ((node = container.firstChild)) { 18085 frag.appendChild(node); 18086 } 18087 18088 return frag; 18089 }, 18090 18091 /** 18092 * Removes/deletes the specified element(s) from the DOM. 18093 * 18094 * @method remove 18095 * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids. 18096 * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be 18097 * placed at the location of the removed element. 18098 * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements 18099 * were passed in. 18100 * @example 18101 * // Removes all paragraphs in the active editor 18102 * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p')); 18103 * 18104 * // Removes an element by id in the document 18105 * tinymce.DOM.remove('mydiv'); 18106 */ 18107 remove: function(node, keepChildren) { 18108 node = this.$$(node); 18109 18110 if (keepChildren) { 18111 node.each(function() { 18112 var child; 18113 18114 while ((child = this.firstChild)) { 18115 if (child.nodeType == 3 && child.data.length === 0) { 18116 this.removeChild(child); 18117 } else { 18118 this.parentNode.insertBefore(child, this); 18119 } 18120 } 18121 }).remove(); 18122 } else { 18123 node.remove(); 18124 } 18125 18126 return node.length > 1 ? node.toArray() : node[0]; 18127 }, 18128 18129 /** 18130 * Sets the CSS style value on a HTML element. The name can be a camelcase string 18131 * or the CSS style name like background-color. 18132 * 18133 * @method setStyle 18134 * @param {String/Element/Array} elm HTML element/Array of elements to set CSS style value on. 18135 * @param {String} name Name of the style value to set. 18136 * @param {String} value Value to set on the style. 18137 * @example
18138 * // Sets a style value on all paragraphs in the currently active editor 18139 * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red'); 18140 * 18141 * // Sets a style value to an element by id in the current document 18142 * tinymce.DOM.setStyle('mydiv', 'background-color', 'red'); 18143 */ 18144 setStyle: function(elm, name, value) { 18145 elm = this.$$(elm).css(name, value); 18146 18147 if (this.settings.update_styles) { 18148 updateInternalStyleAttr(this, elm); 18149 } 18150 }, 18151 18152 /** 18153 * Returns the current style or runtime/computed value of an element. 18154 * 18155 * @method getStyle 18156 * @param {String/Element} elm HTML element or element id string to get style from. 18157 * @param {String} name Style name to return. 18158 * @param {Boolean} computed Computed style. 18159 * @return {String} Current style or computed style value of an element. 18160 */ 18161 getStyle: function(elm, name, computed) { 18162 elm = this.$$(elm); 18163 18164 if (computed) { 18165 return elm.css(name); 18166 } 18167 18168 // Camelcase it, if needed 18169 name = name.replace(/-(\D)/g, function(a, b) { 18170 return b.toUpperCase(); 18171 }); 18172 18173 if (name == 'float') { 18174 name = Env.ie && Env.ie < 12 ? 'styleFloat' : 'cssFloat'; 18175 } 18176 18177 return elm[0] && elm[0].style ? elm[0].style[name] : undefined; 18178 }, 18179 18180 /** 18181 * Sets multiple styles on the specified element(s). 18182 * 18183 * @method setStyles 18184 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set styles on. 18185 * @param {Object} styles Name/Value collection of style items to add to the element(s). 18186 * @example 18187 * // Sets styles on all paragraphs in the currently active editor 18188 * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'}); 18189 *
18190 * // Sets styles to an element by id in the current document 18191 * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'}); 18192 */ 18193 setStyles: function(elm, styles) { 18194 elm = this.$$(elm).css(styles); 18195 18196 if (this.settings.update_styles) { 18197 updateInternalStyleAttr(this, elm); 18198 } 18199 }, 18200 18201 /** 18202 * Removes all attributes from an element or elements. 18203 * 18204 * @method removeAllAttribs 18205 * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from. 18206 */ 18207 removeAllAttribs: function(e) { 18208 return this.run(e, function(e) { 18209 var i, attrs = e.attributes; 18210 for (i = attrs.length - 1; i >= 0; i--) { 18211 e.removeAttributeNode(attrs.item(i)); 18212 } 18213 }); 18214 }, 18215 18216 /** 18217 * Sets the specified attribute of an element or elements. 18218 * 18219 * @method setAttrib 18220 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attribute on. 18221 * @param {String} name Name of attribute to set. 18222 * @param {String} value Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove 18223 * the attribute instead. 18224 * @example 18225 * // Sets class attribute on all paragraphs in the active editor 18226 * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass'); 18227 *
18228 * // Sets class attribute on a specific element in the current page 18229 * tinymce.dom.setAttrib('mydiv', 'class', 'myclass'); 18230 */ 18231 setAttrib: function(elm, name, value) { 18232 var self = this, originalValue, hook, settings = self.settings; 18233 18234 if (value === '') { 18235 value = null; 18236 } 18237 18238 elm = self.$$(elm); 18239 originalValue = elm.attr(name); 18240 18241 if (!elm.length) { 18242 return; 18243 } 18244 18245 hook = self.attrHooks[name]; 18246 if (hook && hook.set) { 18247 hook.set(elm, value, name); 18248 } else { 18249 elm.attr(name, value); 18250 } 18251 18252 if (originalValue != value && settings.onSetAttrib) { 18253 settings.onSetAttrib({ 18254 attrElm: elm, 18255 attrName: name, 18256 attrValue: value 18257 }); 18258 } 18259 }, 18260 18261 /** 18262 * Sets two or more specified attributes of an element or elements. 18263 * 18264 * @method setAttribs 18265 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on. 18266 * @param {Object} attrs Name/Value collection of attribute items to add to the element(s). 18267 * @example 18268 * // Sets class and title attributes on all paragraphs in the active editor 18269 * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'}); 18270 *
18271 * // Sets class and title attributes on a specific element in the current page 18272 * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'}); 18273 */ 18274 setAttribs: function(elm, attrs) { 18275 var self = this; 18276 18277 self.$$(elm).each(function(i, node) { 18278 each(attrs, function(value, name) { 18279 self.setAttrib(node, name, value); 18280 }); 18281 }); 18282 }, 18283 18284 /** 18285 * Returns the specified attribute by name. 18286 * 18287 * @method getAttrib 18288 * @param {String/Element} elm Element string id or DOM element to get attribute from. 18289 * @param {String} name Name of attribute to get. 18290 * @param {String} defaultVal Optional default value to return if the attribute didn't exist. 18291 * @return {String} Attribute value string, default value or null if the attribute wasn't found. 18292 */ 18293 getAttrib: function(elm, name, defaultVal) { 18294 var self = this, hook, value; 18295 18296 elm = self.$$(elm); 18297 18298 if (elm.length) { 18299 hook = self.attrHooks[name]; 18300 18301 if (hook && hook.get) { 18302 value = hook.get(elm, name); 18303 } else { 18304 value = elm.attr(name); 18305 } 18306 } 18307 18308 if (typeof value == 'undefined') { 18309 value = defaultVal || ''; 18310 } 18311 18312 return value; 18313 }, 18314 18315 /** 18316 * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields. 18317 * 18318 * @method getPos 18319 * @param {Element/String} elm HTML element or element id to get x, y position from. 18320 * @param {Element} rootElm Optional root element to stop calculations at. 18321 * @return {object} Absolute position of the specified element object with x, y fields. 18322 */ 18323 getPos: function(elm, rootElm) { 18324 var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos; 18325 18326 elm = self.get(elm); 18327 rootElm = rootElm || body; 18328 18329 if (elm) { 18330 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes 18331 // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root 18332 if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') { 18333 pos = elm.getBoundingClientRect(); 18334 rootElm = self.boxModel ? doc.documentElement : body; 18335 18336 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit 18337 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position 18338 x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft; 18339 y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop; 18340 18341 return {x: x, y: y}; 18342 } 18343 18344 offsetParent = elm; 18345 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) { 18346 x += offsetParent.offsetLeft || 0; 18347 y += offsetParent.offsetTop || 0; 18348 offsetParent = offsetParent.offsetParent; 18349 } 18350 18351 offsetParent = elm.parentNode; 18352 while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) { 18353 x -= offsetParent.scrollLeft || 0; 18354 y -= offsetParent.scrollTop || 0; 18355 offsetParent = offsetParent.parentNode; 18356 } 18357 } 18358 18359 return {x: x, y: y}; 18360 }, 18361 18362 /**
18363 * Parses the specified style value into an object collection. This parser will also 18364 * merge and remove any redundant items that browsers might have added. It will also convert non-hex 18365 * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings. 18366 * 18367 * @method parseStyle 18368 * @param {String} cssText Style value to parse, for example: border:1px solid red;. 18369 * @return {Object} Object representation of that style, for example: {border: '1px solid red'} 18370 */ 18371 parseStyle: function(cssText) { 18372 return this.styles.parse(cssText); 18373 }, 18374 18375 /** 18376 * Serializes the specified style object into a string. 18377 * 18378 * @method serializeStyle 18379 * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'} 18380 * @param {String} name Optional element name. 18381 * @return {String} String representation of the style object, for example: border: 1px solid red. 18382 */ 18383 serializeStyle: function(styles, name) { 18384 return this.styles.serialize(styles, name); 18385 }, 18386 18387 /** 18388 * Adds a style element at the top of the document with the specified cssText content. 18389 * 18390 * @method addStyle 18391 * @param {String} cssText CSS Text style to add to top of head of document. 18392 */ 18393 addStyle: function(cssText) { 18394 var self = this, doc = self.doc, head, styleElm; 18395 18396 // Prevent inline from loading the same styles twice 18397 if (self !== DOMUtils.DOM && doc === document) { 18398 var addedStyles = DOMUtils.DOM.addedStyles; 18399 18400 addedStyles = addedStyles || []; 18401 if (addedStyles[cssText]) { 18402 return; 18403 } 18404 18405 addedStyles[cssText] = true; 18406 DOMUtils.DOM.addedStyles = addedStyles; 18407 } 18408 18409 // Create style element if needed 18410 styleElm = doc.getElementById('mceDefaultStyles'); 18411 if (!styleElm) { 18412 styleElm = doc.createElement('style'); 18413 styleElm.id = 'mceDefaultStyles'; 18414 styleElm.type = 'text/css'; 18415 18416 head = doc.getElementsByTagName('head')[0]; 18417 if (head.firstChild) { 18418 head.insertBefore(styleElm, head.firstChild); 18419 } else { 18420 head.appendChild(styleElm); 18421 } 18422 } 18423 18424 // Append style data to old or new style element 18425 if (styleElm.styleSheet) { 18426 styleElm.styleSheet.cssText += cssText; 18427 } else { 18428 styleElm.appendChild(doc.createTextNode(cssText)); 18429 } 18430 }, 18431 18432 /** 18433 * Imports/loads the specified CSS file into the document bound to the class. 18434 * 18435 * @method loadCSS 18436 * @param {String} url URL to CSS file to load. 18437 * @example 18438 * // Loads a CSS file dynamically into the current document 18439 * tinymce.DOM.loadCSS('somepath/some.css'); 18440 * 18441 * // Loads a CSS file into the currently active editor instance 18442 * tinymce.activeEditor.dom.loadCSS('somepath/some.css'); 18443 * 18444 * // Loads a CSS file into an editor instance by id 18445 * tinymce.get('someid').dom.loadCSS('somepath/some.css'); 18446 * 18447 * // Loads multiple CSS files into the current document 18448 * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css'); 18449 */ 18450 loadCSS: function(url) { 18451 var self = this, doc = self.doc, head; 18452 18453 // Prevent inline from loading the same CSS file twice 18454 if (self !== DOMUtils.DOM && doc === document) { 18455 DOMUtils.DOM.loadCSS(url); 18456 return; 18457 } 18458 18459 if (!url) { 18460 url = ''; 18461 } 18462 18463 head = doc.getElementsByTagName('head')[0]; 18464 18465 each(url.split(','), function(url) { 18466 var link; 18467 18468 url = Tools._addCacheSuffix(url); 18469 18470 if (self.files[url]) { 18471 return; 18472 } 18473 18474 self.files[url] = true; 18475 link = self.create('link', {rel: 'stylesheet', href: url}); 18476 18477 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug 18478 // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading 18479 // It's ugly but it seems to work fine. 18480 if (isIE && doc.documentMode && doc.recalc) { 18481 link.onload = function() { 18482 if (doc.recalc) { 18483 doc.recalc(); 18484 } 18485 18486 link.onload = null; 18487 }; 18488 } 18489 18490 head.appendChild(link); 18491 }); 18492 }, 18493 18494 /** 18495 * Adds a class to the specified element or elements. 18496 * 18497 * @method addClass 18498 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 18499 * @param {String} cls Class name to add to each element. 18500 * @return {String/Array}
18500 String with new class value or array with new class values for all elements. 18501 * @example 18502 * // Adds a class to all paragraphs in the active editor 18503 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 18504 * 18505 * // Adds a class to a specific element in the current page 18506 * tinymce.DOM.addClass('mydiv', 'myclass'); 18507 */ 18508 addClass: function(elm, cls) { 18509 this.$$(elm).addClass(cls); 18510 }, 18511 18512 /** 18513 * Removes a class from the specified element or elements. 18514 * 18515 * @method removeClass 18516 * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs. 18517 * @param {String} cls Class name to remove from each element. 18518 * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements 18519 * were passed in. 18520 * @example 18521 * // Removes a class from all paragraphs in the active editor 18522 * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass'); 18523 * 18524 * // Removes a class from a specific element in the current page 18525 * tinymce.DOM.removeClass('mydiv', 'myclass'); 18526 */ 18527 removeClass: function(elm, cls) { 18528 this.toggleClass(elm, cls, false); 18529 }, 18530 18531 /** 18532 * Returns true if the specified element has the specified class. 18533 * 18534 * @method hasClass 18535 * @param {String/Element} elm HTML element or element id string to check CSS class on. 18536 * @param {String} cls CSS class to check for. 18537 * @return {Boolean} true/false if the specified element has the specified class. 18538 */ 18539 hasClass: function(elm, cls) { 18540 return this.$$(elm).hasClass(cls); 18541 }, 18542 18543 /** 18544 * Toggles the specified class on/off. 18545 * 18546 * @method toggleClass 18547 * @param {Element} elm Element to toggle class on. 18548 * @param {[type]} cls Class to toggle on/off. 18549 * @param {[type]} state Optional state to set. 18550 */ 18551 toggleClass: function(elm, cls, state) { 18552 this.$$(elm).toggleClass(cls, state).each(function() { 18553 if (this.className === '') { 18554 $(this).attr('class', null); 18555 } 18556 }); 18557 }, 18558 18559 /** 18560 * Shows the specified element(s) by ID by setting the "display" style. 18561 * 18562 * @method show 18563 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show. 18564 */ 18565 show: function(elm) { 18566 this.$$(elm).show(); 18567 }, 18568 18569 /** 18570 * Hides the specified element(s) by ID by setting the "display" style. 18571 * 18572 * @method hide 18573 * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to hide. 18574 * @example 18575 * // Hides an element by id in the document 18576 * tinymce.DOM.hide('myid'); 18577 */ 18578 hide: function(elm) { 18579 this.$$(elm).hide(); 18580 }, 18581 18582 /** 18583 * Returns true/false if the element is hidden or not by checking the "display" style. 18584 * 18585 * @method isHidden 18586 * @param {String/Element} elm Id or element to check display state on. 18587 * @return {Boolean} true/false if the element is hidden or not. 18588 */ 18589 isHidden: function(elm) { 18590 return this.$$(elm).css('display') == 'none'; 18591 }, 18592 18593 /** 18594 * Returns a unique id. This can be useful when generating elements on the fly. 18595 * This method will not check if the element already exists. 18596 * 18597 * @method uniqueId 18598 * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_". 18599 * @return {String} Unique id. 18600 */ 18601 uniqueId: function(prefix) { 18602 return (!prefix ? 'mce_' : prefix) + (this.counter++); 18603 }, 18604 18605 /** 18606 * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means 18607 * URLs will get converted, hex color values fixed etc. Check processHTML for details. 18608 * 18609 * @method setHTML 18610 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of. 18611 * @param {String} html HTML content to set as inner HTML of the element. 18612 * @example 18613 * // Sets the inner HTML of all paragraphs in the active editor 18614 * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html'); 18615 *
18616 * // Sets the inner HTML of an element by id in the document 18617 * tinymce.DOM.setHTML('mydiv', 'some inner html'); 18618 */ 18619 setHTML: function(elm, html) { 18620 elm = this.$$(elm); 18621 18622 if (isIE) { 18623 elm.each(function(i, target) { 18624 if (target.canHaveHTML === false) { 18625 return; 18626 } 18627 18628 // Remove all child nodes, IE keeps empty text nodes in DOM 18629 while (target.firstChild) { 18630 target.removeChild(target.firstChild); 18631 } 18632 18633 try { 18634 // IE will remove comments from the beginning 18635 // unless you padd the contents with something 18636 target.innerHTML = '<br>' + html; 18637 target.removeChild(target.firstChild); 18638 } catch (ex) { 18639 // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p 18640 $('<div>').html('<br>' + html).contents().slice(1).appendTo(target); 18641 } 18642 18643 return html; 18644 }); 18645 } else { 18646 elm.html(html); 18647 } 18648 }, 18649 18650 /** 18651 * Returns the outer HTML of an element. 18652 * 18653 * @method getOuterHTML 18654 * @param {String/Element} elm Element ID or element object to get outer HTML from. 18655 * @return {String} Outer HTML string. 18656 * @example 18657 * tinymce.DOM.getOuterHTML(editorElement); 18658 * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody()); 18659 */ 18660 getOuterHTML: function(elm) { 18661 elm = this.get(elm); 18662 18663 // Older FF doesn't have outerHTML 3.6 is still used by some orgaizations 18664 return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : $('<div>').append($(elm).clone()).html(); 18665 }, 18666 18667 /** 18668 * Sets the specified outer HTML on an element or elements. 18669 * 18670 * @method setOuterHTML 18671 * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on. 18672 * @param {Object} html HTML code to set as outer value for the element. 18673 * @example 18674 * // Sets the outer HTML of all paragraphs in the active editor 18675 * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>'); 18676 *
18677 * // Sets the outer HTML of an element by id in the document 18678 * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>'); 18679 */ 18680 setOuterHTML: function(elm, html) { 18681 var self = this; 18682 18683 self.$$(elm).each(function() { 18684 try { 18685 // Older FF doesn't have outerHTML 3.6 is still used by some organizations 18686 if ("outerHTML" in this) { 18687 this.outerHTML = html; 18688 return; 18689 } 18690 } catch (ex) { 18691 // Ignore 18692 } 18693 18694 // OuterHTML for IE it sometimes produces an "unknown runtime error" 18695 self.remove($(this).html(html), true); 18696 }); 18697 }, 18698 18699 /** 18700 * Entity decodes a string. This method decodes any HTML entities, such as å. 18701 * 18702 * @method decode 18703 * @param {String} s String to decode entities on. 18704 * @return {String} Entity decoded string. 18705 */ 18706 decode: Entities.decode, 18707 18708 /** 18709 * Entity encodes a string. This method encodes the most common entities, such as <>"&. 18710 * 18711 * @method encode 18712 * @param {String} text String to encode with entities. 18713 * @return {String} Entity encoded string. 18714 */ 18715 encode: Entities.encodeAllRaw, 18716 18717 /** 18718 * Inserts an element after the reference element. 18719 * 18720 * @method insertAfter 18721 * @param {Element} node Element to insert after the reference. 18722 * @param {Element/String/Array} referenceNode Reference element, element id or array of elements to insert after. 18723 * @return {Element/Array} Element that got added or an array with elements. 18724 */ 18725 insertAfter: function(node, referenceNode) { 18726 referenceNode = this.get(referenceNode); 18727 18728 return this.run(node, function(node) { 18729 var parent, nextSibling; 18730 18731 parent = referenceNode.parentNode; 18732 nextSibling = referenceNode.nextSibling; 18733 18734 if (nextSibling) { 18735 parent.insertBefore(node, nextSibling); 18736 } else { 18737 parent.appendChild(node); 18738 } 18739 18740 return node; 18741 }); 18742 }, 18743 18744 /** 18745 * Replaces the specified element or elements with the new element specified. The new element will 18746 * be cloned if multiple input elements are passed in. 18747 * 18748 * @method replace 18749 * @param {Element} newElm New element to replace old ones with. 18750 * @param {Element/String/Array} oldElm Element DOM node, element id or array of elements or ids to replace. 18751 * @param {Boolean} keepChildren Optional keep children state, if set to true child nodes from the old object will be added 18752 * to new ones. 18753 */ 18754 replace: function(newElm, oldElm, keepChildren) { 18755 var self = this; 18756 18757 return self.run(oldElm, function(oldElm) { 18758 if (is(oldElm, 'array')) { 18759 newElm = newElm.cloneNode(true); 18760 } 18761 18762 if (keepChildren) { 18763 each(grep(oldElm.childNodes), function(node) { 18764 newElm.appendChild(node); 18765 }); 18766 } 18767 18768 return oldElm.parentNode.replaceChild(newElm, oldElm); 18769 }); 18770 }, 18771 18772 /** 18773 * Renames the specified element and keeps its attributes and children. 18774 * 18775 * @method rename 18776 * @param {Element} elm Element to rename. 18777 * @param {String} name Name of the new element. 18778 * @return {Element} New element or the old element if it needed renaming. 18779 */ 18780 rename: function(elm, name) { 18781 var self = this, newElm; 18782 18783 if (elm.nodeName != name.toUpperCase()) { 18784 // Rename block element 18785 newElm = self.create(name); 18786 18787 // Copy attribs to new block 18788 each(self.getAttribs(elm), function(attrNode) { 18789 self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName)); 18790 }); 18791 18792 // Replace block 18793 self.replace(newElm, elm, 1); 18794 } 18795 18796 return newElm || elm; 18797 }, 18798 18799 /** 18800 * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic. 18801 * 18802 * @method findCommonAncestor 18803 * @param {Element} a Element to find common ancestor of. 18804 * @param {Element} b Element to find common ancestor of. 18805 * @return {Element} Common ancestor element of the two input elements. 18806 */ 18807 findCommonAncestor: function(a, b) { 18808 var ps = a, pe; 18809 18810 while (ps) { 18811 pe = b; 18812 18813 while (pe && ps != pe) { 18814 pe = pe.parentNode; 18815 } 18816 18817 if (ps == pe) { 18818 break; 18819 } 18820 18821 ps = ps.parentNode; 18822 } 18823 18824 if (!ps && a.ownerDocument) { 18825 return a.ownerDocument.documentElement; 18826 } 18827 18828 return ps; 18829 }, 18830 18831 /** 18832 * Parses the specified RGB color value and returns a hex version of that color. 18833 * 18834 * @method toHex 18835 * @param {String} rgbVal RGB string value like rgb(1,2,3) 18836 * @return {String} Hex version of that RGB value like #FF00FF. 18837 */ 18838 toHex: function(rgbVal) { 18839 return this.styles.toHex(Tools.trim(rgbVal)); 18840 }, 18841 18842 /** 18843 * Executes the specified function on the element by id or dom element node or array of elements/id. 18844 * 18845 * @method run 18846 * @param {String/Element/Array} elm ID or DOM element object or array with ids or elements. 18847 * @param {function}
18847 func Function to execute for each item. 18848 * @param {Object} scope Optional scope to execute the function in. 18849 * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in. 18850 */ 18851 run: function(elm, func, scope) { 18852 var self = this, result; 18853 18854 if (typeof elm === 'string') { 18855 elm = self.get(elm); 18856 } 18857 18858 if (!elm) { 18859 return false; 18860 } 18861 18862 scope = scope || this; 18863 if (!elm.nodeType && (elm.length || elm.length === 0)) { 18864 result = []; 18865 18866 each(elm, function(elm, i) { 18867 if (elm) { 18868 if (typeof elm == 'string') { 18869 elm = self.get(elm); 18870 } 18871 18872 result.push(func.call(scope, elm, i)); 18873 } 18874 }); 18875 18876 return result; 18877 } 18878 18879 return func.call(scope, elm); 18880 }, 18881 18882 /** 18883 * Returns a NodeList with attributes for the element. 18884 * 18885 * @method getAttribs 18886 * @param {HTMLElement/string} elm Element node or string id to get attributes from. 18887 * @return {NodeList} NodeList with attributes. 18888 */ 18889 getAttribs: function(elm) { 18890 var attrs; 18891 18892 elm = this.get(elm); 18893 18894 if (!elm) { 18895 return []; 18896 } 18897 18898 if (isIE) { 18899 attrs = []; 18900 18901 // Object will throw exception in IE 18902 if (elm.nodeName == 'OBJECT') { 18903 return elm.attributes; 18904 } 18905 18906 // IE doesn't keep the selected attribute if you clone option elements 18907 if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) { 18908 attrs.push({specified: 1, nodeName: 'selected'}); 18909 } 18910 18911 // It's crazy that this is faster in IE but it's because it returns all attributes all the time 18912 var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi; 18913 elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) { 18914 attrs.push({specified: 1, nodeName: a}); 18915 }); 18916 18917 return attrs; 18918 } 18919 18920 return elm.attributes; 18921 }, 18922 18923 /** 18924 * Returns true/false if the specified node is to be considered empty or not. 18925 * 18926 * @example 18927 * tinymce.DOM.isEmpty(node, {img: true}); 18928 * @method isEmpty 18929 * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements. 18930 * @return {Boolean} true/false if the node is empty or not. 18931 */ 18932 isEmpty: function(node, elements) { 18933 var self = this, i, attributes, type, walker, name, brCount = 0; 18934 18935 node = node.firstChild; 18936 if (node) { 18937 walker = new TreeWalker(node, node.parentNode); 18938 elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null); 18939 18940 do { 18941 type = node.nodeType; 18942 18943 if (type === 1) { 18944 // Ignore bogus elements 18945 if (node.getAttribute('data-mce-bogus')) { 18946 continue; 18947 } 18948 18949 // Keep empty elements like <img /> 18950 name = node.nodeName.toLowerCase(); 18951 if (elements && elements[name]) { 18952 // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p> 18953 if (name === 'br') { 18954 brCount++; 18955 continue; 18956 } 18957 18958 return false; 18959 } 18960 18961 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a> 18962 attributes = self.getAttribs(node); 18963 i = attributes.length; 18964 while (i--) { 18965 name = attributes[i].nodeName; 18966 if (name === "name" || name === 'data-mce-bookmark') { 18967 return false; 18968 } 18969 } 18970 } 18971 18972 // Keep comment nodes 18973 if (type == 8) { 18974 return false; 18975 } 18976 18977 // Keep non whitespace text nodes 18978 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) { 18979 return false; 18980 } 18981 } while ((node = walker.next())); 18982 } 18983 18984 return brCount <= 1; 18985 }, 18986 18987 /** 18988 * Creates a new DOM Range object. This will use the native DOM Range API if it's 18989 * available. If it's not, it will fall back to the custom TinyMCE implementation. 18990 * 18991 * @method createRng 18992 * @return {DOMRange} DOM Range object. 18993 * @example 18994 * var rng = tinymce.DOM.createRng(); 18995 * alert(rng.startContainer + "," + rng.startOffset); 18996 */ 18997 createRng: function() { 18998 var doc = this.doc; 18999 19000 return doc.createRange ? doc.createRange() : new Range(this); 19001 }, 19002 19003 /** 19004 * Returns the index of the specified node within its parent. 19005 * 19006 * @method nodeIndex 19007 * @param {Node} node Node to look for. 19008 * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization. 19009 * @return {Number} Index of the specified node. 19010 */ 19011 nodeIndex: nodeIndex, 19012 19013 /** 19014 * Splits an element into two new elements and places the specified split 19015 * element or elements between the new ones. For example splitting the paragraph at the bold element in 19016 * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>. 19017 * 19018 * @method split 19019 * @param {Element} parentElm Parent element to split. 19020 * @param {Element} splitElm Element to split at. 19021 * @param {Element} replacementElm Optional replacement element to replace the split element with. 19022 * @return {Element} Returns the split element or the replacement element if that is specified. 19023 */ 19024 split: function(parentElm, splitElm, replacementElm) { 19025 var self = this, r = self.createRng(), bef, aft, pa; 19026 19027 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
19028 // but we don't want that in our code since it serves no purpose for the end user 19029 // For example splitting this html at the bold element: 19030 // <p>text 1<span><b>CHOP</b></span>text 2</p> 19031 // would produce: 19032 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p> 19033 // this function will then trim off empty edges and produce: 19034 // <p>text 1</p><b>CHOP</b><p>text 2</p> 19035 function trimNode(node) { 19036 var i, children = node.childNodes, type = node.nodeType; 19037 19038 function surroundedBySpans(node) { 19039 var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN'; 19040 var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN'; 19041 return previousIsSpan && nextIsSpan; 19042 } 19043 19044 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') { 19045 return; 19046 } 19047 19048 for (i = children.length - 1; i >= 0; i--) { 19049 trimNode(children[i]); 19050 } 19051 19052 if (type != 9) { 19053 // Keep non whitespace text nodes 19054 if (type == 3 && node.nodeValue.length > 0) { 19055 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>" 19056 // Also keep text nodes with only spaces if surrounded by spans. 19057 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b 19058 var trimmedLength = trim(node.nodeValue).length; 19059 if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) { 19060 return; 19061 } 19062 } else if (type == 1) { 19063 // If the only child is a bookmark then move it up 19064 children = node.childNodes; 19065 19066 // TODO fix this complex if 19067 if (children.length == 1 && children[0] && children[0].nodeType == 1 && 19068 children[0].getAttribute('data-mce-type') == 'bookmark') { 19069 node.parentNode.insertBefore(children[0], node); 19070 } 19071 19072 // Keep non empty elements or img, hr etc 19073 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) { 19074 return; 19075 } 19076 } 19077 19078 self.remove(node); 19079 } 19080 19081 return node; 19082 } 19083 19084 if (parentElm && splitElm) { 19085 // Get before chunk 19086 r.setStart(parentElm.parentNode, self.nodeIndex(parentElm)); 19087 r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm)); 19088 bef = r.extractContents(); 19089 19090 // Get after chunk 19091 r = self.createRng(); 19092 r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1); 19093 r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1); 19094 aft = r.extractContents(); 19095 19096 // Insert before chunk 19097 pa = parentElm.parentNode; 19098 pa.insertBefore(trimNode(bef), parentElm); 19099 19100 // Insert middle chunk 19101 if (replacementElm) { 19102 pa.insertBefore(replacementElm, parentElm); 19103 //pa.replaceChild(replacementElm, splitElm); 19104 } else { 19105 pa.insertBefore(splitElm, parentElm); 19106 } 19107 19108 // Insert after chunk 19109 pa.insertBefore(trimNode(aft), parentElm); 19110 self.remove(parentElm); 19111 19112 return replacementElm || splitElm; 19113 } 19114 }, 19115 19116 /** 19117 * Adds an event handler to the specified object. 19118 * 19119 * @method bind 19120 * @param {Element/Document/Window/Array} target Target element to bind events to. 19121 * handler to or an array of elements/ids/documents. 19122 * @param {String} name Name of event handler to add, for example: click. 19123 * @param {function} func Function to execute when the event occurs. 19124 * @param {Object} scope Optional scope to execute the function in. 19125 * @return {function} Function callback handler the same as the one passed in. 19126 */ 19127 bind: function(target, name, func, scope) { 19128 var self = this; 19129 19130 if (Tools.isArray(target)) { 19131 var i = target.length; 19132 19133 while (i--) { 19134 target[i] = self.bind(target[i], name, func, scope); 19135 } 19136 19137 return target; 19138 } 19139 19140 // Collect all window/document events bound by editor instance 19141 if (self.settings.collect && (target === self.doc || target === self.win)) { 19142 self.boundEvents.push([target, name, func, scope]); 19143 } 19144 19145 return self.events.bind(target, name, func, scope || self); 19146 }, 19147 19148 /** 19149 * Removes the specified event handler by name and function from an element or collection of elements. 19150 * 19151 * @method unbind 19152 * @param {Element/Document/Window/Array} target Target element to unbind events on. 19153 * @param {String} name Event handler name, for example: "click" 19154 * @param {function}
19154 func Function to remove. 19155 * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements 19156 * were passed in. 19157 */ 19158 unbind: function(target, name, func) { 19159 var self = this, i; 19160 19161 if (Tools.isArray(target)) { 19162 i = target.length; 19163 19164 while (i--) { 19165 target[i] = self.unbind(target[i], name, func); 19166 } 19167 19168 return target; 19169 } 19170 19171 // Remove any bound events matching the input 19172 if (self.boundEvents && (target === self.doc || target === self.win)) { 19173 i = self.boundEvents.length; 19174 19175 while (i--) { 19176 var item = self.boundEvents[i]; 19177 19178 if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) { 19179 this.events.unbind(item[0], item[1], item[2]); 19180 } 19181 } 19182 } 19183 19184 return this.events.unbind(target, name, func); 19185 }, 19186 19187 /** 19188 * Fires the specified event name with object on target. 19189 * 19190 * @method fire 19191 * @param {Node/Document/Window} target Target element or object to fire event on. 19192 * @param {String} name Name of the event to fire. 19193 * @param {Object} evt Event object to send. 19194 * @return {Event} Event object. 19195 */ 19196 fire: function(target, name, evt) { 19197 return this.events.fire(target, name, evt); 19198 }, 19199 19200 // Returns the content editable state of a node 19201 getContentEditable: function(node) { 19202 var contentEditable; 19203 19204 // Check type 19205 if (!node || node.nodeType != 1) { 19206 return null; 19207 } 19208 19209 // Check for fake content editable 19210 contentEditable = node.getAttribute("data-mce-contenteditable"); 19211 if (contentEditable && contentEditable !== "inherit") { 19212 return contentEditable; 19213 } 19214 19215 // Check for real content editable 19216 return node.contentEditable !== "inherit" ? node.contentEditable : null; 19217 }, 19218 19219 getContentEditableParent: function(node) { 19220 var root = this.getRoot(), state = null; 19221 19222 for (; node && node !== root; node = node.parentNode) { 19223 state = this.getContentEditable(node); 19224 19225 if (state !== null) { 19226 break; 19227 } 19228 } 19229 19230 return state; 19231 }, 19232 19233 /** 19234 * Destroys all internal references to the DOM to solve IE leak issues. 19235 * 19236 * @method destroy 19237 */ 19238 destroy: function() { 19239 var self = this; 19240 19241 // Unbind all events bound to window/document by editor instance 19242 if (self.boundEvents) { 19243 var i = self.boundEvents.length; 19244 19245 while (i--) { 19246 var item = self.boundEvents[i]; 19247 this.events.unbind(item[0], item[1], item[2]); 19248 } 19249 19250 self.boundEvents = null; 19251 } 19252 19253 // Restore sizzle document to window.document 19254 // Since the current document might be removed producing "Permission denied" on IE see #6325 19255 if (Sizzle.setDocument) { 19256 Sizzle.setDocument(); 19257 } 19258 19259 self.win = self.doc = self.root = self.events = self.frag = null; 19260 }, 19261 19262 isChildOf: function(node, parent) { 19263 while (node) { 19264 if (parent === node) { 19265 return true; 19266 } 19267 19268 node = node.parentNode; 19269 } 19270 19271 return false; 19272 }, 19273 19274 // #ifdef debug 19275 19276 dumpRng: function(r) { 19277 return ( 19278 'startContainer: ' + r.startContainer.nodeName + 19279 ', startOffset: ' + r.startOffset + 19280 ', endContainer: ' + r.endContainer.nodeName + 19281 ', endOffset: ' + r.endOffset 19282 ); 19283 }, 19284 19285 // #endif 19286 19287 _findSib: function(node, selector, name) { 19288 var self = this, func = selector; 19289 19290 if (node) { 19291 // If expression make a function of it using is 19292 if (typeof func == 'string') { 19293 func = function(node) { 19294 return self.is(node, selector); 19295 }; 19296 } 19297 19298 // Loop all siblings 19299 for (node = node[name]; node; node = node[name]) { 19300 if (func(node)) { 19301 return node; 19302 } 19303 } 19304 } 19305 19306 return null; 19307 } 19308 }; 19309 19310 /** 19311 * Instance of DOMUtils for the current document. 19312 * 19313 * @static 19314 * @property DOM 19315 * @type tinymce.dom.DOMUtils 19316 * @example 19317 * // Example of how to add a class to some element by id 19318 * tinymce.DOM.addClass('someid', 'someclass'); 19319 */ 19320 DOMUtils.DOM = new DOMUtils(document); 19321 DOMUtils.nodeIndex = nodeIndex; 19322 19323 return DOMUtils; 19324}); 19325 19326// Included from: js/tinymce/classes/dom/ScriptLoader.js 19327 19328/** 19329 * ScriptLoader.js 19330 * 19331 * Released under LGPL License. 19332 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 19333 * 19334 * License: http://www.tinymce.com/license 19335 * Contributing: http://www.tinymce.com/contributing 19336 */ 19337 19338/*globals console*/ 19339 19340/** 19341 * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks 19342 * when various items gets loaded. This class is useful to load external JavaScript files. 19343 * 19344 * @class tinymce.dom.ScriptLoader 19345 * @example 19346 * // Load a script from a specific URL using the global script loader 19347 * tinymce.ScriptLoader.load('somescript.js'); 19348 *
19349 * // Load a script using a unique instance of the script loader 19350 * var scriptLoader = new tinymce.dom.ScriptLoader(); 19351 * 19352 * scriptLoader.load('somescript.js'); 19353 * 19354 * // Load multiple scripts 19355 * var scriptLoader = new tinymce.dom.ScriptLoader(); 19356 * 19357 * scriptLoader.add('somescript1.js'); 19358 * scriptLoader.add('somescript2.js'); 19359 * scriptLoader.add('somescript3.js'); 19360 * 19361 * scriptLoader.loadQueue(function() { 19362 * alert('All scripts are now loaded.'); 19363 * }); 19364 */ 19365define("tinymce/dom/ScriptLoader", [ 19366 "tinymce/dom/DOMUtils", 19367 "tinymce/util/Tools" 19368], function(DOMUtils, Tools) { 19369 var DOM = DOMUtils.DOM; 19370 var each = Tools.each, grep = Tools.grep; 19371 19372 function ScriptLoader() { 19373 var QUEUED = 0, 19374 LOADING = 1, 19375 LOADED = 2, 19376 states = {}, 19377 queue = [], 19378 scriptLoadedCallbacks = {}, 19379 queueLoadedCallbacks = [], 19380 loading = 0, 19381 undef; 19382 19383 /** 19384 * Loads a specific script directly without adding it to the load queue. 19385 * 19386 * @method load 19387 * @param {String} url Absolute URL to script to add. 19388 * @param {function} callback Optional callback function to execute ones this script gets loaded. 19389 */ 19390 function loadScript(url, callback) { 19391 var dom = DOM, elm, id; 19392 19393 // Execute callback when script is loaded 19394 function done() { 19395 dom.remove(id); 19396 19397 if (elm) { 19398 elm.onreadystatechange = elm.onload = elm = null; 19399 } 19400 19401 callback(); 19402 } 19403 19404 function error() { 19405 /*eslint no-console:0 */ 19406 19407 // Report the error so it's easier for people to spot loading errors 19408 if (typeof console !== "undefined" && console.log) { 19409 console.log("Failed to load: " + url); 19410 } 19411 19412 // We can't mark it as done if there is a load error since 19413 // A) We don't want to produce 404 errors on the server and 19414 // B) the onerror event won't fire on all browsers. 19415 // done(); 19416 } 19417 19418 id = dom.uniqueId(); 19419 19420 // Create new script element 19421 elm = document.createElement('script'); 19422 elm.id = id; 19423 elm.type = 'text/javascript'; 19424 elm.src = Tools._addCacheSuffix(url); 19425 19426 // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly 19427 if ("onreadystatechange" in elm) { 19428 elm.onreadystatechange = function() { 19429 if (/loaded|complete/.test(elm.readyState)) { 19430 done(); 19431 } 19432 }; 19433 } else { 19434 elm.onload = done; 19435 } 19436 19437 // Add onerror event will get fired on some browsers but not all of them 19438 elm.onerror = error; 19439 19440 // Add script to document 19441 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm); 19442 } 19443 19444 /** 19445 * Returns true/false if a script has been loaded or not. 19446 * 19447 * @method isDone 19448 * @param {String} url URL to check for. 19449 * @return {Boolean} true/false if the URL is loaded. 19450 */ 19451 this.isDone = function(url) { 19452 return states[url] == LOADED; 19453 }; 19454 19455 /** 19456 * Marks a specific script to be loaded. This can be useful if a script got loaded outside 19457 * the script loader or to skip it from loading some script. 19458 * 19459 * @method markDone 19460 * @param {string} url Absolute URL to the script to mark as loaded. 19461 */ 19462 this.markDone = function(url) { 19463 states[url] = LOADED; 19464 }; 19465 19466 /** 19467 * Adds a specific script to the load queue of the script loader. 19468 * 19469 * @method add 19470 * @param {String} url Absolute URL to script to add. 19471 * @param {function} callback Optional callback function to execute ones this script gets loaded. 19472 * @param {Object} scope Optional scope to execute callback in. 19473 */ 19474 this.add = this.load = function(url, callback, scope) { 19475 var state = states[url]; 19476 19477 // Add url to load queue 19478 if (state == undef) { 19479 queue.push(url); 19480 states[url] = QUEUED; 19481 } 19482 19483 if (callback) { 19484 // Store away callback for later execution 19485 if (!scriptLoadedCallbacks[url]) { 19486 scriptLoadedCallbacks[url] = []; 19487 } 19488 19489 scriptLoadedCallbacks[url].push({ 19490 func: callback, 19491 scope: scope || this 19492 }); 19493 } 19494 }; 19495 19496 /** 19497 * Starts the loading of the queue. 19498 * 19499 * @method loadQueue 19500 * @param {function} callback Optional callback to execute when all queued items are loaded. 19501 * @param {Object} scope Optional scope to execute the callback in. 19502 */ 19503 this.loadQueue = function(callback, scope) { 19504 this.loadScripts(queue, callback, scope); 19505 }; 19506 19507 /** 19508 * Loads the specified queue of files and executes the callback ones they are loaded. 19509 * This method is generally not used outside this class but it might be useful in some scenarios. 19510 * 19511 * @method loadScripts 19512 * @param {Array} scripts Array of queue items to load. 19513 * @param {function} callback Optional callback to execute ones all items are loaded. 19514 * @param {Object} scope Optional scope to execute callback in. 19515 */ 19516 this.loadScripts = function(scripts, callback, scope) { 19517 var loadScripts; 19518 19519 function execScriptLoadedCallbacks(url) { 19520 // Execute URL callback functions 19521 each(scriptLoadedCallbacks[url], function(callback) { 19522 callback.func.call(callback.scope); 19523 }); 19524 19525 scriptLoadedCallbacks[url] = undef; 19526 } 19527 19528 queueLoadedCallbacks.push({ 19529 func: callback, 19530 scope: scope || this 19531 }); 19532 19533 loadScripts = function() { 19534 var loadingScripts = grep(scripts); 19535 19536 // Current scripts has been handled 19537 scripts.length = 0; 19538 19539 // Load scripts that needs to be loaded 19540 each(loadingScripts, function(url) { 19541 // Script is already loaded then execute script callbacks directly 19542 if (states[url] == LOADED) { 19543 execScriptLoadedCallbacks(url); 19544 return; 19545 } 19546 19547 // Is script not loading then start loading it 19548 if (states[url] != LOADING) { 19549 states[url] = LOADING; 19550 loading++; 19551 19552 loadScript(url, function() { 19553 states[url] = LOADED; 19554 loading--; 19555 19556 execScriptLoadedCallbacks(url); 19557 19558 // Load more scripts if they where added by the recently loaded script 19559 loadScripts(); 19560 }); 19561 } 19562 }); 19563 19564 // No scripts are currently loading then execute all pending queue loaded callbacks 19565 if (!loading) { 19566 each(queueLoadedCallbacks, function(callback) { 19567 callback.func.call(callback.scope); 19568 }); 19569 19570 queueLoadedCallbacks.length = 0; 19571 } 19572 }; 19573 19574 loadScripts(); 19575 }; 19576 } 19577 19578 ScriptLoader.ScriptLoader = new ScriptLoader(); 19579 19580 return ScriptLoader; 19581}); 19582 19583// Included from: js/tinymce/classes/AddOnManager.js 19584 19585/** 19586 * AddOnManager.js 19587 * 19588 * Released under LGPL License. 19589 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 19590 * 19591 * License: http://www.tinymce.com/license 19592 * Contributing: http://www.tinymce.com/contributing 19593 */ 19594 19595/** 19596 * This class handles the loading of themes/plugins or other add-ons and their language packs. 19597 * 19598 * @class tinymce.AddOnManager 19599 */ 19600define("tinymce/AddOnManager", [ 19601 "tinymce/dom/ScriptLoader", 19602 "tinymce/util/Tools" 19603], function(ScriptLoader, Tools) { 19604 var each = Tools.each; 19605 19606 function AddOnManager() { 19607 var self = this; 19608 19609 self.items = []; 19610 self.urls = {}; 19611 self.lookup = {}; 19612 } 19613 19614 AddOnManager.prototype = { 19615 /** 19616 * Returns the specified add on by the short name. 19617 * 19618 * @method get 19619 * @param {String} name Add-on to look for. 19620 * @return {tinymce.Theme/tinymce.Plugin}
19620 Theme or plugin add-on instance or undefined. 19621 */ 19622 get: function(name) { 19623 if (this.lookup[name]) { 19624 return this.lookup[name].instance; 19625 } 19626 19627 return undefined; 19628 }, 19629 19630 dependencies: function(name) { 19631 var result; 19632 19633 if (this.lookup[name]) { 19634 result = this.lookup[name].dependencies; 19635 } 19636 19637 return result || []; 19638 }, 19639 19640 /** 19641 * Loads a language pack for the specified add-on. 19642 * 19643 * @method requireLangPack 19644 * @param {String} name Short name of the add-on. 19645 * @param {String} languages Optional comma or space separated list of languages to check if it matches the name. 19646 */ 19647 requireLangPack: function(name, languages) { 19648 var language = AddOnManager.language; 19649 19650 if (language && AddOnManager.languageLoad !== false) { 19651 if (languages) { 19652 languages = ',' + languages + ','; 19653 19654 // Load short form sv.js or long form sv_SE.js 19655 if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) { 19656 language = language.substr(0, 2); 19657 } else if (languages.indexOf(',' + language + ',') == -1) { 19658 return; 19659 } 19660 } 19661 19662 ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js'); 19663 } 19664 }, 19665 19666 /** 19667 * Adds a instance of the add-on by it's short name. 19668 * 19669 * @method add 19670 * @param {String} id Short name/id for the add-on. 19671 * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add. 19672 * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in. 19673 * @example 19674 * // Create a simple plugin 19675 * tinymce.create('tinymce.plugins.TestPlugin', { 19676 * TestPlugin: function(ed, url) { 19677 * ed.on('click', function(e) { 19678 * ed.windowManager.alert('Hello World!'); 19679 * }); 19680 * } 19681 * }); 19682 * 19683 * // Register plugin using the add method 19684 * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin); 19685 * 19686 * // Initialize TinyMCE 19687 * tinymce.init({ 19688 * ... 19689 * plugins: '-test' // Init the plugin but don't try to load it 19690 * }); 19691 */ 19692 add: function(id, addOn, dependencies) { 19693 this.items.push(addOn); 19694 this.lookup[id] = {instance: addOn, dependencies: dependencies}; 19695 19696 return addOn; 19697 }, 19698 19699 createUrl: function(baseUrl, dep) { 19700 if (typeof dep === "object") { 19701 return dep; 19702 } 19703 19704 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; 19705 }, 19706 19707 /** 19708 * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url. 19709 * This should be used in development mode. A new compressor/javascript munger process will ensure that the 19710 * components are put together into the plugin.js file and compressed correctly. 19711 * 19712 * @method addComponents 19713 * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins). 19714 * @param {Array} scripts Array containing the names of the scripts to load. 19715 */ 19716 addComponents: function(pluginName, scripts) { 19717 var pluginUrl = this.urls[pluginName]; 19718 19719 each(scripts, function(script) { 19720 ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script); 19721 }); 19722 }, 19723 19724 /** 19725 * Loads an add-on from a specific url. 19726 * 19727 * @method load 19728 * @param {String} name Short name of the add-on that gets loaded. 19729 * @param {String} addOnUrl URL to the add-on that will get loaded. 19730 * @param {function} callback Optional callback to execute ones the add-on is loaded. 19731 * @param {Object} scope Optional scope to execute the callback in. 19732 * @example 19733 * // Loads a plugin from an external URL 19734 * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js'); 19735 * 19736 * // Initialize TinyMCE 19737 * tinymce.init({ 19738 * ... 19739 * plugins: '-myplugin' // Don't try to load it again 19740 * }); 19741 */ 19742 load: function(name, addOnUrl, callback, scope) { 19743 var self = this, url = addOnUrl; 19744 19745 function loadDependencies() { 19746 var dependencies = self.dependencies(name); 19747 19748 each(dependencies, function(dep) { 19749 var newUrl = self.createUrl(addOnUrl, dep); 19750 19751 self.load(newUrl.resource, newUrl, undefined, undefined); 19752 }); 19753 19754 if (callback) { 19755 if (scope) { 19756 callback.call(scope); 19757 } else { 19758 callback.call(ScriptLoader); 19759 } 19760 } 19761 } 19762 19763 if (self.urls[name]) { 19764 return; 19765 } 19766 19767 if (typeof addOnUrl === "object") { 19768 url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix; 19769 } 19770 19771 if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) { 19772 url = AddOnManager.baseURL + '/' + url; 19773 } 19774 19775 self.urls[name] = url.substring(0, url.lastIndexOf('/')); 19776 19777 if (self.lookup[name]) { 19778 loadDependencies(); 19779 } else { 19780 ScriptLoader.ScriptLoader.add(url, loadDependencies, scope); 19781 } 19782 } 19783 }; 19784 19785 AddOnManager.PluginManager = new AddOnManager(); 19786 AddOnManager.ThemeManager = new AddOnManager(); 19787 19788 return AddOnManager; 19789}); 19790 19791/** 19792 * TinyMCE theme class. 19793 * 19794 * @class tinymce.Theme 19795 */ 19796 19797/** 19798 * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc. 19799 * 19800 * @method renderUI 19801 * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance. 19802 * @return {Object}
19802 an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight. 19803 */ 19804 19805/** 19806 * 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. 19807 * 19808 * @class tinymce.Plugin 19809 * @example 19810 * tinymce.PluginManager.add('example', function(editor, url) { 19811 * // Add a button that opens a window 19812 * editor.addButton('example', { 19813 * text: 'My button', 19814 * icon: false, 19815 * onclick: function() { 19816 * // Open window 19817 * editor.windowManager.open({ 19818 * title: 'Example plugin', 19819 * body: [ 19820 * {type: 'textbox', name: 'title', label: 'Title'} 19821 * ], 19822 * onsubmit: function(e) { 19823 * // Insert content when the window form is submitted 19824 * editor.insertContent('Title: ' + e.data.title); 19825 * } 19826 * }); 19827 * } 19828 * }); 19829 * 19830 * // Adds a menu item to the tools menu 19831 * editor.addMenuItem('example', { 19832 * text: 'Example plugin', 19833 * context: 'tools', 19834 * onclick: function() { 19835 * // Open window with a specific url 19836 * editor.windowManager.open({ 19837 * title: 'TinyMCE site', 19838 * url: 'http://www.tinymce.com', 19839 * width: 800, 19840 * height: 600, 19841 * buttons: [{ 19842 * text: 'Close', 19843 * onclick: 'close' 19844 * }] 19845 * }); 19846 * } 19847 * }); 19848 * }); 19849 */ 19850 19851// Included from: js/tinymce/classes/dom/NodeType.js 19852 19853/** 19854 * NodeType.js 19855 * 19856 * Released under LGPL License. 19857 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 19858 * 19859 * License: http://www.tinymce.com/license 19860 * Contributing: http://www.tinymce.com/contributing 19861 */ 19862 19863/** 19864 * Contains various node validation functions. 19865 * 19866 * @private 19867 * @class tinymce.dom.NodeType 19868 */ 19869define("tinymce/dom/NodeType", [], function() { 19870 function isNodeType(type) { 19871 return function(node) { 19872 return !!node && node.nodeType == type; 19873 }; 19874 } 19875 19876 var isElement = isNodeType(1); 19877 19878 function matchNodeNames(names) { 19879 names = names.toLowerCase().split(' '); 19880 19881 return function(node) { 19882 var i, name; 19883 19884 if (node && node.nodeType) { 19885 name = node.nodeName.toLowerCase(); 19886 19887 for (i = 0; i < names.length; i++) { 19888 if (name === names[i]) { 19889 return true; 19890 } 19891 } 19892 } 19893 19894 return false; 19895 }; 19896 } 19897 19898 function matchStyleValues(name, values) { 19899 values = values.toLowerCase().split(' '); 19900 19901 return function(node) { 19902 var i, cssValue; 19903 19904 if (isElement(node)) { 19905 for (i = 0; i < values.length; i++) { 19906 cssValue = getComputedStyle(node, null).getPropertyValue(name); 19907 if (cssValue === values[i]) { 19908 return true; 19909 } 19910 } 19911 } 19912 19913 return false; 19914 }; 19915 } 19916 19917 function hasPropValue(propName, propValue) { 19918 return function(node) { 19919 return isElement(node) && node[propName] === propValue; 19920 }; 19921 } 19922 19923 function hasAttributeValue(attrName, attrValue) { 19924 return function(node) { 19925 return isElement(node) && node.getAttribute(attrName) === attrValue; 19926 }; 19927 } 19928 19929 function isBogus(node) { 19930 return isElement(node) && node.hasAttribute('data-mce-bogus'); 19931 } 19932 19933 function hasContentEditableState(value) { 19934 return function(node) { 19935 if (isElement(node)) { 19936 if (node.contentEditable === value) { 19937 return true; 19938 } 19939 19940 if (node.getAttribute('data-mce-contenteditable') === value) { 19941 return true; 19942 } 19943 } 19944 19945 return false; 19946 }; 19947 } 19948 19949 return { 19950 isText: isNodeType(3), 19951 isElement: isElement, 19952 isComment: isNodeType(8), 19953 isBr: matchNodeNames('br'), 19954 isContentEditableTrue: hasContentEditableState('true'), 19955 isContentEditableFalse: hasContentEditableState('false'), 19956 matchNodeNames: matchNodeNames, 19957 hasPropValue: hasPropValue, 19958 hasAttributeValue: hasAttributeValue, 19959 matchStyleValues: matchStyleValues, 19960 isBogus: isBogus 19961 }; 19962}); 19963 19964// Included from: js/tinymce/classes/text/Zwsp.js 19965 19966/** 19967 * Zwsp.js 19968 * 19969 * Released under LGPL License. 19970 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 19971 * 19972 * License: http://www.tinymce.com/license 19973 * Contributing: http://www.tinymce.com/contributing 19974 */ 19975 19976/** 19977 * .... 19978 * 19979 * @private 19980 * @class tinymce.text.Zwsp 19981 * @example 19982 * var isZwsp = Zwsp.isZwsp('\u200b'); 19983 * var abc = Zwsp.trim('a\u200bc'); 19984 */ 19985define("tinymce/text/Zwsp", [], function() { 19986 var ZWSP = '\u200b'; 19987 19988 function isZwsp(chr) { 19989 return chr == ZWSP; 19990 } 19991 19992 function trim(str) { 19993 return str.replace(new RegExp(ZWSP, 'g'), ''); 19994 } 19995 19996 return { 19997 isZwsp: isZwsp, 19998 ZWSP: ZWSP, 19999 trim: trim 20000 }; 20001}); 20002 20003// Included from: js/tinymce/classes/caret/CaretContainer.js 20004 20005/** 20006 * CaretContainer.js 20007 * 20008 * Released under LGPL License. 20009 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 20010 * 20011 * License: http://www.tinymce.com/license 20012 * Contributing: http://www.tinymce.com/contributing 20013 */ 20014 20015/** 20016 * This module handles caret containers. A caret container is a
20016node that 20017 * holds the caret for positional purposes. 20018 * 20019 * @private 20020 * @class tinymce.caret.CaretContainer 20021 */ 20022define("tinymce/caret/CaretContainer", [ 20023 "tinymce/dom/NodeType", 20024 "tinymce/text/Zwsp" 20025], function(NodeType, Zwsp) { 20026 var isElement = NodeType.isElement, 20027 isText = NodeType.isText; 20028 20029 function isCaretContainerBlock(node) { 20030 if (isText(node)) { 20031 node = node.parentNode; 20032 } 20033 20034 return isElement(node) && node.hasAttribute('data-mce-caret'); 20035 } 20036 20037 function isCaretContainerInline(node) { 20038 return isText(node) && Zwsp.isZwsp(node.data); 20039 } 20040 20041 function isCaretContainer(node) { 20042 return isCaretContainerBlock(node) || isCaretContainerInline(node); 20043 } 20044 20045 function insertInline(node, before) { 20046 var doc, sibling, textNode, parentNode; 20047 20048 doc = node.ownerDocument; 20049 textNode = doc.createTextNode(Zwsp.ZWSP); 20050 parentNode = node.parentNode; 20051 20052 if (!before) { 20053 sibling = node.nextSibling; 20054 if (isText(sibling)) { 20055 if (isCaretContainer(sibling)) { 20056 return sibling; 20057 } 20058 20059 if (startsWithCaretContainer(sibling)) { 20060 sibling.splitText(1); 20061 return sibling; 20062 } 20063 } 20064 20065 if (node.nextSibling) { 20066 parentNode.insertBefore(textNode, node.nextSibling); 20067 } else { 20068 parentNode.appendChild(textNode); 20069 } 20070 } else { 20071 sibling = node.previousSibling; 20072 if (isText(sibling)) { 20073 if (isCaretContainer(sibling)) { 20074 return sibling; 20075 } 20076 20077 if (endsWithCaretContainer(sibling)) { 20078 return sibling.splitText(sibling.data.length - 1); 20079 } 20080 } 20081 20082 parentNode.insertBefore(textNode, node); 20083 } 20084 20085 return textNode; 20086 } 20087 20088 function insertBlock(blockName, node, before) { 20089 var doc, blockNode, parentNode; 20090 20091 doc = node.ownerDocument; 20092 blockNode = doc.createElement(blockName); 20093 blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after'); 20094 blockNode.setAttribute('data-mce-bogus', 'all'); 20095 blockNode.appendChild(doc.createTextNode('\u00a0')); 20096 parentNode = node.parentNode; 20097 20098 if (!before) { 20099 if (node.nextSibling) { 20100 parentNode.insertBefore(blockNode, node.nextSibling); 20101 } else { 20102 parentNode.appendChild(blockNode); 20103 } 20104 } else { 20105 parentNode.insertBefore(blockNode, node); 20106 } 20107 20108 return blockNode; 20109 } 20110 20111 function remove(caretContainerNode) { 20112 var text; 20113 20114 if (isElement(caretContainerNode) && isCaretContainer(caretContainerNode)) { 20115 if (caretContainerNode.innerHTML != ' ') { 20116 caretContainerNode.removeAttribute('data-mce-caret'); 20117 } else { 20118 if (caretContainerNode.parentNode) { 20119 caretContainerNode.parentNode.removeChild(caretContainerNode); 20120 } 20121 } 20122 } 20123 20124 if (isText(caretContainerNode)) { 20125 text = Zwsp.trim(caretContainerNode.data); 20126 20127 if (text.length === 0) { 20128 if (caretContainerNode.parentNode) { 20129 caretContainerNode.parentNode.removeChild(caretContainerNode); 20130 } 20131 } 20132 20133 caretContainerNode.nodeValue = text; 20134 } 20135 } 20136 20137 function startsWithCaretContainer(node) { 20138 return isText(node) && node.data[0] == Zwsp.ZWSP; 20139 } 20140 20141 function endsWithCaretContainer(node) { 20142 return isText(node) && node.data[node.data.length - 1] == Zwsp.ZWSP; 20143 } 20144 20145 return { 20146 isCaretContainer: isCaretContainer, 20147 isCaretContainerBlock: isCaretContainerBlock, 20148 isCaretContainerInline: isCaretContainerInline, 20149 insertInline: insertInline, 20150 insertBlock: insertBlock, 20151 remove: remove, 20152 startsWithCaretContainer: startsWithCaretContainer, 20153 endsWithCaretContainer: endsWithCaretContainer 20154 }; 20155}); 20156 20157// Included from: js/tinymce/classes/dom/RangeUtils.js 20158 20159/** 20160 * RangeUtils.js 20161 * 20162 * Released under LGPL License. 20163 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 20164 * 20165 * License: http://www.tinymce.com/license 20166 * Contributing: http://www.tinymce.com/contributing 20167 */ 20168 20169/** 20170 * This class contains a few utility methods for ranges. 20171 * 20172 * @class tinymce.dom.RangeUtils 20173 */ 20174define("tinymce/dom/RangeUtils", [ 20175 "tinymce/util/Tools", 20176 "tinymce/dom/TreeWalker", 20177 "tinymce/dom/NodeType", 20178 "tinymce/caret/CaretContainer" 20179], function(Tools, TreeWalker, NodeType, CaretContainer) { 20180 var each = Tools.each, 20181 isContentEditableFalse = NodeType.isContentEditableFalse, 20182 isCaretContainer = CaretContainer.isCaretContainer; 20183 20184 function getEndChild(container, index) { 20185 var childNodes = container.childNodes; 20186 20187 index--; 20188 20189 if (index > childNodes.length - 1) { 20190 index = childNodes.length - 1; 20191 } else if (index < 0) { 20192 index = 0; 20193 } 20194 20195 return childNodes[index] || container; 20196 } 20197 20198 function RangeUtils(dom) { 20199 /** 20200 * Walks the specified range like object and executes the callback for each sibling collection it finds. 20201 * 20202 * @private 20203 * @method walk 20204 * @param {Object} rng Range like object. 20205 * @param {function} callback Callback function to execute for each sibling collection. 20206 */ 20207 this.walk = function(rng, callback) { 20208 var startContainer = rng.startContainer, 20209 startOffset = rng.startOffset, 20210 endContainer = rng.endContainer, 20211 endOffset = rng.endOffset, 20212 ancestor, startPoint, 20213 endPoint, node, parent, siblings, nodes; 20214 20215 // Handle table cell selection the table plugin enables 20216 // you to fake select table cells and perform formatting actions on them
20217 nodes = dom.select('td.mce-item-selected,th.mce-item-selected'); 20218 if (nodes.length > 0) { 20219 each(nodes, function(node) { 20220 callback([node]); 20221 }); 20222 20223 return; 20224 } 20225 20226 /** 20227 * Excludes start/end text node if they are out side the range 20228 * 20229 * @private 20230 * @param {Array} nodes Nodes to exclude items from. 20231 * @return {Array} Array with nodes excluding the start/end container if needed. 20232 */ 20233 function exclude(nodes) { 20234 var node; 20235 20236 // First node is excluded 20237 node = nodes[0]; 20238 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) { 20239 nodes.splice(0, 1); 20240 } 20241 20242 // Last node is excluded 20243 node = nodes[nodes.length - 1]; 20244 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) { 20245 nodes.splice(nodes.length - 1, 1); 20246 } 20247 20248 return nodes; 20249 } 20250 20251 /** 20252 * Collects siblings 20253 * 20254 * @private 20255 * @param {Node} node Node to collect siblings from. 20256 * @param {String} name Name of the sibling to check for. 20257 * @param {Node} end_node 20258 * @return {Array} Array of collected siblings. 20259 */ 20260 function collectSiblings(node, name, end_node) { 20261 var siblings = []; 20262 20263 for (; node && node != end_node; node = node[name]) { 20264 siblings.push(node); 20265 } 20266 20267 return siblings; 20268 } 20269 20270 /** 20271 * Find an end point this is the node just before the common ancestor root. 20272 * 20273 * @private 20274 * @param {Node} node Node to start at. 20275 * @param {Node} root Root/ancestor element to stop just before. 20276 * @return {Node} Node just before the root element. 20277 */ 20278 function findEndPoint(node, root) { 20279 do { 20280 if (node.parentNode == root) { 20281 return node; 20282 } 20283 20284 node = node.parentNode; 20285 } while (node); 20286 } 20287
20288 function walkBoundary(start_node, end_node, next) { 20289 var siblingName = next ? 'nextSibling' : 'previousSibling'; 20290 20291 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) { 20292 parent = node.parentNode; 20293 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName); 20294 20295 if (siblings.length) { 20296 if (!next) { 20297 siblings.reverse(); 20298 } 20299 20300 callback(exclude(siblings)); 20301 } 20302 } 20303 } 20304 20305 // If index based start position then resolve it 20306 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 20307 startContainer = startContainer.childNodes[startOffset]; 20308 } 20309 20310 // If index based end position then resolve it 20311 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 20312 endContainer = getEndChild(endContainer, endOffset); 20313 } 20314 20315 // Same container 20316 if (startContainer == endContainer) { 20317 return callback(exclude([startContainer])); 20318 } 20319 20320 // Find common ancestor and end points 20321 ancestor = dom.findCommonAncestor(startContainer, endContainer); 20322 20323 // Process left side 20324 for (node = startContainer; node; node = node.parentNode) { 20325 if (node === endContainer) { 20326 return walkBoundary(startContainer, ancestor, true); 20327 } 20328 20329 if (node === ancestor) { 20330 break; 20331 } 20332 } 20333 20334 // Process right side 20335 for (node = endContainer; node; node = node.parentNode) { 20336 if (node === startContainer) { 20337 return walkBoundary(endContainer, ancestor); 20338 } 20339 20340 if (node === ancestor) { 20341 break; 20342 } 20343 } 20344 20345 // Find start/end point 20346 startPoint = findEndPoint(startContainer, ancestor) || startContainer; 20347 endPoint = findEndPoint(endContainer, ancestor) || endContainer; 20348 20349 // Walk left leaf 20350 walkBoundary(startContainer, startPoint, true); 20351 20352 // Walk the middle from start to end point 20353 siblings = collectSiblings( 20354 startPoint == startContainer ? startPoint : startPoint.nextSibling, 20355 'nextSibling', 20356 endPoint == endContainer ? endPoint.nextSibling : endPoint 20357 ); 20358 20359 if (siblings.length) { 20360 callback(exclude(siblings)); 20361 } 20362 20363 // Walk right leaf 20364 walkBoundary(endContainer, endPoint); 20365 }; 20366 20367 /** 20368 * Splits the specified range at it's start/end points. 20369 * 20370 * @private 20371 * @param {Range/RangeObject} rng Range to split. 20372 * @return {Object} Range position object. 20373 */ 20374 this.split = function(rng) { 20375 var startContainer = rng.startContainer, 20376 startOffset = rng.startOffset, 20377 endContainer = rng.endContainer, 20378 endOffset = rng.endOffset; 20379 20380 function splitText(node, offset) { 20381 return node.splitText(offset); 20382 } 20383 20384 // Handle single text node 20385 if (startContainer == endContainer && startContainer.nodeType == 3) { 20386 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) { 20387 endContainer = splitText(startContainer, startOffset); 20388 startContainer = endContainer.previousSibling; 20389 20390 if (endOffset > startOffset) { 20391 endOffset = endOffset - startOffset; 20392 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling; 20393 endOffset = endContainer.nodeValue.length; 20394 startOffset = 0; 20395 } else { 20396 endOffset = 0; 20397 } 20398 } 20399 } else { 20400 // Split startContainer text node if needed 20401 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) { 20402 startContainer = splitText(startContainer, startOffset); 20403 startOffset = 0; 20404 } 20405 20406 // Split endContainer text node if needed 20407 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) { 20408 endContainer = splitText(endContainer, endOffset).previousSibling; 20409 endOffset = endContainer.nodeValue.length; 20410 } 20411 } 20412 20413 return { 20414 startContainer: startContainer, 20415 startOffset: startOffset, 20416 endContainer: endContainer, 20417 endOffset: endOffset 20418 }; 20419 }; 20420 20421 /** 20422 * Normalizes the specified range by finding the closest best suitable caret location. 20423 * 20424 * @private 20425 * @param {Range} rng Range to normalize. 20426 * @return {Boolean} True/false if the specified range was normalized or not. 20427 */ 20428 this.normalize = function(rng) { 20429 var normalized, collapsed; 20430 20431 function normalizeEndPoint(start) { 20432 var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap; 20433 var directionLeft, isAfterNode; 20434 20435 function isTableCell(node) { 20436 return node && /^(TD|TH|CAPTION)$/.test(node.nodeName); 20437 } 20438 20439 function hasBrBeforeAfter(node, left) { 20440 var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body); 20441 20442 while ((node = walker[left ? 'prev' : 'next']())) { 20443 if (node.nodeName === "BR") { 20444 return true; 20445 } 20446 } 20447 } 20448 20449 function hasContentEditableFalseParent(node) { 20450 while (node && node != body) { 20451 if (isContentEditableFalse(node)) { 20452 return true; 20453 } 20454 20455 node = node.parentNode; 20456 } 20457 20458 return false;
20459 } 20460 20461 function isPrevNode(node, name) { 20462 return node.previousSibling && node.previousSibling.nodeName == name; 20463 } 20464 20465 // Walks the dom left/right to find a suitable text node to move the endpoint into 20466 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG 20467 function findTextNodeRelative(left, startNode) { 20468 var walker, lastInlineElement, parentBlockContainer; 20469 20470 startNode = startNode || container; 20471 parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body; 20472 20473 // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680 20474 // This: <p><br>|</p> becomes <p>|<br></p> 20475 if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) { 20476 container = startNode.parentNode; 20477 offset = dom.nodeIndex(startNode); 20478 normalized = true; 20479 return; 20480 } 20481 20482 // Walk left until we hit a text node we can move to or a block/br/img 20483 walker = new TreeWalker(startNode, parentBlockContainer); 20484 while ((node = walker[left ? 'prev' : 'next']())) { 20485 // Break if we hit a non content editable node 20486 if (dom.getContentEditableParent(node) === "false" || isCaretContainer(node)) { 20487 return; 20488 } 20489 20490 // Found text node that has a length 20491 if (node.nodeType === 3 && node.nodeValue.length > 0) { 20492 container = node; 20493 offset = left ? node.nodeValue.length : 0; 20494 normalized = true; 20495 return; 20496 } 20497 20498 // Break if we find a block or a BR/IMG/INPUT etc 20499 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 20500 return; 20501 } 20502 20503 lastInlineElement = node; 20504 } 20505 20506 // Only fetch the last inline element when in caret mode for now 20507 if (collapsed && lastInlineElement) { 20508 container = lastInlineElement; 20509 normalized = true; 20510 offset = 0; 20511 } 20512 } 20513 20514 container = rng[(start ? 'start' : 'end') + 'Container']; 20515 offset = rng[(start ? 'start' : 'end') + 'Offset']; 20516 isAfterNode = container.nodeType == 1 && offset === container.childNodes.length; 20517 nonEmptyElementsMap = dom.schema.getNonEmptyElements(); 20518 directionLeft = start; 20519 20520 if (isCaretContainer(container)) { 20521 return; 20522 } 20523 20524 if (container.nodeType == 1 && offset > container.childNodes.length - 1) { 20525 directionLeft = false; 20526 } 20527 20528 // If the container is a document move it to the body element 20529 if (container.nodeType === 9) { 20530 container = dom.getRoot(); 20531 offset = 0; 20532 } 20533 20534 // If the container is body try move it into the closest text node or position 20535 if (container === body) { 20536 // If start is before/after a image, table etc 20537 if (directionLeft) { 20538 node = container.childNodes[offset > 0 ? offset - 1 : 0]; 20539 if (node) { 20540 if (isCaretContainer(node)) { 20541 return; 20542 } 20543 20544 if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") { 20545 return; 20546 } 20547 } 20548 } 20549 20550 // Resolve the index 20551 if (container.hasChildNodes()) { 20552 offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1); 20553 container = container.childNodes[offset]; 20554 offset = 0; 20555 20556 if (hasContentEditableFalseParent(container) || isCaretContainer(container)) { 20557 return; 20558 } 20559 20560 // Don't walk into elements that doesn't have any child nodes like a IMG 20561 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) { 20562 // Walk the DOM to find a text node to place the caret at or a BR 20563 node = container; 20564 walker = new TreeWalker(container, body); 20565 20566 do { 20567 if (isContentEditableFalse(node) || isCaretContainer(node)) { 20568 normalized = false; 20569 break; 20570 } 20571 20572 // Found a text node use that position 20573 if (node.nodeType === 3 && node.nodeValue.length > 0) { 20574 offset = directionLeft ? 0 : node.nodeValue.length; 20575 container = node; 20576 normalized = true; 20577 break; 20578 } 20579 20580 // Found a BR/IMG element that we can place the caret before 20581 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell(node)) { 20582 offset = dom.nodeIndex(node); 20583 container = node.parentNode; 20584 20585 // Put caret after image when moving the end point 20586 if (node.nodeName == "IMG" && !directionLeft) { 20587 offset++; 20588 } 20589 20590 normalized = true; 20591 break; 20592 } 20593 } while ((node = (directionLeft ? walker.next() : walker.prev()))); 20594 } 20595 } 20596 } 20597 20598 // Lean the caret to the left if possible 20599 if (collapsed) { 20600 // So this: <b>x</b><i>|x</i> 20601 // Becomes: <b>x|</b><i>x</i> 20602 // Seems that only gecko has issues with this 20603 if (container.nodeType === 3 && offset === 0) { 20604 findTextNodeRelative(true); 20605 } 20606 20607 // Lean left into empty inline elements when the caret is before a BR 20608 // So this: <i><b></b><i>|<br></i> 20609 // Becomes: <i><b>|</b><i><br></i> 20610 // Seems that only gecko has issues with this. 20611 // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p> 20612 if (container.nodeType === 1) { 20613 node = container.childNodes[offset]; 20614 20615 // Offset is after the containers last child 20616 // then use the previous child for normalization 20617 if (!node) { 20618 node = container.childNodes[offset - 1]; 20619 } 20620 20621 if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') && 20622 !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) { 20623 findTextNodeRelative(true, node); 20624 } 20625 } 20626 } 20627 20628 // Lean the start of the selection right if possible 20629 // So this: x[<b>x]</b> 20630 // Becomes: x<b>[x]</b> 20631 if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) { 20632 findTextNodeRelative(false); 20633 } 20634 20635 // Set endpoint if it was normalized 20636 if (normalized) { 20637 rng['set' + (start ? 'Start' : 'End')](container, offset); 20638 } 20639 } 20640 20641 collapsed = rng.collapsed; 20642 20643 normalizeEndPoint(true); 20644 20645 if (!collapsed) { 20646 normalizeEndPoint(); 20647 } 20648 20649 // If it was collapsed then make sure it still is 20650 if (normalized && collapsed) { 20651 rng.collapse(true); 20652 } 20653 20654 return normalized; 20655 }; 20656 } 20657 20658 /** 20659 * Compares two ranges and checks if they are equal. 20660 * 20661 * @static 20662 * @method compareRanges 20663 * @param {DOMRange} rng1 First range to compare. 20664 * @param {DOMRange} rng2 First range to compare. 20665 * @return {Boolean} true/false if the ranges are equal. 20666 */ 20667 RangeUtils.compareRanges = function(rng1, rng2) { 20668 if (rng1 && rng2) { 20669 // Compare native IE ranges 20670 if (rng1.item || rng1.duplicate) { 20671 // Both are control ranges and the selected element matches 20672 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) { 20673 return true; 20674 } 20675 20676 // Both are text ranges and the range matches 20677 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) { 20678 return true; 20679 } 20680 } else { 20681 // Compare w3c ranges 20682 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset; 20683 } 20684 } 20685 20686 return false;
20687 }; 20688 20689 /** 20690 * Finds the closest selection rect tries to get the range from that. 20691 */ 20692 function findClosestIeRange(clientX, clientY, doc) { 20693 var element, rng, rects; 20694 20695 element = doc.elementFromPoint(clientX, clientY); 20696 rng = doc.body.createTextRange(); 20697 20698 if (element.tagName == 'HTML') { 20699 element = doc.body; 20700 } 20701 20702 rng.moveToElementText(element); 20703 rects = Tools.toArray(rng.getClientRects()); 20704 20705 rects = rects.sort(function(a, b) { 20706 a = Math.abs(Math.max(a.top - clientY, a.bottom - clientY)); 20707 b = Math.abs(Math.max(b.top - clientY, b.bottom - clientY)); 20708 20709 return a - b; 20710 }); 20711 20712 if (rects.length > 0) { 20713 clientY = (rects[0].bottom + rects[0].top) / 2; 20714 20715 try { 20716 rng.moveToPoint(clientX, clientY); 20717 rng.collapse(true); 20718 20719 return rng; 20720 } catch (ex) { 20721 // At least we tried 20722 } 20723 } 20724 20725 return null; 20726 } 20727 20728 /** 20729 * Gets the caret range for the given x/y location. 20730 * 20731 * @static 20732 * @method getCaretRangeFromPoint 20733 * @param {Number} clientX X coordinate for range 20734 * @param {Number} clientY Y coordinate for range 20735 * @param {Document} doc Document that x/y are relative to 20736 * @returns {Range} caret range 20737 */ 20738 RangeUtils.getCaretRangeFromPoint = function(clientX, clientY, doc) { 20739 var rng, point; 20740 20741 if (doc.caretPositionFromPoint) { 20742 point = doc.caretPositionFromPoint(clientX, clientY); 20743 rng = doc.createRange(); 20744 rng.setStart(point.offsetNode, point.offset); 20745 rng.collapse(true); 20746 } else if (doc.caretRangeFromPoint) { 20747 rng = doc.caretRangeFromPoint(clientX, clientY); 20748 } else if (doc.body.createTextRange) { 20749 rng = doc.body.createTextRange(); 20750 20751 try { 20752 rng.moveToPoint(clientX, clientY); 20753 rng.collapse(true); 20754 } catch (ex) { 20755 rng = findClosestIeRange(clientX, clientY, doc); 20756 } 20757 } 20758 20759 return rng; 20760 }; 20761 20762 RangeUtils.getSelectedNode = function(range) { 20763 var startContainer = range.startContainer, 20764 startOffset = range.startOffset; 20765 20766 if (startContainer.hasChildNodes() && range.endOffset == startOffset + 1) { 20767 return startContainer.childNodes[startOffset]; 20768 } 20769 20770 return null; 20771 }; 20772 20773 RangeUtils.getNode = function(container, offset) { 20774 if (container.nodeType == 1 && container.hasChildNodes()) { 20775 if (offset >= container.childNodes.length) { 20776 offset = container.childNodes.length - 1; 20777 } 20778 20779 container = container.childNodes[offset]; 20780 } 20781 20782 return container; 20783 }; 20784 20785 return RangeUtils; 20786}); 20787 20788// Included from: js/tinymce/classes/NodeChange.js 20789 20790/** 20791 * NodeChange.js 20792 * 20793 * Released under LGPL License. 20794 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 20795 * 20796 * License: http://www.tinymce.com/license 20797 * Contributing: http://www.tinymce.com/contributing 20798 */ 20799 20800/** 20801 * This class handles the nodechange event dispatching both manual and through selection change events. 20802 * 20803 * @class tinymce.NodeChange 20804 * @private 20805 */ 20806define("tinymce/NodeChange", [ 20807 "tinymce/dom/RangeUtils", 20808 "tinymce/Env", 20809 "tinymce/util/Delay" 20810], function(RangeUtils, Env, Delay) { 20811 return function(editor) { 20812 var lastRng, lastPath = []; 20813 20814 /** 20815 * Returns true/false if the current element path has been changed or not. 20816 * 20817 * @private 20818 * @return {Boolean} True if the element path is the same false if it's not. 20819 */ 20820 function isSameElementPath(startElm) { 20821 var i, currentPath; 20822 20823 currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm); 20824 if (currentPath.length === lastPath.length) { 20825 for (i = currentPath.length; i >= 0; i--) { 20826 if (currentPath[i] !== lastPath[i]) { 20827 break; 20828 } 20829 } 20830 20831 if (i === -1) { 20832 lastPath = currentPath; 20833 return true; 20834 } 20835 } 20836 20837 lastPath = currentPath; 20838 20839 return false; 20840 } 20841 20842 // Gecko doesn't support the "selectionchange" event 20843 if (!('onselectionchange' in editor.getDoc())) { 20844 editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) { 20845 var nativeRng, fakeRng; 20846 20847 // Since DOM Ranges mutate on modification 20848 // of the DOM we need to clone it's contents 20849 nativeRng = editor.selection.getRng(); 20850 fakeRng = { 20851 startContainer: nativeRng.startContainer, 20852 startOffset: nativeRng.startOffset, 20853 endContainer: nativeRng.endContainer, 20854 endOffset: nativeRng.endOffset 20855 }; 20856 20857 // Always treat nodechange as a selectionchange since applying 20858 // formatting to the current range wouldn't update the range but it's parent 20859 if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) { 20860 editor.fire('SelectionChange'); 20861 } 20862 20863 lastRng = fakeRng; 20864 }); 20865 } 20866 20867 // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body 20868 // When the contextmenu event fires the selection is located at the right location 20869 editor.on('contextmenu', function() { 20870 editor.fire('SelectionChange'); 20871 }); 20872 20873 // Selection change is delayed ~200ms on IE when you click inside the current range 20874 editor.on('SelectionChange', function() { 20875 var startElm = editor.selection.getStart(true); 20876 20877 // IE 8 will fire a selectionchange event with an incorrect selection
20878 // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event 20879 if (!Env.range && editor.selection.isCollapsed()) { 20880 return; 20881 } 20882 20883 if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) { 20884 editor.nodeChanged({selectionChange: true}); 20885 } 20886 }); 20887 20888 // Fire an extra nodeChange on mouseup for compatibility reasons 20889 editor.on('MouseUp', function(e) { 20890 if (!e.isDefaultPrevented()) { 20891 // Delay nodeChanged call for WebKit edge case issue where the range 20892 // isn't updated until after you click outside a selected image 20893 if (editor.selection.getNode().nodeName == 'IMG') { 20894 Delay.setEditorTimeout(editor, function() { 20895 editor.nodeChanged(); 20896 }); 20897 } else { 20898 editor.nodeChanged(); 20899 } 20900 } 20901 }); 20902 20903 /** 20904 * Dispatches out a onNodeChange event to all observers. This method should be called when you 20905 * need to update the UI states or element path etc. 20906 * 20907 * @method nodeChanged 20908 * @param {Object} args Optional args to pass to NodeChange event handlers. 20909 */ 20910 this.nodeChanged = function(args) { 20911 var selection = editor.selection, node, parents, root; 20912 20913 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading 20914 if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) { 20915 // Get start node 20916 root = editor.getBody(); 20917 node = selection.getStart() || root; 20918 node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node; 20919 20920 // Edge case for <p>|<img></p> 20921 if (node.nodeName == 'IMG' && selection.isCollapsed()) { 20922 node = node.parentNode; 20923 } 20924 20925 // Get parents and add them to object 20926 parents = []; 20927 editor.dom.getParent(node, function(node) { 20928 if (node === root) { 20929 return true; 20930 } 20931 20932 parents.push(node); 20933 }); 20934 20935 args = args || {}; 20936 args.element = node; 20937 args.parents = parents; 20938 20939 editor.fire('NodeChange', args); 20940 } 20941 }; 20942 }; 20943}); 20944 20945// Included from: js/tinymce/classes/html/Node.js 20946 20947/** 20948 * Node.js 20949 * 20950 * Released under LGPL License. 20951 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 20952 * 20953 * License: http://www.tinymce.com/license 20954 * Contributing: http://www.tinymce.com/contributing 20955 */ 20956 20957/** 20958 * This class is a minimalistic implementation of a DOM like node used by the DomParser class. 20959 * 20960 * @example 20961 * var node = new tinymce.html.Node('strong', 1); 20962 * someRoot.append(node); 20963 * 20964 * @class tinymce.html.Node 20965 * @version 3.4 20966 */ 20967define("tinymce/html/Node", [], function() { 20968 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { 20969 '#text': 3, 20970 '#comment': 8, 20971 '#cdata': 4, 20972 '#pi': 7, 20973 '#doctype': 10, 20974 '#document-fragment': 11 20975 }; 20976 20977 // Walks the tree left/right 20978 function walk(node, root_node, prev) { 20979 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; 20980 20981 // Walk into nodes if it has a start 20982 if (node[startName]) { 20983 return node[startName]; 20984 } 20985 20986 // Return the sibling if it has one 20987 if (node !== root_node) { 20988 sibling = node[siblingName]; 20989 20990 if (sibling) { 20991 return sibling; 20992 } 20993 20994 // Walk up the parents to look for siblings 20995 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { 20996 sibling = parent[siblingName]; 20997 20998 if (sibling) { 20999 return sibling; 21000 } 21001 } 21002 } 21003 } 21004 21005 /** 21006 * Constructs a new Node instance. 21007 * 21008 * @constructor 21009 * @method Node 21010 * @param {String} name Name of the node type. 21011 * @param {Number} type Numeric type representing the node. 21012 */ 21013 function Node(name, type) { 21014 this.name = name; 21015 this.type = type; 21016 21017 if (type === 1) { 21018 this.attributes = []; 21019 this.attributes.map = {}; 21020 } 21021 } 21022 21023 Node.prototype = { 21024 /** 21025 * Replaces the current node with the specified one. 21026 * 21027 * @example 21028 * someNode.replace(someNewNode); 21029 * 21030 * @method replace 21031 * @param {tinymce.html.Node} node Node to replace the current node with. 21032 * @return {tinymce.html.Node} The old node that got replaced. 21033 */ 21034 replace: function(node) { 21035 var self = this; 21036 21037 if (node.parent) { 21038 node.remove(); 21039 } 21040 21041 self.insert(node, self); 21042 self.remove(); 21043 21044 return self; 21045 }, 21046 21047 /** 21048 * Gets/sets or removes an attribute by name. 21049 * 21050 * @example 21051 * someNode.attr("name", "value"); // Sets an attribute 21052 * console.log(someNode.attr("name")); // Gets an attribute 21053 * someNode.attr("name", null); // Removes an attribute 21054 * 21055 * @method attr 21056 * @param {String} name Attribute name to set or get. 21057 * @param {String} value Optional value to set. 21058 * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation. 21059 */ 21060 attr: function(name, value) { 21061 var self = this, attrs, i, undef; 21062 21063 if (typeof name !== "string") { 21064 for (i in name) { 21065 self.attr(i, name[i]); 21066 } 21067 21068 return self; 21069 } 21070 21071 if ((attrs = self.attributes)) { 21072 if (value !== undef) { 21073 // Remove attribute 21074 if (value === null) { 21075 if (name in attrs.map) { 21076 delete attrs.map[name]; 21077 21078 i = attrs.length; 21079 while (i--) { 21080 if (attrs[i].name === name) { 21081 attrs = attrs.splice(i, 1); 21082 return self; 21083 } 21084 } 21085 } 21086 21087 return self; 21088 } 21089 21090 // Set attribute 21091 if (name in attrs.map) { 21092 // Set attribute 21093 i = attrs.length; 21094 while (i--) { 21095 if (attrs[i].name === name) { 21096 attrs[i].value = value; 21097 break; 21098 } 21099 } 21100 } else { 21101 attrs.push({name: name, value: value}); 21102 } 21103 21104 attrs.map[name] = value; 21105 21106 return self; 21107 } 21108 21109 return attrs.map[name]; 21110 } 21111 }, 21112 21113 /**
21114 * Does a shallow clones the node into a new node. It will also exclude id attributes since 21115 * there should only be one id per document. 21116 * 21117 * @example 21118 * var clonedNode = node.clone(); 21119 * 21120 * @method clone 21121 * @return {tinymce.html.Node} New copy of the original node. 21122 */ 21123 clone: function() { 21124 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; 21125 21126 // Clone element attributes 21127 if ((selfAttrs = self.attributes)) { 21128 cloneAttrs = []; 21129 cloneAttrs.map = {}; 21130 21131 for (i = 0, l = selfAttrs.length; i < l; i++) { 21132 selfAttr = selfAttrs[i]; 21133 21134 // Clone everything except id 21135 if (selfAttr.name !== 'id') { 21136 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; 21137 cloneAttrs.map[selfAttr.name] = selfAttr.value; 21138 } 21139 } 21140 21141 clone.attributes = cloneAttrs; 21142 } 21143 21144 clone.value = self.value; 21145 clone.shortEnded = self.shortEnded; 21146 21147 return clone; 21148 }, 21149 21150 /** 21151 * Wraps the node in in another node. 21152 * 21153 * @example 21154 * node.wrap(wrapperNode); 21155 * 21156 * @method wrap 21157 */ 21158 wrap: function(wrapper) { 21159 var self = this; 21160 21161 self.parent.insert(wrapper, self); 21162 wrapper.append(self); 21163 21164 return self; 21165 }, 21166 21167 /** 21168 * Unwraps the node in other words it removes the node but keeps the children. 21169 * 21170 * @example 21171 * node.unwrap(); 21172 * 21173 * @method unwrap 21174 */ 21175 unwrap: function() { 21176 var self = this, node, next; 21177 21178 for (node = self.firstChild; node;) { 21179 next = node.next; 21180 self.insert(node, self, true); 21181 node = next; 21182 } 21183 21184 self.remove(); 21185 }, 21186 21187 /** 21188 * Removes the node from it's parent. 21189 * 21190 * @example 21191 * node.remove(); 21192 * 21193 * @method remove 21194 * @return {tinymce.html.Node} Current node that got removed. 21195 */ 21196 remove: function() { 21197 var self = this, parent = self.parent, next = self.next, prev = self.prev; 21198 21199 if (parent) { 21200 if (parent.firstChild === self) { 21201 parent.firstChild = next; 21202 21203 if (next) { 21204 next.prev = null; 21205 } 21206 } else { 21207 prev.next = next; 21208 } 21209 21210 if (parent.lastChild === self) { 21211 parent.lastChild = prev; 21212 21213 if (prev) { 21214 prev.next = null; 21215 } 21216 } else { 21217 next.prev = prev; 21218 } 21219 21220 self.parent = self.next = self.prev = null; 21221 } 21222 21223 return self; 21224 }, 21225 21226 /** 21227 * Appends a new node as a child of the current node. 21228 * 21229 * @example 21230 * node.append(someNode); 21231 * 21232 * @method append 21233 * @param {tinymce.html.Node} node Node to append as a child of the current one. 21234 * @return {tinymce.html.Node} The node that got appended. 21235 */ 21236 append: function(node) { 21237 var self = this, last; 21238 21239 if (node.parent) { 21240 node.remove(); 21241 } 21242 21243 last = self.lastChild; 21244 if (last) { 21245 last.next = node; 21246 node.prev = last; 21247 self.lastChild = node; 21248 } else { 21249 self.lastChild = self.firstChild = node; 21250 } 21251 21252 node.parent = self; 21253 21254 return node; 21255 }, 21256 21257 /** 21258 * Inserts a node at a specific position as a child of the current node. 21259 * 21260 * @example 21261 * parentNode.insert(newChildNode, oldChildNode); 21262 * 21263 * @method insert 21264 * @param {tinymce.html.Node} node Node to insert as a child of the current node. 21265 * @param {tinymce.html.Node} ref_node Reference node to set node before/after. 21266 * @param {Boolean} before Optional state to insert the node before the reference node. 21267 * @return {tinymce.html.Node} The node that got inserted. 21268 */ 21269 insert: function(node, ref_node, before) { 21270 var parent; 21271 21272 if (node.parent) { 21273 node.remove(); 21274 } 21275 21276 parent = ref_node.parent || this; 21277 21278 if (before) { 21279 if (ref_node === parent.firstChild) { 21280 parent.firstChild = node; 21281 } else { 21282 ref_node.prev.next = node; 21283 } 21284 21285 node.prev = ref_node.prev; 21286 node.next = ref_node; 21287 ref_node.prev = node; 21288 } else { 21289 if (ref_node === parent.lastChild) { 21290 parent.lastChild = node; 21291 } else { 21292 ref_node.next.prev = node; 21293 } 21294 21295 node.next = ref_node.next; 21296 node.prev = ref_node; 21297 ref_node.next = node; 21298 } 21299 21300 node.parent = parent; 21301 21302 return node; 21303 }, 21304 21305 /** 21306 * Get all children by name. 21307 * 21308 * @method getAll 21309 * @param {String} name Name of the child nodes to collect. 21310 * @return {Array} Array with child nodes matchin the specified name. 21311 */ 21312 getAll: function(name) { 21313 var self = this, node, collection = []; 21314 21315 for (node = self.firstChild; node; node = walk(node, self)) { 21316 if (node.name === name) { 21317 collection.push(node); 21318 } 21319 } 21320 21321 return collection; 21322 }, 21323 21324 /** 21325 * Removes all children of the current node. 21326 * 21327 * @method empty 21328 * @return {tinymce.html.Node} The current node that got cleared. 21329 */ 21330 empty: function() { 21331 var self = this, nodes, i, node; 21332 21333 // Remove all children 21334 if (self.firstChild) { 21335 nodes = []; 21336 21337 // Collect the children 21338 for (node = self.firstChild; node; node = walk(node, self)) { 21339 nodes.push(node); 21340 } 21341 21342 // Remove the children 21343 i = nodes.length; 21344 while (i--) { 21345 node = nodes[i]; 21346 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; 21347 } 21348 } 21349 21350 self.firstChild = self.lastChild = null; 21351 21352 return self; 21353 }, 21354 21355 /** 21356 * Returns true/false if the node is to be considered empty or not. 21357 * 21358 * @example 21359 * node.isEmpty({img: true}); 21360 * @method isEmpty 21361 * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements. 21362 * @return {Boolean} true/false if the node is empty or not. 21363 */ 21364 isEmpty: function(elements) { 21365 var self = this, node = self.firstChild, i, name; 21366 21367 if (node) { 21368 do { 21369 if (node.type === 1) { 21370 // Ignore bogus elements 21371 if (node.attributes.map['data-mce-bogus']) { 21372 continue; 21373 } 21374 21375 // Keep empty elements like <img /> 21376 if (elements[node.name]) { 21377 return false;
21378 } 21379 21380 // Keep bookmark nodes and name attribute like <a name="1"></a> 21381 i = node.attributes.length; 21382 while (i--) { 21383 name = node.attributes[i].name; 21384 if (name === "name" || name.indexOf('data-mce-bookmark') === 0) { 21385 return false; 21386 } 21387 } 21388 } 21389 21390 // Keep comments 21391 if (node.type === 8) { 21392 return false; 21393 } 21394 21395 // Keep non whitespace text nodes 21396 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) { 21397 return false; 21398 } 21399 } while ((node = walk(node, self))); 21400 } 21401 21402 return true; 21403 }, 21404 21405 /** 21406 * Walks to the next or previous node and returns that node or null if it wasn't found. 21407 * 21408 * @method walk 21409 * @param {Boolean} prev Optional previous node state defaults to false. 21410 * @return {tinymce.html.Node} Node that is next to or previous of the current node. 21411 */ 21412 walk: function(prev) { 21413 return walk(this, null, prev); 21414 } 21415 }; 21416 21417 /** 21418 * Creates a node of a specific type. 21419 * 21420 * @static 21421 * @method create 21422 * @param {String} name Name of the node type to create for example "b" or "#text". 21423 * @param {Object} attrs Name/value collection of attributes that will be applied to elements. 21424 */ 21425 Node.create = function(name, attrs) { 21426 var node, attrName; 21427 21428 // Create node 21429 node = new Node(name, typeLookup[name] || 1); 21430 21431 // Add attributes if needed 21432 if (attrs) { 21433 for (attrName in attrs) { 21434 node.attr(attrName, attrs[attrName]); 21435 } 21436 } 21437 21438 return node; 21439 }; 21440 21441 return Node; 21442}); 21443 21444// Included from: js/tinymce/classes/html/Schema.js 21445 21446/** 21447 * Schema.js 21448 * 21449 * Released under LGPL License. 21450 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 21451 * 21452 * License: http://www.tinymce.com/license 21453 * Contributing: http://www.tinymce.com/contributing 21454 */ 21455 21456/** 21457 * Schema validator class. 21458 * 21459 * @class tinymce.html.Schema 21460 * @example 21461 * if (tinymce.activeEditor.schema.isValidChild('p', 'span')) 21462 * alert('span is valid child of p.'); 21463 * 21464 * if (tinymce.activeEditor.schema.getElementRule('p')) 21465 * alert('P is a valid element.'); 21466 * 21467 * @class tinymce.html.Schema 21468 * @version 3.4 21469 */ 21470define("tinymce/html/Schema", [ 21471 "tinymce/util/Tools" 21472], function(Tools) { 21473 var mapCache = {}, dummyObj = {}; 21474 var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray; 21475 21476 function split(items, delim) { 21477 return items ? items.split(delim || ' ') : []; 21478 } 21479 21480 /** 21481 * Builds a schema lookup table 21482 * 21483 * @private 21484 * @param {String} type html4, html5 or html5-strict schema type. 21485 * @return {Object} Schema lookup table. 21486 */ 21487 function compileSchema(type) { 21488 var schema = {}, globalAttributes, blockContent; 21489 var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent; 21490 21491 function add(name, attributes, children) { 21492 var ni, i, attributesOrder, args = arguments; 21493 21494 function arrayToMap(array, obj) { 21495 var map = {}, i, l; 21496 21497 for (i = 0, l = array.length; i < l; i++) { 21498 map[array[i]] = obj || {}; 21499 } 21500 21501 return map; 21502 } 21503 21504 children = children || []; 21505 attributes = attributes || ""; 21506 21507 if (typeof children === "string") { 21508 children = split(children); 21509 } 21510 21511 // Split string children 21512 for (i = 3; i < args.length; i++) { 21513 if (typeof args[i] === "string") { 21514 args[i] = split(args[i]); 21515 } 21516 21517 children.push.apply(children, args[i]); 21518 } 21519 21520 name = split(name); 21521 ni = name.length; 21522 while (ni--) { 21523 attributesOrder = [].concat(globalAttributes, split(attributes)); 21524 schema[name[ni]] = { 21525 attributes: arrayToMap(attributesOrder), 21526 attributesOrder: attributesOrder, 21527 children: arrayToMap(children, dummyObj) 21528 }; 21529 } 21530 } 21531 21532 function addAttrs(name, attributes) { 21533 var ni, schemaItem, i, l; 21534 21535 name = split(name); 21536 ni = name.length; 21537 attributes = split(attributes); 21538 while (ni--) { 21539 schemaItem = schema[name[ni]]; 21540 for (i = 0, l = attributes.length; i < l; i++) { 21541 schemaItem.attributes[attributes[i]] = {}; 21542 schemaItem.attributesOrder.push(attributes[i]); 21543 } 21544 } 21545 } 21546 21547 // Use cached schema 21548 if (mapCache[type]) { 21549 return mapCache[type]; 21550 } 21551 21552 // Attributes present on all elements 21553 globalAttributes = split("id accesskey class dir lang style tabindex title"); 21554 21555 // Event attributes can be opt-in/opt-out 21556 /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " + 21557 "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " + 21558 "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " + 21559 "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " + 21560 "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " + 21561 "onwaiting" 21562 );*/ 21563 21564 // Block content elements 21565 blockContent = split( 21566 "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul" 21567 ); 21568 21569 // Phrasing content elements from the HTML5 spec (inline) 21570 phrasingContent = split( 21571 "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " + 21572 "label map noscript object q s samp script select small span strong sub sup " + 21573 "textarea u var #text #comment" 21574 ); 21575 21576 // Add HTML5 items to globalAttributes, blockContent, phrasingContent 21577 if (type != "html4") { 21578 globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " + 21579 "hidden spellcheck translate")); 21580 blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
21581 phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output picture " + 21582 "progress time wbr video ruby bdi keygen")); 21583 } 21584 21585 // Add HTML4 elements unless it's html5-strict 21586 if (type != "html5-strict") { 21587 globalAttributes.push("xml:lang"); 21588 21589 html4PhrasingContent = split("acronym applet basefont big font strike tt"); 21590 phrasingContent.push.apply(phrasingContent, html4PhrasingContent); 21591 21592 each(html4PhrasingContent, function(name) { 21593 add(name, "", phrasingContent); 21594 }); 21595 21596 html4BlockContent = split("center dir isindex noframes"); 21597 blockContent.push.apply(blockContent, html4BlockContent); 21598 21599 // Flow content elements from the HTML5 spec (block+inline) 21600 flowContent = [].concat(blockContent, phrasingContent); 21601 21602 each(html4BlockContent, function(name) { 21603 add(name, "", flowContent); 21604 }); 21605 } 21606 21607 // Flow content elements from the HTML5 spec (block+inline) 21608 flowContent = flowContent || [].concat(blockContent, phrasingContent); 21609 21610 // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement 21611 // Schema items <element name>, <specific attributes>, <children ..> 21612 add("html", "manifest", "head body"); 21613 add("head", "", "base command link meta noscript script style title"); 21614 add("title hr noscript br"); 21615 add("base", "href target"); 21616 add("link", "href rel media hreflang type sizes hreflang"); 21617 add("meta", "name http-equiv content charset"); 21618 add("style", "media type scoped"); 21619 add("script", "src async defer type charset"); 21620 add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " + 21621 "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " + 21622 "onpopstate onresize onscroll onstorage onunload", flowContent); 21623 add("address dt dd div caption", "", flowContent); 21624 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); 21625 add("blockquote", "cite", flowContent); 21626 add("ol", "reversed start type", "li"); 21627 add("ul", "", "li"); 21628 add("li", "value", flowContent); 21629 add("dl", "", "dt dd"); 21630 add("a", "href target rel media hreflang type", phrasingContent); 21631 add("q", "cite", phrasingContent); 21632 add("ins del", "cite datetime", flowContent); 21633 add("img", "src sizes srcset alt usemap ismap width height"); 21634 add("iframe", "src name width height", flowContent); 21635 add("embed", "src type width height"); 21636 add("object", "data type typemustmatch name usemap form width height", flowContent, "param"); 21637 add("param", "name value"); 21638 add("map", "name", flowContent, "area"); 21639 add("area", "alt coords shape href target rel media hreflang type"); 21640 add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : "")); 21641 add("colgroup", "span", "col"); 21642 add("col", "span"); 21643 add("tbody thead tfoot", "", "tr"); 21644 add("tr", "", "td th"); 21645 add("td", "colspan rowspan headers", flowContent); 21646 add("th", "colspan rowspan headers scope abbr", flowContent); 21647 add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent); 21648 add("fieldset", "disabled form name", flowContent, "legend"); 21649 add("label", "form for", phrasingContent); 21650 add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " + 21651 "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width" 21652 ); 21653 add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value", 21654 type == "html4" ? flowContent : phrasingContent); 21655 add("select", "disabled form multiple name required size", "option optgroup"); 21656 add("optgroup", "disabled label", "option"); 21657 add("option", "disabled label selected value"); 21658 add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap"); 21659 add("menu", "type label", flowContent, "li"); 21660 add("noscript", "", flowContent); 21661 21662 // Extend with HTML5 elements 21663 if (type != "html4") { 21664 add("wbr"); 21665 add("ruby", "", phrasingContent, "rt rp"); 21666 add("figcaption", "", flowContent); 21667 add("mark rt rp summary bdi", "", phrasingContent); 21668 add("canvas", "width height", flowContent); 21669 add("video", "src crossorigin poster preload autoplay mediagroup loop " + 21670 "muted controls width height buffered", flowContent, "track source"); 21671 add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source"); 21672 add("picture", "", "img source"); 21673 add("source", "src srcset type media sizes"); 21674 add("track", "kind src srclang label default"); 21675 add("datalist", "", phrasingContent, "option"); 21676 add("article section nav aside header footer", "", flowContent); 21677 add("hgroup", "", "h1 h2 h3 h4 h5 h6"); 21678 add("figure", "", flowContent, "figcaption"); 21679 add("time", "datetime", phrasingContent); 21680 add("dialog", "open", flowContent); 21681 add("command", "type label icon disabled checked radiogroup command"); 21682 add("output", "for form name", phrasingContent); 21683 add("progress", "value max", phrasingContent); 21684 add("meter", "value min max low high optimum", phrasingContent); 21685 add("details", "open", flowContent, "summary"); 21686 add("keygen", "autofocus challenge disabled form keytype name"); 21687 } 21688 21689 // Extend with HTML4 attributes unless it's html5-strict 21690 if (type != "html5-strict") { 21691 addAttrs("script", "language xml:space"); 21692 addAttrs("style", "xml:space"); 21693 addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace"); 21694 addAttrs("embed", "align name hspace vspace"); 21695 addAttrs("param", "valuetype type"); 21696 addAttrs("a", "charset name rev shape coords"); 21697 addAttrs("br", "clear"); 21698 addAttrs("applet", "codebase archive code object alt name width height align hspace vspace"); 21699 addAttrs("img", "name longdesc align border hspace vspace"); 21700 addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align"); 21701 addAttrs("font basefont", "size color face"); 21702 addAttrs("input", "usemap align"); 21703 addAttrs("select", "onchange"); 21704 addAttrs("textarea"); 21705 addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align"); 21706 addAttrs("ul", "type compact"); 21707 addAttrs("li", "type"); 21708 addAttrs("ol dl menu dir", "compact"); 21709 addAttrs("pre", "width xml:space"); 21710 addAttrs("hr", "align noshade size width"); 21711 addAttrs("isindex", "prompt"); 21712 addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor"); 21713 addAttrs("col", "width align char charoff valign"); 21714 addAttrs("colgroup", "width align char charoff valign"); 21715 addAttrs("thead", "align char charoff valign"); 21716 addAttrs("tr", "align char charoff valign bgcolor"); 21717 addAttrs("th", "axis align char charoff valign nowrap bgcolor width height"); 21718 addAttrs("form", "accept"); 21719 addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height"); 21720 addAttrs("tfoot", "align char charoff valign"); 21721 addAttrs("tbody", "align char charoff valign"); 21722 addAttrs("area", "nohref"); 21723 addAttrs("body", "background bgcolor text link vlink alink"); 21724 } 21725 21726 // Extend with HTML5 attributes unless it's html4 21727 if (type != "html4") { 21728 addAttrs("input button select textarea", "autofocus"); 21729 addAttrs("input textarea", "placeholder"); 21730 addAttrs("a", "download"); 21731 addAttrs("link script img", "crossorigin"); 21732 addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc 21733 } 21734 21735 // Special: iframe, ruby, video, audio, label 21736 21737 // Delete children of the same name from it's parent 21738 // For example: form can't have a child of the name form 21739 each(split('a form meter progress dfn'), function(name) { 21740 if (schema[name]) { 21741 delete schema[name].children[name]; 21742 } 21743 }); 21744 21745 // Delete header, footer, sectioning and heading content descendants 21746 /*each('dt th address', function(name) { 21747 delete schema[name].children[name]; 21748 });*/ 21749 21750 // Caption can't have tables 21751 delete schema.caption.children.table; 21752 21753 // Delete scripts by default due to possible XSS 21754 delete schema.script; 21755 21756 // TODO: LI:s can only have value if parent is OL 21757 21758 // TODO: Handle transparent elements 21759 // a ins del canvas map 21760 21761 mapCache[type] = schema; 21762 21763 return schema; 21764 } 21765 21766 function compileElementMap(value, mode) { 21767 var styles; 21768 21769 if (value) { 21770 styles = {}; 21771 21772 if (typeof value == 'string') { 21773 value = { 21774 '*': value 21775 }; 21776 } 21777 21778 // Convert styles into a rule list 21779 each(value, function(value, key) { 21780 styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/); 21781 }); 21782 } 21783 21784 return styles; 21785 } 21786 21787 /** 21788 * Constructs a new Schema instance. 21789 * 21790 * @constructor 21791 * @method Schema 21792 * @param {Object} settings Name/value settings object. 21793 */ 21794 return function(settings) { 21795 var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems; 21796 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses; 21797 var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap; 21798 var customElementsMap = {}, specialElements = {}; 21799 21800 // Creates an lookup table map object for the specified option or the default value 21801 function createLookupTable(option, default_value, extendWith) { 21802 var value = settings[option]; 21803 21804 if (!value) { 21805 // Get cached default map or make it if needed 21806 value = mapCache[option]; 21807 21808 if (!value) { 21809 value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' ')); 21810 value = extend(value, extendWith); 21811 21812 mapCache[option] = value; 21813 } 21814 } else { 21815 // Create custom map 21816 value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/)); 21817 } 21818 21819 return value; 21820 } 21821 21822 settings = settings || {}; 21823 schemaItems = compileSchema(settings.schema); 21824 21825 // Allow all elements and attributes if verify_html is set to false 21826 if (settings.verify_html === false) {
21827 settings.valid_elements = '*[*]'; 21828 } 21829 21830 validStyles = compileElementMap(settings.valid_styles); 21831 invalidStyles = compileElementMap(settings.invalid_styles, 'map'); 21832 validClasses = compileElementMap(settings.valid_classes, 'map'); 21833 21834 // Setup map objects 21835 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object'); 21836 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr'); 21837 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' + 21838 'meta param embed source wbr track'); 21839 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' + 21840 'noshade nowrap readonly selected autoplay loop controls'); 21841 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap); 21842 moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap); 21843 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 21844 'blockquote center dir fieldset header footer article section hgroup aside nav figure'); 21845 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 21846 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' + 21847 'datalist select optgroup figcaption', textBlockElementsMap); 21848 textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' + 21849 'dfn code mark q sup sub samp'); 21850 21851 each((settings.special || 'script noscript style textarea').split(' '), function(name) { 21852 specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi'); 21853 }); 21854 21855 // Converts a wildcard expression string to a regexp for example *a will become /.*a/. 21856 function patternToRegExp(str) { 21857 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); 21858 } 21859 21860 // Parses the specified valid_elements string and adds to the current rules 21861 // This function is a bit hard to read since it's heavily optimized for speed 21862 function addValidElements(validElements) { 21863 var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, 21864 prefix, outputName, globalAttributes, globalAttributesOrder, key, value, 21865 elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/, 21866 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, 21867 hasPatternsRegExp = /[*?+]/; 21868 21869 if (validElements) { 21870 // Split valid elements into an array with rules 21871 validElements = split(validElements, ','); 21872 21873 if (elements['@']) { 21874 globalAttributes = elements['@'].attributes; 21875 globalAttributesOrder = elements['@'].attributesOrder; 21876 } 21877 21878 // Loop all rules 21879 for (ei = 0, el = validElements.length; ei < el; ei++) { 21880 // Parse element rule 21881 matches = elementRuleRegExp.exec(validElements[ei]); 21882 if (matches) { 21883 // Setup local names for matches 21884 prefix = matches[1]; 21885 elementName = matches[2]; 21886 outputName = matches[3]; 21887 attrData = matches[5]; 21888 21889 // Create new attributes and attributesOrder 21890 attributes = {}; 21891 attributesOrder = []; 21892 21893 // Create the new element 21894 element = { 21895 attributes: attributes, 21896 attributesOrder: attributesOrder 21897 }; 21898 21899 // Padd empty elements prefix 21900 if (prefix === '#') { 21901 element.paddEmpty = true; 21902 } 21903 21904 // Remove empty elements prefix 21905 if (prefix === '-') { 21906 element.removeEmpty = true; 21907 } 21908 21909 if (matches[4] === '!') { 21910 element.removeEmptyAttrs = true; 21911 } 21912 21913 // Copy attributes from global rule into current rule 21914 if (globalAttributes) { 21915 for (key in globalAttributes) { 21916 attributes[key] = globalAttributes[key]; 21917 } 21918 21919 attributesOrder.push.apply(attributesOrder, globalAttributesOrder); 21920 } 21921 21922 // Attributes defined 21923 if (attrData) { 21924 attrData = split(attrData, '|'); 21925 for (ai = 0, al = attrData.length; ai < al; ai++) { 21926 matches = attrRuleRegExp.exec(attrData[ai]); 21927 if (matches) { 21928 attr = {}; 21929 attrType = matches[1]; 21930 attrName = matches[2].replace(/::/g, ':'); 21931 prefix = matches[3]; 21932 value = matches[4]; 21933 21934 // Required 21935 if (attrType === '!') { 21936 element.attributesRequired = element.attributesRequired || []; 21937 element.attributesRequired.push(attrName); 21938 attr.required = true; 21939 } 21940
21941 // Denied from global 21942 if (attrType === '-') { 21943 delete attributes[attrName]; 21944 attributesOrder.splice(inArray(attributesOrder, attrName), 1); 21945 continue; 21946 } 21947 21948 // Default value 21949 if (prefix) { 21950 // Default value 21951 if (prefix === '=') { 21952 element.attributesDefault = element.attributesDefault || []; 21953 element.attributesDefault.push({name: attrName, value: value}); 21954 attr.defaultValue = value; 21955 } 21956 21957 // Forced value 21958 if (prefix === ':') { 21959 element.attributesForced = element.attributesForced || []; 21960 element.attributesForced.push({name: attrName, value: value}); 21961 attr.forcedValue = value; 21962 } 21963 21964 // Required values 21965 if (prefix === '<') { 21966 attr.validValues = makeMap(value, '?'); 21967 } 21968 } 21969 21970 // Check for attribute patterns 21971 if (hasPatternsRegExp.test(attrName)) { 21972 element.attributePatterns = element.attributePatterns || []; 21973 attr.pattern = patternToRegExp(attrName); 21974 element.attributePatterns.push(attr); 21975 } else { 21976 // Add attribute to order list if it doesn't already exist 21977 if (!attributes[attrName]) { 21978 attributesOrder.push(attrName); 21979 } 21980 21981 attributes[attrName] = attr; 21982 } 21983 } 21984 } 21985 } 21986 21987 // Global rule, store away these for later usage 21988 if (!globalAttributes && elementName == '@') { 21989 globalAttributes = attributes; 21990 globalAttributesOrder = attributesOrder; 21991 } 21992 21993 // Handle substitute elements such as b/strong 21994 if (outputName) { 21995 element.outputName = elementName; 21996 elements[outputName] = element; 21997 } 21998 21999 // Add pattern or exact element 22000 if (hasPatternsRegExp.test(elementName)) { 22001 element.pattern = patternToRegExp(elementName); 22002 patternElements.push(element); 22003 } else { 22004 elements[elementName] = element; 22005 } 22006 } 22007 } 22008 } 22009 } 22010 22011 function setValidElements(validElements) { 22012 elements = {}; 22013 patternElements = []; 22014 22015 addValidElements(validElements); 22016 22017 each(schemaItems, function(element, name) { 22018 children[name] = element.children; 22019 }); 22020 } 22021 22022 // Adds custom non HTML elements to the schema 22023 function addCustomElements(customElements) { 22024 var customElementRegExp = /^(~)?(.+)$/; 22025 22026 if (customElements) { 22027 // Flush cached items since we are altering the default maps 22028 mapCache.text_block_elements = mapCache.block_elements = null; 22029 22030 each(split(customElements, ','), function(rule) { 22031 var matches = customElementRegExp.exec(rule), 22032 inline = matches[1] === '~', 22033 cloneName = inline ? 'span' : 'div', 22034 name = matches[2]; 22035 22036 children[name] = children[cloneName]; 22037 customElementsMap[name] = cloneName; 22038 22039 // If it's not marked as inline then add it to valid block elements 22040 if (!inline) { 22041 blockElementsMap[name.toUpperCase()] = {}; 22042 blockElementsMap[name] = {}; 22043 } 22044 22045 // Add elements clone if needed 22046 if (!elements[name]) { 22047 var customRule = elements[cloneName]; 22048 22049 customRule = extend({}, customRule); 22050 delete customRule.removeEmptyAttrs; 22051 delete customRule.removeEmpty; 22052 22053 elements[name] = customRule; 22054 } 22055 22056 // Add custom elements at span/div positions 22057 each(children, function(element, elmName) { 22058 if (element[cloneName]) { 22059 children[elmName] = element = extend({}, children[elmName]); 22060 element[name] = element[cloneName]; 22061 } 22062 }); 22063 }); 22064 } 22065 } 22066 22067 // Adds valid children to the schema object 22068 function addValidChildren(validChildren) { 22069 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; 22070 22071 // Invalidate the schema cache if the schema is mutated 22072 mapCache[settings.schema] = null; 22073 22074 if (validChildren) { 22075 each(split(validChildren, ','), function(rule) { 22076 var matches = childRuleRegExp.exec(rule), parent, prefix; 22077 22078 if (matches) { 22079 prefix = matches[1]; 22080 22081 // Add/remove items from default 22082 if (prefix) { 22083 parent = children[matches[2]]; 22084 } else { 22085 parent = children[matches[2]] = {'#comment': {}}; 22086 } 22087 22088 parent = children[matches[2]]; 22089 22090 each(split(matches[3], '|'), function(child) { 22091 if (prefix === '-') { 22092 delete parent[child]; 22093 } else { 22094 parent[child] = {}; 22095 } 22096 }); 22097 } 22098 }); 22099 } 22100 } 22101 22102 function getElementRule(name) { 22103 var element = elements[name], i; 22104 22105 // Exact match found 22106 if (element) { 22107 return element; 22108 } 22109 22110 // No exact match then try the patterns 22111 i = patternElements.length; 22112 while (i--) { 22113 element = patternElements[i]; 22114 22115 if (element.pattern.test(name)) { 22116 return element; 22117 } 22118 } 22119 } 22120
22121 if (!settings.valid_elements) { 22122 // No valid elements defined then clone the elements from the schema spec 22123 each(schemaItems, function(element, name) { 22124 elements[name] = { 22125 attributes: element.attributes, 22126 attributesOrder: element.attributesOrder 22127 }; 22128 22129 children[name] = element.children; 22130 }); 22131 22132 // Switch these on HTML4 22133 if (settings.schema != "html5") { 22134 each(split('strong/b em/i'), function(item) { 22135 item = split(item, '/'); 22136 elements[item[1]].outputName = item[0]; 22137 }); 22138 } 22139 22140 // Add default alt attribute for images 22141 elements.img.attributesDefault = [{name: 'alt', value: ''}]; 22142 22143 // Remove these if they are empty by default 22144 each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) { 22145 if (elements[name]) { 22146 elements[name].removeEmpty = true; 22147 } 22148 }); 22149 22150 // Padd these by default 22151 each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) { 22152 elements[name].paddEmpty = true; 22153 }); 22154 22155 // Remove these if they have no attributes 22156 each(split('span'), function(name) { 22157 elements[name].removeEmptyAttrs = true; 22158 }); 22159 22160 // Remove these by default 22161 // TODO: Reenable in 4.1 22162 /*each(split('script style'), function(name) { 22163 delete elements[name]; 22164 });*/ 22165 } else { 22166 setValidElements(settings.valid_elements); 22167 } 22168 22169 addCustomElements(settings.custom_elements); 22170 addValidChildren(settings.valid_children); 22171 addValidElements(settings.extended_valid_elements); 22172 22173 // Todo: Remove this when we fix list handling to be valid 22174 addValidChildren('+ol[ul|ol],+ul[ul|ol]'); 22175 22176 // Delete invalid elements 22177 if (settings.invalid_elements) { 22178 each(explode(settings.invalid_elements), function(item) { 22179 if (elements[item]) { 22180 delete elements[item]; 22181 } 22182 }); 22183 } 22184 22185 // If the user didn't allow span only allow internal spans 22186 if (!getElementRule('span')) { 22187 addValidElements('span[!data-mce-type|*]'); 22188 } 22189 22190 /** 22191 * Name/value map object with valid parents and children to those parents. 22192 * 22193 * @example 22194 * children = { 22195 * div:{p:{}, h1:{}} 22196 * }; 22197 * @field children 22198 * @type Object 22199 */ 22200 self.children = children; 22201 22202 /** 22203 * Name/value map object with valid styles for each element. 22204 * 22205 * @method getValidStyles 22206 * @type Object 22207 */ 22208 self.getValidStyles = function() { 22209 return validStyles; 22210 }; 22211 22212 /** 22213 * Name/value map object with valid styles for each element. 22214 * 22215 * @method getInvalidStyles 22216 * @type Object 22217 */ 22218 self.getInvalidStyles = function() { 22219 return invalidStyles; 22220 }; 22221 22222 /** 22223 * Name/value map object with valid classes for each element. 22224 * 22225 * @method getValidClasses 22226 * @type Object 22227 */ 22228 self.getValidClasses = function() { 22229 return validClasses; 22230 }; 22231 22232 /** 22233 * Returns a map with boolean attributes. 22234 * 22235 * @method getBoolAttrs 22236 * @return {Object} Name/value lookup map for boolean attributes. 22237 */ 22238 self.getBoolAttrs = function() { 22239 return boolAttrMap; 22240 }; 22241 22242 /** 22243 * Returns a map with block elements. 22244 * 22245 * @method getBlockElements 22246 * @return {Object} Name/value lookup map for block elements. 22247 */ 22248 self.getBlockElements = function() { 22249 return blockElementsMap; 22250 }; 22251 22252 /** 22253 * Returns a map with text block elements. Such as: p,h1-h6,div,address 22254 * 22255 * @method getTextBlockElements 22256 * @return {Object} Name/value lookup map for block elements. 22257 */ 22258 self.getTextBlockElements = function() { 22259 return textBlockElementsMap; 22260 }; 22261 22262 /** 22263 * Returns a map of inline text format nodes for example strong/span or ins. 22264 * 22265 * @method getTextInlineElements 22266 * @return {Object} Name/value lookup map for text format elements. 22267 */ 22268 self.getTextInlineElements = function() { 22269 return textInlineElementsMap; 22270 }; 22271 22272 /** 22273 * Returns a map with short ended elements such as BR or IMG. 22274 * 22275 * @method getShortEndedElements 22276 * @return {Object} Name/value lookup map for short ended elements. 22277 */ 22278 self.getShortEndedElements = function() { 22279 return shortEndedElementsMap; 22280 }; 22281 22282 /** 22283 * Returns a map with self closing tags such as <li>. 22284 * 22285 * @method getSelfClosingElements 22286 * @return {Object} Name/value lookup map for self closing tags elements. 22287 */ 22288 self.getSelfClosingElements = function() { 22289 return selfClosingElementsMap; 22290 }; 22291 22292 /** 22293 * Returns a map with elements that should be treated as contents regardless if it has text 22294 * content in them or not such as TD, VIDEO or IMG. 22295 * 22296 * @method getNonEmptyElements 22297 * @return {Object} Name/value lookup map for non empty elements. 22298 */ 22299 self.getNonEmptyElements = function() { 22300 return nonEmptyElementsMap; 22301 }; 22302 22303 /** 22304 * Returns a map with elements that the caret should be moved in front of after enter is 22305 * pressed 22306 * 22307 * @method getMoveCaretBeforeOnEnterElements 22308 * @return {Object} Name/value lookup map for elements to place the caret in front of. 22309 */ 22310 self.getMoveCaretBeforeOnEnterElements = function() { 22311 return moveCaretBeforeOnEnterElementsMap; 22312 }; 22313 22314 /** 22315 * Returns a map with elements where white space is to be preserved like PRE or SCRIPT. 22316 * 22317 * @method getWhiteSpaceElements 22318 * @return {Object} Name/value lookup map for white space elements. 22319 */ 22320 self.getWhiteSpaceElements = function() { 22321 return whiteSpaceElementsMap; 22322 }; 22323 22324 /** 22325 * Returns a map with special elements. These are elements that needs to be parsed 22326 * in a special way such as script, style, textarea etc. The map object values 22327 * are regexps used to find the end of the element. 22328 * 22329 * @method getSpecialElements 22330 * @return {Object} Name/value lookup map for special elements. 22331 */ 22332 self.getSpecialElements = function() { 22333 return specialElements; 22334 }; 22335 22336 /** 22337 * Returns true/false if the specified element and it's child is valid or not 22338 * according to the schema. 22339 * 22340 * @method isValidChild 22341 * @param {String} name Element name to check for. 22342 * @param {String} child Element child to verify. 22343 * @return {Boolean} True/false if the element is a valid child of the specified parent. 22344 */ 22345 self.isValidChild = function(name, child) { 22346 var parent = children[name]; 22347 22348 return !!(parent && parent[child]); 22349 }; 22350 22351 /** 22352 * Returns true/false if the specified element name and optional attribute is 22353 * valid according to the schema. 22354 * 22355 * @method isValid 22356 * @param {String} name Name of element to check. 22357 * @param {String} attr Optional attribute name to check for. 22358 * @return {Boolean} True/false if the element and attribute is valid. 22359 */ 22360 self.isValid = function(name, attr) { 22361 var attrPatterns, i, rule = getElementRule(name); 22362 22363 // Check if it's a valid element 22364 if (rule) { 22365 if (attr) { 22366 // Check if attribute name exists 22367 if (rule.attributes[attr]) { 22368 return true; 22369 } 22370 22371 // Check if attribute matches a regexp pattern 22372 attrPatterns = rule.attributePatterns; 22373 if (attrPatterns) { 22374 i = attrPatterns.length; 22375 while (i--) { 22376 if (attrPatterns[i].pattern.test(name)) { 22377 return true; 22378 } 22379 } 22380 } 22381 } else { 22382 return true; 22383 } 22384 } 22385 22386 // No match 22387 return false;
22388 }; 22389 22390 /** 22391 * Returns true/false if the specified element is valid or not 22392 * according to the schema. 22393 * 22394 * @method getElementRule 22395 * @param {String} name Element name to check for. 22396 * @return {Object} Element object or undefined if the element isn't valid. 22397 */ 22398 self.getElementRule = getElementRule; 22399 22400 /** 22401 * Returns an map object of all custom elements. 22402 * 22403 * @method getCustomElements 22404 * @return {Object} Name/value map object of all custom elements. 22405 */ 22406 self.getCustomElements = function() { 22407 return customElementsMap; 22408 }; 22409 22410 /** 22411 * Parses a valid elements string and adds it to the schema. The valid elements 22412 * format is for example "element[attr=default|otherattr]". 22413 * Existing rules will be replaced with the ones specified, so this extends the schema. 22414 * 22415 * @method addValidElements 22416 * @param {String} valid_elements String in the valid elements format to be parsed. 22417 */ 22418 self.addValidElements = addValidElements; 22419 22420 /** 22421 * Parses a valid elements string and sets it to the schema. The valid elements 22422 * format is for example "element[attr=default|otherattr]". 22423 * Existing rules will be replaced with the ones specified, so this extends the schema. 22424 * 22425 * @method setValidElements 22426 * @param {String} valid_elements String in the valid elements format to be parsed. 22427 */ 22428 self.setValidElements = setValidElements; 22429 22430 /** 22431 * Adds custom non HTML elements to the schema. 22432 * 22433 * @method addCustomElements 22434 * @param {String} custom_elements Comma separated list of custom elements to add. 22435 */ 22436 self.addCustomElements = addCustomElements; 22437 22438 /** 22439 * Parses a valid children string and adds them to the schema structure. The valid children 22440 * format is for example: "element[child1|child2]". 22441 * 22442 * @method addValidChildren 22443 * @param {String} valid_children Valid children elements string to parse 22444 */ 22445 self.addValidChildren = addValidChildren; 22446 22447 self.elements = elements; 22448 }; 22449}); 22450 22451// Included from: js/tinymce/classes/html/SaxParser.js 22452 22453/** 22454 * SaxParser.js 22455 * 22456 * Released under LGPL License. 22457 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 22458 * 22459 * License: http://www.tinymce.com/license 22460 * Contributing: http://www.tinymce.com/contributing 22461 */ 22462 22463/*eslint max-depth:[2, 9] */ 22464 22465/** 22466 * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will 22467 * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements 22468 * and attributes that doesn't fit the schema if the validate setting is enabled. 22469 * 22470 * @example 22471 * var parser = new tinymce.html.SaxParser({ 22472 * validate: true, 22473 * 22474 * comment: function(text) { 22475 * console.log('Comment:', text); 22476 * }, 22477 * 22478 * cdata: function(text) { 22479 * console.log('CDATA:', text); 22480 * }, 22481 * 22482 * text: function(text, raw) { 22483 * console.log('Text:', text, 'Raw:', raw); 22484 * }, 22485 * 22486 * start: function(name, attrs, empty) { 22487 * console.log('Start:', name, attrs, empty); 22488 * }, 22489 * 22490 * end: function(name) { 22491 * console.log('End:', name); 22492 * }, 22493 * 22494 * pi: function(name, text) { 22495 * console.log('PI:', name, text); 22496 * }, 22497 * 22498 * doctype: function(text) { 22499 * console.log('DocType:', text); 22500 * } 22501 * }, schema); 22502 * @class tinymce.html.SaxParser 22503 * @version 3.4 22504 */ 22505define("tinymce/html/SaxParser", [ 22506 "tinymce/html/Schema", 22507 "tinymce/html/Entities", 22508 "tinymce/util/Tools" 22509], function(Schema, Entities, Tools) { 22510 var each = Tools.each; 22511 22512 /** 22513 * Returns the index of the end tag for a specific start tag. This can be 22514 * used to skip all children of a parent element from being processed. 22515 * 22516 * @private 22517 * @method findEndTag 22518 * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements. 22519 * @param {String} html HTML string to find the end tag in. 22520 * @param {Number} startIndex Indext to start searching at should be after the start tag. 22521 * @return {Number} Index of the end tag. 22522 */ 22523 function findEndTag(schema, html, startIndex) { 22524 var count = 1, index, matches, tokenRegExp, shortEndedElements; 22525 22526 shortEndedElements = schema.getShortEndedElements(); 22527 tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g; 22528 tokenRegExp.lastIndex = index = startIndex; 22529 22530 while ((matches = tokenRegExp.exec(html))) { 22531 index = tokenRegExp.lastIndex; 22532 22533 if (matches[1] === '/') { // End element 22534 count--; 22535 } else if (!matches[1]) { // Start element 22536 if (matches[2] in shortEndedElements) { 22537 continue; 22538 } 22539 22540 count++; 22541 } 22542 22543 if (count === 0) { 22544 break; 22545 } 22546 } 22547 22548 return index; 22549 } 22550 22551 /** 22552 * Constructs a new SaxParser instance. 22553 * 22554 * @constructor 22555 * @method SaxParser 22556 * @param {Object}
22556 settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 22557 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 22558 */ 22559 function SaxParser(settings, schema) { 22560 var self = this; 22561 22562 function noop() {} 22563 22564 settings = settings || {}; 22565 self.schema = schema = schema || new Schema(); 22566 22567 if (settings.fix_self_closing !== false) { 22568 settings.fix_self_closing = true; 22569 } 22570 22571 // Add handler functions from settings and setup default handlers 22572 each('comment cdata text start end pi doctype'.split(' '), function(name) { 22573 if (name) { 22574 self[name] = settings[name] || noop; 22575 } 22576 }); 22577 22578 /** 22579 * Parses the specified HTML string and executes the callbacks for each item it finds. 22580 * 22581 * @example 22582 * new SaxParser({...}).parse('<b>text</b>'); 22583 * @method parse 22584 * @param {String} html Html string to sax parse. 22585 */ 22586 self.parse = function(html) { 22587 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name; 22588 var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded; 22589 var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns; 22590 var attributesRequired, attributesDefault, attributesForced; 22591 var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0; 22592 var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster'); 22593 var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i; 22594 22595 function processEndTag(name) { 22596 var pos, i; 22597 22598 // Find position of parent of the same type 22599 pos = stack.length; 22600 while (pos--) { 22601 if (stack[pos].name === name) { 22602 break; 22603 } 22604 } 22605 22606 // Found parent 22607 if (pos >= 0) { 22608 // Close all the open elements 22609 for (i = stack.length - 1; i >= pos; i--) { 22610 name = stack[i]; 22611 22612 if (name.valid) { 22613 self.end(name.name); 22614 } 22615 } 22616 22617 // Remove the open elements from the stack 22618 stack.length = pos; 22619 } 22620 } 22621 22622 function parseAttribute(match, name, value, val2, val3) { 22623 var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g; 22624 22625 name = name.toLowerCase(); 22626 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute 22627 22628 // Validate name and value pass through all data- attributes 22629 if (validate && !isInternalElement && name.indexOf('data-') !== 0) { 22630 attrRule = validAttributesMap[name]; 22631 22632 // Find rule by pattern matching 22633 if (!attrRule && validAttributePatterns) { 22634 i = validAttributePatterns.length; 22635 while (i--) { 22636 attrRule = validAttributePatterns[i]; 22637 if (attrRule.pattern.test(name)) { 22638 break; 22639 } 22640 } 22641 22642 // No rule matched 22643 if (i === -1) { 22644 attrRule = null; 22645 } 22646 } 22647 22648 // No attribute rule found 22649 if (!attrRule) { 22650 return; 22651 } 22652 22653 // Validate value 22654 if (attrRule.validValues && !(value in attrRule.validValues)) { 22655 return; 22656 } 22657 } 22658 22659 // Block any javascript: urls or non image data uris 22660 if (filteredUrlAttrs[name] && !settings.allow_script_urls) { 22661 var uri = value.replace(trimRegExp, ''); 22662 22663 try { 22664 // Might throw malformed URI sequence 22665 uri = decodeURIComponent(uri); 22666 } catch (ex) { 22667 // Fallback to non UTF-8 decoder 22668 uri = unescape(uri); 22669 } 22670 22671 if (scriptUriRegExp.test(uri)) { 22672 return; 22673 } 22674 22675 if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) { 22676 return; 22677 } 22678 } 22679 22680 // Add attribute to list and map 22681 attrList.map[name] = value; 22682 attrList.push({ 22683 name: name, 22684 value: value 22685 }); 22686 } 22687 22688 // Precompile RegExps and map objects 22689 tokenRegExp = new RegExp('<(?:' + 22690 '(?:!--([\\w\\W]*?)-->)|' + // Comment 22691 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA 22692 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE 22693 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI 22694 '(?:\\/([^>]+)>)|' + // End element 22695 '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element 22696 ')', 'g'); 22697 22698 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g; 22699 22700 // Setup lookup tables for empty elements and boolean attributes 22701 shortEndedElements = schema.getShortEndedElements(); 22702 selfClosing = settings.self_closing_elements || schema.getSelfClosingElements(); 22703 fillAttrsMap = schema.getBoolAttrs();
22704 validate = settings.validate; 22705 removeInternalElements = settings.remove_internals; 22706 fixSelfClosing = settings.fix_self_closing; 22707 specialElements = schema.getSpecialElements(); 22708 22709 while ((matches = tokenRegExp.exec(html))) { 22710 // Text 22711 if (index < matches.index) { 22712 self.text(decode(html.substr(index, matches.index - index))); 22713 } 22714 22715 if ((value = matches[6])) { // End element 22716 value = value.toLowerCase(); 22717 22718 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 22719 if (value.charAt(0) === ':') { 22720 value = value.substr(1); 22721 } 22722 22723 processEndTag(value); 22724 } else if ((value = matches[7])) { // Start element 22725 value = value.toLowerCase(); 22726 22727 // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements 22728 if (value.charAt(0) === ':') { 22729 value = value.substr(1); 22730 } 22731 22732 isShortEnded = value in shortEndedElements; 22733 22734 // Is self closing tag for example an <li> after an open <li> 22735 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) { 22736 processEndTag(value); 22737 } 22738 22739 // Validate element 22740 if (!validate || (elementRule = schema.getElementRule(value))) { 22741 isValidElement = true; 22742 22743 // Grab attributes map and patters when validation is enabled 22744 if (validate) { 22745 validAttributesMap = elementRule.attributes; 22746 validAttributePatterns = elementRule.attributePatterns; 22747 } 22748 22749 // Parse attributes 22750 if ((attribsValue = matches[8])) { 22751 isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element 22752 22753 // If the element has internal attributes then remove it if we are told to do so 22754 if (isInternalElement && removeInternalElements) { 22755 isValidElement = false; 22756 } 22757 22758 attrList = []; 22759 attrList.map = {}; 22760 22761 attribsValue.replace(attrRegExp, parseAttribute); 22762 } else { 22763 attrList = []; 22764 attrList.map = {}; 22765 } 22766 22767 // Process attributes if validation is enabled 22768 if (validate && !isInternalElement) { 22769 attributesRequired = elementRule.attributesRequired; 22770 attributesDefault = elementRule.attributesDefault; 22771 attributesForced = elementRule.attributesForced; 22772 anyAttributesRequired = elementRule.removeEmptyAttrs; 22773 22774 // Check if any attribute exists 22775 if (anyAttributesRequired && !attrList.length) { 22776 isValidElement = false; 22777 } 22778 22779 // Handle forced attributes 22780 if (attributesForced) { 22781 i = attributesForced.length; 22782 while (i--) { 22783 attr = attributesForced[i]; 22784 name = attr.name; 22785 attrValue = attr.value; 22786 22787 if (attrValue === '{$uid}') { 22788 attrValue = 'mce_' + idCount++; 22789 } 22790 22791 attrList.map[name] = attrValue; 22792 attrList.push({name: name, value: attrValue}); 22793 } 22794 } 22795 22796 // Handle default attributes 22797 if (attributesDefault) { 22798 i = attributesDefault.length; 22799 while (i--) { 22800 attr = attributesDefault[i]; 22801 name = attr.name; 22802 22803 if (!(name in attrList.map)) { 22804 attrValue = attr.value; 22805 22806 if (attrValue === '{$uid}') { 22807 attrValue = 'mce_' + idCount++; 22808 } 22809 22810 attrList.map[name] = attrValue; 22811 attrList.push({name: name, value: attrValue}); 22812 } 22813 } 22814 } 22815 22816 // Handle required attributes 22817 if (attributesRequired) { 22818 i = attributesRequired.length; 22819 while (i--) { 22820 if (attributesRequired[i] in attrList.map) { 22821 break; 22822 } 22823 } 22824 22825 // None of the required attributes where found 22826 if (i === -1) { 22827 isValidElement = false; 22828 } 22829 } 22830 22831 // Invalidate element if it's marked as bogus 22832 if ((attr = attrList.map['data-mce-bogus'])) { 22833 if (attr === 'all') { 22834 index = findEndTag(schema, html, tokenRegExp.lastIndex); 22835 tokenRegExp.lastIndex = index; 22836 continue; 22837 } 22838 22839 isValidElement = false;
22840 } 22841 } 22842 22843 if (isValidElement) { 22844 self.start(value, attrList, isShortEnded); 22845 } 22846 } else { 22847 isValidElement = false; 22848 } 22849 22850 // Treat script, noscript and style a bit different since they may include code that looks like elements 22851 if ((endRegExp = specialElements[value])) { 22852 endRegExp.lastIndex = index = matches.index + matches[0].length; 22853 22854 if ((matches = endRegExp.exec(html))) { 22855 if (isValidElement) { 22856 text = html.substr(index, matches.index - index); 22857 } 22858 22859 index = matches.index + matches[0].length; 22860 } else { 22861 text = html.substr(index); 22862 index = html.length; 22863 } 22864 22865 if (isValidElement) { 22866 if (text.length > 0) { 22867 self.text(text, true); 22868 } 22869 22870 self.end(value); 22871 } 22872 22873 tokenRegExp.lastIndex = index; 22874 continue; 22875 } 22876 22877 // Push value on to stack 22878 if (!isShortEnded) { 22879 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) { 22880 stack.push({name: value, valid: isValidElement}); 22881 } else if (isValidElement) { 22882 self.end(value); 22883 } 22884 } 22885 } else if ((value = matches[1])) { // Comment 22886 // Padd comment value to avoid browsers from parsing invalid comments as HTML 22887 if (value.charAt(0) === '>') { 22888 value = ' ' + value; 22889 } 22890 22891 if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') { 22892 value = ' ' + value; 22893 } 22894 22895 self.comment(value); 22896 } else if ((value = matches[2])) { // CDATA 22897 self.cdata(value); 22898 } else if ((value = matches[3])) { // DOCTYPE 22899 self.doctype(value); 22900 } else if ((value = matches[4])) { // PI 22901 self.pi(value, matches[5]); 22902 } 22903 22904 index = matches.index + matches[0].length; 22905 } 22906 22907 // Text 22908 if (index < html.length) { 22909 self.text(decode(html.substr(index))); 22910 } 22911 22912 // Close any open elements 22913 for (i = stack.length - 1; i >= 0; i--) { 22914 value = stack[i]; 22915 22916 if (value.valid) { 22917 self.end(value.name); 22918 } 22919 } 22920 }; 22921 } 22922 22923 SaxParser.findEndTag = findEndTag; 22924 22925 return SaxParser; 22926}); 22927 22928// Included from: js/tinymce/classes/html/DomParser.js 22929 22930/** 22931 * DomParser.js 22932 * 22933 * Released under LGPL License. 22934 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 22935 * 22936 * License: http://www.tinymce.com/license 22937 * Contributing: http://www.tinymce.com/contributing 22938 */ 22939 22940/** 22941 * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make 22942 * sure that the node tree is valid according to the specified schema. 22943 * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p> 22944 * 22945 * @example 22946 * var parser = new tinymce.html.DomParser({validate: true}, schema); 22947 * var rootNode = parser.parse('<h1>content</h1>'); 22948 * 22949 * @class tinymce.html.DomParser 22950 * @version 3.4 22951 */ 22952define("tinymce/html/DomParser", [ 22953 "tinymce/html/Node", 22954 "tinymce/html/Schema", 22955 "tinymce/html/SaxParser", 22956 "tinymce/util/Tools" 22957], function(Node, Schema, SaxParser, Tools) { 22958 var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend; 22959 22960 /** 22961 * Constructs a new DomParser instance. 22962 * 22963 * @constructor 22964 * @method DomParser 22965 * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks. 22966 * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing. 22967 */ 22968 return function(settings, schema) { 22969 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; 22970 22971 settings = settings || {}; 22972 settings.validate = "validate" in settings ? settings.validate : true; 22973 settings.root_name = settings.root_name || 'body'; 22974 self.schema = schema = schema || new Schema(); 22975 22976 function fixInvalidChildren(nodes) { 22977 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i; 22978 var nonEmptyElements, nonSplitableElements, textBlockElements, specialElements, sibling, nextNode; 22979 22980 nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table'); 22981 nonEmptyElements = schema.getNonEmptyElements(); 22982 textBlockElements = schema.getTextBlockElements(); 22983 specialElements = schema.getSpecialElements(); 22984 22985 for (ni = 0; ni < nodes.length; ni++) { 22986 node = nodes[ni]; 22987 22988 // Already removed or fixed 22989 if (!node.parent || node.fixed) { 22990 continue; 22991 } 22992 22993 // If the invalid element is a text block and the text block is within a parent LI element 22994 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office 22995 if (textBlockElements[node.name] && node.parent.name == 'li') { 22996 // Move sibling text blocks after LI element 22997 sibling = node.next; 22998 while (sibling) { 22999 if (textBlockElements[sibling.name]) { 23000 sibling.name = 'li'; 23001 sibling.fixed = true; 23002 node.parent.insert(sibling, node.parent); 23003 } else { 23004 break; 23005 } 23006 23007 sibling = sibling.next; 23008 } 23009 23010 // Unwrap current text block 23011 node.unwrap(node); 23012 continue; 23013 } 23014 23015 // Get list of all parent nodes until we find a valid parent to stick the child into 23016 parents = [node]; 23017 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && 23018 !nonSplitableElements[parent.name]; parent = parent.parent) { 23019 parents.push(parent); 23020 } 23021 23022 // Found a suitable parent 23023 if (parent && parents.length > 1) { 23024 // Reverse the array since it makes looping easier 23025 parents.reverse(); 23026 23027 // Clone the related parent and insert that after the moved node 23028 newParent = currentNode = self.filterNode(parents[0].clone()); 23029 23030 // Start cloning and moving children on the left side of the target node 23031 for (i = 0; i < parents.length - 1; i++) { 23032 if (schema.isValidChild(currentNode.name, parents[i].name)) { 23033 tempNode = self.filterNode(parents[i].clone()); 23034 currentNode.append(tempNode); 23035 } else { 23036 tempNode = currentNode; 23037 } 23038 23039 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) { 23040 nextNode = childNode.next; 23041 tempNode.append(childNode); 23042 childNode = nextNode; 23043 } 23044 23045 currentNode = tempNode; 23046 } 23047 23048 if (!newParent.isEmpty(nonEmptyElements)) { 23049 parent.insert(newParent, parents[0], true); 23050 parent.insert(node, newParent); 23051 } else { 23052 parent.insert(node, parents[0], true); 23053 } 23054 23055 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p> 23056 parent = parents[0]; 23057 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { 23058 parent.empty().remove(); 23059 } 23060 } else if (node.parent) {
23061 // If it's an LI try to find a UL/OL for it or wrap it 23062 if (node.name === 'li') { 23063 sibling = node.prev; 23064 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 23065 sibling.append(node); 23066 continue; 23067 } 23068 23069 sibling = node.next; 23070 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { 23071 sibling.insert(node, sibling.firstChild, true); 23072 continue; 23073 } 23074 23075 node.wrap(self.filterNode(new Node('ul', 1))); 23076 continue; 23077 } 23078 23079 // Try wrapping the element in a DIV 23080 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { 23081 node.wrap(self.filterNode(new Node('div', 1))); 23082 } else { 23083 // We failed wrapping it, then remove or unwrap it 23084 if (specialElements[node.name]) { 23085 node.empty().remove(); 23086 } else { 23087 node.unwrap(); 23088 } 23089 } 23090 } 23091 } 23092 } 23093 23094 /** 23095 * Runs the specified node though the element and attributes filters. 23096 * 23097 * @method filterNode 23098 * @param {tinymce.html.Node} Node the node to run filters on. 23099 * @return {tinymce.html.Node} The passed in node. 23100 */ 23101 self.filterNode = function(node) { 23102 var i, name, list; 23103 23104 // Run element filters 23105 if (name in nodeFilters) { 23106 list = matchedNodes[name]; 23107 23108 if (list) { 23109 list.push(node); 23110 } else { 23111 matchedNodes[name] = [node]; 23112 } 23113 } 23114 23115 // Run attribute filters 23116 i = attributeFilters.length; 23117 while (i--) { 23118 name = attributeFilters[i].name; 23119 23120 if (name in node.attributes.map) { 23121 list = matchedAttributes[name]; 23122 23123 if (list) { 23124 list.push(node); 23125 } else { 23126 matchedAttributes[name] = [node]; 23127 } 23128 } 23129 } 23130 23131 return node; 23132 }; 23133 23134 /** 23135 * Adds a node filter function to the parser, the parser will collect the specified nodes by name 23136 * and then execute the callback ones it has finished parsing the document. 23137 * 23138 * @example 23139 * parser.addNodeFilter('p,h1', function(nodes, name) { 23140 * for (var i = 0; i < nodes.length; i++) { 23141 * console.log(nodes[i].name); 23142 * } 23143 * }); 23144 * @method addNodeFilter 23145 * @method {String} name Comma separated list of nodes to collect. 23146 * @param {function} callback Callback function to execute once it has collected nodes. 23147 */ 23148 self.addNodeFilter = function(name, callback) { 23149 each(explode(name), function(name) { 23150 var list = nodeFilters[name]; 23151 23152 if (!list) { 23153 nodeFilters[name] = list = []; 23154 } 23155 23156 list.push(callback); 23157 }); 23158 }; 23159 23160 /** 23161 * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes 23162 * and then execute the callback ones it has finished parsing the document. 23163 * 23164 * @example 23165 * parser.addAttributeFilter('src,href', function(nodes, name) { 23166 * for (var i = 0; i < nodes.length; i++) { 23167 * console.log(nodes[i].name); 23168 * } 23169 * }); 23170 * @method addAttributeFilter 23171 * @method {String} name Comma separated list of nodes to collect. 23172 * @param {function} callback Callback function to execute once it has collected nodes. 23173 */ 23174 self.addAttributeFilter = function(name, callback) { 23175 each(explode(name), function(name) { 23176 var i; 23177 23178 for (i = 0; i < attributeFilters.length; i++) { 23179 if (attributeFilters[i].name === name) { 23180 attributeFilters[i].callbacks.push(callback); 23181 return; 23182 } 23183 } 23184 23185 attributeFilters.push({name: name, callbacks: [callback]}); 23186 }); 23187 }; 23188 23189 /** 23190 * Parses the specified HTML string into a DOM like node tree and returns the result. 23191 * 23192 * @example 23193 * var rootNode = new DomParser({...}).parse('<b>text</b>'); 23194 * @method parse 23195 * @param {String} html Html string to sax parse. 23196 * @param {Object} args Optional args object that gets passed to all filter functions. 23197 * @return {tinymce.html.Node} Root node containing the tree. 23198 */ 23199 self.parse = function(html, args) { 23200 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate; 23201 var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement; 23202 var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements; 23203 var children, nonEmptyElements, rootBlockName; 23204 23205 args = args || {}; 23206 matchedNodes = {}; 23207 matchedAttributes = {}; 23208 blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); 23209 nonEmptyElements = schema.getNonEmptyElements(); 23210 children = schema.children;
23211 validate = settings.validate; 23212 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; 23213 23214 whiteSpaceElements = schema.getWhiteSpaceElements(); 23215 startWhiteSpaceRegExp = /^[ \t\r\n]+/; 23216 endWhiteSpaceRegExp = /[ \t\r\n]+$/; 23217 allWhiteSpaceRegExp = /[ \t\r\n]+/g; 23218 isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/; 23219 23220 function addRootBlocks() { 23221 var node = rootNode.firstChild, next, rootBlockNode; 23222 23223 // Removes whitespace at beginning and end of block so: 23224 // <p> x </p> -> <p>x</p> 23225 function trim(rootBlockNode) { 23226 if (rootBlockNode) { 23227 node = rootBlockNode.firstChild; 23228 if (node && node.type == 3) { 23229 node.value = node.value.replace(startWhiteSpaceRegExp, ''); 23230 } 23231 23232 node = rootBlockNode.lastChild; 23233 if (node && node.type == 3) { 23234 node.value = node.value.replace(endWhiteSpaceRegExp, ''); 23235 } 23236 } 23237 } 23238 23239 // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root 23240 if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) { 23241 return; 23242 } 23243 23244 while (node) { 23245 next = node.next; 23246 23247 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && 23248 !blockElements[node.name] && !node.attr('data-mce-type'))) { 23249 if (!rootBlockNode) { 23250 // Create a new root block element 23251 rootBlockNode = createNode(rootBlockName, 1); 23252 rootBlockNode.attr(settings.forced_root_block_attrs); 23253 rootNode.insert(rootBlockNode, node); 23254 rootBlockNode.append(node); 23255 } else { 23256 rootBlockNode.append(node); 23257 } 23258 } else { 23259 trim(rootBlockNode); 23260 rootBlockNode = null; 23261 } 23262 23263 node = next; 23264 } 23265 23266 trim(rootBlockNode); 23267 } 23268 23269 function createNode(name, type) { 23270 var node = new Node(name, type), list; 23271 23272 if (name in nodeFilters) { 23273 list = matchedNodes[name]; 23274 23275 if (list) { 23276 list.push(node); 23277 } else { 23278 matchedNodes[name] = [node]; 23279 } 23280 } 23281 23282 return node; 23283 } 23284 23285 function removeWhitespaceBefore(node) { 23286 var textNode, textNodeNext, textVal, sibling, blockElements = schema.getBlockElements(); 23287 23288 for (textNode = node.prev; textNode && textNode.type === 3;) { 23289 textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); 23290 23291 // Found a text node with non whitespace then trim that and break 23292 if (textVal.length > 0) { 23293 textNode.value = textVal; 23294 return; 23295 } 23296 23297 textNodeNext = textNode.next; 23298 23299 // Fix for bug #7543 where bogus nodes would produce empty 23300 // text nodes and these would be removed if a nested list was before it 23301 if (textNodeNext) { 23302 if (textNodeNext.type == 3 && textNodeNext.value.length) { 23303 textNode = textNode.prev; 23304 continue; 23305 } 23306 23307 if (!blockElements[textNodeNext.name] && textNodeNext.name != 'script' && textNodeNext.name != 'style') { 23308 textNode = textNode.prev; 23309 continue; 23310 } 23311 } 23312 23313 sibling = textNode.prev; 23314 textNode.remove(); 23315 textNode = sibling; 23316 } 23317 } 23318 23319 function cloneAndExcludeBlocks(input) { 23320 var name, output = {}; 23321 23322 for (name in input) { 23323 if (name !== 'li' && name != 'p') { 23324 output[name] = input[name]; 23325 } 23326 } 23327 23328 return output; 23329 } 23330 23331 parser = new SaxParser({ 23332 validate: validate, 23333 allow_script_urls: settings.allow_script_urls, 23334 allow_conditional_comments: settings.allow_conditional_comments, 23335 23336 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser 23337 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()), 23338 23339 cdata: function(text) { 23340 node.append(createNode('#cdata', 4)).value = text; 23341 }, 23342 23343 text: function(text, raw) { 23344 var textNode; 23345 23346 // Trim all redundant whitespace on non white space elements 23347 if (!isInWhiteSpacePreservedElement) { 23348 text = text.replace(allWhiteSpaceRegExp, ' '); 23349 23350 if (node.lastChild && blockElements[node.lastChild.name]) { 23351 text = text.replace(startWhiteSpaceRegExp, ''); 23352 } 23353 } 23354 23355 // Do we need to create the node 23356 if (text.length !== 0) { 23357 textNode = createNode('#text', 3); 23358 textNode.raw = !!raw; 23359 node.append(textNode).value = text; 23360 } 23361 }, 23362 23363 comment: function(text) { 23364 node.append(createNode('#comment', 8)).value = text; 23365 }, 23366 23367 pi: function(name, text) { 23368 node.append(createNode(name, 7)).value = text; 23369 removeWhitespaceBefore(node); 23370 }, 23371 23372 doctype: function(text) { 23373 var newNode; 23374 23375 newNode = node.append(createNode('#doctype', 10)); 23376 newNode.value = text; 23377 removeWhitespaceBefore(node); 23378 }, 23379 23380 start: function(name, attrs, empty) { 23381 var newNode, attrFiltersLen, elementRule, attrName, parent; 23382 23383 elementRule = validate ? schema.getElementRule(name) : {}; 23384 if (elementRule) {
23385 newNode = createNode(elementRule.outputName || name, 1); 23386 newNode.attributes = attrs; 23387 newNode.shortEnded = empty; 23388 23389 node.append(newNode); 23390 23391 // Check if node is valid child of the parent node is the child is 23392 // unknown we don't collect it since it's probably a custom element 23393 parent = children[node.name]; 23394 if (parent && children[newNode.name] && !parent[newNode.name]) { 23395 invalidChildren.push(newNode); 23396 } 23397 23398 attrFiltersLen = attributeFilters.length; 23399 while (attrFiltersLen--) { 23400 attrName = attributeFilters[attrFiltersLen].name; 23401 23402 if (attrName in attrs.map) { 23403 list = matchedAttributes[attrName]; 23404 23405 if (list) { 23406 list.push(newNode); 23407 } else { 23408 matchedAttributes[attrName] = [newNode]; 23409 } 23410 } 23411 } 23412 23413 // Trim whitespace before block 23414 if (blockElements[name]) { 23415 removeWhitespaceBefore(newNode); 23416 } 23417 23418 // Change current node if the element wasn't empty i.e not <br /> or <img /> 23419 if (!empty) { 23420 node = newNode; 23421 } 23422 23423 // Check if we are inside a whitespace preserved element 23424 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 23425 isInWhiteSpacePreservedElement = true; 23426 } 23427 } 23428 }, 23429 23430 end: function(name) { 23431 var textNode, elementRule, text, sibling, tempNode; 23432 23433 elementRule = validate ? schema.getElementRule(name) : {}; 23434 if (elementRule) { 23435 if (blockElements[name]) { 23436 if (!isInWhiteSpacePreservedElement) { 23437 // Trim whitespace of the first node in a block 23438 textNode = node.firstChild; 23439 if (textNode && textNode.type === 3) { 23440 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 23441 23442 // Any characters left after trim or should we remove it 23443 if (text.length > 0) { 23444 textNode.value = text; 23445 textNode = textNode.next; 23446 } else { 23447 sibling = textNode.next; 23448 textNode.remove(); 23449 textNode = sibling; 23450 23451 // Remove any pure whitespace siblings 23452 while (textNode && textNode.type === 3) { 23453 text = textNode.value; 23454 sibling = textNode.next; 23455 23456 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 23457 textNode.remove(); 23458 textNode = sibling; 23459 } 23460 23461 textNode = sibling; 23462 } 23463 } 23464 } 23465 23466 // Trim whitespace of the last node in a block 23467 textNode = node.lastChild; 23468 if (textNode && textNode.type === 3) { 23469 text = textNode.value.replace(endWhiteSpaceRegExp, ''); 23470 23471 // Any characters left after trim or should we remove it 23472 if (text.length > 0) { 23473 textNode.value = text; 23474 textNode = textNode.prev; 23475 } else { 23476 sibling = textNode.prev; 23477 textNode.remove(); 23478 textNode = sibling; 23479 23480 // Remove any pure whitespace siblings 23481 while (textNode && textNode.type === 3) { 23482 text = textNode.value; 23483 sibling = textNode.prev; 23484 23485 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) { 23486 textNode.remove(); 23487 textNode = sibling; 23488 } 23489 23490 textNode = sibling; 23491 } 23492 } 23493 } 23494 } 23495 23496 // Trim start white space 23497 // Removed due to: #5424 23498 /*textNode = node.prev; 23499 if (textNode && textNode.type === 3) { 23500 text = textNode.value.replace(startWhiteSpaceRegExp, ''); 23501 23502 if (text.length > 0) 23503 textNode.value = text; 23504 else 23505 textNode.remove(); 23506 }*/ 23507 } 23508 23509 // Check if we exited a whitespace preserved element 23510 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) { 23511 isInWhiteSpacePreservedElement = false; 23512 } 23513 23514 // Handle empty nodes 23515 if (elementRule.removeEmpty || elementRule.paddEmpty) { 23516 if (node.isEmpty(nonEmptyElements)) { 23517 if (elementRule.paddEmpty) { 23518 node.empty().append(new Node('#text', '3')).value = '\u00a0'; 23519 } else { 23520 // Leave nodes that have a name like <a name="name"> 23521 if (!node.attributes.map.name && !node.attributes.map.id) { 23522 tempNode = node.parent; 23523 23524 if (blockElements[node.name]) { 23525 node.empty().remove(); 23526 } else { 23527 node.unwrap(); 23528 } 23529 23530 node = tempNode; 23531 return; 23532 } 23533 } 23534 } 23535 } 23536 23537 node = node.parent; 23538 } 23539 } 23540 }, schema); 23541 23542 rootNode = node = new Node(args.context || settings.root_name, 11); 23543 23544 parser.parse(html); 23545 23546 // Fix invalid children or report invalid children in a contextual parsing 23547 if (validate && invalidChildren.length) { 23548 if (!args.context) { 23549 fixInvalidChildren(invalidChildren); 23550 } else { 23551 args.invalid = true; 23552 } 23553 } 23554 23555 // Wrap nodes in the root into block elements if the root is body 23556 if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) { 23557 addRootBlocks(); 23558 } 23559 23560 // Run filters only when the contents is valid 23561 if (!args.invalid) { 23562 // Run node filters 23563 for (name in matchedNodes) { 23564 list = nodeFilters[name]; 23565 nodes = matchedNodes[name]; 23566 23567 // Remove already removed children 23568 fi = nodes.length; 23569 while (fi--) { 23570 if (!nodes[fi].parent) { 23571 nodes.splice(fi, 1); 23572 } 23573 } 23574 23575 for (i = 0, l = list.length; i < l; i++) { 23576 list[i](nodes, name, args); 23577 } 23578 } 23579 23580 // Run attribute filters 23581 for (i = 0, l = attributeFilters.length; i < l; i++) { 23582 list = attributeFilters[i]; 23583 23584 if (list.name in matchedAttributes) { 23585 nodes = matchedAttributes[list.name]; 23586 23587 // Remove already removed children 23588 fi = nodes.length; 23589 while (fi--) { 23590 if (!nodes[fi].parent) { 23591 nodes.splice(fi, 1); 23592 } 23593 } 23594 23595 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) { 23596 list.callbacks[fi](nodes, list.name, args); 23597 } 23598 } 23599 } 23600 } 23601 23602 return rootNode; 23603 }; 23604 23605 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to 23606 // make it possible to place the caret inside empty blocks. This logic tries to remove 23607 // these elements and keep br elements that where intended to be there intact 23608 if (settings.remove_trailing_brs) { 23609 self.addNodeFilter('br', function(nodes) { 23610 var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements()); 23611 var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName; 23612 var elementRule, textNode; 23613 23614 // Remove brs from body element as well 23615 blockElements.body = 1; 23616 23617 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p> 23618 for (i = 0; i < l; i++) { 23619 node = nodes[i]; 23620 parent = node.parent; 23621 23622 if (blockElements[node.parent.name] && node === parent.lastChild) { 23623 // Loop all nodes to the left of the current node and check for other BR elements 23624 // excluding bookmarks since they are invisible 23625 prev = node.prev; 23626 while (prev) { 23627 prevName = prev.name; 23628 23629 // Ignore bookmarks 23630 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { 23631 // Found a non BR element 23632 if (prevName !== "br") { 23633 break; 23634 } 23635 23636 // Found another br it's a <br><br> structure then don't remove anything 23637 if (prevName === 'br') { 23638 node = null; 23639 break; 23640 } 23641 } 23642 23643 prev = prev.prev; 23644 } 23645 23646 if (node) { 23647 node.remove(); 23648 23649 // Is the parent to be considered empty after we removed the BR 23650 if (parent.isEmpty(nonEmptyElements)) { 23651 elementRule = schema.getElementRule(parent.name); 23652 23653 // Remove or padd the element depending on schema rule 23654 if (elementRule) { 23655 if (elementRule.removeEmpty) { 23656 parent.remove(); 23657 } else if (elementRule.paddEmpty) { 23658 parent.empty().append(new Node('#text', 3)).value = '\u00a0'; 23659 } 23660 } 23661 } 23662 } 23663 } else {
23664 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> 23665 // so they become <p><b><i> </i></b></p> 23666 lastParent = node; 23667 while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) { 23668 lastParent = parent; 23669 23670 if (blockElements[parent.name]) { 23671 break; 23672 } 23673 23674 parent = parent.parent; 23675 } 23676 23677 if (lastParent === parent) { 23678 textNode = new Node('#text', 3); 23679 textNode.value = '\u00a0'; 23680 node.replace(textNode); 23681 } 23682 } 23683 } 23684 }); 23685 } 23686 23687 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included. 23688 if (!settings.allow_html_in_named_anchor) { 23689 self.addAttributeFilter('id,name', function(nodes) { 23690 var i = nodes.length, sibling, prevSibling, parent, node; 23691 23692 while (i--) { 23693 node = nodes[i]; 23694 if (node.name === 'a' && node.firstChild && !node.attr('href')) { 23695 parent = node.parent; 23696 23697 // Move children after current node 23698 sibling = node.lastChild; 23699 do { 23700 prevSibling = sibling.prev; 23701 parent.insert(sibling, node); 23702 sibling = prevSibling; 23703 } while (sibling); 23704 } 23705 } 23706 }); 23707 } 23708 23709 if (settings.validate && schema.getValidClasses()) { 23710 self.addAttributeFilter('class', function(nodes) { 23711 var i = nodes.length, node, classList, ci, className, classValue; 23712 var validClasses = schema.getValidClasses(), validClassesMap, valid; 23713 23714 while (i--) { 23715 node = nodes[i]; 23716 classList = node.attr('class').split(' '); 23717 classValue = ''; 23718 23719 for (ci = 0; ci < classList.length; ci++) { 23720 className = classList[ci]; 23721 valid = false; 23722 23723 validClassesMap = validClasses['*']; 23724 if (validClassesMap && validClassesMap[className]) { 23725 valid = true; 23726 } 23727 23728 validClassesMap = validClasses[node.name]; 23729 if (!valid && validClassesMap && validClassesMap[className]) { 23730 valid = true; 23731 } 23732 23733 if (valid) { 23734 if (classValue) { 23735 classValue += ' '; 23736 } 23737 23738 classValue += className; 23739 } 23740 } 23741 23742 if (!classValue.length) { 23743 classValue = null; 23744 } 23745 23746 node.attr('class', classValue); 23747 } 23748 }); 23749 } 23750 }; 23751}); 23752 23753// Included from: js/tinymce/classes/html/Writer.js 23754 23755/** 23756 * Writer.js 23757 * 23758 * Released under LGPL License. 23759 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 23760 * 23761 * License: http://www.tinymce.com/license 23762 * Contributing: http://www.tinymce.com/contributing 23763 */ 23764 23765/** 23766 * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser. 23767 * 23768 * @class tinymce.html.Writer 23769 * @example 23770 * var writer = new tinymce.html.Writer({indent: true}); 23771 * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>'); 23772 * console.log(writer.getContent()); 23773 * 23774 * @class tinymce.html.Writer 23775 * @version 3.4 23776 */ 23777define("tinymce/html/Writer", [ 23778 "tinymce/html/Entities", 23779 "tinymce/util/Tools" 23780], function(Entities, Tools) { 23781 var makeMap = Tools.makeMap; 23782 23783 /** 23784 * Constructs a new Writer instance. 23785 * 23786 * @constructor 23787 * @method Writer 23788 * @param {Object} settings Name/value settings object. 23789 */ 23790 return function(settings) { 23791 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; 23792 23793 settings = settings || {}; 23794 indent = settings.indent; 23795 indentBefore = makeMap(settings.indent_before || ''); 23796 indentAfter = makeMap(settings.indent_after || ''); 23797 encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); 23798 htmlOutput = settings.element_format == "html"; 23799 23800 return { 23801 /** 23802 * Writes the a start element such as <p id="a">. 23803 * 23804 * @method start 23805 * @param {String} name Name of the element. 23806 * @param {Array} attrs Optional attribute array or undefined if it hasn't any. 23807 * @param {Boolean} empty Optional empty state if the tag should end like <br />. 23808 */ 23809 start: function(name, attrs, empty) { 23810 var i, l, attr, value; 23811 23812 if (indent && indentBefore[name] && html.length > 0) { 23813 value = html[html.length - 1]; 23814 23815 if (value.length > 0 && value !== '\n') { 23816 html.push('\n'); 23817 } 23818 } 23819 23820 html.push('<', name); 23821 23822 if (attrs) { 23823 for (i = 0, l = attrs.length; i < l; i++) { 23824 attr = attrs[i]; 23825 html.push(' ', attr.name, '="', encode(attr.value, true), '"'); 23826 } 23827 } 23828 23829 if (!empty || htmlOutput) { 23830 html[html.length] = '>'; 23831 } else { 23832 html[html.length] = ' />'; 23833 } 23834 23835 if (empty && indent && indentAfter[name] && html.length > 0) { 23836 value = html[html.length - 1]; 23837 23838 if (value.length > 0 && value !== '\n') { 23839 html.push('\n'); 23840 } 23841 } 23842 }, 23843 23844 /** 23845 * Writes the a end element such as </p>. 23846 * 23847 * @method end 23848 * @param {String} name Name of the element. 23849 */ 23850 end: function(name) { 23851 var value; 23852 23853 /*if (indent && indentBefore[name] && html.length > 0) { 23854 value = html[html.length - 1]; 23855 23856 if (value.length > 0 && value !== '\n') 23857 html.push('\n'); 23858 }*/ 23859 23860 html.push('</', name, '>'); 23861 23862 if (indent && indentAfter[name] && html.length > 0) { 23863 value = html[html.length - 1]; 23864 23865 if (value.length > 0 && value !== '\n') { 23866 html.push('\n'); 23867 } 23868 } 23869 }, 23870 23871 /** 23872 * Writes a text node. 23873 * 23874 * @method text 23875 * @param {String} text String to write out. 23876 * @param {Boolean} raw Optional raw state if true the contents wont get encoded. 23877 */ 23878 text: function(text, raw) { 23879 if (text.length > 0) { 23880 html[html.length] = raw ? text : encode(text); 23881 } 23882 }, 23883 23884 /** 23885 * Writes a cdata node such as <![CDATA[data]]>. 23886 * 23887 * @method cdata 23888 * @param {String} text String to write out inside the cdata. 23889 */ 23890 cdata: function(text) { 23891 html.push('<![CDATA[', text, ']]>'); 23892 }, 23893 23894 /** 23895 * Writes a comment node such as <!-- Comment -->. 23896 * 23897 * @method cdata 23898 * @param {String} text String to write out inside the comment. 23899 */ 23900 comment: function(text) { 23901 html.push('<!--', text, '-->'); 23902 }, 23903 23904 /** 23905 * Writes a PI node such as <?xml attr="value" ?>. 23906 * 23907 * @method pi 23908 * @param {String} name Name of the pi. 23909 * @param {String} text String to write out inside the pi. 23910 */ 23911 pi: function(name, text) { 23912 if (text) { 23913 html.push('<?', name, ' ', encode(text), '?>'); 23914 } else { 23915 html.push('<?', name, '?>'); 23916 } 23917 23918 if (indent) { 23919 html.push('\n'); 23920 } 23921 }, 23922 23923 /** 23924 * Writes a doctype node such as <!DOCTYPE data>. 23925 * 23926 * @method doctype 23927 * @param {String} text String to write out inside the doctype. 23928 */ 23929 doctype: function(text) { 23930 html.push('<!DOCTYPE', text, '>', indent ? '\n' : ''); 23931 }, 23932 23933 /** 23934 * Resets the internal buffer if one wants to reuse the writer. 23935 * 23936 * @method reset 23937 */ 23938 reset: function() { 23939 html.length = 0; 23940 }, 23941 23942 /** 23943 * Returns the contents that got serialized. 23944 * 23945 * @method getContent 23946 * @return {String} HTML contents that got written down. 23947 */ 23948 getContent: function() { 23949 return html.join('').replace(/\n$/, ''); 23950 } 23951 }; 23952 }; 23953}); 23954 23955// Included from: js/tinymce/classes/html/Serializer.js 23956 23957/** 23958 * Serializer.js 23959 * 23960 * Released under LGPL License. 23961 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 23962 * 23963 * License: http://www.tinymce.com/license 23964 * Contributing: http://www.tinymce.com/contributing 23965 */ 23966 23967/** 23968 * This class is used to serialize down the DOM tree into a string using a Writer instance. 23969 * 23970 * 23971 * @example 23972 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 23973 * @class tinymce.html.Serializer 23974 * @version 3.4 23975 */ 23976define("tinymce/html/Serializer", [ 23977 "tinymce/html/Writer", 23978 "tinymce/html/Schema" 23979], function(Writer, Schema) { 23980 /** 23981 * Constructs a new Serializer instance. 23982 * 23983 * @constructor 23984 * @method Serializer 23985 * @param {Object} settings Name/value settings object. 23986 * @param {tinymce.html.Schema} schema Schema instance to use. 23987 */ 23988 return function(settings, schema) { 23989 var self = this, writer = new Writer(settings); 23990 23991 settings = settings || {};
23992 settings.validate = "validate" in settings ? settings.validate : true; 23993 23994 self.schema = schema = schema || new Schema(); 23995 self.writer = writer; 23996 23997 /** 23998 * Serializes the specified node into a string. 23999 * 24000 * @example 24001 * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>')); 24002 * @method serialize 24003 * @param {tinymce.html.Node} node Node instance to serialize. 24004 * @return {String} String with HTML based on DOM tree. 24005 */ 24006 self.serialize = function(node) { 24007 var handlers, validate; 24008 24009 validate = settings.validate; 24010 24011 handlers = { 24012 // #text 24013 3: function(node) { 24014 writer.text(node.value, node.raw); 24015 }, 24016 24017 // #comment 24018 8: function(node) { 24019 writer.comment(node.value); 24020 }, 24021 24022 // Processing instruction 24023 7: function(node) { 24024 writer.pi(node.name, node.value); 24025 }, 24026 24027 // Doctype 24028 10: function(node) { 24029 writer.doctype(node.value); 24030 }, 24031 24032 // CDATA 24033 4: function(node) { 24034 writer.cdata(node.value); 24035 }, 24036 24037 // Document fragment 24038 11: function(node) { 24039 if ((node = node.firstChild)) { 24040 do { 24041 walk(node); 24042 } while ((node = node.next)); 24043 } 24044 } 24045 }; 24046 24047 writer.reset(); 24048 24049 function walk(node) { 24050 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; 24051 24052 if (!handler) { 24053 name = node.name; 24054 isEmpty = node.shortEnded; 24055 attrs = node.attributes; 24056 24057 // Sort attributes 24058 if (validate && attrs && attrs.length > 1) { 24059 sortedAttrs = []; 24060 sortedAttrs.map = {}; 24061 24062 elementRule = schema.getElementRule(node.name); 24063 if (elementRule) { 24064 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { 24065 attrName = elementRule.attributesOrder[i]; 24066 24067 if (attrName in attrs.map) { 24068 attrValue = attrs.map[attrName]; 24069 sortedAttrs.map[attrName] = attrValue; 24070 sortedAttrs.push({name: attrName, value: attrValue}); 24071 } 24072 } 24073 24074 for (i = 0, l = attrs.length; i < l; i++) { 24075 attrName = attrs[i].name; 24076 24077 if (!(attrName in sortedAttrs.map)) { 24078 attrValue = attrs.map[attrName]; 24079 sortedAttrs.map[attrName] = attrValue; 24080 sortedAttrs.push({name: attrName, value: attrValue}); 24081 } 24082 } 24083 24084 attrs = sortedAttrs; 24085 } 24086 } 24087 24088 writer.start(node.name, attrs, isEmpty); 24089 24090 if (!isEmpty) { 24091 if ((node = node.firstChild)) { 24092 do { 24093 walk(node); 24094 } while ((node = node.next)); 24095 } 24096 24097 writer.end(name); 24098 } 24099 } else { 24100 handler(node); 24101 } 24102 } 24103 24104 // Serialize element and treat all non elements as fragments 24105 if (node.type == 1 && !settings.inner) { 24106 walk(node); 24107 } else { 24108 handlers[11](node); 24109 } 24110 24111 return writer.getContent(); 24112 }; 24113 }; 24114}); 24115 24116// Included from: js/tinymce/classes/dom/Serializer.js 24117 24118/** 24119 * Serializer.js 24120 * 24121 * Released under LGPL License. 24122 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 24123 * 24124 * License: http://www.tinymce.com/license 24125 * Contributing: http://www.tinymce.com/contributing 24126 */ 24127 24128/** 24129 * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for 24130 * more details and examples on how to use this class. 24131 * 24132 * @class tinymce.dom.Serializer 24133 */ 24134define("tinymce/dom/Serializer", [ 24135 "tinymce/dom/DOMUtils", 24136 "tinymce/html/DomParser", 24137 "tinymce/html/SaxParser", 24138 "tinymce/html/Entities", 24139 "tinymce/html/Serializer", 24140 "tinymce/html/Node", 24141 "tinymce/html/Schema", 24142 "tinymce/Env", 24143 "tinymce/util/Tools", 24144 "tinymce/text/Zwsp" 24145], function(DOMUtils, DomParser, SaxParser, Entities, Serializer, Node, Schema, Env, Tools, Zwsp) { 24146 var each = Tools.each, trim = Tools.trim; 24147 var DOM = DOMUtils.DOM; 24148 var trimContentRegExp = new RegExp([ 24149 '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers 24150 '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected 24151 ].join('|'), 'gi'); 24152 24153 /** 24154 * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when 24155 * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
24156 * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML 24157 * but not as the lastChild of the body. So this fix simply removes the last two 24158 * BR elements at the end of the document. 24159 * 24160 * Example of what happens: <body>text</body> becomes <body>text<br><br></body> 24161 */ 24162 function trimTrailingBr(rootNode) { 24163 var brNode1, brNode2; 24164 24165 function isBr(node) { 24166 return node && node.name === 'br'; 24167 } 24168 24169 brNode1 = rootNode.lastChild; 24170 if (isBr(brNode1)) { 24171 brNode2 = brNode1.prev; 24172 24173 if (isBr(brNode2)) { 24174 brNode1.remove(); 24175 brNode2.remove(); 24176 } 24177 } 24178 } 24179 24180 /** 24181 * Constructs a new DOM serializer class. 24182 * 24183 * @constructor 24184 * @method Serializer 24185 * @param {Object} settings Serializer settings object. 24186 * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from. 24187 */ 24188 return function(settings, editor) { 24189 var dom, schema, htmlParser; 24190 24191 if (editor) { 24192 dom = editor.dom; 24193 schema = editor.schema; 24194 } 24195 24196 /** 24197 * Returns a trimmed version of the editor contents to be used for the undo level. This 24198 * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also 24199 * remove the data-mce-selected attributes used for selection of objects and caret containers. 24200 * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will 24201 * be removed by the serialization logic when you save. 24202 * 24203 * @private 24204 * @return {String} HTML contents of the editor excluding some internal bogus elements. 24205 */ 24206 function getTrimmedContent() { 24207 var content = editor.getBody().innerHTML; 24208 var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g; 24209 var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema; 24210 24211 content = Zwsp.trim(content.replace(trimContentRegExp, '')); 24212 shortEndedElements = schema.getShortEndedElements(); 24213 24214 // Remove all bogus elements marked with "all" 24215 while ((matches = bogusAllRegExp.exec(content))) { 24216 index = bogusAllRegExp.lastIndex; 24217 matchLength = matches[0].length; 24218 24219 if (shortEndedElements[matches[1]]) { 24220 endTagIndex = index; 24221 } else { 24222 endTagIndex = SaxParser.findEndTag(schema, content, index); 24223 } 24224 24225 content = content.substring(0, index - matchLength) + content.substring(endTagIndex); 24226 bogusAllRegExp.lastIndex = index - matchLength; 24227 } 24228 24229 return trim(content); 24230 } 24231 24232 // Default DOM and Schema if they are undefined 24233 dom = dom || DOM; 24234 schema = schema || new Schema(settings); 24235 settings.entity_encoding = settings.entity_encoding || 'named'; 24236 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true; 24237 24238 htmlParser = new DomParser(settings, schema); 24239 24240 // Convert tabindex back to elements when serializing contents 24241 htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) { 24242 var i = nodes.length, node; 24243 24244 while (i--) { 24245 node = nodes[i]; 24246 node.attr('tabindex', node.attributes.map['data-mce-tabindex']); 24247 node.attr(name, null); 24248 } 24249 }); 24250 24251 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed 24252 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { 24253 var i = nodes.length, node, value, internalName = 'data-mce-' + name; 24254 var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; 24255 24256 while (i--) { 24257 node = nodes[i]; 24258 24259 value = node.attributes.map[internalName]; 24260 if (value !== undef) { 24261 // Set external name to internal value and remove internal 24262 node.attr(name, value.length > 0 ? value : null); 24263 node.attr(internalName, null); 24264 } else { 24265 // No internal attribute found then convert the value we have in the DOM 24266 value = node.attributes.map[name]; 24267 24268 if (name === "style") { 24269 value = dom.serializeStyle(dom.parseStyle(value), node.name); 24270 } else if (urlConverter) { 24271 value = urlConverter.call(urlConverterScope, value, name, node.name); 24272 } 24273 24274 node.attr(name, value.length > 0 ? value : null); 24275 } 24276 } 24277 }); 24278
24279 // Remove internal classes mceItem<..> or mceSelected 24280 htmlParser.addAttributeFilter('class', function(nodes) { 24281 var i = nodes.length, node, value; 24282 24283 while (i--) { 24284 node = nodes[i]; 24285 value = node.attr('class'); 24286 24287 if (value) { 24288 value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, ''); 24289 node.attr('class', value.length > 0 ? value : null); 24290 } 24291 } 24292 }); 24293 24294 // Remove bookmark elements 24295 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { 24296 var i = nodes.length, node; 24297 24298 while (i--) { 24299 node = nodes[i]; 24300 24301 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) { 24302 node.remove(); 24303 } 24304 } 24305 }); 24306 24307 htmlParser.addNodeFilter('noscript', function(nodes) { 24308 var i = nodes.length, node; 24309 24310 while (i--) { 24311 node = nodes[i].firstChild; 24312 24313 if (node) { 24314 node.value = Entities.decode(node.value); 24315 } 24316 } 24317 }); 24318 24319 // Force script into CDATA sections and remove the mce- prefix also add comments around styles 24320 htmlParser.addNodeFilter('script,style', function(nodes, name) { 24321 var i = nodes.length, node, value, type; 24322 24323 function trim(value) { 24324 /*jshint maxlen:255 */ 24325 /*eslint max-len:0 */ 24326 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n') 24327 .replace(/^[\r\n]*|[\r\n]*$/g, '') 24328 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '') 24329 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, ''); 24330 } 24331 24332 while (i--) { 24333 node = nodes[i]; 24334 value = node.firstChild ? node.firstChild.value : ''; 24335 24336 if (name === "script") { 24337 // Remove mce- prefix from script elements and remove default type since the user specified 24338 // a script element without type attribute 24339 type = node.attr('type'); 24340 if (type) { 24341 node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, '')); 24342 } 24343 24344 if (value.length > 0) { 24345 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>'; 24346 } 24347 } else { 24348 if (value.length > 0) { 24349 node.firstChild.value = '<!--\n' + trim(value) + '\n-->'; 24350 } 24351 } 24352 } 24353 }); 24354 24355 // Convert comments to cdata and handle protected comments 24356 htmlParser.addNodeFilter('#comment', function(nodes) { 24357 var i = nodes.length, node; 24358 24359 while (i--) { 24360 node = nodes[i]; 24361 24362 if (node.value.indexOf('[CDATA[') === 0) { 24363 node.name = '#cdata'; 24364 node.type = 4; 24365 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); 24366 } else if (node.value.indexOf('mce:protected ') === 0) { 24367 node.name = "#text"; 24368 node.type = 3; 24369 node.raw = true; 24370 node.value = unescape(node.value).substr(14); 24371 } 24372 } 24373 }); 24374 24375 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { 24376 var i = nodes.length, node; 24377 24378 while (i--) { 24379 node = nodes[i]; 24380 if (node.type === 7) { 24381 node.remove(); 24382 } else if (node.type === 1) { 24383 if (name === "input" && !("type" in node.attributes.map)) { 24384 node.attr('type', 'text'); 24385 } 24386 } 24387 } 24388 }); 24389 24390 // Fix list elements, TODO: Replace this later 24391 if (settings.fix_list_elements) { 24392 htmlParser.addNodeFilter('ul,ol', function(nodes) { 24393 var i = nodes.length, node, parentNode; 24394 24395 while (i--) { 24396 node = nodes[i]; 24397 parentNode = node.parent; 24398 24399 if (parentNode.name === 'ul' || parentNode.name === 'ol') { 24400 if (node.prev && node.prev.name === 'li') { 24401 node.prev.append(node); 24402 } 24403 } 24404 } 24405 }); 24406 } 24407 24408 // Remove internal data attributes 24409 htmlParser.addAttributeFilter( 24410 'data-mce-src,data-mce-href,data-mce-style,' + 24411 'data-mce-selected,data-mce-expando,' + 24412 'data-mce-type,data-mce-resize', 24413 24414 function(nodes, name) { 24415 var i = nodes.length; 24416 24417 while (i--) { 24418 nodes[i].attr(name, null); 24419 } 24420 } 24421 ); 24422 24423 // Return public methods 24424 return { 24425 /** 24426 * Schema instance that was used to when the Serializer was constructed. 24427 * 24428 * @field {tinymce.html.Schema} schema 24429 */ 24430 schema: schema, 24431 24432 /** 24433 * Adds a node filter function to the parser used by the serializer, the parser will c
24433ollect the specified nodes by name 24434 * and then execute the callback ones it has finished parsing the document. 24435 * 24436 * @example 24437 * parser.addNodeFilter('p,h1', function(nodes, name) { 24438 * for (var i = 0; i < nodes.length; i++) { 24439 * console.log(nodes[i].name); 24440 * } 24441 * }); 24442 * @method addNodeFilter 24443 * @method {String} name Comma separated list of nodes to collect. 24444 * @param {function} callback Callback function to execute once it has collected nodes. 24445 */ 24446 addNodeFilter: htmlParser.addNodeFilter, 24447 24448 /** 24449 * Adds a attribute filter function to the parser used by the serializer, the parser will 24450 * collect nodes that has the specified attributes 24451 * and then execute the callback ones it has finished parsing the document. 24452 * 24453 * @example 24454 * parser.addAttributeFilter('src,href', function(nodes, name) { 24455 * for (var i = 0; i < nodes.length; i++) { 24456 * console.log(nodes[i].name); 24457 * } 24458 * }); 24459 * @method addAttributeFilter 24460 * @method {String} name Comma separated list of nodes to collect. 24461 * @param {function} callback Callback function to execute once it has collected nodes. 24462 */ 24463 addAttributeFilter: htmlParser.addAttributeFilter, 24464 24465 /** 24466 * Serializes the specified browser DOM node into a HTML string. 24467 * 24468 * @method serialize 24469 * @param {DOMNode} node DOM node to serialize. 24470 * @param {Object} args Arguments option that gets passed to event handlers. 24471 */ 24472 serialize: function(node, args) { 24473 var self = this, impl, doc, oldDoc, htmlSerializer, content, rootNode; 24474 24475 // Explorer won't clone contents of script and style and the 24476 // selected index of select elements are cleared on a clone operation. 24477 if (Env.ie && dom.select('script,style,select,map').length > 0) { 24478 content = node.innerHTML; 24479 node = node.cloneNode(false); 24480 dom.setHTML(node, content); 24481 } else { 24482 node = node.cloneNode(true); 24483 } 24484 24485 // Nodes needs to be attached to something in WebKit/Opera 24486 // This fix will make DOM ranges and make Sizzle happy! 24487 impl = node.ownerDocument.implementation; 24488 if (impl.createHTMLDocument) { 24489 // Create an empty HTML document 24490 doc = impl.createHTMLDocument(""); 24491 24492 // Add the element or it's children if it's a body element to the new document 24493 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { 24494 doc.body.appendChild(doc.importNode(node, true)); 24495 }); 24496 24497 // Grab first child or body element for serialization 24498 if (node.nodeName != 'BODY') { 24499 node = doc.body.firstChild; 24500 } else { 24501 node = doc.body; 24502 } 24503 24504 // set the new document in DOMUtils so createElement etc works 24505 oldDoc = dom.doc; 24506 dom.doc = doc; 24507 } 24508 24509 args = args || {}; 24510 args.format = args.format || 'html'; 24511 24512 // Don't wrap content if we want selected html 24513 if (args.selection) { 24514 args.forced_root_block = ''; 24515 } 24516 24517 // Pre process 24518 if (!args.no_events) { 24519 args.node = node; 24520 self.onPreProcess(args); 24521 } 24522 24523 // Parse HTML 24524 rootNode = htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args); 24525 trimTrailingBr(rootNode); 24526 24527 // Serialize HTML 24528 htmlSerializer = new Serializer(settings, schema); 24529 args.content = htmlSerializer.serialize(rootNode); 24530 24531 // Replace all BOM characters for now until we can find a better solution 24532 if (!args.cleanup) { 24533 args.content = Zwsp.trim(args.content); 24534 args.content = args.content.replace(/\uFEFF/g, ''); 24535 } 24536 24537 // Post process 24538 if (!args.no_events) { 24539 self.onPostProcess(args); 24540 } 24541 24542 // Restore the old document if it was changed 24543 if (oldDoc) { 24544 dom.doc = oldDoc; 24545 } 24546 24547 args.node = null; 24548 24549 return args.content; 24550 }, 24551 24552 /** 24553 * Adds valid elements rules to the serializers schema instance this enables you to specify things 24554 * like what elements should be outputted and what attributes specific elements might have. 24555 * Consult the Wiki for more details on this format. 24556 * 24557 * @method addRules 24558 * @param {String} rules Valid elements rules string to add to schema. 24559 */ 24560 addRules: function(rules) { 24561 schema.addValidElements(rules); 24562 }, 24563 24564 /** 24565 * Sets the valid elements rules to the serializers schema instance this enables you to specify things 24566 * like what elements should be outputted and what attributes specific elements might have. 24567 * Consult the Wiki for more details on this format. 24568 * 24569 * @method setRules 24570 * @param {String} rules Valid elements rules string. 24571 */ 24572 setRules: function(rules) { 24573 schema.setValidElements(rules); 24574 }, 24575 24576 onPreProcess: function(args) { 24577 if (editor) {
24578 editor.fire('PreProcess', args); 24579 } 24580 }, 24581 24582 onPostProcess: function(args) { 24583 if (editor) { 24584 editor.fire('PostProcess', args); 24585 } 24586 }, 24587 24588 getTrimmedContent: getTrimmedContent 24589 }; 24590 }; 24591}); 24592 24593// Included from: js/tinymce/classes/dom/TridentSelection.js 24594 24595/** 24596 * TridentSelection.js 24597 * 24598 * Released under LGPL License. 24599 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 24600 * 24601 * License: http://www.tinymce.com/license 24602 * Contributing: http://www.tinymce.com/contributing 24603 */ 24604 24605/** 24606 * Selection class for old explorer versions. This one fakes the 24607 * native selection object available on modern browsers. 24608 * 24609 * @private 24610 * @class tinymce.dom.TridentSelection 24611 */ 24612define("tinymce/dom/TridentSelection", [], function() { 24613 function Selection(selection) { 24614 var self = this, dom = selection.dom, FALSE = false; 24615 24616 function getPosition(rng, start) { 24617 var checkRng, startIndex = 0, endIndex, inside, 24618 children, child, offset, index, position = -1, parent; 24619 24620 // Setup test range, collapse it and get the parent 24621 checkRng = rng.duplicate(); 24622 checkRng.collapse(start); 24623 parent = checkRng.parentElement(); 24624 24625 // Check if the selection is within the right document 24626 if (parent.ownerDocument !== selection.dom.doc) { 24627 return; 24628 } 24629 24630 // IE will report non editable elements as it's parent so look for an editable one 24631 while (parent.contentEditable === "false") { 24632 parent = parent.parentNode; 24633 } 24634 24635 // If parent doesn't have any children then return that we are inside the element 24636 if (!parent.hasChildNodes()) { 24637 return {node: parent, inside: 1}; 24638 } 24639 24640 // Setup node list and endIndex 24641 children = parent.children; 24642 endIndex = children.length - 1; 24643 24644 // Perform a binary search for the position 24645 while (startIndex <= endIndex) { 24646 index = Math.floor((startIndex + endIndex) / 2); 24647 24648 // Move selection to node and compare the ranges 24649 child = children[index]; 24650 checkRng.moveToElementText(child); 24651 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); 24652 24653 // Before/after or an exact match 24654 if (position > 0) { 24655 endIndex = index - 1; 24656 } else if (position < 0) { 24657 startIndex = index + 1; 24658 } else { 24659 return {node: child}; 24660 } 24661 } 24662 24663 // Check if child position is before or we didn't find a position 24664 if (position < 0) { 24665 // No element child was found use the parent element and the offset inside that 24666 if (!child) { 24667 checkRng.moveToElementText(parent); 24668 checkRng.collapse(true); 24669 child = parent; 24670 inside = true; 24671 } else { 24672 checkRng.collapse(false); 24673 } 24674 24675 // Walk character by character in text node until we hit the selected range endpoint, 24676 // hit the end of document or parent isn't the right one 24677 // We need to walk char by char since rng.text or rng.htmlText will trim line endings 24678 offset = 0; 24679 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 24680 if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) { 24681 break; 24682 } 24683 24684 offset++; 24685 } 24686 } else { 24687 // Child position is after the selection endpoint 24688 checkRng.collapse(true); 24689 24690 // Walk character by character in text node until we hit the selected range endpoint, hit 24691 // the end of document or parent isn't the right one 24692 offset = 0; 24693 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) { 24694 if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) { 24695 break; 24696 } 24697 24698 offset++; 24699 } 24700 } 24701 24702 return {node: child, position: position, offset: offset, inside: inside}; 24703 } 24704 24705 // Returns a W3C DOM compatible range object by using the IE Range API 24706 function getRange() { 24707 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark; 24708 24709 // If selection is outside the current document just return an empty range 24710 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); 24711 if (element.ownerDocument != dom.doc) { 24712 return domRange; 24713 } 24714 24715 collapsed = selection.isCollapsed(); 24716 24717 // Handle control selection 24718 if (ieRange.item) { 24719 domRange.setStart(element.parentNode, dom.nodeIndex(element)); 24720 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); 24721 24722 return domRange; 24723 } 24724 24725 function findEndPoint(start) { 24726 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; 24727 24728 container = endPoint.node;
24729 offset = endPoint.offset; 24730 24731 if (endPoint.inside && !container.hasChildNodes()) { 24732 domRange[start ? 'setStart' : 'setEnd'](container, 0); 24733 return; 24734 } 24735 24736 if (offset === undef) { 24737 domRange[start ? 'setStartBefore' : 'setEndAfter'](container); 24738 return; 24739 } 24740 24741 if (endPoint.position < 0) { 24742 sibling = endPoint.inside ? container.firstChild : container.nextSibling; 24743 24744 if (!sibling) { 24745 domRange[start ? 'setStartAfter' : 'setEndAfter'](container); 24746 return; 24747 } 24748 24749 if (!offset) { 24750 if (sibling.nodeType == 3) { 24751 domRange[start ? 'setStart' : 'setEnd'](sibling, 0); 24752 } else { 24753 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); 24754 } 24755 24756 return; 24757 } 24758 24759 // Find the text node and offset 24760 while (sibling) { 24761 if (sibling.nodeType == 3) { 24762 nodeValue = sibling.nodeValue; 24763 textNodeOffset += nodeValue.length; 24764 24765 // We are at or passed the position we where looking for 24766 if (textNodeOffset >= offset) { 24767 container = sibling; 24768 textNodeOffset -= offset; 24769 textNodeOffset = nodeValue.length - textNodeOffset; 24770 break; 24771 } 24772 } 24773 24774 sibling = sibling.nextSibling; 24775 } 24776 } else { 24777 // Find the text node and offset 24778 sibling = container.previousSibling; 24779 24780 if (!sibling) { 24781 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); 24782 } 24783 24784 // If there isn't any text to loop then use the first position 24785 if (!offset) { 24786 if (container.nodeType == 3) { 24787 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); 24788 } else { 24789 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); 24790 } 24791 24792 return; 24793 } 24794 24795 while (sibling) { 24796 if (sibling.nodeType == 3) { 24797 textNodeOffset += sibling.nodeValue.length; 24798 24799 // We are at or passed the position we where looking for 24800 if (textNodeOffset >= offset) { 24801 container = sibling; 24802 textNodeOffset -= offset; 24803 break; 24804 } 24805 } 24806 24807 sibling = sibling.previousSibling; 24808 } 24809 } 24810 24811 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); 24812 } 24813 24814 try { 24815 // Find start point 24816 findEndPoint(true); 24817 24818 // Find end point if needed 24819 if (!collapsed) { 24820 findEndPoint(); 24821 } 24822 } catch (ex) { 24823 // IE has a nasty bug where text nodes might throw "invalid argument" when you 24824 // access the nodeValue or other properties of text nodes. This seems to happen when 24825 // text nodes are split into two nodes by a delete/backspace call. 24826 // So let us detect and try to fix it. 24827 if (ex.number == -2147024809) { 24828 // Get the current selection 24829 bookmark = self.getBookmark(2); 24830 24831 // Get start element 24832 tmpRange = ieRange.duplicate(); 24833 tmpRange.collapse(true); 24834 element = tmpRange.parentElement(); 24835 24836 // Get end element 24837 if (!collapsed) { 24838 tmpRange = ieRange.duplicate(); 24839 tmpRange.collapse(false); 24840 element2 = tmpRange.parentElement(); 24841 element2.innerHTML = element2.innerHTML; 24842 } 24843 24844 // Remove the broken elements 24845 element.innerHTML = element.innerHTML; 24846 24847 // Restore the selection 24848 self.moveToBookmark(bookmark); 24849 24850 // Since the range has moved we need to re-get it 24851 ieRange = selection.getRng(); 24852 24853 // Find start point 24854 findEndPoint(true); 24855 24856 // Find end point if needed 24857 if (!collapsed) { 24858 findEndPoint(); 24859 } 24860 } else { 24861 throw ex; // Throw other errors 24862 } 24863 } 24864 24865 return domRange; 24866 } 24867 24868 this.getBookmark = function(type) { 24869 var rng = selection.getRng(), bookmark = {}; 24870 24871 function getIndexes(node) { 24872 var parent, root, children, i, indexes = []; 24873 24874 parent = node.parentNode; 24875 root = dom.getRoot().parentNode; 24876 24877 while (parent != root && parent.nodeType !== 9) { 24878 children = parent.children; 24879 24880 i = children.length; 24881 while (i--) { 24882 if (node === children[i]) { 24883 indexes.push(i); 24884 break; 24885 } 24886 } 24887 24888 node = parent; 24889 parent = parent.parentNode; 24890 } 24891 24892 return indexes; 24893 } 24894 24895 function getBookmarkEndPoint(start) { 24896 var position; 24897 24898 position = getPosition(rng, start); 24899 if (position) { 24900 return { 24901 position: position.position, 24902 offset: position.offset, 24903 indexes: getIndexes(position.node), 24904 inside: position.inside 24905 }; 24906 } 24907 } 24908 24909 // Non ubstructive bookmark 24910 if (type === 2) { 24911 // Handle text selection 24912 if (!rng.item) { 24913 bookmark.start = getBookmarkEndPoint(true); 24914 24915 if (!selection.isCollapsed()) { 24916 bookmark.end = getBookmarkEndPoint(); 24917 } 24918 } else { 24919 bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))}; 24920 } 24921 } 24922 24923 return bookmark; 24924 }; 24925 24926 this.moveToBookmark = function(bookmark) { 24927 var rng, body = dom.doc.body; 24928 24929 function resolveIndexes(indexes) { 24930 var node, i, idx, children; 24931 24932 node = dom.getRoot(); 24933 for (i = indexes.length - 1; i >= 0; i--) { 24934 children = node.children; 24935 idx = indexes[i]; 24936 24937 if (idx <= children.length - 1) { 24938 node = children[idx]; 24939 } 24940 } 24941 24942 return node; 24943 } 24944 24945 function setBookmarkEndPoint(start) { 24946 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset; 24947 24948 if (endPoint) { 24949 moveLeft = endPoint.position > 0; 24950 24951 moveRng = body.createTextRange(); 24952 moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); 24953 24954 offset = endPoint.offset; 24955 if (offset !== undef) { 24956 moveRng.collapse(endPoint.inside || moveLeft); 24957 moveRng.moveStart('character', moveLeft ? -offset : offset); 24958 } else { 24959 moveRng.collapse(start); 24960 } 24961 24962 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); 24963 24964 if (start) { 24965 rng.collapse(true); 24966 } 24967 } 24968 } 24969 24970 if (bookmark.start) { 24971 if (bookmark.start.ctrl) { 24972 rng = body.createControlRange(); 24973 rng.addElement(resolveIndexes(bookmark.start.indexes)); 24974 rng.select(); 24975 } else { 24976 rng = body.createTextRange(); 24977 setBookmarkEndPoint(true); 24978 setBookmarkEndPoint(); 24979 rng.select(); 24980 } 24981 } 24982 }; 24983 24984 this.addRange = function(rng) { 24985 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, 24986 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm; 24987 24988 function setEndPoint(start) { 24989 var container, offset, marker, tmpRng, nodes; 24990 24991 marker = dom.create('a'); 24992 container = start ? startContainer : endContainer; 24993 offset = start ? startOffset : endOffset; 24994 tmpRng = ieRng.duplicate(); 24995 24996 if (container == doc || container == doc.documentElement) { 24997 container = body; 24998 offset = 0; 24999 } 25000 25001 if (container.nodeType == 3) { 25002 container.parentNode.insertBefore(marker, container); 25003 tmpRng.moveToElementText(marker); 25004 tmpRng.moveStart('character', offset); 25005 dom.remove(marker); 25006 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 25007 } else { 25008 nodes = container.childNodes; 25009 25010 if (nodes.length) { 25011 if (offset >= nodes.length) { 25012 dom.insertAfter(marker, nodes[nodes.length - 1]); 25013 } else { 25014 container.insertBefore(marker, nodes[offset]); 25015 } 25016 25017 tmpRng.moveToElementText(marker); 25018 } else if (container.canHaveHTML) { 25019 // Empty node selection for example <div>|</div> 25020 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open 25021 container.innerHTML = '<span></span>'; 25022 marker = container.firstChild; 25023 tmpRng.moveToElementText(marker); 25024 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason 25025 } 25026 25027 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); 25028 dom.remove(marker); 25029 } 25030 } 25031 25032 // Setup some shorter versions 25033 startContainer = rng.startContainer; 25034 startOffset = rng.startOffset; 25035 endContainer = rng.endContainer; 25036 endOffset = rng.endOffset; 25037 ieRng = body.createTextRange(); 25038 25039 // If single element selection then try making a control selection out of it 25040 if (startContainer == endContainer && startContainer.nodeType == 1) { 25041 // Trick to place the caret inside an empty block element like <p></p> 25042 if (startOffset == endOffset && !startContainer.hasChildNodes()) { 25043 if (startContainer.canHaveHTML) {
25044 // Check if previous sibling is an empty block if it is then we need to render it 25045 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236 25046 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p> 25047 sibling = startContainer.previousSibling; 25048 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) { 25049 sibling.innerHTML = ''; 25050 } else { 25051 sibling = null; 25052 } 25053 25054 startContainer.innerHTML = '<span></span><span></span>'; 25055 ieRng.moveToElementText(startContainer.lastChild); 25056 ieRng.select(); 25057 dom.doc.selection.clear(); 25058 startContainer.innerHTML = ''; 25059 25060 if (sibling) { 25061 sibling.innerHTML = ''; 25062 } 25063 return; 25064 } 25065 25066 startOffset = dom.nodeIndex(startContainer); 25067 startContainer = startContainer.parentNode; 25068 } 25069 25070 if (startOffset == endOffset - 1) { 25071 try { 25072 ctrlElm = startContainer.childNodes[startOffset]; 25073 ctrlRng = body.createControlRange(); 25074 ctrlRng.addElement(ctrlElm); 25075 ctrlRng.select(); 25076 25077 // Check if the range produced is on the correct element and is a control range 25078 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398 25079 nativeRng = selection.getRng(); 25080 if (nativeRng.item && ctrlElm === nativeRng.item(0)) { 25081 return; 25082 } 25083 } catch (ex) { 25084 // Ignore 25085 } 25086 } 25087 } 25088 25089 // Set start/end point of selection 25090 setEndPoint(true); 25091 setEndPoint(); 25092 25093 // Select the new range and scroll it into view 25094 ieRng.select(); 25095 }; 25096 25097 // Expose range method 25098 this.getRangeAt = getRange; 25099 } 25100 25101 return Selection; 25102}); 25103 25104// Included from: js/tinymce/classes/util/VK.js 25105 25106/** 25107 * VK.js 25108 * 25109 * Released under LGPL License. 25110 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 25111 * 25112 * License: http://www.tinymce.com/license 25113 * Contributing: http://www.tinymce.com/contributing 25114 */ 25115 25116/** 25117 * This file exposes a set of the common KeyCodes for use. Please grow it as needed. 25118 */ 25119define("tinymce/util/VK", [ 25120 "tinymce/Env" 25121], function(Env) { 25122 return { 25123 BACKSPACE: 8, 25124 DELETE: 46, 25125 DOWN: 40, 25126 ENTER: 13, 25127 LEFT: 37, 25128 RIGHT: 39, 25129 SPACEBAR: 32, 25130 TAB: 9, 25131 UP: 38, 25132 25133 modifierPressed: function(e) { 25134 return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e); 25135 }, 25136 25137 metaKeyPressed: function(e) { 25138 // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states 25139 return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey); 25140 } 25141 }; 25142}); 25143 25144// Included from: js/tinymce/classes/dom/ControlSelection.js 25145 25146/** 25147 * ControlSelection.js 25148 * 25149 * Released under LGPL License. 25150 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 25151 * 25152 * License: http://www.tinymce.com/license 25153 * Contributing: http://www.tinymce.com/contributing 25154 */ 25155 25156/** 25157 * This class handles control selection of elements. Controls are elements 25158 * that can be resized and needs to be selected as a whole. It adds custom resize handles 25159 * to all browser engines that support properly disabling the built in resize logic. 25160 * 25161 * @class tinymce.dom.ControlSelection 25162 */ 25163define("tinymce/dom/ControlSelection", [ 25164 "tinymce/util/VK", 25165 "tinymce/util/Tools", 25166 "tinymce/util/Delay", 25167 "tinymce/Env", 25168 "tinymce/dom/NodeType" 25169], function(VK, Tools, Delay, Env, NodeType) { 25170 var isContentEditableFalse = NodeType.isContentEditableFalse; 25171 25172 return function(selection, editor) { 25173 var dom = editor.dom, each = Tools.each; 25174 var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent; 25175 var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted; 25176 var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11; 25177 var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight; 25178 25179 // Details about each resize handle how to scale etc 25180 resizeHandles = { 25181 // Name: x multiplier, y multiplier, delta size x, delta size y 25182 /*n: [0.5, 0, 0, -1], 25183 e: [1, 0.5, 1, 0], 25184 s: [0.5, 1, 0, 1], 25185 w: [0, 0.5, -1, 0],*/ 25186 nw: [0, 0, -1, -1], 25187 ne: [1, 0, 1, -1], 25188 se: [1, 1, 1, 1], 25189 sw: [0, 1, -1, 1] 25190 }; 25191 25192 // Add CSS for resize handles, cloned element and selected 25193 var rootClass = '.mce-content-body'; 25194 editor.contentStyles.push( 25195 rootClass + ' div.mce-resizehandle {' + 25196 'position: absolute;' + 25197 'border: 1px solid black;' + 25198 'background: #FFF;' + 25199 'width: 7px;' + 25200 'height: 7px;' + 25201 'z-index: 10000' + 25202 '}' + 25203 rootClass + ' .mce-resizehandle:hover {' + 25204 'background: #000' + 25205 '}' + 25206 rootClass + ' *[data-mce-selected] {' + 25207 'outline: 1px solid black;' + 25208 'resize: none' + // Have been talks about implementing this in browsers 25209 '}' + 25210 rootClass + ' .mce-clonedresizable {' + 25211 'position: absolute;' + 25212 (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing 25213 'opacity: .5;' + 25214 'filter: alpha(opacity=50);' + 25215 'z-index: 10000' + 25216 '}' + 25217 rootClass + ' .mce-resize-helper {' + 25218 'background: #555;' + 25219 'background: rgba(0,0,0,0.75);' + 25220 'border-radius: 3px;' + 25221 'border: 1px;' + 25222 'color: white;' + 25223 'display: none;' + 25224 'font-family: sans-serif;' + 25225 'font-size: 12px;' + 25226 'white-space: nowrap;' + 25227 'line-height: 14px;' + 25228 'margin: 5px 10px;' + 25229 'padding: 5px;' + 25230 'position: absolute;' + 25231 'z-index: 10001' + 25232 '}' 25233 ); 25234 25235 function isResizable(elm) { 25236 var selector = editor.settings.object_resizing; 25237 25238 if (selector === false || Env.iOS) { 25239 return false;
25240 } 25241 25242 if (typeof selector != 'string') { 25243 selector = 'table,img,div'; 25244 } 25245 25246 if (elm.getAttribute('data-mce-resize') === 'false') { 25247 return false; 25248 } 25249 25250 if (elm == editor.getBody()) { 25251 return false; 25252 } 25253 25254 return editor.dom.is(elm, selector); 25255 } 25256 25257 function resizeGhostElement(e) { 25258 var deltaX, deltaY, proportional; 25259 var resizeHelperX, resizeHelperY; 25260 25261 // Calc new width/height 25262 deltaX = e.screenX - startX; 25263 deltaY = e.screenY - startY; 25264 25265 // Calc new size 25266 width = deltaX * selectedHandle[2] + startW; 25267 height = deltaY * selectedHandle[3] + startH; 25268 25269 // Never scale down lower than 5 pixels 25270 width = width < 5 ? 5 : width; 25271 height = height < 5 ? 5 : height; 25272 25273 if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) { 25274 proportional = !VK.modifierPressed(e); 25275 } else { 25276 proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0); 25277 } 25278 25279 // Constrain proportions 25280 if (proportional) { 25281 if (abs(deltaX) > abs(deltaY)) { 25282 height = round(width * ratio); 25283 width = round(height / ratio); 25284 } else { 25285 width = round(height / ratio); 25286 height = round(width * ratio); 25287 } 25288 } 25289 25290 // Update ghost size 25291 dom.setStyles(selectedElmGhost, { 25292 width: width, 25293 height: height 25294 }); 25295 25296 // Update resize helper position 25297 resizeHelperX = selectedHandle.startPos.x + deltaX; 25298 resizeHelperY = selectedHandle.startPos.y + deltaY; 25299 resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0; 25300 resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0; 25301 25302 dom.setStyles(resizeHelper, { 25303 left: resizeHelperX, 25304 top: resizeHelperY, 25305 display: 'block' 25306 }); 25307 25308 resizeHelper.innerHTML = width + ' × ' + height; 25309 25310 // Update ghost X position if needed 25311 if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) { 25312 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width)); 25313 } 25314 25315 // Update ghost Y position if needed 25316 if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) { 25317 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height)); 25318 } 25319 25320 // Calculate how must overflow we got 25321 deltaX = rootElement.scrollWidth - startScrollWidth; 25322 deltaY = rootElement.scrollHeight - startScrollHeight; 25323 25324 // Re-position the resize helper based on the overflow 25325 if (deltaX + deltaY !== 0) { 25326 dom.setStyles(resizeHelper, { 25327 left: resizeHelperX - deltaX, 25328 top: resizeHelperY - deltaY 25329 }); 25330 } 25331 25332 if (!resizeStarted) { 25333 editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH}); 25334 resizeStarted = true; 25335 } 25336 } 25337 25338 function endGhostResize() { 25339 resizeStarted = false; 25340 25341 function setSizeProp(name, value) { 25342 if (value) { 25343 // Resize by using style or attribute 25344 if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) { 25345 dom.setStyle(selectedElm, name, value); 25346 } else { 25347 dom.setAttrib(selectedElm, name, value); 25348 } 25349 } 25350 } 25351 25352 // Set width/height properties 25353 setSizeProp('width', width); 25354 setSizeProp('height', height); 25355 25356 dom.unbind(editableDoc, 'mousemove', resizeGhostElement); 25357 dom.unbind(editableDoc, 'mouseup', endGhostResize); 25358 25359 if (rootDocument != editableDoc) { 25360 dom.unbind(rootDocument, 'mousemove', resizeGhostElement); 25361 dom.unbind(rootDocument, 'mouseup', endGhostResize); 25362 } 25363 25364 // Remove ghost/helper and update resize handle positions 25365 dom.remove(selectedElmGhost); 25366 dom.remove(resizeHelper); 25367 25368 if (!isIE || selectedElm.nodeName == "TABLE") { 25369 showResizeRect(selectedElm); 25370 } 25371 25372 editor.fire('ObjectResized', {target: selectedElm, width: width, height: height}); 25373 dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style')); 25374 editor.nodeChanged(); 25375 } 25376 25377 function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) { 25378 var position, targetWidth, targetHeight, e, rect; 25379 25380 hideResizeRect(); 25381 unbindResizeHandleEvents(); 25382 25383 // Get position and size of target 25384 position = dom.getPos(targetElm, rootElement); 25385 selectedElmX = position.x; 25386 selectedElmY = position.y; 25387 rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption 25388 targetWidth = rect.width || (rect.right - rect.left); 25389 targetHeight = rect.height || (rect.bottom - rect.top); 25390 25391 // Reset width/height if user selects a new image/table 25392 if (selectedElm != targetElm) { 25393 detachResizeStartListener(); 25394 selectedElm = targetElm; 25395 width = height = 0; 25396 } 25397 25398 // Makes it possible to disable resizing 25399 e = editor.fire('ObjectSelected', {target: targetElm}); 25400 25401 if (isResizable(targetElm) && !e.isDefaultPrevented()) { 25402 each(resizeHandles, function(handle, name) { 25403 var handleElm; 25404 25405 function startDrag(e) { 25406 startX = e.screenX; 25407 startY = e.screenY; 25408 startW = selectedElm.clientWidth; 25409 startH = selectedElm.clientHeight; 25410 ratio = startH / startW; 25411 selectedHandle = handle; 25412 25413 handle.startPos = { 25414 x: targetWidth * handle[0] + selectedElmX, 25415 y: targetHeight * handle[1] + selectedElmY 25416 }; 25417 25418 startScrollWidth = rootElement.scrollWidth; 25419 startScrollHeight = rootElement.scrollHeight; 25420 25421 selectedElmGhost = selectedElm.cloneNode(true); 25422 dom.addClass(selectedElmGhost, 'mce-clonedresizable'); 25423 dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all'); 25424 selectedElmGhost.contentEditable = false; // Hides IE move layer cursor 25425 selectedElmGhost.unSelectabe = true; 25426 dom.setStyles(selectedElmGhost, { 25427 left: selectedElmX, 25428 top: selectedElmY, 25429 margin: 0 25430 }); 25431 25432 selectedElmGhost.removeAttribute('data-mce-selected'); 25433 rootElement.appendChild(selectedElmGhost); 25434 25435 dom.bind(editableDoc, 'mousemove', resizeGhostElement); 25436 dom.bind(editableDoc, 'mouseup', endGhostResize); 25437 25438 if (rootDocument != editableDoc) { 25439 dom.bind(rootDocument, 'mousemove', resizeGhostElement); 25440 dom.bind(rootDocument, 'mouseup', endGhostResize); 25441 } 25442 25443 resizeHelper = dom.add(rootElement, 'div', { 25444 'class': 'mce-resize-helper', 25445 'data-mce-bogus': 'all' 25446 }, startW + ' × ' + startH); 25447 } 25448 25449 if (mouseDownHandleName) { 25450 // Drag started by IE native resizestart 25451 if (name == mouseDownHandleName) { 25452 startDrag(mouseDownEvent); 25453 } 25454 25455 return; 25456 } 25457 25458 // Get existing or render resize handle 25459 handleElm = dom.get('mceResizeHandle' + name); 25460 if (handleElm) { 25461 dom.remove(handleElm); 25462 } 25463 25464 handleElm = dom.add(rootElement, 'div', { 25465 id: 'mceResizeHandle' + name, 25466 'data-mce-bogus': 'all', 25467 'class': 'mce-resizehandle', 25468 unselectable: true, 25469 style: 'cursor:' + name + '-resize; margin:0; padding:0' 25470 }); 25471 25472 // Hides IE move layer cursor 25473 // If we set it on Chrome we get this wounderful bug: #6725 25474 if (Env.ie) { 25475 handleElm.contentEditable = false;
25476 } 25477 25478 dom.bind(handleElm, 'mousedown', function(e) { 25479 e.stopImmediatePropagation(); 25480 e.preventDefault(); 25481 startDrag(e); 25482 }); 25483 25484 handle.elm = handleElm; 25485 25486 // Position element 25487 dom.setStyles(handleElm, { 25488 left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2), 25489 top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2) 25490 }); 25491 }); 25492 } else { 25493 hideResizeRect(); 25494 } 25495 25496 selectedElm.setAttribute('data-mce-selected', '1'); 25497 } 25498 25499 function hideResizeRect() { 25500 var name, handleElm; 25501 25502 unbindResizeHandleEvents(); 25503 25504 if (selectedElm) { 25505 selectedElm.removeAttribute('data-mce-selected'); 25506 } 25507 25508 for (name in resizeHandles) { 25509 handleElm = dom.get('mceResizeHandle' + name); 25510 if (handleElm) { 25511 dom.unbind(handleElm); 25512 dom.remove(handleElm); 25513 } 25514 } 25515 } 25516 25517 function updateResizeRect(e) { 25518 var startElm, controlElm; 25519 25520 function isChildOrEqual(node, parent) { 25521 if (node) { 25522 do { 25523 if (node === parent) { 25524 return true; 25525 } 25526 } while ((node = node.parentNode)); 25527 } 25528 } 25529 25530 // Ignore all events while resizing or if the editor instance was removed 25531 if (resizeStarted || editor.removed) { 25532 return; 25533 } 25534 25535 // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v 25536 each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) { 25537 img.removeAttribute('data-mce-selected'); 25538 }); 25539 25540 controlElm = e.type == 'mousedown' ? e.target : selection.getNode(); 25541 controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0]; 25542 25543 if (isChildOrEqual(controlElm, rootElement)) { 25544 disableGeckoResize(); 25545 startElm = selection.getStart(true); 25546 25547 if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) { 25548 if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) { 25549 showResizeRect(controlElm); 25550 return; 25551 } 25552 } 25553 } 25554 25555 hideResizeRect(); 25556 } 25557 25558 function attachEvent(elm, name, func) { 25559 if (elm && elm.attachEvent) { 25560 elm.attachEvent('on' + name, func); 25561 } 25562 } 25563 25564 function detachEvent(elm, name, func) { 25565 if (elm && elm.detachEvent) { 25566 elm.detachEvent('on' + name, func); 25567 } 25568 } 25569 25570 function resizeNativeStart(e) { 25571 var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY; 25572 25573 pos = target.getBoundingClientRect(); 25574 relativeX = lastMouseDownEvent.clientX - pos.left; 25575 relativeY = lastMouseDownEvent.clientY - pos.top; 25576 25577 // Figure out what corner we are draging on 25578 for (name in resizeHandles) { 25579 corner = resizeHandles[name]; 25580 25581 cornerX = target.offsetWidth * corner[0]; 25582 cornerY = target.offsetHeight * corner[1]; 25583 25584 if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) { 25585 selectedHandle = corner; 25586 break; 25587 } 25588 } 25589 25590 // Remove native selection and let the magic begin 25591 resizeStarted = true; 25592 editor.fire('ObjectResizeStart', { 25593 target: selectedElm, 25594 width: selectedElm.clientWidth, 25595 height: selectedElm.clientHeight 25596 }); 25597 editor.getDoc().selection.empty(); 25598 showResizeRect(target, name, lastMouseDownEvent); 25599 } 25600 25601 function preventDefault(e) { 25602 if (e.preventDefault) { 25603 e.preventDefault(); 25604 } else { 25605 e.returnValue = false; // IE 25606 } 25607 } 25608 25609 function nativeControlSelect(e) { 25610 var target = e.srcElement; 25611 25612 if (isContentEditableFalse(target)) { 25613 preventDefault(e); 25614 return; 25615 } 25616 25617 if (target != selectedElm) { 25618 editor.fire('ObjectSelected', {target: target}); 25619 detachResizeStartListener(); 25620 25621 if (target.id.indexOf('mceResizeHandle') === 0) { 25622 e.returnValue = false; 25623 return; 25624 } 25625 25626 if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') { 25627 hideResizeRect(); 25628 selectedElm = target; 25629 attachEvent(target, 'resizestart', resizeNativeStart); 25630 } 25631 } 25632 } 25633 25634 function detachResizeStartListener() { 25635 detachEvent(selectedElm, 'resizestart', resizeNativeStart); 25636 } 25637 25638 function unbindResizeHandleEvents() { 25639 for (var name in resizeHandles) { 25640 var handle = resizeHandles[name]; 25641 25642 if (handle.elm) { 25643 dom.unbind(handle.elm); 25644 delete handle.elm; 25645 } 25646 } 25647 } 25648 25649 function disableGeckoResize() { 25650 try { 25651 // Disable object resizing on Gecko 25652 editor.getDoc().execCommand('enableObjectResizing', false, false); 25653 } catch (ex) { 25654 // Ignore 25655 } 25656 } 25657 25658 function controlSelect(elm) { 25659 var ctrlRng; 25660 25661 if (!isIE) { 25662 return; 25663 } 25664 25665 ctrlRng = editableDoc.body.createControlRange(); 25666 25667 try { 25668 ctrlRng.addElement(elm); 25669 ctrlRng.select(); 25670 return true; 25671 } catch (ex) { 25672 // Ignore since the element can't be control selected for example a P tag 25673 } 25674 } 25675 25676 editor.on('init', function() { 25677 if (isIE) { 25678 // Hide the resize rect on resize and reselect the image
25679 editor.on('ObjectResized', function(e) { 25680 if (e.target.nodeName != 'TABLE') { 25681 hideResizeRect(); 25682 controlSelect(e.target); 25683 } 25684 }); 25685 25686 attachEvent(rootElement, 'controlselect', nativeControlSelect); 25687 25688 editor.on('mousedown', function(e) { 25689 lastMouseDownEvent = e; 25690 }); 25691 } else { 25692 disableGeckoResize(); 25693 25694 // Sniff sniff, hard to feature detect this stuff 25695 if (Env.ie >= 11) { 25696 // Needs to be mousedown for drag/drop to work on IE 11 25697 // Needs to be click on Edge to properly select images 25698 editor.on('mousedown click', function(e) { 25699 var nodeName = e.target.nodeName; 25700 25701 if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName)) { 25702 editor.selection.select(e.target, nodeName == 'TABLE'); 25703 25704 // Only fire once since nodeChange is expensive 25705 if (e.type == 'mousedown') { 25706 editor.nodeChanged(); 25707 } 25708 } 25709 }); 25710 25711 editor.dom.bind(rootElement, 'mscontrolselect', function(e) { 25712 function delayedSelect(node) { 25713 Delay.setEditorTimeout(editor, function() { 25714 editor.selection.select(node); 25715 }); 25716 } 25717 25718 if (isContentEditableFalse(e.target)) { 25719 e.preventDefault(); 25720 delayedSelect(e.target); 25721 return; 25722 } 25723 25724 if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) { 25725 e.preventDefault(); 25726 25727 // This moves the selection from being a control selection to a text like selection like in WebKit #6753 25728 // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections. 25729 if (e.target.tagName == 'IMG') { 25730 delayedSelect(e.target); 25731 } 25732 } 25733 }); 25734 } 25735 } 25736 25737 var throttledUpdateResizeRect = Delay.throttle(updateResizeRect); 25738 25739 editor.on('nodechange ResizeEditor ResizeWindow drop', throttledUpdateResizeRect); 25740 25741 // Update resize rect while typing in a table 25742 editor.on('keydown keyup', function(e) { 25743 if (selectedElm && selectedElm.nodeName == "TABLE") { 25744 throttledUpdateResizeRect(e); 25745 } 25746 }); 25747 25748 editor.on('hide blur', hideResizeRect); 25749 25750 // Hide rect on focusout since it would float on top of windows otherwise 25751 //editor.on('focusout', hideResizeRect); 25752 }); 25753 25754 editor.on('remove', unbindResizeHandleEvents); 25755 25756 function destroy() { 25757 selectedElm = selectedElmGhost = null; 25758 25759 if (isIE) { 25760 detachResizeStartListener(); 25761 detachEvent(rootElement, 'controlselect', nativeControlSelect); 25762 } 25763 } 25764 25765 return { 25766 isResizable: isResizable, 25767 showResizeRect: showResizeRect, 25768 hideResizeRect: hideResizeRect, 25769 updateResizeRect: updateResizeRect, 25770 controlSelect: controlSelect, 25771 destroy: destroy 25772 }; 25773 }; 25774}); 25775 25776// Included from: js/tinymce/classes/util/Fun.js 25777 25778/** 25779 * Fun.js 25780 * 25781 * Released under LGPL License. 25782 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 25783 * 25784 * License: http://www.tinymce.com/license 25785 * Contributing: http://www.tinymce.com/contributing 25786 */ 25787 25788/** 25789 * Functional utility class. 25790 * 25791 * @private 25792 * @class tinymce.util.Fun 25793 */ 25794define("tinymce/util/Fun", [], function() { 25795 var slice = [].slice; 25796 25797 function constant(value) { 25798 return function() { 25799 return value; 25800 }; 25801 } 25802 25803 function negate(predicate) { 25804 return function(x) { 25805 return !predicate(x); 25806 }; 25807 } 25808 25809 function compose(f, g) { 25810 return function(x) { 25811 return f(g(x)); 25812 }; 25813 } 25814 25815 function or() { 25816 var args = slice.call(arguments); 25817 25818 return function(x) { 25819 for (var i = 0; i < args.length; i++) { 25820 if (args[i](x)) { 25821 return true; 25822 } 25823 } 25824 25825 return false; 25826 }; 25827 } 25828 25829 function and() { 25830 var args = slice.call(arguments); 25831 25832 return function(x) { 25833 for (var i = 0; i < args.length; i++) { 25834 if (!args[i](x)) { 25835 return false; 25836 } 25837 } 25838 25839 return true; 25840 }; 25841 } 25842 25843 function curry(fn) { 25844 var args = slice.call(arguments); 25845 25846 if (args.length - 1 >= fn.length) { 25847 return fn.apply(this, args.slice(1)); 25848 } 25849 25850 return function() { 25851 var tempArgs = args.concat([].slice.call(arguments)); 25852 return curry.apply(this, tempArgs); 25853 }; 25854 } 25855 25856 return { 25857 constant: constant, 25858 negate: negate, 25859 and: and, 25860 or: or, 25861 curry: curry, 25862 compose: compose 25863 }; 25864}); 25865 25866// Included from: js/tinymce/classes/caret/CaretCandidate.js 25867 25868/** 25869 * CaretCandidate.js 25870 * 25871 * Released under LGPL License. 25872 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 25873 * 25874 * License: http://www.tinymce.com/license 25875 * Contributing: http://www.tinymce.com/contributing 25876 */ 25877 25878/** 25879 * This module contains logic for handling caret candidates. A caret candidate is 25880 * for example text nodes, images, input elements, cE=false elements etc. 25881 * 25882 * @private 25883 * @class tinymce.caret.CaretCandidate 25884 */ 25885define("tinymce/caret/CaretCandidate", [ 25886 "tinymce/dom/NodeType", 25887 "tinymce/util/Arr", 25888 "tinymce/caret/CaretContainer" 25889], function(NodeType, Arr, CaretContainer) { 25890 var isContentEditableTrue = NodeType.isContentEditableTrue, 25891 isContentEditableFalse = NodeType.isContentEditableFalse, 25892 isBr = NodeType.isBr, 25893 isText = NodeType.isText, 25894 isInvalidTextElement = NodeType.matchNodeNames('script style textarea'), 25895 isAtomicInline = NodeType.matchNodeNames('img input textarea hr iframe video audio object'), 25896 isTable = NodeType.matchNodeNames('table'), 25897 isCaretContainer = CaretContainer.isCaretContainer; 25898 25899 function isCaretCandidate(node) { 25900 if (isCaretContainer(node)) { 25901 return false;
25902 } 25903 25904 if (isText(node)) { 25905 if (isInvalidTextElement(node.parentNode)) { 25906 return false; 25907 } 25908 25909 return true; 25910 } 25911 25912 return isAtomicInline(node) || isBr(node) || isTable(node) || isContentEditableFalse(node); 25913 } 25914 25915 function isInEditable(node, rootNode) { 25916 for (node = node.parentNode; node && node != rootNode; node = node.parentNode) { 25917 if (isContentEditableFalse(node)) { 25918 return false; 25919 } 25920 25921 if (isContentEditableTrue(node)) { 25922 return true; 25923 } 25924 } 25925 25926 return true; 25927 } 25928 25929 function isAtomicContentEditableFalse(node) { 25930 if (!isContentEditableFalse(node)) { 25931 return false; 25932 } 25933 25934 return Arr.reduce(node.getElementsByTagName('*'), function(result, elm) { 25935 return result || isContentEditableTrue(elm); 25936 }, false) !== true; 25937 } 25938 25939 function isAtomic(node) { 25940 return isAtomicInline(node) || isAtomicContentEditableFalse(node); 25941 } 25942 25943 function isEditableCaretCandidate(node, rootNode) { 25944 return isCaretCandidate(node) && isInEditable(node, rootNode); 25945 } 25946 25947 return { 25948 isCaretCandidate: isCaretCandidate, 25949 isInEditable: isInEditable, 25950 isAtomic: isAtomic, 25951 isEditableCaretCandidate: isEditableCaretCandidate 25952 }; 25953}); 25954 25955// Included from: js/tinymce/classes/geom/ClientRect.js 25956 25957/** 25958 * ClientRect.js 25959 * 25960 * Released under LGPL License. 25961 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 25962 * 25963 * License: http://www.tinymce.com/license 25964 * Contributing: http://www.tinymce.com/contributing 25965 */ 25966 25967/** 25968 * Utility functions for working with client rects. 25969 * 25970 * @private 25971 * @class tinymce.geom.ClientRect 25972 */ 25973define("tinymce/geom/ClientRect", [], function() { 25974 var round = Math.round; 25975 25976 function clone(rect) { 25977 if (!rect) { 25978 return {left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0}; 25979 } 25980 25981 return { 25982 left: round(rect.left), 25983 top: round(rect.top), 25984 bottom: round(rect.bottom), 25985 right: round(rect.right),
25986 width: round(rect.width), 25987 height: round(rect.height) 25988 }; 25989 } 25990 25991 function collapse(clientRect, toStart) { 25992 clientRect = clone(clientRect); 25993 25994 if (toStart) { 25995 clientRect.right = clientRect.left; 25996 } else { 25997 clientRect.left = clientRect.left + clientRect.width; 25998 clientRect.right = clientRect.left; 25999 } 26000 26001 clientRect.width = 0; 26002 26003 return clientRect; 26004 } 26005 26006 function isEqual(rect1, rect2) { 26007 return ( 26008 rect1.left === rect2.left && 26009 rect1.top === rect2.top && 26010 rect1.bottom === rect2.bottom && 26011 rect1.right === rect2.right 26012 ); 26013 } 26014 26015 function isValidOverflow(overflowY, clientRect1, clientRect2) { 26016 return overflowY >= 0 && overflowY <= Math.min(clientRect1.height, clientRect2.height) / 2; 26017 26018 } 26019 26020 function isAbove(clientRect1, clientRect2) { 26021 if (clientRect1.bottom < clientRect2.top) { 26022 return true; 26023 } 26024 26025 if (clientRect1.top > clientRect2.bottom) { 26026 return false; 26027 } 26028 26029 return isValidOverflow(clientRect2.top - clientRect1.bottom, clientRect1, clientRect2); 26030 } 26031 26032 function isBelow(clientRect1, clientRect2) { 26033 if (clientRect1.top > clientRect2.bottom) { 26034 return true; 26035 } 26036 26037 if (clientRect1.bottom < clientRect2.top) { 26038 return false; 26039 } 26040 26041 return isValidOverflow(clientRect2.bottom - clientRect1.top, clientRect1, clientRect2); 26042 } 26043 26044 function isLeft(clientRect1, clientRect2) { 26045 return clientRect1.left < clientRect2.left; 26046 } 26047 26048 function isRight(clientRect1, clientRect2) { 26049 return clientRect1.right > clientRect2.right; 26050 } 26051 26052 function compare(clientRect1, clientRect2) { 26053 if (isAbove(clientRect1, clientRect2)) { 26054 return -1; 26055 } 26056 26057 if (isBelow(clientRect1, clientRect2)) { 26058 return 1; 26059 } 26060 26061 if (isLeft(clientRect1, clientRect2)) { 26062 return -1; 26063 } 26064 26065 if (isRight(clientRect1, clientRect2)) { 26066 return 1; 26067 } 26068 26069 return 0; 26070 } 26071 26072 return { 26073 clone: clone, 26074 collapse: collapse, 26075 isEqual: isEqual, 26076 isAbove: isAbove, 26077 isBelow: isBelow, 26078 isLeft: isLeft, 26079 isRight: isRight, 26080 compare: compare 26081 }; 26082}); 26083 26084// Included from: js/tinymce/classes/text/ExtendingChar.js 26085 26086/** 26087 * ExtendingChar.js 26088 * 26089 * Released under LGPL License. 26090 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 26091 * 26092 * License: http://www.tinymce.com/license 26093 * Contributing: http://www.tinymce.com/contributing 26094 */ 26095 26096/** 26097 * This class contains logic for detecting extending characters. 26098 * 26099 * @private 26100 * @class tinymce.text.ExtendingChar 26101 * @example 26102 * var isExtending = ExtendingChar.isExtendingChar('a'); 26103 */ 26104define("tinymce/text/ExtendingChar", [], function() { 26105 // Generated from: http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt 26106 // Only includes the characters in that fit into UCS-2 16 bit 26107 var extendingChars = new RegExp( 26108 "[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A" + 26109 "\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0" + 26110 "\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C" + 26111 "\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09BE\u09C1-\u09C4\u09CD\u09D7\u09E2-\u09E3" + 26112 "\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC" + 26113 "\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3E\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B57" + 26114 "\u0B62-\u0B63\u0B82\u0BBE\u0BC0\u0BCD\u0BD7\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56" + 26115 "\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC2\u0CC6\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0D01\u0D3E\u0D41-\u0D44" + 26116 "\u0D4D\u0D57\u0D62-\u0D63\u0DCA\u0DCF\u0DD2-\u0DD4\u0DD6\u0DDF\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9" + 26117 "\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97" + 26118 "\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074" + 26119 "\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5" + 26120 "\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18" + 26121 "\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABE\u1B00-\u1B03\u1B34" + 26122 "\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9" + 26123 "\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9" + 26124 "\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C-\u200D\u20D0-\u20DC\u20DD-\u20E0\u20E1\u20E2-\u20E4\u20E5-\u20F0\u2CEF-\u2CF1" + 26125 "\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u302E-\u302F\u3099-\u309A\uA66F\uA670-\uA672\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1" + 26126 "\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC" + 26127 "\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1" + 26128 "\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFF9E-\uFF9F]" 26129 ); 26130 26131 function isExtendingChar(ch) {
26132 return typeof ch == "string" && ch.charCodeAt(0) >= 768 && extendingChars.test(ch); 26133 } 26134 26135 return { 26136 isExtendingChar: isExtendingChar 26137 }; 26138}); 26139 26140// Included from: js/tinymce/classes/caret/CaretPosition.js 26141 26142/** 26143 * CaretPosition.js 26144 * 26145 * Released under LGPL License. 26146 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 26147 * 26148 * License: http://www.tinymce.com/license 26149 * Contributing: http://www.tinymce.com/contributing 26150 */ 26151 26152/** 26153 * This module contains logic for creating caret positions within a document a caretposition 26154 * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight 26155 * since it's now updated live when the DOM changes. 26156 * 26157 * @private 26158 * @class tinymce.caret.CaretPosition 26159 * @example 26160 * var caretPos1 = new CaretPosition(container, offset); 26161 * var caretPos2 = CaretPosition.fromRangeStart(someRange); 26162 */ 26163define("tinymce/caret/CaretPosition", [ 26164 "tinymce/util/Fun", 26165 "tinymce/dom/NodeType", 26166 "tinymce/dom/DOMUtils", 26167 "tinymce/dom/RangeUtils", 26168 "tinymce/caret/CaretCandidate", 26169 "tinymce/geom/ClientRect", 26170 "tinymce/text/ExtendingChar" 26171], function(Fun, NodeType, DOMUtils, RangeUtils, CaretCandidate, ClientRect, ExtendingChar) { 26172 var isElement = NodeType.isElement, 26173 isCaretCandidate = CaretCandidate.isCaretCandidate, 26174 isBlock = NodeType.matchStyleValues('display', 'block table'), 26175 isFloated = NodeType.matchStyleValues('float', 'left right'), 26176 isValidElementCaretCandidate = Fun.and(isElement, isCaretCandidate, Fun.negate(isFloated)), 26177 isNotPre = Fun.negate(NodeType.matchStyleValues('white-space', 'pre pre-line pre-wrap')), 26178 isText = NodeType.isText, 26179 isBr = NodeType.isBr, 26180 nodeIndex = DOMUtils.nodeIndex, 26181 resolveIndex = RangeUtils.getNode; 26182 26183 function isWhiteSpace(chr) { 26184 return chr && /[\r\n\t ]/.test(chr); 26185 } 26186 26187 function isHiddenWhiteSpaceRange(range) { 26188 var container = range.startContainer, 26189 offset = range.startOffset, 26190 text; 26191 26192 if (isWhiteSpace(range.toString()) && isNotPre(container.parentNode)) { 26193 text = container.data; 26194 26195 if (isWhiteSpace(text[offset - 1]) || isWhiteSpace(text[offset + 1])) { 26196 return true; 26197 } 26198 } 26199 26200 return false; 26201 } 26202 26203 function getCaretPositionClientRects(caretPosition) { 26204 var clientRects = [], beforeNode, node; 26205 26206 // Hack for older WebKit versions that doesn't 26207 // support getBoundingClientRect on BR elements 26208 function getBrClientRect(brNode) { 26209 var doc = brNode.ownerDocument, 26210 rng = doc.createRange(), 26211 nbsp = doc.createTextNode('\u00a0'), 26212 parentNode = brNode.parentNode, 26213 clientRect; 26214 26215 parentNode.insertBefore(nbsp, brNode); 26216 rng.setStart(nbsp, 0); 26217 rng.setEnd(nbsp, 1); 26218 clientRect = ClientRect.clone(rng.getBoundingClientRect()); 26219 parentNode.removeChild(nbsp); 26220 26221 return clientRect; 26222 } 26223 26224 function getBoundingClientRect(item) { 26225 var clientRect, clientRects; 26226 26227 clientRects = item.getClientRects(); 26228 if (clientRects.length > 0) { 26229 clientRect = ClientRect.clone(clientRects[0]); 26230 } else { 26231 clientRect = ClientRect.clone(item.getBoundingClientRect()); 26232 } 26233 26234 if (isBr(item) && clientRect.left === 0) { 26235 return getBrClientRect(item); 26236 } 26237 26238 return clientRect; 26239 } 26240 26241 function collapseAndInflateWidth(clientRect, toStart) { 26242 clientRect = ClientRect.collapse(clientRect, toStart); 26243 clientRect.width = 1; 26244 clientRect.right = clientRect.left + 1; 26245 26246 return clientRect; 26247 } 26248 26249 function addUniqueAndValidRect(clientRect) { 26250 if (clientRect.height === 0) { 26251 return; 26252 } 26253 26254 if (clientRects.length > 0) { 26255 if (ClientRect.isEqual(clientRect, clientRects[clientRects.length - 1])) { 26256 return; 26257 } 26258 } 26259 26260 clientRects.push(clientRect); 26261 } 26262 26263 function addCharacterOffset(container, offset) { 26264 var range = container.ownerDocument.createRange(); 26265 26266 if (offset < container.data.length) { 26267 if (ExtendingChar.isExtendingChar(container.data[offset])) { 26268 return clientRects; 26269 } 26270 } 26271 26272 if (offset > 0) { 26273 range.setStart(container, offset - 1); 26274 range.setEnd(container, offset); 26275 26276 if (!isHiddenWhiteSpaceRange(range)) { 26277 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false)); 26278 } 26279 } 26280 26281 if (offset < container.data.length) { 26282 range.setStart(container, offset); 26283 range.setEnd(container, offset + 1); 26284 26285 if (!isHiddenWhiteSpaceRange(range)) { 26286 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), true)); 26287 } 26288 } 26289 } 26290 26291 if (isText(caretPosition.container())) { 26292 addCharacterOffset(caretPosition.container(), caretPosition.offset()); 26293 return clientRects; 26294 } 26295 26296 if (isElement(caretPosition.container())) {
26297 if (caretPosition.isAtEnd()) { 26298 node = resolveIndex(caretPosition.container(), caretPosition.offset()); 26299 if (isText(node)) { 26300 addCharacterOffset(node, node.data.length); 26301 } 26302 26303 if (isValidElementCaretCandidate(node) && !isBr(node)) { 26304 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false)); 26305 } 26306 } else { 26307 node = resolveIndex(caretPosition.container(), caretPosition.offset()); 26308 if (isText(node)) { 26309 addCharacterOffset(node, 0); 26310 } 26311 26312 if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) { 26313 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false)); 26314 return clientRects; 26315 } 26316 26317 beforeNode = resolveIndex(caretPosition.container(), caretPosition.offset() - 1); 26318 if (isValidElementCaretCandidate(beforeNode) && !isBr(beforeNode)) { 26319 if (isBlock(beforeNode) || isBlock(node) || !isValidElementCaretCandidate(node)) { 26320 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(beforeNode), false)); 26321 } 26322 } 26323 26324 if (isValidElementCaretCandidate(node)) { 26325 addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), true)); 26326 } 26327 } 26328 } 26329 26330 return clientRects; 26331 } 26332 26333 /** 26334 * Represents a location within the document by a container and an offset. 26335 * 26336 * @constructor 26337 * @param {Node} container Container node. 26338 * @param {Number} offset Offset within that container node. 26339 * @param {Array} clientRects Optional client rects array for the position. 26340 */ 26341 function CaretPosition(container, offset, clientRects) { 26342 function isAtStart() { 26343 if (isText(container)) { 26344 return offset === 0; 26345 } 26346 26347 return offset === 0; 26348 } 26349 26350 function isAtEnd() { 26351 if (isText(container)) { 26352 return offset >= container.data.length; 26353 } 26354 26355 return offset >= container.childNodes.length; 26356 } 26357 26358 function toRange() { 26359 var range; 26360 26361 range = container.ownerDocument.createRange(); 26362 range.setStart(container, offset); 26363 range.setEnd(container, offset); 26364 26365 return range; 26366 } 26367 26368 function getClientRects() { 26369 if (!clientRects) { 26370 clientRects = getCaretPositionClientRects(new CaretPosition(container, offset)); 26371 } 26372 26373 return clientRects; 26374 } 26375 26376 function isVisible() { 26377 return getClientRects().length > 0; 26378 } 26379 26380 function isEqual(caretPosition) { 26381 return caretPosition && container === caretPosition.container() && offset === caretPosition.offset(); 26382 } 26383 26384 function getNode(before) { 26385 return resolveIndex(container, before ? offset - 1 : offset); 26386 } 26387 26388 return { 26389 /** 26390 * Returns the container node. 26391 * 26392 * @method container 26393 * @return {Node} Container node. 26394 */ 26395 container: Fun.constant(container), 26396 26397 /** 26398 * Returns the offset within the container node. 26399 * 26400 * @method offset 26401 * @return {Number} Offset within the container node. 26402 */ 26403 offset: Fun.constant(offset), 26404 26405 /** 26406 * Returns a range out of a the caret position. 26407 * 26408 * @method toRange 26409 * @return {DOMRange} range for the caret position. 26410 */ 26411 toRange: toRange, 26412 26413 /** 26414 * Returns the client rects for the caret position. Might be multiple rects between 26415 * block elements. 26416 * 26417 * @method getClientRects 26418 * @return {Array} Array of client rects. 26419 */ 26420 getClientRects: getClientRects, 26421 26422 /** 26423 * Returns true if the caret location is visible/displayed on screen. 26424 * 26425 * @method isVisible 26426 * @return {Boolean} true/false if the position is visible or not. 26427 */ 26428 isVisible: isVisible, 26429 26430 /** 26431 * Returns true if the caret location is at the beginning of text node or container. 26432 * 26433 * @method isVisible 26434 * @return {Boolean} true/false if the position is at the beginning. 26435 */ 26436 isAtStart: isAtStart, 26437 26438 /** 26439 * Returns true if the caret location is at the end of text node or container. 26440 * 26441 * @method isVisible 26442 * @return {Boolean} true/false if the position is at the end. 26443 */ 26444 isAtEnd: isAtEnd, 26445 26446 /** 26447 * Compares the caret position to another caret position. This will only compare the 26448 * container and offset not it's visual position. 26449 * 26450 * @method isEqual 26451 * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with. 26452 * @return {Boolean} true if the caret positions are equal. 26453 */ 26454 isEqual: isEqual, 26455 26456 /** 26457 * Returns the closest resolved node from a node index. That means if you have an offset after the 26458 * last node in a container it will return that last node. 26459 * 26460 * @method getNode 26461 * @return {Node} Node that is closest to the index. 26462 */ 26463 getNode: getNode 26464 }; 26465 } 26466 26467 /** 26468 * Creates a caret position from the start of a range. 26469 * 26470 * @method fromRangeStart 26471 * @param {DOMRange} range DOM Range to create caret position from. 26472 * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range. 26473 */ 26474 CaretPosition.fromRangeStart = function(range) { 26475 return new CaretPosition(range.startContainer, range.startOffset); 26476 }; 26477 26478 /** 26479 * Creates a caret position from the end of a range. 26480 * 26481 * @method fromRangeEnd 26482 * @param {DOMRange} range DOM Range to create caret position from. 26483 * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range. 26484 */ 26485 CaretPosition.fromRangeEnd = function(range) { 26486 return new CaretPosition(range.endContainer, range.endOffset); 26487 }; 26488 26489 /** 26490 * Creates a caret position from a node and places the offset after it. 26491 * 26492 * @method after 26493 * @param {Node} node Node to get caret position from. 26494 * @return {tinymce.caret.CaretPosition} Caret position from the node. 26495 */ 26496 CaretPosition.after = function(node) { 26497 return new CaretPosition(node.parentNode, nodeIndex(node) + 1); 26498 }; 26499 26500 /** 26501 * Creates a caret position from a node and places the offset before it. 26502 * 26503 * @method before 26504 * @param {Node} node Node to get caret position from. 26505 * @return {tinymce.caret.CaretPosition} Caret position from the node. 26506 */ 26507 CaretPosition.before = function(node) { 26508 return new CaretPosition(node.parentNode, nodeIndex(node)); 26509 }; 26510 26511 return CaretPosition; 26512}); 26513 26514// Included from: js/tinymce/classes/caret/CaretBookmark.js 26515 26516/** 26517 * CaretBookmark.js 26518 * 26519 * Released under LGPL License. 26520 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 26521 * 26522 * License: http://www.tinymce.com/license 26523 * Contributing: http://www.tinymce.com/contributing 26524 */ 26525 26526/** 26527 * This module creates or resolves xpath like string representation of a CaretPositions. 26528 * 26529 * The format is a / separated list of chunks with: 26530 * <element|text()>[index|after|before] 26531 * 26532 * For example: 26533 * p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p> 26534 * p[0]/img[0],before = <p>|<img></p> 26535 * p[0]/img[0],after = <p><img>|</p> 26536 * 26537 * @private
26538 * @static 26539 * @class tinymce.caret.CaretBookmark 26540 * @example 26541 * var bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild)); 26542 * var caretPosition = CaretBookmark.resolve(bookmark); 26543 */ 26544define('tinymce/caret/CaretBookmark', [ 26545 'tinymce/dom/NodeType', 26546 'tinymce/dom/DOMUtils', 26547 'tinymce/util/Fun', 26548 'tinymce/util/Arr', 26549 'tinymce/caret/CaretPosition' 26550], function(NodeType, DomUtils, Fun, Arr, CaretPosition) { 26551 var isText = NodeType.isText, 26552 isBogus = NodeType.isBogus, 26553 nodeIndex = DomUtils.nodeIndex; 26554 26555 function normalizedParent(node) { 26556 var parentNode = node.parentNode; 26557 26558 if (isBogus(parentNode)) { 26559 return normalizedParent(parentNode); 26560 } 26561 26562 return parentNode; 26563 } 26564 26565 function getChildNodes(node) { 26566 if (!node) { 26567 return []; 26568 } 26569 26570 return Arr.reduce(node.childNodes, function(result, node) { 26571 if (isBogus(node) && node.nodeName != 'BR') { 26572 result = result.concat(getChildNodes(node)); 26573 } else { 26574 result.push(node); 26575 } 26576 26577 return result; 26578 }, []); 26579 } 26580 26581 function normalizedTextOffset(textNode, offset) { 26582 while ((textNode = textNode.previousSibling)) { 26583 if (!isText(textNode)) { 26584 break; 26585 } 26586 26587 offset += textNode.data.length; 26588 } 26589 26590 return offset; 26591 } 26592 26593 function equal(targetValue) { 26594 return function(value) { 26595 return targetValue === value; 26596 }; 26597 } 26598 26599 function normalizedNodeIndex(node) { 26600 var nodes, index, numTextFragments; 26601 26602 nodes = getChildNodes(normalizedParent(node)); 26603 index = Arr.findIndex(nodes, equal(node), node); 26604 nodes = nodes.slice(0, index + 1); 26605 numTextFragments = Arr.reduce(nodes, function(result, node, i) { 26606 if (isText(node) && isText(nodes[i - 1])) { 26607 result++; 26608 } 26609 26610 return result; 26611 }, 0); 26612 26613 nodes = Arr.filter(nodes, NodeType.matchNodeNames(node.nodeName)); 26614 index = Arr.findIndex(nodes, equal(node), node); 26615 26616 return index - numTextFragments; 26617 } 26618 26619 function createPathItem(node) { 26620 var name; 26621 26622 if (isText(node)) { 26623 name = 'text()'; 26624 } else { 26625 name = node.nodeName.toLowerCase(); 26626 } 26627 26628 return name + '[' + normalizedNodeIndex(node) + ']'; 26629 } 26630 26631 function parentsUntil(rootNode, node, predicate) { 26632 var parents = []; 26633 26634 for (node = node.parentNode; node != rootNode; node = node.parentNode) { 26635 if (predicate && predicate(node)) { 26636 break; 26637 } 26638 26639 parents.push(node); 26640 } 26641 26642 return parents; 26643 } 26644 26645 function create(rootNode, caretPosition) { 26646 var container, offset, path = [], 26647 outputOffset, childNodes, parents; 26648 26649 container = caretPosition.container(); 26650 offset = caretPosition.offset(); 26651 26652 if (isText(container)) { 26653 outputOffset = normalizedTextOffset(container, offset); 26654 } else { 26655 childNodes = container.childNodes; 26656 if (offset >= childNodes.length) { 26657 outputOffset = 'after'; 26658 offset = childNodes.length - 1; 26659 } else { 26660 outputOffset = 'before'; 26661 } 26662 26663 container = childNodes[offset]; 26664 } 26665 26666 path.push(createPathItem(container)); 26667 parents = parentsUntil(rootNode, container); 26668 parents = Arr.filter(parents, Fun.negate(NodeType.isBogus)); 26669 path = path.concat(Arr.map(parents, function(node) { 26670 return createPathItem(node); 26671 })); 26672 26673 return path.reverse().join('/') + ',' + outputOffset; 26674 } 26675 26676 function resolvePathItem(node, name, index) { 26677 var nodes = getChildNodes(node); 26678 26679 nodes = Arr.filter(nodes, function(node, index) { 26680 return !isText(node) || !isText(nodes[index - 1]); 26681 }); 26682 26683 nodes = Arr.filter(nodes, NodeType.matchNodeNames(name)); 26684 return nodes[index]; 26685 } 26686 26687 function findTextPosition(container, offset) { 26688 var node = container, targetOffset = 0, dataLen; 26689 26690 while (isText(node)) { 26691 dataLen = node.data.length; 26692 26693 if (offset >= targetOffset && offset <= targetOffset + dataLen) { 26694 container = node; 26695 offset = offset - targetOffset; 26696 break; 26697 } 26698 26699 if (!isText(node.nextSibling)) { 26700 container = node; 26701 offset = dataLen; 26702 break; 26703 } 26704 26705 targetOffset += dataLen; 26706 node = node.nextSibling; 26707 } 26708 26709 if (offset > container.data.length) { 26710 offset = container.data.length; 26711 } 26712 26713 return new CaretPosition(container, offset); 26714 } 26715 26716 function resolve(rootNode, path) { 26717 var parts, container, offset; 26718 26719 if (!path) { 26720 return null; 26721 } 26722 26723 parts = path.split(','); 26724 path = parts[0].split('/'); 26725 offset = parts.length > 1 ? parts[1] : 'before'; 26726 26727 container = Arr.reduce(path, function(result, value) { 26728 value = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value); 26729 if (!value) { 26730 return null; 26731 } 26732 26733 if (value[1] === 'text()') { 26734 value[1] = '#text'; 26735 } 26736 26737 return resolvePathItem(result, value[1], parseInt(value[2], 10)); 26738 }, rootNode); 26739 26740 if (!container) { 26741 return null; 26742 } 26743 26744 if (!isText(container)) { 26745 if (offset === 'after') { 26746 offset = nodeIndex(container) + 1; 26747 } else { 26748 offset = nodeIndex(container); 26749 } 26750 26751 return new CaretPosition(container.parentNode, offset); 26752 } 26753 26754 return findTextPosition(container, parseInt(offset, 10)); 26755 } 26756 26757 return { 26758 /** 26759 * Create a xpath bookmark location for the specified caret position. 26760 * 26761 * @method create 26762 * @param {Node} rootNode Root node to create bookmark within. 26763 * @param {tinymce.caret.CaretPosition} caretPosition Caret position within the root node. 26764 * @return {String} String xpath like location of caret position. 26765 */ 26766 create: create, 26767 26768 /** 26769 * Resolves a xpath like bookmark location to the a caret position. 26770 * 26771 * @method resolve 26772 * @param {Node} rootNode Root node to resolve xpath bookmark within. 26773 * @param {String} bookmark Bookmark string to resolve. 26774 * @return {tinymce.caret.CaretPosition} Caret position resolved from xpath like bookmark. 26775 */ 26776 resolve: resolve 26777 }; 26778}); 26779 26780// Included from: js/tinymce/classes/dom/BookmarkManager.js 26781 26782/** 26783 * BookmarkManager.js 26784 * 26785 * Released under LGPL License. 26786 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 26787 * 26788 * License: http://www.tinymce.com/license 26789 * Contributing: http://www.tinymce.com/contributing 26790 */ 26791 26792/** 26793 * This class handles selection bookmarks. 26794 * 26795 * @class tinymce.dom.BookmarkManager 26796 */
26797define("tinymce/dom/BookmarkManager", [ 26798 "tinymce/Env", 26799 "tinymce/util/Tools", 26800 "tinymce/caret/CaretContainer", 26801 "tinymce/caret/CaretBookmark", 26802 "tinymce/caret/CaretPosition", 26803 "tinymce/dom/NodeType" 26804], function(Env, Tools, CaretContainer, CaretBookmark, CaretPosition, NodeType) { 26805 var isContentEditableFalse = NodeType.isContentEditableFalse; 26806 26807 /** 26808 * Constructs a new BookmarkManager instance for a specific selection instance. 26809 * 26810 * @constructor 26811 * @method BookmarkManager 26812 * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for. 26813 */ 26814 function BookmarkManager(selection) { 26815 var dom = selection.dom; 26816 26817 /** 26818 * Returns a bookmark location for the current selection. This bookmark object 26819 * can then be used to restore the selection after some content modification to the document. 26820 * 26821 * @method getBookmark 26822 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. 26823 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. 26824 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. 26825 * @example 26826 * // Stores a bookmark of the current selection 26827 * var bm = tinymce.activeEditor.selection.getBookmark(); 26828 * 26829 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 26830 * 26831 * // Restore the selection bookmark 26832 * tinymce.activeEditor.selection.moveToBookmark(bm); 26833 */ 26834 this.getBookmark = function(type, normalized) { 26835 var rng, rng2, id, collapsed, name, element, chr = '', styles; 26836 26837 function findIndex(name, element) { 26838 var count = 0; 26839 26840 Tools.each(dom.select(name), function(node) { 26841 if (node.getAttribute('data-mce-bogus') === 'all') { 26842 return; 26843 } 26844 26845 if (node == element) { 26846 return false; 26847 } 26848 26849 count++; 26850 }); 26851 26852 return count; 26853 } 26854 26855 function normalizeTableCellSelection(rng) { 26856 function moveEndPoint(start) { 26857 var container, offset, childNodes, prefix = start ? 'start' : 'end'; 26858 26859 container = rng[prefix + 'Container']; 26860 offset = rng[prefix + 'Offset']; 26861 26862 if (container.nodeType == 1 && container.nodeName == "TR") { 26863 childNodes = container.childNodes; 26864 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; 26865 if (container) { 26866 offset = start ? 0 : container.childNodes.length; 26867 rng['set' + (start ? 'Start' : 'End')](container, offset); 26868 } 26869 } 26870 } 26871 26872 moveEndPoint(true); 26873 moveEndPoint(); 26874 26875 return rng; 26876 } 26877 26878 function getLocation(rng) { 26879 var root = dom.getRoot(), bookmark = {}; 26880 26881 function getPoint(rng, start) { 26882 var container = rng[start ? 'startContainer' : 'endContainer'], 26883 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; 26884 26885 if (container.nodeType == 3) { 26886 if (normalized) { 26887 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) { 26888 offset += node.nodeValue.length; 26889 } 26890 } 26891 26892 point.push(offset); 26893 } else { 26894 childNodes = container.childNodes; 26895 26896 if (offset >= childNodes.length && childNodes.length) { 26897 after = 1; 26898 offset = Math.max(0, childNodes.length - 1); 26899 } 26900 26901 point.push(dom.nodeIndex(childNodes[offset], normalized) + after); 26902 } 26903 26904 for (; container && container != root; container = container.parentNode) { 26905 point.push(dom.nodeIndex(container, normalized)); 26906 } 26907 26908 return point; 26909 } 26910 26911 bookmark.start = getPoint(rng, true); 26912 26913 if (!selection.isCollapsed()) { 26914 bookmark.end = getPoint(rng); 26915 } 26916 26917 return bookmark; 26918 } 26919 26920 function findAdjacentContentEditableFalseElm(rng) { 26921 function findSibling(node) { 26922 var sibling; 26923 26924 if (CaretContainer.isCaretContainer(node)) { 26925 if (NodeType.isText(node) && CaretContainer.isCaretContainerBlock(node)) { 26926 node = node.parentNode; 26927 } 26928 26929 sibling = node.previousSibling; 26930 if (isContentEditableFalse(sibling)) { 26931 return sibling; 26932 } 26933 26934 sibling = node.nextSibling; 26935 if (isContentEditableFalse(sibling)) { 26936 return sibling; 26937 } 26938 } 26939 } 26940 26941 return findSibling(rng.startContainer) || findSibling(rng.endContainer); 26942 } 26943 26944 if (type == 2) { 26945 element = selection.getNode(); 26946 name = element ? element.nodeName : null; 26947 rng = selection.getRng(); 26948 26949 if (isContentEditableFalse(element) || name == 'IMG') { 26950 return {name: name, index: findIndex(name, element)}; 26951 } 26952 26953 if (selection.tridentSel) { 26954 return selection.tridentSel.getBookmark(type); 26955 } 26956 26957 element = findAdjacentContentEditableFalseElm(rng); 26958 if (element) { 26959 name = element.tagName; 26960 return {name: name, index: findIndex(name, element)}; 26961 } 26962 26963 return getLocation(rng); 26964 } 26965 26966 if (type == 3) { 26967 rng = selection.getRng(); 26968 26969 return { 26970 start: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeStart(rng)), 26971 end: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeEnd(rng)) 26972 }; 26973 } 26974 26975 // Handle simple range 26976 if (type) { 26977 return {rng: selection.getRng()}; 26978 } 26979 26980 rng = selection.getRng(); 26981 id = dom.uniqueId(); 26982 collapsed = selection.isCollapsed(); 26983 styles = 'overflow:hidden;line-height:0px'; 26984 26985 // Explorer method 26986 if (rng.duplicate || rng.item) { 26987 // Text selection 26988 if (!rng.item) { 26989 rng2 = rng.duplicate(); 26990 26991 try { 26992 // Insert start marker 26993 rng.collapse(); 26994 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>'); 26995 26996 // Insert end marker 26997 if (!collapsed) { 26998 rng2.collapse(false); 26999 27000 // Detect the empty space after block elements in IE and move the 27001 // end back one character <p></p>] becomes <p>]</p> 27002 rng.moveToElementText(rng2.parentElement()); 27003 if (rng.compareEndPoints('StartToEnd', rng2) === 0) { 27004 rng2.move('character', -1); 27005 } 27006 27007 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>'); 27008 } 27009 } catch (ex) { 27010 // IE might throw unspecified error so lets ignore it 27011 return null; 27012 } 27013 } else { 27014 // Control selection 27015 element = rng.item(0); 27016 name = element.nodeName; 27017 27018 return {name: name, index: findIndex(name, element)}; 27019 } 27020 } else { 27021 element = selection.getNode(); 27022 name = element.nodeName; 27023 if (name == 'IMG') { 27024 return {name: name, index: findIndex(name, element)}; 27025 } 27026 27027 // W3C method 27028 rng2 = normalizeTableCellSelection(rng.cloneRange()); 27029 27030 // Insert end marker 27031 if (!collapsed) { 27032 rng2.collapse(false);
27033 rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr)); 27034 } 27035 27036 rng = normalizeTableCellSelection(rng); 27037 rng.collapse(true); 27038 rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr)); 27039 } 27040 27041 selection.moveToBookmark({id: id, keep: 1}); 27042 27043 return {id: id}; 27044 }; 27045 27046 /** 27047 * Restores the selection to the specified bookmark. 27048 * 27049 * @method moveToBookmark 27050 * @param {Object} bookmark Bookmark to restore selection from. 27051 * @return {Boolean} true/false if it was successful or not. 27052 * @example 27053 * // Stores a bookmark of the current selection 27054 * var bm = tinymce.activeEditor.selection.getBookmark(); 27055 * 27056 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 27057 * 27058 * // Restore the selection bookmark 27059 * tinymce.activeEditor.selection.moveToBookmark(bm); 27060 */ 27061 this.moveToBookmark = function(bookmark) { 27062 var rng, root, startContainer, endContainer, startOffset, endOffset; 27063 27064 function setEndPoint(start) { 27065 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; 27066 27067 if (point) { 27068 offset = point[0]; 27069 27070 // Find container node 27071 for (node = root, i = point.length - 1; i >= 1; i--) { 27072 children = node.childNodes; 27073 27074 if (point[i] > children.length - 1) { 27075 return; 27076 } 27077 27078 node = children[point[i]]; 27079 } 27080 27081 // Move text offset to best suitable location 27082 if (node.nodeType === 3) { 27083 offset = Math.min(point[0], node.nodeValue.length); 27084 } 27085 27086 // Move element offset to best suitable location 27087 if (node.nodeType === 1) { 27088 offset = Math.min(point[0], node.childNodes.length); 27089 } 27090 27091 // Set offset within container node 27092 if (start) { 27093 rng.setStart(node, offset); 27094 } else { 27095 rng.setEnd(node, offset); 27096 } 27097 } 27098 27099 return true; 27100 } 27101 27102 function restoreEndPoint(suffix) { 27103 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; 27104 27105 if (marker) { 27106 node = marker.parentNode; 27107 27108 if (suffix == 'start') { 27109 if (!keep) { 27110 idx = dom.nodeIndex(marker); 27111 } else { 27112 node = marker.firstChild; 27113 idx = 1; 27114 } 27115 27116 startContainer = endContainer = node; 27117 startOffset = endOffset = idx; 27118 } else { 27119 if (!keep) { 27120 idx = dom.nodeIndex(marker); 27121 } else { 27122 node = marker.firstChild; 27123 idx = 1; 27124 } 27125 27126 endContainer = node; 27127 endOffset = idx; 27128 } 27129 27130 if (!keep) { 27131 prev = marker.previousSibling; 27132 next = marker.nextSibling; 27133 27134 // Remove all marker text nodes 27135 Tools.each(Tools.grep(marker.childNodes), function(node) { 27136 if (node.nodeType == 3) { 27137 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); 27138 } 27139 }); 27140 27141 // Remove marker but keep children if for example contents where inserted into the marker 27142 // Also remove duplicated instances of the marker for example by a 27143 // split operation or by WebKit auto split on paste feature 27144 while ((marker = dom.get(bookmark.id + '_' + suffix))) { 27145 dom.remove(marker, 1); 27146 } 27147 27148 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node 27149 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market 27150 // isn't worth the effort. Sorry, Opera but it's just a fact 27151 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) { 27152 idx = prev.nodeValue.length; 27153 prev.appendData(next.nodeValue); 27154 dom.remove(next); 27155 27156 if (suffix == 'start') { 27157 startContainer = endContainer = prev; 27158 startOffset = endOffset = idx; 27159 } else { 27160 endContainer = prev; 27161 endOffset = idx; 27162 } 27163 } 27164 } 27165 } 27166 } 27167 27168 function addBogus(node) { 27169 // Adds a bogus BR element for empty block elements 27170 if (dom.isBlock(node) && !node.innerHTML && !Env.ie) { 27171 node.innerHTML = '<br data-mce-bogus="1" />'; 27172 } 27173 27174 return node; 27175 } 27176
27177 function resolveCaretPositionBookmark() { 27178 var rng, pos; 27179 27180 rng = dom.createRng(); 27181 pos = CaretBookmark.resolve(dom.getRoot(), bookmark.start); 27182 rng.setStart(pos.container(), pos.offset()); 27183 27184 pos = CaretBookmark.resolve(dom.getRoot(), bookmark.end); 27185 rng.setEnd(pos.container(), pos.offset()); 27186 27187 return rng; 27188 } 27189 27190 if (bookmark) { 27191 if (Tools.isArray(bookmark.start)) { 27192 rng = dom.createRng(); 27193 root = dom.getRoot(); 27194 27195 if (selection.tridentSel) { 27196 return selection.tridentSel.moveToBookmark(bookmark); 27197 } 27198 27199 if (setEndPoint(true) && setEndPoint()) { 27200 selection.setRng(rng); 27201 } 27202 } else if (typeof bookmark.start == 'string') { 27203 selection.setRng(resolveCaretPositionBookmark(bookmark)); 27204 } else if (bookmark.id) { 27205 // Restore start/end points 27206 restoreEndPoint('start'); 27207 restoreEndPoint('end'); 27208 27209 if (startContainer) { 27210 rng = dom.createRng(); 27211 rng.setStart(addBogus(startContainer), startOffset); 27212 rng.setEnd(addBogus(endContainer), endOffset); 27213 selection.setRng(rng); 27214 } 27215 } else if (bookmark.name) { 27216 selection.select(dom.select(bookmark.name)[bookmark.index]); 27217 } else if (bookmark.rng) { 27218 selection.setRng(bookmark.rng); 27219 } 27220 } 27221 }; 27222 } 27223 27224 /** 27225 * Returns true/false if the specified node is a bookmark node or not. 27226 * 27227 * @static 27228 * @method isBookmarkNode 27229 * @param {DOMNode} node DOM Node to check if it's a bookmark node or not. 27230 * @return {Boolean} true/false if the node is a bookmark node or not. 27231 */ 27232 BookmarkManager.isBookmarkNode = function(node) { 27233 return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark'; 27234 }; 27235 27236 return BookmarkManager; 27237}); 27238 27239// Included from: js/tinymce/classes/dom/Selection.js 27240 27241/** 27242 * Selection.js 27243 * 27244 * Released under LGPL License. 27245 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 27246 * 27247 * License: http://www.tinymce.com/license 27248 * Contributing: http://www.tinymce.com/contributing 27249 */ 27250 27251/** 27252 * This class handles text and control selection it's an crossbrowser utility class. 27253 * Consult the TinyMCE Wiki API for more details and examples on how to use this class. 27254 * 27255 * @class tinymce.dom.Selection 27256 * @example 27257 * // Getting the currently selected node for the active editor 27258 * alert(tinymce.activeEditor.selection.getNode().nodeName); 27259 */ 27260define("tinymce/dom/Selection", [ 27261 "tinymce/dom/TreeWalker", 27262 "tinymce/dom/TridentSelection", 27263 "tinymce/dom/ControlSelection", 27264 "tinymce/dom/RangeUtils", 27265 "tinymce/dom/BookmarkManager", 27266 "tinymce/dom/NodeType", 27267 "tinymce/Env", 27268 "tinymce/util/Tools" 27269], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, NodeType, Env, Tools) { 27270 var each = Tools.each, trim = Tools.trim; 27271 var isIE = Env.ie; 27272 27273 /** 27274 * Constructs a new selection instance. 27275 * 27276 * @constructor 27277 * @method Selection 27278 * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference. 27279 * @param {Window} win Window to bind the selection object to. 27280 * @param {tinymce.Editor} editor Editor instance of the selection. 27281 * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent. 27282 */ 27283 function Selection(dom, win, serializer, editor) { 27284 var self = this; 27285 27286 self.dom = dom; 27287 self.win = win; 27288 self.serializer = serializer; 27289 self.editor = editor; 27290 self.bookmarkManager = new BookmarkManager(self); 27291 self.controlSelection = new ControlSelection(self, editor); 27292 27293 // No W3C Range support 27294 if (!self.win.getSelection) { 27295 self.tridentSel = new TridentSelection(self); 27296 } 27297 } 27298 27299 Selection.prototype = { 27300 /** 27301 * Move the selection cursor range to the specified node and offset. 27302 * If there is no node specified it will move it to the first suitable location within the body. 27303 * 27304 * @method setCursorLocation 27305 * @param {Node} node Optional node to put the cursor in. 27306 * @param {Number} offset Optional offset from the start of the node to put the cursor at. 27307 */ 27308 setCursorLocation: function(node, offset) { 27309 var self = this, rng = self.dom.createRng(); 27310 27311 if (!node) { 27312 self._moveEndPoint(rng, self.editor.getBody(), true); 27313 self.setRng(rng); 27314 } else { 27315 rng.setStart(node, offset); 27316 rng.setEnd(node, offset); 27317 self.setRng(rng); 27318 self.collapse(false); 27319 } 27320 }, 27321 27322 /** 27323 * Returns the selected contents using the DOM serializer passed in to this class. 27324 * 27325 * @method getContent 27326 * @param {Object} args Optional settings class with for example output format text or html. 27327 * @return {String} Selected contents in for example HTML format. 27328 * @example 27329 * // Alerts the currently selected contents 27330 * alert(tinymce.activeEditor.selection.getContent()); 27331 * 27332 * // Alerts the currently selected contents as plain text 27333 * alert(tinymce.activeEditor.selection.getContent({format: 'text'})); 27334 */ 27335 getContent: function(args) { 27336 var self = this, rng = self.getRng(), tmpElm = self.dom.create("body"); 27337 var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment; 27338 27339 args = args || {}; 27340 whiteSpaceBefore = whiteSpaceAfter = ''; 27341 args.get = true; 27342 args.format = args.format || 'html'; 27343 args.selection = true; 27344 self.editor.fire('BeforeGetContent', args); 27345
27346 if (args.format == 'text') { 27347 return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : '')); 27348 } 27349 27350 if (rng.cloneContents) { 27351 fragment = rng.cloneContents(); 27352 27353 if (fragment) { 27354 tmpElm.appendChild(fragment); 27355 } 27356 } else if (rng.item !== undefined || rng.htmlText !== undefined) { 27357 // IE will produce invalid markup if elements are present that 27358 // it doesn't understand like custom elements or HTML5 elements. 27359 // Adding a BR in front of the contents and then remoiving it seems to fix it though. 27360 tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText); 27361 tmpElm.removeChild(tmpElm.firstChild); 27362 } else { 27363 tmpElm.innerHTML = rng.toString(); 27364 } 27365 27366 // Keep whitespace before and after 27367 if (/^\s/.test(tmpElm.innerHTML)) { 27368 whiteSpaceBefore = ' '; 27369 } 27370 27371 if (/\s+$/.test(tmpElm.innerHTML)) { 27372 whiteSpaceAfter = ' '; 27373 } 27374 27375 args.getInner = true; 27376 27377 args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter; 27378 self.editor.fire('GetContent', args); 27379 27380 return args.content; 27381 }, 27382 27383 /** 27384 * Sets the current selection to the specified content. If any contents is selected it will be replaced 27385 * with the contents passed in to this function. If there is no selection the contents will be inserted 27386 * where the caret is placed in the editor/page. 27387 * 27388 * @method setContent 27389 * @param {String} content HTML contents to set could also be other formats depending on settings. 27390 * @param {Object} args Optional settings object with for example data format. 27391 * @example 27392 * // Inserts some HTML contents at the current selection 27393 * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>'); 27394 */ 27395 setContent: function(content, args) { 27396 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; 27397 27398 args = args || {format: 'html'}; 27399 args.set = true; 27400 args.selection = true; 27401 args.content = content; 27402 27403 // Dispatch before set content event 27404 if (!args.no_events) { 27405 self.editor.fire('BeforeSetContent', args); 27406 } 27407 27408 content = args.content; 27409 27410 if (rng.insertNode) { 27411 // Make caret marker since insertNode places the caret in the beginning of text after insert 27412 content += '<span id="__caret">_</span>'; 27413 27414 // Delete and insert new node 27415 if (rng.startContainer == doc && rng.endContainer == doc) { 27416 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents 27417 doc.body.innerHTML = content; 27418 } else { 27419 rng.deleteContents(); 27420 27421 if (doc.body.childNodes.length === 0) { 27422 doc.body.innerHTML = content; 27423 } else { 27424 // createContextualFragment doesn't exists in IE 9 DOMRanges 27425 if (rng.createContextualFragment) { 27426 rng.insertNode(rng.createContextualFragment(content)); 27427 } else { 27428 // Fake createContextualFragment call in IE 9 27429 frag = doc.createDocumentFragment(); 27430 temp = doc.createElement('div'); 27431 27432 frag.appendChild(temp); 27433 temp.outerHTML = content; 27434 27435 rng.insertNode(frag); 27436 } 27437 } 27438 } 27439 27440 // Move to caret marker
27441 caretNode = self.dom.get('__caret'); 27442 27443 // Make sure we wrap it compleatly, Opera fails with a simple select call 27444 rng = doc.createRange(); 27445 rng.setStartBefore(caretNode); 27446 rng.setEndBefore(caretNode); 27447 self.setRng(rng); 27448 27449 // Remove the caret position 27450 self.dom.remove('__caret'); 27451 27452 try { 27453 self.setRng(rng); 27454 } catch (ex) { 27455 // Might fail on Opera for some odd reason 27456 } 27457 } else { 27458 if (rng.item) { 27459 // Delete content and get caret text selection 27460 doc.execCommand('Delete', false, null); 27461 rng = self.getRng(); 27462 } 27463 27464 // Explorer removes spaces from the beginning of pasted contents 27465 if (/^\s+/.test(content)) { 27466 rng.pasteHTML('<span id="__mce_tmp">_</span>' + content); 27467 self.dom.remove('__mce_tmp'); 27468 } else { 27469 rng.pasteHTML(content); 27470 } 27471 } 27472 27473 // Dispatch set content event 27474 if (!args.no_events) { 27475 self.editor.fire('SetContent', args); 27476 } 27477 }, 27478 27479 /** 27480 * Returns the start element of a selection range. If the start is in a text 27481 * node the parent element will be returned. 27482 * 27483 * @method getStart 27484 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. 27485 * @return {Element} Start element of selection range. 27486 */ 27487 getStart: function(real) { 27488 var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node; 27489 27490 if (rng.duplicate || rng.item) { 27491 // Control selection, return first item 27492 if (rng.item) { 27493 return rng.item(0); 27494 } 27495 27496 // Get start element 27497 checkRng = rng.duplicate(); 27498 checkRng.collapse(1); 27499 startElement = checkRng.parentElement(); 27500 if (startElement.ownerDocument !== self.dom.doc) { 27501 startElement = self.dom.getRoot(); 27502 } 27503 27504 // Check if range parent is inside the start element, then return the inner parent element 27505 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element 27506 parentElement = node = rng.parentElement(); 27507 while ((node = node.parentNode)) { 27508 if (node == startElement) { 27509 startElement = parentElement; 27510 break; 27511 } 27512 } 27513 27514 return startElement; 27515 } 27516 27517 startElement = rng.startContainer; 27518 27519 if (startElement.nodeType == 1 && startElement.hasChildNodes()) { 27520 if (!real || !rng.collapsed) { 27521 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; 27522 } 27523 } 27524 27525 if (startElement && startElement.nodeType == 3) { 27526 return startElement.parentNode; 27527 } 27528 27529 return startElement; 27530 }, 27531 27532 /** 27533 * Returns the end element of a selection range. If the end is in a text 27534 * node the parent element will be returned. 27535 * 27536 * @method getEnd 27537 * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element. 27538 * @return {Element} End element of selection range. 27539 */ 27540 getEnd: function(real) { 27541 var self = this, rng = self.getRng(), endElement, endOffset; 27542 27543 if (rng.duplicate || rng.item) { 27544 if (rng.item) { 27545 return rng.item(0); 27546 } 27547 27548 rng = rng.duplicate(); 27549 rng.collapse(0); 27550 endElement = rng.parentElement(); 27551 if (endElement.ownerDocument !== self.dom.doc) { 27552 endElement = self.dom.getRoot(); 27553 } 27554 27555 if (endElement && endElement.nodeName == 'BODY') { 27556 return endElement.lastChild || endElement; 27557 } 27558 27559 return endElement; 27560 } 27561 27562 endElement = rng.endContainer; 27563 endOffset = rng.endOffset; 27564 27565 if (endElement.nodeType == 1 && endElement.hasChildNodes()) { 27566 if (!real || !rng.collapsed) { 27567 endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset]; 27568 } 27569 } 27570 27571 if (endElement && endElement.nodeType == 3) { 27572 return endElement.parentNode; 27573 } 27574 27575 return endElement; 27576 }, 27577 27578 /** 27579 * Returns a bookmark location for the current selection. This bookmark object 27580 * can then be used to restore the selection after some content modification to the document. 27581 * 27582 * @method getBookmark 27583 * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex. 27584 * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization. 27585 * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection. 27586 * @example
27587 * // Stores a bookmark of the current selection 27588 * var bm = tinymce.activeEditor.selection.getBookmark(); 27589 * 27590 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 27591 * 27592 * // Restore the selection bookmark 27593 * tinymce.activeEditor.selection.moveToBookmark(bm); 27594 */ 27595 getBookmark: function(type, normalized) { 27596 return this.bookmarkManager.getBookmark(type, normalized); 27597 }, 27598 27599 /** 27600 * Restores the selection to the specified bookmark. 27601 * 27602 * @method moveToBookmark 27603 * @param {Object} bookmark Bookmark to restore selection from. 27604 * @return {Boolean} true/false if it was successful or not. 27605 * @example 27606 * // Stores a bookmark of the current selection 27607 * var bm = tinymce.activeEditor.selection.getBookmark(); 27608 * 27609 * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content'); 27610 * 27611 * // Restore the selection bookmark 27612 * tinymce.activeEditor.selection.moveToBookmark(bm); 27613 */ 27614 moveToBookmark: function(bookmark) { 27615 return this.bookmarkManager.moveToBookmark(bookmark); 27616 }, 27617 27618 /** 27619 * Selects the specified element. This will place the start and end of the selection range around the element. 27620 * 27621 * @method select 27622 * @param {Element} node HTML DOM element to select. 27623 * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser. 27624 * @return {Element} Selected element the same element as the one that got passed in. 27625 * @example 27626 * // Select the first paragraph in the active editor 27627 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 27628 */ 27629 select: function(node, content) { 27630 var self = this, dom = self.dom, rng = dom.createRng(), idx; 27631 27632 // Clear stored range set by FocusManager 27633 self.lastFocusBookmark = null; 27634 27635 if (node) { 27636 if (!content && self.controlSelection.controlSelect(node)) { 27637 return; 27638 } 27639 27640 idx = dom.nodeIndex(node); 27641 rng.setStart(node.parentNode, idx); 27642 rng.setEnd(node.parentNode, idx + 1); 27643 27644 // Find first/last text node or BR element 27645 if (content) { 27646 self._moveEndPoint(rng, node, true); 27647 self._moveEndPoint(rng, node); 27648 } 27649 27650 self.setRng(rng); 27651 } 27652 27653 return node; 27654 }, 27655 27656 /** 27657 * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection. 27658 * 27659 * @method isCollapsed 27660 * @return {Boolean} true/false state if the selection range is collapsed or not. 27661 * Collapsed means if it's a caret or a larger selection. 27662 */ 27663 isCollapsed: function() { 27664 var self = this, rng = self.getRng(), sel = self.getSel(); 27665 27666 if (!rng || rng.item) { 27667 return false; 27668 } 27669 27670 if (rng.compareEndPoints) { 27671 return rng.compareEndPoints('StartToEnd', rng) === 0; 27672 } 27673 27674 return !sel || rng.collapsed; 27675 }, 27676 27677 /** 27678 * Collapse the selection to start or end of range. 27679 * 27680 * @method collapse 27681 * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to false. 27682 */ 27683 collapse: function(toStart) { 27684 var self = this, rng = self.getRng(), node; 27685 27686 // Control range on IE 27687 if (rng.item) { 27688 node = rng.item(0); 27689 rng = self.win.document.body.createTextRange(); 27690 rng.moveToElementText(node); 27691 } 27692 27693 rng.collapse(!!toStart); 27694 self.setRng(rng); 27695 }, 27696 27697 /** 27698 * Returns the browsers internal selection object. 27699 * 27700 * @method getSel 27701 * @return {Selection} Internal browser selection object. 27702 */ 27703 getSel: function() { 27704 var win = this.win; 27705 27706 return win.getSelection ? win.getSelection() : win.document.selection; 27707 }, 27708 27709 /** 27710 * Returns the browsers internal range object. 27711 * 27712 * @method getRng 27713 * @param {Boolean} w3c Forces a compatible W3C range on IE. 27714 * @return {Range} Internal browser range object. 27715 * @see http://www.quirksmode.org/dom/range_intro.html 27716 * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/ 27717 */ 27718 getRng: function(w3c) { 27719 var self = this, selection, rng, elm, doc, ieRng, evt; 27720 27721 function tryCompareBoundaryPoints(how, sourceRange, destinationRange) { 27722 try { 27723 return sourceRange.compareBoundaryPoints(how, destinationRange); 27724 } catch (ex) { 27725 // Gecko throws wrong document exception if the range points 27726 // to nodes that where removed from the dom #6690 27727 // Browsers should mutate existing DOMRange instances so that they always point 27728 // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink 27729 // For performance reasons just return -1 27730 return -1; 27731 } 27732 } 27733 27734 if (!self.win) { 27735 return null; 27736 } 27737 27738 doc = self.win.document; 27739 27740 // Use last rng passed from FocusManager if it's available this enables 27741 // calls to editor.selection.getStart() to work when caret focus is lost on IE 27742 if (!w3c && self.lastFocusBookmark) { 27743 var bookmark = self.lastFocusBookmark; 27744 27745 // Convert bookmark to range IE 11 fix 27746 if (bookmark.startContainer) { 27747 rng = doc.createRange(); 27748 rng.setStart(bookmark.startContainer, bookmark.startOffset); 27749 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 27750 } else { 27751 rng = bookmark; 27752 } 27753 27754 return rng; 27755 } 27756 27757 // Found tridentSel object then we need to use that one 27758 if (w3c && self.tridentSel) { 27759 return self.tridentSel.getRangeAt(0); 27760 } 27761 27762 try { 27763 if ((selection = self.getSel())) { 27764 if (selection.rangeCount > 0) { 27765 rng = selection.getRangeAt(0); 27766 } else { 27767 rng = selection.createRange ? selection.createRange() : doc.createRange(); 27768 } 27769 } 27770 } catch (ex) { 27771 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe 27772 } 27773 27774 evt = self.editor.fire('GetSelectionRange', {range: rng}); 27775 if (evt.range !== rng) { 27776 return evt.range; 27777 } 27778 27779 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet 27780 // IE 11 doesn't support the selection object so we check for that as well 27781 if (isIE && rng && rng.setStart && doc.selection) { 27782 try { 27783 // IE will sometimes throw an exception here 27784 ieRng = doc.selection.createRange(); 27785 } catch (ex) { 27786 // Ignore 27787 } 27788 27789 if (ieRng && ieRng.item) { 27790 elm = ieRng.item(0); 27791 rng = doc.createRange(); 27792 rng.setStartBefore(elm); 27793 rng.setEndAfter(elm); 27794 } 27795 } 27796 27797 // No range found then create an empty one 27798 // This can occur when the editor is placed in a hidden container element on Gecko 27799 // Or on IE when there was an exception 27800 if (!rng) { 27801 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange(); 27802 } 27803 27804 // If range is at start of document then move it to start of body 27805 if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) { 27806 elm = self.dom.getRoot(); 27807 rng.setStart(elm, 0); 27808 rng.setEnd(elm, 0); 27809 } 27810 27811 if (self.selectedRange && self.explicitRange) { 27812 if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 && 27813 tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) { 27814 // Safari, Opera and Chrome only ever select text which causes the range to change. 27815 // This lets us use the originally set range if the selection hasn't been changed by the user. 27816 rng = self.explicitRange; 27817 } else { 27818 self.selectedRange = null; 27819 self.explicitRange = null; 27820 } 27821 } 27822 27823 return rng; 27824 }, 27825 27826 /** 27827 * Changes the selection to the specified DOM range. 27828 * 27829 * @method setRng 27830 * @param {Range} rng Range to select. 27831 * @param {Boolean} forward Optional boolean if the selection is forwards or backwards. 27832 */ 27833 setRng: function(rng, forward) { 27834 var self = this, sel, node, evt; 27835 27836 if (!rng) { 27837 return; 27838 } 27839 27840 // Is IE specific range 27841 if (rng.select) { 27842 self.explicitRange = null; 27843 27844 try { 27845 rng.select(); 27846 } catch (ex) { 27847 // Needed for some odd IE bug #1843306 27848 } 27849 27850 return; 27851 } 27852 27853 if (!self.tridentSel) { 27854 sel = self.getSel(); 27855 27856 evt = self.editor.fire('SetSelectionRange', {range: rng}); 27857 rng = evt.range; 27858 27859 if (sel) { 27860 self.explicitRange = rng; 27861 27862 try { 27863 sel.removeAllRanges(); 27864 sel.addRange(rng); 27865 } catch (ex) { 27866 // IE might throw errors here if the editor is within a hidden container and selection is changed 27867 } 27868 27869 // Forward is set to false and we have an extend function 27870 if (forward === false && sel.extend) { 27871 sel.collapse(rng.endContainer, rng.endOffset); 27872 sel.extend(rng.startContainer, rng.startOffset); 27873 } 27874 27875 // adding range isn't always successful so we need to check range count otherwise an exception can occur 27876 self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null; 27877 } 27878 27879 // WebKit egde case selecting images works better using setBaseAndExtent 27880 if (!rng.collapsed && rng.startContainer == rng.endContainer && sel.setBaseAndExtent && !Env.ie) { 27881 if (rng.endOffset - rng.startOffset < 2) { 27882 if (rng.startContainer.hasChildNodes()) { 27883 node = rng.startContainer.childNodes[rng.startOffset]; 27884 if (node && node.tagName == 'IMG') { 27885 self.getSel().setBaseAndExtent(node, 0, node, 1); 27886 } 27887 } 27888 } 27889 } 27890 } else { 27891 // Is W3C Range fake range on IE 27892 if (rng.cloneRange) { 27893 try { 27894 self.tridentSel.addRange(rng); 27895 } catch (ex) { 27896 //IE9 throws an error here if called before selection is placed in the editor
27897 } 27898 } 27899 } 27900 }, 27901 27902 /** 27903 * Sets the current selection to the specified DOM element. 27904 * 27905 * @method setNode 27906 * @param {Element} elm Element to set as the contents of the selection. 27907 * @return {Element} Returns the element that got passed in. 27908 * @example 27909 * // Inserts a DOM node at current selection/caret location 27910 * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'})); 27911 */ 27912 setNode: function(elm) { 27913 var self = this; 27914 27915 self.setContent(self.dom.getOuterHTML(elm)); 27916 27917 return elm; 27918 }, 27919 27920 /** 27921 * Returns the currently selected element or the common ancestor element for both start and end of the selection. 27922 * 27923 * @method getNode 27924 * @return {Element} Currently selected element or common ancestor element. 27925 * @example 27926 * // Alerts the currently selected elements node name 27927 * alert(tinymce.activeEditor.selection.getNode().nodeName); 27928 */ 27929 getNode: function() { 27930 var self = this, rng = self.getRng(), elm; 27931 var startContainer = rng.startContainer, endContainer = rng.endContainer; 27932 var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot(); 27933 27934 function skipEmptyTextNodes(node, forwards) { 27935 var orig = node; 27936 27937 while (node && node.nodeType === 3 && node.length === 0) { 27938 node = forwards ? node.nextSibling : node.previousSibling; 27939 } 27940 27941 return node || orig; 27942 } 27943 27944 // Range maybe lost after the editor is made visible again 27945 if (!rng) { 27946 return root; 27947 } 27948 27949 if (rng.setStart) { 27950 elm = rng.commonAncestorContainer; 27951 27952 // Handle selection a image or other control like element such as anchors 27953 if (!rng.collapsed) { 27954 if (startContainer == endContainer) { 27955 if (endOffset - startOffset < 2) { 27956 if (startContainer.hasChildNodes()) { 27957 elm = startContainer.childNodes[startOffset]; 27958 } 27959 } 27960 } 27961 27962 // If the anchor node is a element instead of a text node then return this element 27963 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 27964 // return sel.anchorNode.childNodes[sel.anchorOffset]; 27965 27966 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. 27967 // This happens when you double click an underlined word in FireFox. 27968 if (startContainer.nodeType === 3 && endContainer.nodeType === 3) { 27969 if (startContainer.length === startOffset) { 27970 startContainer = skipEmptyTextNodes(startContainer.nextSibling, true); 27971 } else { 27972 startContainer = startContainer.parentNode; 27973 } 27974 27975 if (endOffset === 0) { 27976 endContainer = skipEmptyTextNodes(endContainer.previousSibling, false); 27977 } else { 27978 endContainer = endContainer.parentNode; 27979 } 27980 27981 if (startContainer && startContainer === endContainer) { 27982 return startContainer; 27983 } 27984 } 27985 } 27986 27987 if (elm && elm.nodeType == 3) { 27988 return elm.parentNode; 27989 } 27990 27991 return elm; 27992 } 27993 27994 elm = rng.item ? rng.item(0) : rng.parentElement(); 27995 27996 // IE 7 might return elements outside the iframe 27997 if (elm.ownerDocument !== self.win.document) { 27998 elm = root; 27999 } 28000 28001 return elm; 28002 }, 28003 28004 getSelectedBlocks: function(startElm, endElm) { 28005 var self = this, dom = self.dom, node, root, selectedBlocks = []; 28006 28007 root = dom.getRoot(); 28008 startElm = dom.getParent(startElm || self.getStart(), dom.isBlock); 28009 endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock); 28010 28011 if (startElm && startElm != root) { 28012 selectedBlocks.push(startElm); 28013 } 28014 28015 if (startElm && endElm && startElm != endElm) { 28016 node = startElm; 28017 28018 var walker = new TreeWalker(startElm, root); 28019 while ((node = walker.next()) && node != endElm) { 28020 if (dom.isBlock(node)) { 28021 selectedBlocks.push(node); 28022 } 28023 } 28024 } 28025 28026 if (endElm && startElm != endElm && endElm != root) { 28027 selectedBlocks.push(endElm); 28028 } 28029 28030 return selectedBlocks; 28031 }, 28032 28033 isForward: function() { 28034 var dom = this.dom, sel = this.getSel(), anchorRange, focusRange; 28035 28036 // No support for selection direction then always return true 28037 if (!sel || !sel.anchorNode || !sel.focusNode) { 28038 return true; 28039 } 28040 28041 anchorRange = dom.createRng(); 28042 anchorRange.setStart(sel.anchorNode, sel.anchorOffset); 28043 anchorRange.collapse(true); 28044 28045 focusRange = dom.createRng(); 28046 focusRange.setStart(sel.focusNode, sel.focusOffset); 28047 focusRange.collapse(true); 28048 28049 return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0; 28050 }, 28051 28052 normalize: function() { 28053 var self = this, rng = self.getRng(); 28054 28055 if (Env.range && new RangeUtils(self.dom).normalize(rng)) { 28056 self.setRng(rng, self.isForward()); 28057 } 28058 28059 return rng; 28060 }, 28061 28062 /** 28063 * Executes callback when the current selection starts/stops matching the specified selector. The current 28064 * state will be passed to the callback as it's first argument. 28065 * 28066 * @method selectorChanged 28067 * @param {String} selector CSS selector to check for. 28068 * @param {function} callback Callback with state and args when the selector is matches or not. 28069 */ 28070 selectorChanged: function(selector, callback) { 28071 var self = this, currentSelectors; 28072 28073 if (!self.selectorChangedData) { 28074 self.selectorChangedData = {}; 28075 currentSelectors = {}; 28076 28077 self.editor.on('NodeChange', function(e) { 28078 var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {}; 28079 28080 // Check for new matching selectors 28081 each(self.selectorChangedData, function(callbacks, selector) { 28082 each(parents, function(node) { 28083 if (dom.is(node, selector)) { 28084 if (!currentSelectors[selector]) { 28085 // Execute callbacks 28086 each(callbacks, function(callback) { 28087 callback(true, {node: node, selector: selector, parents: parents}); 28088 }); 28089 28090 currentSelectors[selector] = callbacks; 28091 } 28092 28093 matchedSelectors[selector] = callbacks; 28094 return false;
28095 } 28096 }); 28097 }); 28098 28099 // Check if current selectors still match 28100 each(currentSelectors, function(callbacks, selector) { 28101 if (!matchedSelectors[selector]) { 28102 delete currentSelectors[selector]; 28103 28104 each(callbacks, function(callback) { 28105 callback(false, {node: node, selector: selector, parents: parents}); 28106 }); 28107 } 28108 }); 28109 }); 28110 } 28111 28112 // Add selector listeners 28113 if (!self.selectorChangedData[selector]) { 28114 self.selectorChangedData[selector] = []; 28115 } 28116 28117 self.selectorChangedData[selector].push(callback); 28118 28119 return self; 28120 }, 28121 28122 getScrollContainer: function() { 28123 var scrollContainer, node = this.dom.getRoot(); 28124 28125 while (node && node.nodeName != 'BODY') { 28126 if (node.scrollHeight > node.clientHeight) { 28127 scrollContainer = node; 28128 break; 28129 } 28130 28131 node = node.parentNode; 28132 } 28133 28134 return scrollContainer; 28135 }, 28136 28137 scrollIntoView: function(elm, alignToTop) { 28138 var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH, offsetY = 0; 28139 28140 function getPos(elm) { 28141 var x = 0, y = 0; 28142 28143 var offsetParent = elm; 28144 while (offsetParent && offsetParent.nodeType) { 28145 x += offsetParent.offsetLeft || 0; 28146 y += offsetParent.offsetTop || 0; 28147 offsetParent = offsetParent.offsetParent; 28148 } 28149 28150 return {x: x, y: y}; 28151 } 28152 28153 if (!NodeType.isElement(elm)) { 28154 return; 28155 } 28156 28157 if (alignToTop === false) { 28158 offsetY = elm.offsetHeight; 28159 } 28160 28161 if (root.nodeName != 'BODY') { 28162 var scrollContainer = self.getScrollContainer(); 28163 if (scrollContainer) { 28164 y = getPos(elm).y - getPos(scrollContainer).y + offsetY; 28165 viewPortH = scrollContainer.clientHeight; 28166 viewPortY = scrollContainer.scrollTop; 28167 if (y < viewPortY || y + 25 > viewPortY + viewPortH) { 28168 scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25; 28169 } 28170 28171 return; 28172 } 28173 } 28174 28175 viewPort = dom.getViewPort(self.editor.getWin()); 28176 y = dom.getPos(elm).y + offsetY; 28177 viewPortY = viewPort.y; 28178 viewPortH = viewPort.h; 28179 if (y < viewPort.y || y + 25 > viewPortY + viewPortH) { 28180 self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25); 28181 } 28182 }, 28183 28184 placeCaretAt: function(clientX, clientY) { 28185 this.setRng(RangeUtils.getCaretRangeFromPoint(clientX, clientY, this.editor.getDoc())); 28186 }, 28187 28188 _moveEndPoint: function(rng, node, start) { 28189 var root = node, walker = new TreeWalker(node, root); 28190 var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements(); 28191 28192 do { 28193 // Text node 28194 if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) { 28195 if (start) { 28196 rng.setStart(node, 0); 28197 } else { 28198 rng.setEnd(node, node.nodeValue.length); 28199 } 28200 28201 return; 28202 } 28203 28204 // BR/IMG/INPUT elements but not table cells 28205 if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) { 28206 if (start) { 28207 rng.setStartBefore(node); 28208 } else { 28209 if (node.nodeName == 'BR') { 28210 rng.setEndBefore(node); 28211 } else { 28212 rng.setEndAfter(node); 28213 } 28214 } 28215 28216 return; 28217 } 28218 28219 // Found empty text block old IE can place the selection inside those 28220 if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) { 28221 if (start) { 28222 rng.setStart(node, 0); 28223 } else { 28224 rng.setEnd(node, 0); 28225 } 28226 28227 return; 28228 } 28229 } while ((node = (start ? walker.next() : walker.prev()))); 28230 28231 // Failed to find any text node or other suitable location then move to the root of body 28232 if (root.nodeName == 'BODY') { 28233 if (start) { 28234 rng.setStart(root, 0); 28235 } else { 28236 rng.setEnd(root, root.childNodes.length); 28237 } 28238 } 28239 }, 28240 28241 destroy: function() { 28242 this.win = null; 28243 this.controlSelection.destroy(); 28244 } 28245 }; 28246 28247 return Selection; 28248}); 28249 28250// Included from: js/tinymce/classes/dom/ElementUtils.js 28251 28252/** 28253 * ElementUtils.js 28254 * 28255 * Released under LGPL License. 28256 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 28257 * 28258 * License: http://www.tinymce.com/license 28259 * Contributing: http://www.tinymce.com/contributing 28260 */ 28261 28262/** 28263 * Utility class for various element specific functions. 28264 * 28265 * @private 28266 * @class tinymce.dom.ElementUtils 28267 */ 28268define("tinymce/dom/ElementUtils", [
28269 "tinymce/dom/BookmarkManager", 28270 "tinymce/util/Tools" 28271], function(BookmarkManager, Tools) { 28272 var each = Tools.each; 28273 28274 function ElementUtils(dom) { 28275 /** 28276 * Compares two nodes and checks if it's attributes and styles matches. 28277 * This doesn't compare classes as items since their order is significant. 28278 * 28279 * @method compare 28280 * @param {Node} node1 First node to compare with. 28281 * @param {Node} node2 Second node to compare with. 28282 * @return {boolean} True/false if the nodes are the same or not. 28283 */ 28284 this.compare = function(node1, node2) { 28285 // Not the same name 28286 if (node1.nodeName != node2.nodeName) { 28287 return false; 28288 } 28289 28290 /** 28291 * Returns all the nodes attributes excluding internal ones, styles and classes. 28292 * 28293 * @private 28294 * @param {Node} node Node to get attributes from. 28295 * @return {Object} Name/value object with attributes and attribute values. 28296 */ 28297 function getAttribs(node) { 28298 var attribs = {}; 28299 28300 each(dom.getAttribs(node), function(attr) { 28301 var name = attr.nodeName.toLowerCase(); 28302 28303 // Don't compare internal attributes or style 28304 if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') { 28305 attribs[name] = dom.getAttrib(node, name); 28306 } 28307 }); 28308 28309 return attribs; 28310 } 28311 28312 /** 28313 * Compares two objects checks if it's key + value exists in the other one. 28314 * 28315 * @private 28316 * @param {Object} obj1 First object to compare. 28317 * @param {Object} obj2 Second object to compare. 28318 * @return {boolean} True/false if the objects matches or not. 28319 */ 28320 function compareObjects(obj1, obj2) { 28321 var value, name; 28322 28323 for (name in obj1) { 28324 // Obj1 has item obj2 doesn't have 28325 if (obj1.hasOwnProperty(name)) { 28326 value = obj2[name]; 28327 28328 // Obj2 doesn't have obj1 item 28329 if (typeof value == "undefined") { 28330 return false; 28331 } 28332 28333 // Obj2 item has a different value 28334 if (obj1[name] != value) { 28335 return false; 28336 } 28337 28338 // Delete similar value 28339 delete obj2[name]; 28340 } 28341 } 28342 28343 // Check if obj 2 has something obj 1 doesn't have 28344 for (name in obj2) { 28345 // Obj2 has item obj1 doesn't have 28346 if (obj2.hasOwnProperty(name)) { 28347 return false; 28348 } 28349 } 28350 28351 return true; 28352 } 28353 28354 // Attribs are not the same 28355 if (!compareObjects(getAttribs(node1), getAttribs(node2))) { 28356 return false; 28357 } 28358 28359 // Styles are not the same 28360 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) { 28361 return false; 28362 } 28363 28364 return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2); 28365 }; 28366 } 28367 28368 return ElementUtils; 28369}); 28370 28371// Included from: js/tinymce/classes/fmt/Preview.js 28372 28373/** 28374 * Preview.js 28375 * 28376 * Released under LGPL License. 28377 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 28378 * 28379 * License: http://www.tinymce.com/license 28380 * Contributing: http://www.tinymce.com/contributing 28381 */ 28382 28383/** 28384 * Internal class for generating previews styles for formats. 28385 * 28386 * Example: 28387 * Preview.getCssText(editor, 'bold'); 28388 * 28389 * @private 28390 * @class tinymce.fmt.Preview 28391 */ 28392define("tinymce/fmt/Preview", [ 28393 "tinymce/util/Tools" 28394], function(Tools) { 28395 var each = Tools.each; 28396 28397 function getCssText(editor, format) { 28398 var name, previewElm, dom = editor.dom; 28399 var previewCss = '', parentFontSize, previewStyles; 28400 28401 previewStyles = editor.settings.preview_styles; 28402 28403 // No preview forced 28404 if (previewStyles === false) { 28405 return ''; 28406 } 28407 28408 // Default preview 28409 if (!previewStyles) { 28410 previewStyles = 'font-family font-size font-weight font-style text-decoration ' + 28411 'text-transform color background-color border border-radius outline text-shadow'; 28412 } 28413 28414 // Removes any variables since these can't be previewed 28415 function removeVars(val) { 28416 return val.replace(/%(\w+)/g, ''); 28417 } 28418 28419 // Create block/inline element to use for preview 28420 if (typeof format == "string") { 28421 format = editor.formatter.get(format); 28422 if (!format) { 28423 return; 28424 } 28425 28426 format = format[0]; 28427 } 28428 28429 name = format.block || format.inline || 'span'; 28430 previewElm = dom.create(name); 28431 28432 // Add format styles to preview element 28433 each(format.styles, function(value, name) { 28434 value = removeVars(value); 28435 28436 if (value) { 28437 dom.setStyle(previewElm, name, value); 28438 } 28439 }); 28440 28441 // Add attributes to preview element 28442 each(format.attributes, function(value, name) { 28443 value = removeVars(value); 28444 28445 if (value) { 28446 dom.setAttrib(previewElm, name, value); 28447 } 28448 }); 28449 28450 // Add classes to preview element 28451 each(format.classes, function(value) { 28452 value = removeVars(value); 28453 28454 if (!dom.hasClass(previewElm, value)) { 28455 dom.addClass(previewElm, value); 28456 } 28457 }); 28458 28459 editor.fire('PreviewFormats'); 28460 28461 // Add the previewElm outside the visual area 28462 dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF}); 28463 editor.getBody().appendChild(previewElm); 28464 28465 // Get parent container font size so we can compute px values out of em/% for older IE:s 28466 parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true); 28467 parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0; 28468 28469 each(previewStyles.split(' '), function(name) { 28470 var value = dom.getStyle(previewElm, name, true); 28471 28472 // If background is transparent then check if the body has a background color we can use
28473 if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) { 28474 value = dom.getStyle(editor.getBody(), name, true); 28475 28476 // Ignore white since it's the default color, not the nicest fix 28477 // TODO: Fix this by detecting runtime style 28478 if (dom.toHex(value).toLowerCase() == '#ffffff') { 28479 return; 28480 } 28481 } 28482 28483 if (name == 'color') { 28484 // Ignore black since it's the default color, not the nicest fix 28485 // TODO: Fix this by detecting runtime style 28486 if (dom.toHex(value).toLowerCase() == '#000000') { 28487 return; 28488 } 28489 } 28490 28491 // Old IE won't calculate the font size so we need to do that manually 28492 if (name == 'font-size') { 28493 if (/em|%$/.test(value)) { 28494 if (parentFontSize === 0) { 28495 return; 28496 } 28497 28498 // Convert font size from em/% to px 28499 value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1); 28500 value = (value * parentFontSize) + 'px'; 28501 } 28502 } 28503 28504 if (name == "border" && value) { 28505 previewCss += 'padding:0 2px;'; 28506 } 28507 28508 previewCss += name + ':' + value + ';'; 28509 }); 28510 28511 editor.fire('AfterPreviewFormats'); 28512 28513 //previewCss += 'line-height:normal'; 28514 28515 dom.remove(previewElm); 28516 28517 return previewCss; 28518 } 28519 28520 return { 28521 getCssText: getCssText 28522 }; 28523}); 28524 28525// Included from: js/tinymce/classes/Formatter.js 28526 28527/** 28528 * Formatter.js 28529 * 28530 * Released under LGPL License. 28531 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 28532 * 28533 * License: http://www.tinymce.com/license 28534 * Contributing: http://www.tinymce.com/contributing 28535 */ 28536 28537/** 28538 * Text formatter engine class. This class is used to apply formats like bold, italic, font size 28539 * etc to the current selection or specific nodes. This engine was built to replace the browser's 28540 * default formatting logic for execCommand due to its inconsistent and buggy behavior. 28541 * 28542 * @class tinymce.Formatter 28543 * @example 28544 * tinymce.activeEditor.formatter.register('mycustomformat', { 28545 * inline: 'span', 28546 * styles: {color: '#ff0000'} 28547 * }); 28548 * 28549 * tinymce.activeEditor.formatter.apply('mycustomformat'); 28550 */ 28551define("tinymce/Formatter", [ 28552 "tinymce/dom/TreeWalker", 28553 "tinymce/dom/RangeUtils", 28554 "tinymce/dom/BookmarkManager", 28555 "tinymce/dom/ElementUtils", 28556 "tinymce/util/Tools", 28557 "tinymce/fmt/Preview" 28558], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview) { 28559 /** 28560 * Constructs a new formatter instance. 28561 * 28562 * @constructor Formatter 28563 * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to. 28564 */ 28565 return function(ed) { 28566 var formats = {}, 28567 dom = ed.dom, 28568 selection = ed.selection, 28569 rangeUtils = new RangeUtils(dom), 28570 isValid = ed.schema.isValidChild, 28571 isBlock = dom.isBlock, 28572 forcedRootBlock = ed.settings.forced_root_block, 28573 nodeIndex = dom.nodeIndex, 28574 INVISIBLE_CHAR = '\uFEFF', 28575 MCE_ATTR_RE = /^(src|href|style)$/, 28576 FALSE = false, 28577 TRUE = true, 28578 formatChangeData, 28579 undef, 28580 getContentEditable = dom.getContentEditable, 28581 disableCaretContainer, 28582 markCaretContainersBogus, 28583 isBookmarkNode = BookmarkManager.isBookmarkNode; 28584 28585 var each = Tools.each, 28586 grep = Tools.grep, 28587 walk = Tools.walk, 28588 extend = Tools.extend; 28589 28590 function isTextBlock(name) { 28591 if (name.nodeType) { 28592 name = name.nodeName; 28593 } 28594 28595 return !!ed.schema.getTextBlockElements()[name.toLowerCase()]; 28596 } 28597 28598 function isTableCell(node) { 28599 return /^(TH|TD)$/.test(node.nodeName); 28600 } 28601 28602 function isInlineBlock(node) { 28603 return node && /^(IMG)$/.test(node.nodeName); 28604 } 28605 28606 function getParents(node, selector) { 28607 return dom.getParents(node, selector, dom.getRoot()); 28608 } 28609 28610 function isCaretNode(node) { 28611 return node.nodeType === 1 && node.id === '_mce_caret'; 28612 } 28613 28614 function defaultFormats() { 28615 register({ 28616 valigntop: [ 28617 {selector: 'td,th', styles: {'verticalAlign': 'top'}} 28618 ], 28619 28620 valignmiddle: [ 28621 {selector: 'td,th', styles: {'verticalAlign': 'middle'}} 28622 ], 28623 28624 valignbottom: [ 28625 {selector: 'td,th', styles: {'verticalAlign': 'bottom'}} 28626 ], 28627 28628 alignleft: [ 28629 {selector: 'figure.image', collapsed: false, classes: 'align-left', ceFalseOverride: true}, 28630 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
28631 {selector: 'img,table', collapsed: false, styles: {'float': 'left'}} 28632 ], 28633 28634 aligncenter: [ 28635 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'}, 28636 {selector: 'figure.image', collapsed: false, classes: 'align-center', ceFalseOverride: true}, 28637 {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}}, 28638 {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}} 28639 ], 28640 28641 alignright: [ 28642 {selector: 'figure.image', collapsed: false, classes: 'align-right', ceFalseOverride: true}, 28643 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'}, 28644 {selector: 'img,table', collapsed: false, styles: {'float': 'right'}} 28645 ], 28646 28647 alignjustify: [ 28648 {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'} 28649 ], 28650 28651 bold: [ 28652 {inline: 'strong', remove: 'all'}, 28653 {inline: 'span', styles: {fontWeight: 'bold'}}, 28654 {inline: 'b', remove: 'all'} 28655 ], 28656 28657 italic: [ 28658 {inline: 'em', remove: 'all'}, 28659 {inline: 'span', styles: {fontStyle: 'italic'}}, 28660 {inline: 'i', remove: 'all'} 28661 ], 28662 28663 underline: [ 28664 {inline: 'span', styles: {textDecoration: 'underline'}, exact: true}, 28665 {inline: 'u', remove: 'all'} 28666 ], 28667 28668 strikethrough: [ 28669 {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true}, 28670 {inline: 'strike', remove: 'all'} 28671 ], 28672 28673 forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true}, 28674 hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true}, 28675 fontname: {inline: 'span', styles: {fontFamily: '%value'}}, 28676 fontsize: {inline: 'span', styles: {fontSize: '%value'}}, 28677 fontsize_class: {inline: 'span', attributes: {'class': '%value'}}, 28678 blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'}, 28679 subscript: {inline: 'sub'}, 28680 superscript: {inline: 'sup'}, 28681 code: {inline: 'code'}, 28682 28683 link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true, 28684 onmatch: function() { 28685 return true; 28686 }, 28687 28688 onformat: function(elm, fmt, vars) { 28689 each(vars, function(value, key) { 28690 dom.setAttrib(elm, key, value); 28691 }); 28692 } 28693 }, 28694 28695 removeformat: [ 28696 { 28697 selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins', 28698 remove: 'all', 28699 split: true, 28700 expand: false, 28701 block_expand: true, 28702 deep: true 28703 }, 28704 {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true}, 28705 {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true} 28706 ] 28707 }); 28708 28709 // Register default block formats 28710 each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) { 28711 register(name, {block: name, remove: 'all'}); 28712 }); 28713 28714 // Register user defined formats 28715 register(ed.settings.formats); 28716 } 28717 28718 function addKeyboardShortcuts() { 28719 // Add some inline shortcuts 28720 ed.addShortcut('meta+b', 'bold_desc', 'Bold'); 28721 ed.addShortcut('meta+i', 'italic_desc', 'Italic'); 28722 ed.addShortcut('meta+u', 'underline_desc', 'Underline'); 28723 28724 // BlockFormat shortcuts keys 28725 for (var i = 1; i <= 6; i++) { 28726 ed.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]); 28727 } 28728 28729 ed.addShortcut('access+7', '', ['FormatBlock', false, 'p']); 28730 ed.addShortcut('access+8', '', ['FormatBlock', false, 'div']); 28731 ed.addShortcut('access+9', '', ['FormatBlock', false, 'address']); 28732 } 28733 28734 // Public functions 28735 28736 /** 28737 * Returns the format by name or all formats if no name is specified. 28738 * 28739 * @method get 28740 * @param {String} name Optional name to retrieve by. 28741 * @return {Array/Object} Array/Object with all registered formats or a specific format. 28742 */ 28743 function get(name) { 28744 return name ? formats[name] : formats; 28745 } 28746 28747 /** 28748 * Registers a specific format by name. 28749 * 28750 * @method register 28751 * @param {Object/String} name Name of the format for example "bold". 28752 * @param {Object/Array} format Optional format object or array of format variants 28753 * can only be omitted if the first arg is an object. 28754 */ 28755 function register(name, format) { 28756 if (name) { 28757 if (typeof name !== 'string') { 28758 each(name, function(format, name) { 28759 register(name, format); 28760 }); 28761 } else {
28762 // Force format into array and add it to internal collection 28763 format = format.length ? format : [format]; 28764 28765 each(format, function(format) { 28766 // Set deep to false by default on selector formats this to avoid removing 28767 // alignment on images inside paragraphs when alignment is changed on paragraphs 28768 if (format.deep === undef) { 28769 format.deep = !format.selector; 28770 } 28771 28772 // Default to true 28773 if (format.split === undef) { 28774 format.split = !format.selector || format.inline; 28775 } 28776 28777 // Default to true 28778 if (format.remove === undef && format.selector && !format.inline) { 28779 format.remove = 'none'; 28780 } 28781 28782 // Mark format as a mixed format inline + block level 28783 if (format.selector && format.inline) { 28784 format.mixed = true; 28785 format.block_expand = true; 28786 } 28787 28788 // Split classes if needed 28789 if (typeof format.classes === 'string') { 28790 format.classes = format.classes.split(/\s+/); 28791 } 28792 }); 28793 28794 formats[name] = format; 28795 } 28796 } 28797 } 28798 28799 /** 28800 * Unregister a specific format by name. 28801 * 28802 * @method unregister 28803 * @param {String} name Name of the format for example "bold". 28804 */ 28805 function unregister(name) { 28806 if (name && formats[name]) { 28807 delete formats[name]; 28808 } 28809 28810 return formats; 28811 } 28812 28813 function getTextDecoration(node) { 28814 var decoration; 28815 28816 ed.dom.getParent(node, function(n) { 28817 decoration = ed.dom.getStyle(n, 'text-decoration'); 28818 return decoration && decoration !== 'none'; 28819 }); 28820 28821 return decoration; 28822 } 28823 28824 function processUnderlineAndColor(node) { 28825 var textDecoration; 28826 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { 28827 textDecoration = getTextDecoration(node.parentNode); 28828 if (ed.dom.getStyle(node, 'color') && textDecoration) { 28829 ed.dom.setStyle(node, 'text-decoration', textDecoration); 28830 } else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) { 28831 ed.dom.setStyle(node, 'text-decoration', null); 28832 } 28833 } 28834 } 28835 28836 /** 28837 * Applies the specified format to the current selection or specified node. 28838 * 28839 * @method apply 28840 * @param {String} name Name of format to apply. 28841 * @param {Object} vars Optional list of variables to replace within format before applying it. 28842 * @param {Node} node Optional node to apply the format to defaults to current selection. 28843 */ 28844 function apply(name, vars, node) { 28845 var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed(); 28846 28847 function setElementFormat(elm, fmt) { 28848 fmt = fmt || format; 28849 28850 if (elm) { 28851 if (fmt.onformat) { 28852 fmt.onformat(elm, fmt, vars, node); 28853 } 28854 28855 each(fmt.styles, function(value, name) { 28856 dom.setStyle(elm, name, replaceVars(value, vars)); 28857 }); 28858 28859 // Needed for the WebKit span spam bug 28860 // TODO: Remove this once WebKit/Blink fixes this 28861 if (fmt.styles) { 28862 var styleVal = dom.getAttrib(elm, 'style'); 28863 28864 if (styleVal) { 28865 elm.setAttribute('data-mce-style', styleVal); 28866 } 28867 } 28868 28869 each(fmt.attributes, function(value, name) { 28870 dom.setAttrib(elm, name, replaceVars(value, vars)); 28871 }); 28872 28873 each(fmt.classes, function(value) { 28874 value = replaceVars(value, vars); 28875 28876 if (!dom.hasClass(elm, value)) { 28877 dom.addClass(elm, value); 28878 } 28879 }); 28880 } 28881 } 28882 28883 function adjustSelectionToVisibleSelection() { 28884 function findSelectionEnd(start, end) { 28885 var walker = new TreeWalker(end); 28886 for (node = walker.current(); node; node = walker.prev()) { 28887 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') { 28888 return node; 28889 } 28890 } 28891 } 28892 28893 // Adjust selection so that a end container with a end offset of zero is not included in the selection 28894 // as this isn't visible to the user. 28895 var rng = ed.selection.getRng(); 28896 var start = rng.startContainer; 28897 var end = rng.endContainer; 28898 28899 if (start != end && rng.endOffset === 0) { 28900 var newEnd = findSelectionEnd(start, end); 28901 var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length; 28902 28903 rng.setEnd(newEnd, endOffset); 28904 } 28905 28906 return rng; 28907 } 28908 28909 function applyRngStyle(rng, bookmark, node_specific) { 28910 var newWrappers = [], wrapName, wrapElm, contentEditable = true; 28911 28912 // Setup wrapper element 28913 wrapName = format.inline || format.block; 28914 wrapElm = dom.create(wrapName); 28915 setElementFormat(wrapElm); 28916 28917 rangeUtils.walk(rng, function(nodes) { 28918 var currentWrapElm; 28919 28920 /** 28921 * Process a list of nodes wrap them. 28922 */ 28923 function process(node) { 28924 var nodeName, parentName, found, hasContentEditableState, lastContentEditable; 28925 28926 lastContentEditable = contentEditable; 28927 nodeName = node.nodeName.toLowerCase(); 28928 parentName = node.parentNode.nodeName.toLowerCase(); 28929 28930 // Node has a contentEditable value 28931 if (node.nodeType === 1 && getContentEditable(node)) { 28932 lastContentEditable = contentEditable; 28933 contentEditable = getContentEditable(node) === "true"; 28934 hasContentEditableState = true; // We don't want to wrap the container only it's children 28935 } 28936 28937 // Stop wrapping on br elements 28938 if (isEq(nodeName, 'br')) { 28939 currentWrapElm = 0; 28940 28941 // Remove any br elements when we wrap things 28942 if (format.block) { 28943 dom.remove(node); 28944 } 28945 28946 return; 28947 } 28948 28949 // If node is wrapper type 28950 if (format.wrapper && matchNode(node, name, vars)) { 28951 currentWrapElm = 0; 28952 return; 28953 } 28954 28955 // Can we rename the block 28956 // TODO: Break this if up, too complex 28957 if (contentEditable && !hasContentEditableState && format.block && 28958 !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) { 28959 node = dom.rename(node, wrapName); 28960 setElementFormat(node); 28961 newWrappers.push(node); 28962 currentWrapElm = 0; 28963 return; 28964 } 28965 28966 // Handle selector patterns 28967 if (format.selector) { 28968 // Look for matching formats 28969 each(formatList, function(format) {
28970 // Check collapsed state if it exists 28971 if ('collapsed' in format && format.collapsed !== isCollapsed) { 28972 return; 28973 } 28974 28975 if (dom.is(node, format.selector) && !isCaretNode(node)) { 28976 setElementFormat(node, format); 28977 found = true; 28978 return false; 28979 } 28980 }); 28981 28982 // Continue processing if a selector match wasn't found and a inline element is defined 28983 if (!format.inline || found) { 28984 currentWrapElm = 0; 28985 return; 28986 } 28987 } 28988 28989 // Is it valid to wrap this item 28990 // TODO: Break this if up, too complex 28991 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) && 28992 !(!node_specific && node.nodeType === 3 && 28993 node.nodeValue.length === 1 && 28994 node.nodeValue.charCodeAt(0) === 65279) && 28995 !isCaretNode(node) && 28996 (!format.inline || !isBlock(node))) { 28997 // Start wrapping 28998 if (!currentWrapElm) { 28999 // Wrap the node 29000 currentWrapElm = dom.clone(wrapElm, FALSE); 29001 node.parentNode.insertBefore(currentWrapElm, node); 29002 newWrappers.push(currentWrapElm); 29003 } 29004 29005 currentWrapElm.appendChild(node); 29006 } else { 29007 // Start a new wrapper for possible children 29008 currentWrapElm = 0; 29009 29010 each(grep(node.childNodes), process); 29011 29012 if (hasContentEditableState) { 29013 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 29014 } 29015 29016 // End the last wrapper 29017 currentWrapElm = 0; 29018 } 29019 } 29020 29021 // Process siblings from range 29022 each(nodes, process); 29023 }); 29024 29025 // Apply formats to links as well to get the color of the underline to change as well 29026 if (format.links === true) { 29027 each(newWrappers, function(node) { 29028 function process(node) { 29029 if (node.nodeName === 'A') { 29030 setElementFormat(node, format); 29031 } 29032 29033 each(grep(node.childNodes), process); 29034 } 29035 29036 process(node); 29037 }); 29038 } 29039 29040 // Cleanup 29041 each(newWrappers, function(node) { 29042 var childCount; 29043 29044 function getChildCount(node) { 29045 var count = 0; 29046 29047 each(node.childNodes, function(node) { 29048 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) { 29049 count++; 29050 } 29051 }); 29052 29053 return count; 29054 } 29055 29056 function mergeStyles(node) { 29057 var child, clone; 29058 29059 each(node.childNodes, function(node) { 29060 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) { 29061 child = node; 29062 return FALSE; // break loop 29063 } 29064 }); 29065 29066 // If child was found and of the same type as the current node 29067 if (child && !isBookmarkNode(child) && matchName(child, format)) { 29068 clone = dom.clone(child, FALSE); 29069 setElementFormat(clone); 29070 29071 dom.replace(clone, node, TRUE); 29072 dom.remove(child, 1); 29073 } 29074 29075 return clone || node; 29076 } 29077 29078 childCount = getChildCount(node); 29079 29080 // Remove empty nodes but only if there is multiple wrappers and they are not block 29081 // elements so never remove single <h1></h1> since that would remove the 29082 // current empty block element where the caret is at 29083 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { 29084 dom.remove(node, 1); 29085 return; 29086 } 29087 29088 if (format.inline || format.wrapper) { 29089 // Merges the current node with it's children of similar type to reduce the number of elements 29090 if (!format.exact && childCount === 1) { 29091 node = mergeStyles(node); 29092 } 29093 29094 // Remove/merge children 29095 each(formatList, function(format) { 29096 // Merge all children of similar type will move styles from child to parent 29097 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span> 29098 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span> 29099 each(dom.select(format.inline, node), function(child) { 29100 if (isBookmarkNode(child)) { 29101 return; 29102 } 29103 29104 removeFormat(format, vars, child, format.exact ? child : null); 29105 }); 29106 }); 29107 29108 // Remove child if direct parent is of same type 29109 if (matchNode(node.parentNode, name, vars)) { 29110 dom.remove(node, 1); 29111 node = 0; 29112 return TRUE; 29113 } 29114 29115 // Look for parent with similar style format 29116 if (format.merge_with_parents) { 29117 dom.getParent(node.parentNode, function(parent) { 29118 if (matchNode(parent, name, vars)) { 29119 dom.remove(node, 1); 29120 node = 0; 29121 return TRUE; 29122 } 29123 }); 29124 } 29125 29126 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b> 29127 if (node && format.merge_siblings !== false) { 29128 node = mergeSiblings(getNonWhiteSpaceSibling(node), node); 29129 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE)); 29130 } 29131 } 29132 }); 29133 } 29134
29135 if (getContentEditable(selection.getNode()) === "false") { 29136 node = selection.getNode(); 29137 for (var i = 0, l = formatList.length; i < l; i++) { 29138 if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) { 29139 setElementFormat(node, formatList[i]); 29140 return; 29141 } 29142 } 29143 29144 return; 29145 } 29146 29147 if (format) { 29148 if (node) { 29149 if (node.nodeType) { 29150 rng = dom.createRng(); 29151 rng.setStartBefore(node); 29152 rng.setEndAfter(node); 29153 applyRngStyle(expandRng(rng, formatList), null, true); 29154 } else { 29155 applyRngStyle(node, null, true); 29156 } 29157 } else { 29158 if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) { 29159 // Obtain selection node before selection is unselected by applyRngStyle() 29160 var curSelNode = ed.selection.getNode(); 29161 29162 // If the formats have a default block and we can't find a parent block then 29163 // start wrapping it with a DIV this is for forced_root_blocks: false 29164 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way 29165 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) { 29166 apply(formatList[0].defaultBlock); 29167 } 29168 29169 // Apply formatting to selection 29170 ed.selection.setRng(adjustSelectionToVisibleSelection()); 29171 bookmark = selection.getBookmark(); 29172 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark); 29173 29174 // Colored nodes should be underlined so that the color of the underline matches the text color. 29175 if (format.styles && (format.styles.color || format.styles.textDecoration)) { 29176 walk(curSelNode, processUnderlineAndColor, 'childNodes'); 29177 processUnderlineAndColor(curSelNode); 29178 } 29179 29180 selection.moveToBookmark(bookmark); 29181 moveStart(selection.getRng(TRUE)); 29182 ed.nodeChanged(); 29183 } else { 29184 performCaretAction('apply', name, vars); 29185 } 29186 } 29187 } 29188 } 29189 29190 /** 29191 * Removes the specified format from the current selection or specified node. 29192 * 29193 * @method remove 29194 * @param {String} name Name of format to remove. 29195 * @param {Object} vars Optional list of variables to replace within format before removing it. 29196 * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection. 29197 */ 29198 function remove(name, vars, node, similar) { 29199 var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true; 29200 29201 // Merges the styles for each node 29202 function process(node) { 29203 var children, i, l, lastContentEditable, hasContentEditableState; 29204 29205 // Node has a contentEditable value 29206 if (node.nodeType === 1 && getContentEditable(node)) { 29207 lastContentEditable = contentEditable; 29208 contentEditable = getContentEditable(node) === "true"; 29209 hasContentEditableState = true; // We don't want to wrap the container only it's children 29210 } 29211 29212 // Grab the children first since the nodelist might be changed 29213 children = grep(node.childNodes); 29214 29215 // Process current node 29216 if (contentEditable && !hasContentEditableState) { 29217 for (i = 0, l = formatList.length; i < l; i++) { 29218 if (removeFormat(formatList[i], vars, node, node)) { 29219 break; 29220 } 29221 } 29222 } 29223 29224 // Process the children 29225 if (format.deep) { 29226 if (children.length) { 29227 for (i = 0, l = children.length; i < l; i++) { 29228 process(children[i]); 29229 } 29230 29231 if (hasContentEditableState) { 29232 contentEditable = lastContentEditable; // Restore last contentEditable state from stack 29233 } 29234 } 29235 } 29236 } 29237 29238 function findFormatRoot(container) { 29239 var formatRoot; 29240 29241 // Find format root 29242 each(getParents(container.parentNode).reverse(), function(parent) { 29243 var format; 29244 29245 // Find format root element 29246 if (!formatRoot && parent.id != '_start' && parent.id != '_end') { 29247 // Is the node matching the format we are looking for 29248 format = matchNode(parent, name, vars, similar); 29249 if (format && format.split !== false) { 29250 formatRoot = parent; 29251 } 29252 } 29253 }); 29254 29255 return formatRoot; 29256 } 29257 29258 function wrapAndSplit(formatRoot, container, target, split) { 29259 var parent, clone, lastClone, firstClone, i, formatRootParent; 29260 29261 // Format root found then clone formats and split it 29262 if (formatRoot) { 29263 formatRootParent = formatRoot.parentNode; 29264 29265 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) { 29266 clone = dom.clone(parent, FALSE); 29267 29268 for (i = 0; i < formatList.length; i++) { 29269 if (removeFormat(formatList[i], vars, clone, clone)) { 29270 clone = 0; 29271 break; 29272 } 29273 } 29274 29275 // Build wrapper node 29276 if (clone) { 29277 if (lastClone) { 29278 clone.appendChild(lastClone); 29279 } 29280 29281 if (!firstClone) { 29282 firstClone = clone; 29283 } 29284 29285 lastClone = clone; 29286 } 29287 } 29288 29289 // Never split block elements if the format is mixed 29290 if (split && (!format.mixed || !isBlock(formatRoot))) { 29291 container = dom.split(formatRoot, container); 29292 } 29293 29294 // Wrap container in cloned formats 29295 if (lastClone) { 29296 target.parentNode.insertBefore(lastClone, target); 29297 firstClone.appendChild(target); 29298 } 29299 } 29300 29301 return container; 29302 } 29303 29304 function splitToFormatRoot(container) { 29305 return wrapAndSplit(findFormatRoot(container), container, container, true); 29306 } 29307 29308 function unwrap(start) { 29309 var node = dom.get(start ? '_start' : '_end'), 29310 out = node[start ? 'firstChild' : 'lastChild']; 29311 29312 // If the end is placed within the start the result will be removed 29313 // So this checks if the out node is a bookmark node if it is it 29314 // checks for another more suitable node 29315 if (isBookmarkNode(out)) { 29316 out = out[start ? 'firstChild' : 'lastChild']; 29317 } 29318 29319 // Since dom.remove removes empty text nodes then we need to try to find a better node 29320 if (out.nodeType == 3 && out.data.length === 0) { 29321 out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling; 29322 } 29323 29324 dom.remove(node, true); 29325 29326 return out; 29327 } 29328 29329 function removeRngStyle(rng) { 29330 var startContainer, endContainer; 29331 var commonAncestorContainer = rng.commonAncestorContainer; 29332 29333 rng = expandRng(rng, formatList, TRUE); 29334 29335 if (format.split) { 29336 startContainer = getContainer(rng, TRUE); 29337 endContainer = getContainer(rng); 29338 29339 if (startContainer != endContainer) { 29340 // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN 29341 // so let's see if we can use the first child instead 29342 // This will happen if you triple click a table cell and use remove formatting 29343 if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) { 29344 if (startContainer.nodeName == "TR") { 29345 startContainer = startContainer.firstChild.firstChild || startContainer; 29346 } else { 29347 startContainer = startContainer.firstChild || startContainer; 29348 } 29349 } 29350 29351 // Try to adjust endContainer as well if cells on the same row were selected - bug #6410 29352 if (commonAncestorContainer && 29353 /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) && 29354 isTableCell(endContainer) && endContainer.firstChild) { 29355 endContainer = endContainer.firstChild || endContainer; 29356 } 29357 29358 if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) && 29359 !isTableCell(startContainer) && !isTableCell(endContainer)) { 29360 startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'}); 29361 splitToFormatRoot(startContainer); 29362 startContainer = unwrap(TRUE); 29363 return; 29364 } 29365 29366 // Wrap start/end nodes in span element since these might be cloned/moved 29367 startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
29368 endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'}); 29369 29370 // Split start/end 29371 splitToFormatRoot(startContainer); 29372 splitToFormatRoot(endContainer); 29373 29374 // Unwrap start/end to get real elements again 29375 startContainer = unwrap(TRUE); 29376 endContainer = unwrap(); 29377 } else { 29378 startContainer = endContainer = splitToFormatRoot(startContainer); 29379 } 29380 29381 // Update range positions since they might have changed after the split operations 29382 rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer; 29383 rng.startOffset = nodeIndex(startContainer); 29384 rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer; 29385 rng.endOffset = nodeIndex(endContainer) + 1; 29386 } 29387 29388 // Remove items between start/end 29389 rangeUtils.walk(rng, function(nodes) { 29390 each(nodes, function(node) { 29391 process(node); 29392 29393 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. 29394 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && 29395 node.parentNode && getTextDecoration(node.parentNode) === 'underline') { 29396 removeFormat({ 29397 'deep': false, 29398 'exact': true, 29399 'inline': 'span', 29400 'styles': { 29401 'textDecoration': 'underline' 29402 } 29403 }, null, node); 29404 } 29405 }); 29406 }); 29407 } 29408 29409 // Handle node 29410 if (node) { 29411 if (node.nodeType) { 29412 rng = dom.createRng(); 29413 rng.setStartBefore(node); 29414 rng.setEndAfter(node); 29415 removeRngStyle(rng); 29416 } else { 29417 removeRngStyle(node); 29418 } 29419 29420 return; 29421 } 29422 29423 if (getContentEditable(selection.getNode()) === "false") { 29424 node = selection.getNode(); 29425 for (var i = 0, l = formatList.length; i < l; i++) { 29426 if (formatList[i].ceFalseOverride) { 29427 if (removeFormat(formatList[i], vars, node, node)) { 29428 break; 29429 } 29430 } 29431 } 29432 29433 return; 29434 } 29435 29436 if (!selection.isCollapsed() || !format.inline || dom.select('td.mce
29436-item-selected,th.mce-item-selected').length) { 29437 bookmark = selection.getBookmark(); 29438 removeRngStyle(selection.getRng(TRUE)); 29439 selection.moveToBookmark(bookmark); 29440 29441 // Check if start element still has formatting then we are at: "<b>text|</b>text" 29442 // and need to move the start into the next text node 29443 if (format.inline && match(name, vars, selection.getStart())) { 29444 moveStart(selection.getRng(true)); 29445 } 29446 29447 ed.nodeChanged(); 29448 } else { 29449 performCaretAction('remove', name, vars, similar); 29450 } 29451 } 29452 29453 /** 29454 * Toggles the specified format on/off. 29455 * 29456 * @method toggle 29457 * @param {String} name Name of format to apply/remove. 29458 * @param {Object} vars Optional list of variables to replace within format before applying/removing it. 29459 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection. 29460 */ 29461 function toggle(name, vars, node) { 29462 var fmt = get(name); 29463 29464 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) { 29465 remove(name, vars, node); 29466 } else { 29467 apply(name, vars, node); 29468 } 29469 } 29470 29471 /** 29472 * Return true/false if the specified node has the specified format. 29473 * 29474 * @method matchNode 29475 * @param {Node} node Node to check the format on. 29476 * @param {String} name Format name to check. 29477 * @param {Object} vars Optional list of variables to replace before checking it. 29478 * @param {Boolean} similar Match format that has similar properties. 29479 * @return {Object} Returns the format object it matches or undefined if it doesn't match. 29480 */ 29481 function matchNode(node, name, vars, similar) { 29482 var formatList = get(name), format, i, classes; 29483 29484 function matchItems(node, format, item_name) { 29485 var key, value, items = format[item_name], i; 29486 29487 // Custom match 29488 if (format.onmatch) { 29489 return format.onmatch(node, format, item_name); 29490 } 29491 29492 // Check all items 29493 if (items) { 29494 // Non indexed object 29495 if (items.length === undef) { 29496 for (key in items) { 29497 if (items.hasOwnProperty(key)) { 29498 if (item_name === 'attributes') { 29499 value = dom.getAttrib(node, key); 29500 } else { 29501 value = getStyle(node, key); 29502 } 29503 29504 if (similar && !value && !format.exact) { 29505 return; 29506 } 29507 29508 if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) { 29509 return; 29510 } 29511 } 29512 } 29513 } else { 29514 // Only one match needed for indexed arrays 29515 for (i = 0; i < items.length; i++) { 29516 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) { 29517 return format; 29518 } 29519 } 29520 } 29521 } 29522 29523 return format; 29524 } 29525 29526 if (formatList && node) { 29527 // Check each format in list 29528 for (i = 0; i < formatList.length; i++) { 29529 format = formatList[i]; 29530 29531 // Name name, attributes, styles and classes 29532 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) { 29533 // Match classes 29534 if ((classes = format.classes)) { 29535 for (i = 0; i < classes.length; i++) { 29536 if (!dom.hasClass(node, classes[i])) { 29537 return; 29538 } 29539 } 29540 } 29541 29542 return format; 29543 } 29544 } 29545 } 29546 } 29547 29548 /** 29549 * Matches the current selection or specified node against the specified format name. 29550 * 29551 * @method match 29552 * @param {String} name Name of format to match. 29553 * @param {Object} vars Optional list of variables to replace before checking it. 29554 * @param {Node} node Optional node to check. 29555 * @return {boolean} true/false if the specified selection/node matches the format. 29556 */ 29557 function match(name, vars, node) { 29558 var startNode; 29559 29560 function matchParents(node) { 29561 var root = dom.getRoot(); 29562 29563 if (node === root) { 29564 return false; 29565 } 29566 29567 // Find first node with similar format settings 29568 node = dom.getParent(node, function(node) { 29569 return node.parentNode === root || !!matchNode(node, name, vars, true); 29570 }); 29571 29572 // Do an exact check on the similar format element 29573 return matchNode(node, name, vars); 29574 } 29575
29576 // Check specified node 29577 if (node) { 29578 return matchParents(node); 29579 } 29580 29581 // Check selected node 29582 node = selection.getNode(); 29583 if (matchParents(node)) { 29584 return TRUE; 29585 } 29586 29587 // Check start node if it's different 29588 startNode = selection.getStart(); 29589 if (startNode != node) { 29590 if (matchParents(startNode)) { 29591 return TRUE; 29592 } 29593 } 29594 29595 return FALSE; 29596 } 29597 29598 /** 29599 * Matches the current selection against the array of formats and returns a new array with matching formats. 29600 * 29601 * @method matchAll 29602 * @param {Array} names Name of format to match. 29603 * @param {Object} vars Optional list of variables to replace before checking it. 29604 * @return {Array} Array with matched formats. 29605 */ 29606 function matchAll(names, vars) { 29607 var startElement, matchedFormatNames = [], checkedMap = {}; 29608 29609 // Check start of selection for formats 29610 startElement = selection.getStart(); 29611 dom.getParent(startElement, function(node) { 29612 var i, name; 29613 29614 for (i = 0; i < names.length; i++) { 29615 name = names[i]; 29616 29617 if (!checkedMap[name] && matchNode(node, name, vars)) { 29618 checkedMap[name] = true; 29619 matchedFormatNames.push(name); 29620 } 29621 } 29622 }, dom.getRoot()); 29623 29624 return matchedFormatNames; 29625 } 29626 29627 /** 29628 * Returns true/false if the specified format can be applied to the current selection or not. It 29629 * will currently only check the state for selector formats, it returns true on all other format types. 29630 * 29631 * @method canApply 29632 * @param {String} name Name of format to check. 29633 * @return {boolean} true/false if the specified format can be applied to the current selection/node. 29634 */ 29635 function canApply(name) { 29636 var formatList = get(name), startNode, parents, i, x, selector; 29637 29638 if (formatList) { 29639 startNode = selection.getStart(); 29640 parents = getParents(startNode); 29641 29642 for (x = formatList.length - 1; x >= 0; x--) { 29643 selector = formatList[x].selector; 29644 29645 // Format is not selector based then always return TRUE 29646 // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line 29647 if (!selector || formatList[x].defaultBlock) { 29648 return TRUE; 29649 } 29650 29651 for (i = parents.length - 1; i >= 0; i--) { 29652 if (dom.is(parents[i], selector)) { 29653 return TRUE; 29654 } 29655 } 29656 } 29657 } 29658 29659 return FALSE; 29660 } 29661 29662 /** 29663 * Executes the specified callback when the current selection matches the formats or not. 29664 * 29665 * @method formatChanged 29666 * @param {String} formats Comma separated list of formats to check for. 29667 * @param {function} callback Callback with state and args when the format is changed/toggled on/off. 29668 * @param {Boolean} similar True/false state if the match should handle similar or exact formats. 29669 */ 29670 function formatChanged(formats, callback, similar) { 29671 var currentFormats; 29672 29673 // Setup format node change logic 29674 if (!formatChangeData) { 29675 formatChangeData = {}; 29676 currentFormats = {}; 29677 29678 ed.on('NodeChange', function(e) { 29679 var parents = getParents(e.element), matchedFormats = {}; 29680 29681 // Ignore bogus nodes like the <a> tag created by moveStart() 29682 parents = Tools.grep(parents, function(node) { 29683 return node.nodeType == 1 && !node.getAttribute('data-mce-bogus'); 29684 }); 29685 29686 // Check for new formats 29687 each(formatChangeData, function(callbacks, format) { 29688 each(parents, function(node) { 29689 if (matchNode(node, format, {}, callbacks.similar)) { 29690 if (!currentFormats[format]) { 29691 // Execute callbacks 29692 each(callbacks, function(callback) { 29693 callback(true, {node: node, format: format, parents: parents}); 29694 }); 29695 29696 currentFormats[format] = callbacks; 29697 } 29698 29699 matchedFormats[format] = callbacks; 29700 return false; 29701 } 29702 }); 29703 }); 29704 29705 // Check if current formats still match 29706 each(currentFormats, function(callbacks, format) { 29707 if (!matchedFormats[format]) { 29708 delete currentFormats[format]; 29709 29710 each(callbacks, function(callback) { 29711 callback(false, {node: e.element, format: format, parents: parents}); 29712 }); 29713 } 29714 }); 29715 }); 29716 } 29717 29718 // Add format listeners 29719 each(formats.split(','), function(format) { 29720 if (!formatChangeData[format]) { 29721 formatChangeData[format] = []; 29722 formatChangeData[format].similar = similar; 29723 } 29724 29725 formatChangeData[format].push(callback); 29726 }); 29727 29728 return this; 29729 } 29730 29731 /** 29732 * Returns a preview css text for the specified format. 29733 * 29734 * @method getCssText 29735 * @param {String/Object} format Format to generate preview css text for. 29736 * @return {String} Css text for the specified format. 29737 * @example 29738 * var cssText1 = editor.formatter.getCssText('bold'); 29739 * var cssText2 = editor.formatter.getCssText({inline: 'b'}); 29740 */ 29741 function getCssText(format) { 29742 return Preview.getCssText(ed, format); 29743 } 29744 29745 // Expose to public 29746 extend(this, { 29747 get: get, 29748 register: register, 29749 unregister: unregister, 29750 apply: apply, 29751 remove: remove, 29752 toggle: toggle, 29753 match: match, 29754 matchAll: matchAll, 29755 matchNode: matchNode, 29756 canApply: canApply, 29757 formatChanged: formatChanged, 29758 getCssText: getCssText 29759 }); 29760 29761 // Initialize 29762 defaultFormats(); 29763 addKeyboardShortcuts(); 29764 ed.on('BeforeGetContent', function(e) { 29765 if (markCaretContainersBogus && e.format != 'raw') { 29766 markCaretContainersBogus(); 29767 } 29768 }); 29769 ed.on('mouseup keydown', function(e) { 29770 if (disableCaretContainer) { 29771 disableCaretContainer(e); 29772 } 29773 }); 29774 29775 // Private functions 29776 29777 /** 29778 * Checks if the specified nodes name matches the format inline/block or selector. 29779 * 29780 * @private 29781 * @param {Node} node Node to match against the specified format. 29782 * @param {Object} format Format object o match with. 29783 * @return {boolean} true/false if the format matches. 29784 */ 29785 function matchName(node, format) { 29786 // Check for inline match 29787 if (isEq(node, format.inline)) { 29788 return TRUE; 29789 } 29790 29791 // Check for block match 29792 if (isEq(node, format.block)) { 29793 return TRUE; 29794 } 29795 29796 // Check for selector match 29797 if (format.selector) { 29798 return node.nodeType == 1 && dom.is(node, format.selector); 29799 } 29800 } 29801 29802 /** 29803 * Compares two string/nodes regardless of their case. 29804 * 29805 * @private 29806 * @param {String/Node} str1 Node or string to compare. 29807 * @param {String/Node} str2 Node or string to compare. 29808 * @return {boolean} True/false if they match. 29809 */ 29810 function isEq(str1, str2) { 29811 str1 = str1 || ''; 29812 str2 = str2 || ''; 29813 29814 str1 = '' + (str1.nodeName || str1); 29815 str2 = '' + (str2.nodeName || str2); 29816 29817 return str1.toLowerCase() == str2.toLowerCase(); 29818 } 29819 29820 /** 29821 * Returns the style by name on the specified node. This method modifies the style 29822 * contents to make it more easy to match. This will resolve a few browser issues. 29823 * 29824 * @private 29825 * @param {Node} node to get style from. 29826 * @param {String} name Style name to get. 29827 * @return {String} Style item value. 29828 */ 29829 function getStyle(node, name) { 29830 return normalizeStyleValue(dom.getStyle(node, name), name); 29831 } 29832 29833 /** 29834 * Normalize style value by name. This method modifies the style contents 29835 * to make it more easy to match. This will resolve a few browser issues. 29836 * 29837 * @private 29838 * @param {String} value Value to get style from. 29839 * @param {String} name Style name to get. 29840 * @return {String} Style item value. 29841 */ 29842 function normalizeStyleValue(value, name) { 29843 // Force the format to hex 29844 if (name == 'color' || name == 'backgroundColor') { 29845 value = dom.toHex(value); 29846 } 29847 29848 // Opera will return bold as 700 29849 if (name == 'fontWeight' && value == 700) { 29850 value = 'bold'; 29851 } 29852
29853 // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font" 29854 if (name == 'fontFamily') { 29855 value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ','); 29856 } 29857 29858 return '' + value; 29859 } 29860 29861 /** 29862 * Replaces variables in the value. The variable format is %var. 29863 * 29864 * @private 29865 * @param {String} value Value to replace variables in. 29866 * @param {Object} vars Name/value array with variables to replace. 29867 * @return {String} New value with replaced variables. 29868 */ 29869 function replaceVars(value, vars) { 29870 if (typeof value != "string") { 29871 value = value(vars); 29872 } else if (vars) { 29873 value = value.replace(/%(\w+)/g, function(str, name) { 29874 return vars[name] || str; 29875 }); 29876 } 29877 29878 return value; 29879 } 29880 29881 function isWhiteSpaceNode(node) { 29882 return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue); 29883 } 29884 29885 function wrap(node, name, attrs) { 29886 var wrapper = dom.create(name, attrs); 29887 29888 node.parentNode.insertBefore(wrapper, node); 29889 wrapper.appendChild(node); 29890 29891 return wrapper; 29892 } 29893 29894 /** 29895 * Expands the specified range like object to depending on format. 29896 * 29897 * For example on block formats it will move the start/end position 29898 * to the beginning of the current block. 29899 * 29900 * @private 29901 * @param {Object} rng Range like object. 29902 * @param {Array} format Array with formats to expand by. 29903 * @param {Boolean} remove 29904 * @return {Object} Expanded range like object. 29905 */ 29906 function expandRng(rng, format, remove) { 29907 var lastIdx, leaf, endPoint, 29908 startContainer = rng.startContainer, 29909 startOffset = rng.startOffset, 29910 endContainer = rng.endContainer, 29911 endOffset = rng.endOffset; 29912 29913 // This function walks up the tree if there is no siblings before/after the node 29914 function findParentContainer(start) { 29915 var container, parent, sibling, siblingName, root; 29916 29917 container = parent = start ? startContainer : endContainer; 29918 siblingName = start ? 'previousSibling' : 'nextSibling'; 29919 root = dom.getRoot(); 29920 29921 function isBogusBr(node) { 29922 return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling; 29923 } 29924 29925 // If it's a text node and the offset is inside the text 29926 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) { 29927 if (start ? startOffset > 0 : endOffset < container.nodeValue.length) { 29928 return container; 29929 } 29930 } 29931 29932 /*eslint no-constant-condition:0 */ 29933 while (true) { 29934 // Stop expanding on block elements 29935 if (!format[0].block_expand && isBlock(parent)) { 29936 return parent; 29937 } 29938 29939 // Walk left/right 29940 for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) { 29941 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) { 29942 return parent; 29943 } 29944 } 29945 29946 // Check if we can move up are we at root level or body level 29947 if (parent == root || parent.parentNode == root) { 29948 container = parent; 29949 break; 29950 } 29951 29952 parent = parent.parentNode; 29953 } 29954 29955 return container; 29956 } 29957 29958 // This function walks down the tree to find the leaf at the selection. 29959 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. 29960 function findLeaf(node, offset) { 29961 if (offset === undef) { 29962 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 29963 } 29964 29965 while (node && node.hasChildNodes()) { 29966 node = node.childNodes[offset]; 29967 if (node) { 29968 offset = node.nodeType === 3 ? node.length : node.childNodes.length; 29969 } 29970 } 29971 return {node: node, offset: offset}; 29972 } 29973 29974 // If index based start position then resolve it 29975 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { 29976 lastIdx = startContainer.childNodes.length - 1; 29977 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset]; 29978 29979 if (startContainer.nodeType == 3) { 29980 startOffset = 0; 29981 } 29982 } 29983 29984 // If index based end position then resolve it 29985 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) { 29986 lastIdx = endContainer.childNodes.length - 1; 29987 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1]; 29988 29989 if (endContainer.nodeType == 3) { 29990 endOffset = endContainer.nodeValue.length; 29991 } 29992 } 29993 29994 // Expands the node to the closes contentEditable false element if it exists 29995 function findParentContentEditable(node) { 29996 var parent = node; 29997 29998 while (parent) { 29999 if (parent.nodeType === 1 && getContentEditable(parent)) {
30000 return getContentEditable(parent) === "false" ? parent : node; 30001 } 30002 30003 parent = parent.parentNode; 30004 } 30005 30006 return node; 30007 } 30008 30009 function findWordEndPoint(container, offset, start) { 30010 var walker, node, pos, lastTextNode; 30011 30012 function findSpace(node, offset) { 30013 var pos, pos2, str = node.nodeValue; 30014 30015 if (typeof offset == "undefined") { 30016 offset = start ? str.length : 0; 30017 } 30018 30019 if (start) { 30020 pos = str.lastIndexOf(' ', offset); 30021 pos2 = str.lastIndexOf('\u00a0', offset); 30022 pos = pos > pos2 ? pos : pos2; 30023 30024 // Include the space on remove to avoid tag soup 30025 if (pos !== -1 && !remove) { 30026 pos++; 30027 } 30028 } else { 30029 pos = str.indexOf(' ', offset); 30030 pos2 = str.indexOf('\u00a0', offset); 30031 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2; 30032 } 30033 30034 return pos; 30035 } 30036 30037 if (container.nodeType === 3) { 30038 pos = findSpace(container, offset); 30039 30040 if (pos !== -1) { 30041 return {container: container, offset: pos}; 30042 } 30043 30044 lastTextNode = container; 30045 } 30046 30047 // Walk the nodes inside the block 30048 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody()); 30049 while ((node = walker[start ? 'prev' : 'next']())) { 30050 if (node.nodeType === 3) { 30051 lastTextNode = node; 30052 pos = findSpace(node); 30053 30054 if (pos !== -1) { 30055 return {container: node, offset: pos}; 30056 } 30057 } else if (isBlock(node)) { 30058 break; 30059 } 30060 } 30061 30062 if (lastTextNode) { 30063 if (start) { 30064 offset = 0; 30065 } else { 30066 offset = lastTextNode.length; 30067 } 30068 30069 return {container: lastTextNode, offset: offset}; 30070 } 30071 } 30072 30073 function findSelectorEndPoint(container, sibling_name) { 30074 var parents, i, y, curFormat; 30075 30076 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) { 30077 container = container[sibling_name]; 30078 } 30079 30080 parents = getParents(container); 30081 for (i = 0; i < parents.length; i++) { 30082 for (y = 0; y < format.length; y++) { 30083 curFormat = format[y]; 30084 30085 // If collapsed state is set then skip formats that doesn't match that 30086 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) { 30087 continue; 30088 } 30089 30090 if (dom.is(parents[i], curFormat.selector)) { 30091 return parents[i]; 30092 } 30093 } 30094 } 30095 30096 return container; 30097 } 30098 30099 function findBlockEndPoint(container, sibling_name) { 30100 var node, root = dom.getRoot(); 30101 30102 // Expand to block of similar type 30103 if (!format[0].wrapper) { 30104 node = dom.getParent(container, format[0].block, root); 30105 } 30106 30107 // Expand to first wrappable block element or any block element 30108 if (!node) { 30109 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) { 30110 // Fixes #6183 where it would expand to editable parent element in inline mode 30111 return node != root && isTextBlock(node); 30112 }); 30113 } 30114 30115 // Exclude inner lists from wrapping 30116 if (node && format[0].wrapper) { 30117 node = getParents(node, 'ul,ol').reverse()[0] || node; 30118 } 30119 30120 // Didn't find a block element look for first/last wrappable element 30121 if (!node) { 30122 node = container; 30123 30124 while (node[sibling_name] && !isBlock(node[sibling_name])) { 30125 node = node[sibling_name]; 30126 30127 // Break on BR but include it will be removed later on 30128 // we can't remove it now since we need to check if it can be wrapped 30129 if (isEq(node, 'br')) { 30130 break; 30131 } 30132 } 30133 } 30134 30135 return node || container; 30136 } 30137 30138 // Expand to closest contentEditable element 30139 startContainer = findParentContentEditable(startContainer); 30140 endContainer = findParentContentEditable(endContainer); 30141 30142 // Exclude bookmark nodes if possible 30143 if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) { 30144 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode; 30145 startContainer = startContainer.nextSibling || startContainer; 30146 30147 if (startContainer.nodeType == 3) { 30148 startOffset = 0; 30149 } 30150 } 30151 30152 if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) { 30153 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode; 30154 endContainer = endContainer.previousSibling || endContainer; 30155 30156 if (endContainer.nodeType == 3) { 30157 endOffset = endContainer.length; 30158 } 30159 } 30160 30161 if (format[0].inline) { 30162 if (rng.collapsed) { 30163 // Expand left to closest word boundary 30164 endPoint = findWordEndPoint(startContainer, startOffset, true); 30165 if (endPoint) { 30166 startContainer = endPoint.container; 30167 startOffset = endPoint.offset; 30168 } 30169 30170 // Expand right to closest word boundary 30171 endPoint = findWordEndPoint(endContainer, endOffset); 30172 if (endPoint) { 30173 endContainer = endPoint.container; 30174 endOffset = endPoint.offset; 30175 } 30176 } 30177 30178 // Avoid applying formatting to a trailing space. 30179 leaf = findLeaf(endContainer, endOffset); 30180 if (leaf.node) { 30181 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) { 30182 leaf = findLeaf(leaf.node.previousSibling); 30183 } 30184 30185 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && 30186 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { 30187 30188 if (leaf.offset > 1) { 30189 endContainer = leaf.node;
30190 endContainer.splitText(leaf.offset - 1); 30191 } 30192 } 30193 } 30194 } 30195 30196 // Move start/end point up the tree if the leaves are sharp and if we are in different containers 30197 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>! 30198 // This will reduce the number of wrapper elements that needs to be created 30199 // Move start point up the tree 30200 if (format[0].inline || format[0].block_expand) { 30201 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) { 30202 startContainer = findParentContainer(true); 30203 } 30204 30205 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) { 30206 endContainer = findParentContainer(); 30207 } 30208 } 30209 30210 // Expand start/end container to matching selector 30211 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { 30212 // Find new startContainer/endContainer if there is better one 30213 startContainer = findSelectorEndPoint(startContainer, 'previousSibling'); 30214 endContainer = findSelectorEndPoint(endContainer, 'nextSibling'); 30215 } 30216 30217 // Expand start/end container to matching block element or text node 30218 if (format[0].block || format[0].selector) { 30219 // Find new startContainer/endContainer if there is better one 30220 startContainer = findBlockEndPoint(startContainer, 'previousSibling'); 30221 endContainer = findBlockEndPoint(endContainer, 'nextSibling'); 30222 30223 // Non block element then try to expand up the leaf 30224 if (format[0].block) { 30225 if (!isBlock(startContainer)) { 30226 startContainer = findParentContainer(true); 30227 } 30228 30229 if (!isBlock(endContainer)) { 30230 endContainer = findParentContainer(); 30231 } 30232 } 30233 } 30234 30235 // Setup index for startContainer 30236 if (startContainer.nodeType == 1) { 30237 startOffset = nodeIndex(startContainer); 30238 startContainer = startContainer.parentNode; 30239 } 30240 30241 // Setup index for endContainer 30242 if (endContainer.nodeType == 1) { 30243 endOffset = nodeIndex(endContainer) + 1; 30244 endContainer = endContainer.parentNode; 30245 } 30246 30247 // Return new range like object 30248 return { 30249 startContainer: startContainer, 30250 startOffset: startOffset, 30251 endContainer: endContainer, 30252 endOffset: endOffset 30253 }; 30254 } 30255 30256 function isColorFormatAndAnchor(node, format) { 30257 return format.links && node.tagName == 'A'; 30258 } 30259 30260 /** 30261 * Removes the specified format for the specified node. It will also remove the node if it doesn't have 30262 * any attributes if the format specifies it to do so. 30263 * 30264 * @private 30265 * @param {Object} format Format object with items to remove from node. 30266 * @param {Object} vars Name/value object with variables to apply to format. 30267 * @param {Node} node Node to remove the format styles on. 30268 * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node. 30269 * @return {Boolean} True/false if the node was removed or not. 30270 */ 30271 function removeFormat(format, vars, node, compare_node) { 30272 var i, attrs, stylesModified; 30273 30274 // Check if node matches format 30275 if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) { 30276 return FALSE; 30277 } 30278 30279 // Should we compare with format attribs and styles 30280 if (format.remove != 'all') { 30281 // Remove styles 30282 each(format.styles, function(value, name) { 30283 value = normalizeStyleValue(replaceVars(value, vars), name); 30284 30285 // Indexed array 30286 if (typeof name === 'number') { 30287 name = value; 30288 compare_node = 0; 30289 } 30290 30291 if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) { 30292 dom.setStyle(node, name, ''); 30293 } 30294 30295 stylesModified = 1; 30296 }); 30297 30298 // Remove style attribute if it's empty 30299 if (stylesModified && dom.getAttrib(node, 'style') === '') { 30300 node.removeAttribute('style'); 30301 node.removeAttribute('data-mce-style'); 30302 } 30303 30304 // Remove attributes 30305 each(format.attributes, function(value, name) { 30306 var valueOut; 30307 30308 value = replaceVars(value, vars); 30309 30310 // Indexed array 30311 if (typeof name === 'number') { 30312 name = value; 30313 compare_node = 0; 30314 } 30315 30316 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
30317 // Keep internal classes 30318 if (name == 'class') { 30319 value = dom.getAttrib(node, name); 30320 if (value) { 30321 // Build new class value where everything is removed except the internal prefixed classes 30322 valueOut = ''; 30323 each(value.split(/\s+/), function(cls) { 30324 if (/mce\-\w+/.test(cls)) { 30325 valueOut += (valueOut ? ' ' : '') + cls; 30326 } 30327 }); 30328 30329 // We got some internal classes left 30330 if (valueOut) { 30331 dom.setAttrib(node, name, valueOut); 30332 return; 30333 } 30334 } 30335 } 30336 30337 // IE6 has a bug where the attribute doesn't get removed correctly 30338 if (name == "class") { 30339 node.removeAttribute('className'); 30340 } 30341 30342 // Remove mce prefixed attributes 30343 if (MCE_ATTR_RE.test(name)) { 30344 node.removeAttribute('data-mce-' + name); 30345 } 30346 30347 node.removeAttribute(name); 30348 } 30349 }); 30350 30351 // Remove classes 30352 each(format.classes, function(value) { 30353 value = replaceVars(value, vars); 30354 30355 if (!compare_node || dom.hasClass(compare_node, value)) { 30356 dom.removeClass(node, value); 30357 } 30358 }); 30359 30360 // Check for non internal attributes 30361 attrs = dom.getAttribs(node); 30362 for (i = 0; i < attrs.length; i++) { 30363 if (attrs[i].nodeName.indexOf('_') !== 0) { 30364 return FALSE; 30365 } 30366 } 30367 } 30368 30369 // Remove the inline child if it's empty for example <b> or <span> 30370 if (format.remove != 'none') { 30371 removeNode(node, format); 30372 return TRUE; 30373 } 30374 } 30375 30376 /** 30377 * Removes the node and wrap it's children in paragraphs before doing so or 30378 * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled. 30379 * 30380 * If the div in the node below gets removed: 30381 * text<div>text</div>text 30382 * 30383 * Output becomes: 30384 * text<div><br />text<br /></div>text 30385 * 30386 * So when the div is removed the result is: 30387 * text<br />text<br />text 30388 * 30389 * @private 30390 * @param {Node} node Node to remove + apply BR/P elements to. 30391 * @param {Object} format Format rule. 30392 * @return {Node} Input node. 30393 */ 30394 function removeNode(node, format) { 30395 var parentNode = node.parentNode, rootBlockElm; 30396 30397 function find(node, next, inc) { 30398 node = getNonWhiteSpaceSibling(node, next, inc); 30399 30400 return !node || (node.nodeName == 'BR' || isBlock(node)); 30401 } 30402 30403 if (format.block) { 30404 if (!forcedRootBlock) { 30405 // Append BR elements if needed before we remove the block 30406 if (isBlock(node) && !isBlock(parentNode)) { 30407 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) { 30408 node.insertBefore(dom.create('br'), node.firstChild); 30409 } 30410 30411 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) { 30412 node.appendChild(dom.create('br')); 30413 } 30414 } 30415 } else { 30416 // Wrap the block in a forcedRootBlock if we are at the root of document 30417 if (parentNode == dom.getRoot()) { 30418 if (!format.list_block || !isEq(node, format.list_block)) { 30419 each(grep(node.childNodes), function(node) { 30420 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) { 30421 if (!rootBlockElm) { 30422 rootBlockElm = wrap(node, forcedRootBlock); 30423 dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs); 30424 } else { 30425 rootBlockElm.appendChild(node); 30426 } 30427 } else { 30428 rootBlockElm = 0; 30429 } 30430 }); 30431 } 30432 } 30433 } 30434 } 30435 30436 // Never remove nodes that isn't the specified inline element if a selector is specified too 30437 if (format.selector && format.inline && !isEq(format.inline, node)) { 30438 return; 30439 } 30440 30441 dom.remove(node, 1); 30442 } 30443 30444 /** 30445 * Returns the next/previous non whitespace node. 30446 * 30447 * @private 30448 * @param {Node} node Node to start at. 30449 * @param {boolean} next (Optional) Include next or previous node defaults to previous. 30450 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false. 30451 * @return {Node} Next or previous node or undefined if it wasn't found. 30452 */ 30453 function getNonWhiteSpaceSibling(node, next, inc) { 30454 if (node) { 30455 next = next ? 'nextSibling' : 'previousSibling'; 30456 30457 for (node = inc ? node : node[next]; node; node = node[next]) { 30458 if (node.nodeType == 1 || !isWhiteSpaceNode(node)) { 30459 return node; 30460 } 30461 } 30462 } 30463 } 30464 30465 /** 30466 * Merges the next/previous sibling element if they match. 30467 * 30468 * @private 30469 * @param {Node} prev Previous node to compare/merge. 30470 * @param {Node} next Next node to compare/merge. 30471 * @return {Node} Next node if we didn't merge and prev node if we did. 30472 */ 30473 function mergeSiblings(prev, next) { 30474 var sibling, tmpSibling, elementUtils = new ElementUtils(dom); 30475 30476 function findElementSibling(node, sibling_name) { 30477 for (sibling = node; sibling; sibling = sibling[sibling_name]) { 30478 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) { 30479 return node; 30480 } 30481 30482 if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) { 30483 return sibling; 30484 } 30485 } 30486 30487 return node; 30488 } 30489 30490 // Check if next/prev exists and that they are elements 30491 if (prev && next) { 30492 // If previous sibling is empty then jump over it 30493 prev = findElementSibling(prev, 'previousSibling'); 30494 next = findElementSibling(next, 'nextSibling'); 30495 30496 // Compare next and previous nodes 30497 if (elementUtils.compare(prev, next)) { 30498 // Append nodes between 30499 for (sibling = prev.nextSibling; sibling && sibling != next;) { 30500 tmpSibling = sibling; 30501 sibling = sibling.nextSibling; 30502 prev.appendChild(tmpSibling); 30503 } 30504 30505 // Remove next node 30506 dom.remove(next); 30507 30508 // Move children into prev node 30509 each(grep(next.childNodes), function(node) { 30510 prev.appendChild(node); 30511 }); 30512 30513 return prev; 30514 } 30515 } 30516 30517 return next; 30518 } 30519 30520 function getContainer(rng, start) { 30521 var container, offset, lastIdx; 30522 30523 container = rng[start ? 'startContainer' : 'endContainer']; 30524 offset = rng[start ? 'startOffset' : 'endOffset']; 30525 30526 if (container.nodeType == 1) { 30527 lastIdx = container.childNodes.length - 1; 30528 30529 if (!start && offset) { 30530 offset--; 30531 } 30532 30533 container = container.childNodes[offset > lastIdx ? lastIdx : offset]; 30534 } 30535 30536 // If start text node is excluded then walk to the next node 30537 if (container.nodeType === 3 && start && offset >= container.nodeValue.length) { 30538 container = new TreeWalker(container, ed.getBody()).next() || container; 30539 } 30540 30541 // If end text node is excluded then walk to the previous node 30542 if (container.nodeType === 3 && !start && offset === 0) { 30543 container = new TreeWalker(container, ed.getBody()).prev() || container; 30544 } 30545 30546 return container; 30547 } 30548 30549 function performCaretAction(type, name, vars, similar) { 30550 var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug; 30551 30552 // Creates a caret container bogus element 30553 function createCaretContainer(fill) { 30554 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''}); 30555 30556 if (fill) { 30557 caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR)); 30558 } 30559 30560 return caretContainer; 30561 } 30562 30563 function isCaretContainerEmpty(node, nodes) { 30564 while (node) { 30565 if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) { 30566 return false;
30567 } 30568 30569 // Collect nodes 30570 if (nodes && node.nodeType === 1) { 30571 nodes.push(node); 30572 } 30573 30574 node = node.firstChild; 30575 } 30576 30577 return true; 30578 } 30579 30580 // Returns any parent caret container element 30581 function getParentCaretContainer(node) { 30582 while (node) { 30583 if (node.id === caretContainerId) { 30584 return node; 30585 } 30586 30587 node = node.parentNode; 30588 } 30589 } 30590 30591 // Finds the first text node in the specified node 30592 function findFirstTextNode(node) { 30593 var walker; 30594 30595 if (node) { 30596 walker = new TreeWalker(node, node); 30597 30598 for (node = walker.current(); node; node = walker.next()) { 30599 if (node.nodeType === 3) { 30600 return node; 30601 } 30602 } 30603 } 30604 } 30605 30606 // Removes the caret container for the specified node or all on the current document 30607 function removeCaretContainer(node, move_caret) { 30608 var child, rng; 30609 30610 if (!node) { 30611 node = getParentCaretContainer(selection.getStart()); 30612 30613 if (!node) { 30614 while ((node = dom.get(caretContainerId))) { 30615 removeCaretContainer(node, false); 30616 } 30617 } 30618 } else { 30619 rng = selection.getRng(true); 30620 30621 if (isCaretContainerEmpty(node)) { 30622 if (move_caret !== false) { 30623 rng.setStartBefore(node); 30624 rng.setEndBefore(node); 30625 } 30626 30627 dom.remove(node); 30628 } else { 30629 child = findFirstTextNode(node); 30630 30631 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) { 30632 child.deleteData(0, 1); 30633 30634 // Fix for bug #6976 30635 if (rng.startContainer == child && rng.startOffset > 0) { 30636 rng.setStart(child, rng.startOffset - 1); 30637 } 30638 30639 if (rng.endContainer == child && rng.endOffset > 0) { 30640 rng.setEnd(child, rng.endOffset - 1); 30641 } 30642 } 30643 30644 dom.remove(node, 1); 30645 } 30646 30647 selection.setRng(rng); 30648 } 30649 } 30650 30651 // Applies formatting to the caret position 30652 function applyCaretFormat() { 30653 var rng, caretContainer, textNode, offset, bookmark, container, text; 30654 30655 rng = selection.getRng(true); 30656 offset = rng.startOffset; 30657 container = rng.startContainer; 30658 text = container.nodeValue; 30659 30660 caretContainer = getParentCaretContainer(selection.getStart()); 30661 if (caretContainer) { 30662 textNode = findFirstTextNode(caretContainer); 30663 } 30664 30665 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character 30666 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) { 30667 // Get bookmark of caret position 30668 bookmark = selection.getBookmark(); 30669 30670 // Collapse bookmark range (WebKit) 30671 rng.collapse(true); 30672 30673 // Expand the range to the closest word and split it at those points 30674 rng = expandRng(rng, get(name)); 30675 rng = rangeUtils.split(rng); 30676 30677 // Apply the format to the range 30678 apply(name, vars, rng); 30679 30680 // Move selection back to caret position 30681 selection.moveToBookmark(bookmark); 30682 } else { 30683 if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) { 30684 caretContainer = createCaretContainer(true); 30685 textNode = caretContainer.firstChild; 30686 30687 rng.insertNode(caretContainer); 30688 offset = 1; 30689 30690 apply(name, vars, caretContainer); 30691 } else { 30692 apply(name, vars, caretContainer); 30693 } 30694 30695 // Move selection to text node 30696 selection.setCursorLocation(textNode, offset); 30697 } 30698 } 30699 30700 function removeCaretFormat() { 30701 var rng = selection.getRng(true), container, offset, bookmark, 30702 hasContentAfter, node, formatNode, parents = [], i, caretContainer; 30703 30704 container = rng.startContainer; 30705 offset = rng.startOffset; 30706 node = container; 30707 30708 if (container.nodeType == 3) { 30709 if (offset != container.nodeValue.length) { 30710 hasContentAfter = true; 30711 } 30712 30713 node = node.parentNode; 30714 } 30715 30716 while (node) { 30717 if (matchNode(node, name, vars, similar)) { 30718 formatNode = node; 30719 break; 30720 } 30721 30722 if (node.nextSibling) { 30723 hasContentAfter = true; 30724 } 30725 30726 parents.push(node); 30727 node = node.parentNode; 30728 } 30729 30730 // Node doesn't have the specified format 30731 if (!formatNode) { 30732 return; 30733 } 30734 30735 // Is there contents after the caret then remove the format on the element 30736 if (hasContentAfter) { 30737 // Get bookmark of caret position 30738 bookmark = selection.getBookmark(); 30739 30740 // Collapse bookmark range (WebKit) 30741 rng.collapse(true); 30742 30743 // Expand the range to the closest word and split it at those points 30744 rng = expandRng(rng, get(name), true); 30745 rng = rangeUtils.split(rng); 30746 30747 // Remove the format from the range 30748 remove(name, vars, rng); 30749 30750 // Move selection back to caret position 30751 selection.moveToBookmark(bookmark); 30752 } else { 30753 caretContainer = createCaretContainer(); 30754 30755 node = caretContainer; 30756 for (i = parents.length - 1; i >= 0; i--) { 30757 node.appendChild(dom.clone(parents[i], false)); 30758 node = node.firstChild; 30759 } 30760 30761 // Insert invisible character into inner most format element 30762 node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR)); 30763 node = node.firstChild; 30764 30765 var block = dom.getParent(formatNode, isTextBlock); 30766 30767 if (block && dom.isEmpty(block)) { 30768 // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p> 30769 formatNode.parentNode.replaceChild(caretContainer, formatNode); 30770 } else { 30771 // Insert caret container after the formatted node 30772 dom.insertAfter(caretContainer, formatNode); 30773 } 30774 30775 // Move selection to text node 30776 selection.setCursorLocation(node, 1); 30777 30778 // If the formatNode is empty, we can remove it safely. 30779 if (dom.isEmpty(formatNode)) { 30780 dom.remove(formatNode); 30781 } 30782 } 30783 } 30784 30785 // Checks if the parent caret container node isn't empty if that is the case it 30786 // will remove the bogus state on all children that isn't empty 30787 function unmarkBogusCaretParents() { 30788 var caretContainer; 30789 30790 caretContainer = getParentCaretContainer(selection.getStart()); 30791 if (caretContainer && !dom.isEmpty(caretContainer)) { 30792 walk(caretContainer, function(node) { 30793 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) { 30794 dom.setAttrib(node, 'data-mce-bogus', null); 30795 } 30796 }, 'childNodes'); 30797 } 30798 } 30799 30800 // Only bind the caret events once 30801 if (!ed._hasCaretEvents) { 30802 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements 30803 markCaretContainersBogus = function() { 30804 var nodes = [], i; 30805 30806 if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) { 30807 // Mark children 30808 i = nodes.length; 30809 while (i--) { 30810 dom.setAttrib(nodes[i], 'data-mce-bogus', '1'); 30811 } 30812 } 30813 }; 30814 30815 disableCaretContainer = function(e) { 30816 var keyCode = e.keyCode; 30817 30818 removeCaretContainer(); 30819 30820 // Remove caret container if it's empty 30821 if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) { 30822 removeCaretContainer(getParentCaretContainer(selection.getStart())); 30823 } 30824 30825 // Remove caret container on keydown and it's left/right arrow keys 30826 if (keyCode == 37 || keyCode == 39) { 30827 removeCaretContainer(getParentCaretContainer(selection.getStart())); 30828 } 30829 30830 unmarkBogusCaretParents(); 30831 }; 30832 30833 // Remove bogus state if they got filled by contents using editor.selection.setContent 30834 ed.on('SetContent', function(e) { 30835 if (e.selection) { 30836 unmarkBogusCaretParents(); 30837 } 30838 }); 30839 ed._hasCaretEvents = true; 30840 } 30841 30842 // Do apply or remove caret format 30843 if (type == "apply") { 30844 applyCaretFormat(); 30845 } else { 30846 removeCaretFormat(); 30847 } 30848 } 30849 30850 /** 30851 * Moves the start to the first suitable text node. 30852 */ 30853 function moveStart(rng) { 30854 var container = rng.startContainer, 30855 offset = rng.startOffset, isAtEndOfText, 30856 walker, node, nodes, tmpNode; 30857 30858 if (rng.startContainer == rng.endContainer) { 30859 if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) { 30860 return; 30861 } 30862 } 30863 30864 // Convert text node into index if possible 30865 if (container.nodeType == 3 && offset >= container.nodeValue.length) { 30866 // Get the parent container location and walk from there 30867 offset = nodeIndex(container); 30868 container = container.parentNode; 30869 isAtEndOfText = true; 30870 } 30871 30872 // Move startContainer/startOffset in to a suitable node 30873 if (container.nodeType == 1) { 30874 nodes = container.childNodes; 30875 container = nodes[Math.min(offset, nodes.length - 1)]; 30876 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock)); 30877 30878 // If offset is at end of the parent node walk to the next one 30879 if (offset >
30879 nodes.length - 1 || isAtEndOfText) { 30880 walker.next(); 30881 } 30882 30883 for (node = walker.current(); node; node = walker.next()) { 30884 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { 30885 // IE has a "neat" feature where it moves the start node into the closest element 30886 // we can avoid this by inserting an element before it and then remove it after we set the selection 30887 tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR); 30888 node.parentNode.insertBefore(tmpNode, node); 30889 30890 // Set selection and remove tmpNode 30891 rng.setStart(node, 0); 30892 selection.setRng(rng); 30893 dom.remove(tmpNode); 30894 30895 return; 30896 } 30897 } 30898 } 30899 } 30900 }; 30901}); 30902 30903// Included from: js/tinymce/classes/UndoManager.js 30904 30905/** 30906 * UndoManager.js 30907 * 30908 * Released under LGPL License. 30909 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 30910 * 30911 * License: http://www.tinymce.com/license 30912 * Contributing: http://www.tinymce.com/contributing 30913 */ 30914 30915/** 30916 * This class handles the undo/redo history levels for the editor. Since the built-in undo/redo has major drawbacks a custom one was needed. 30917 * 30918 * @class tinymce.UndoManager 30919 */ 30920define("tinymce/UndoManager", [ 30921 "tinymce/util/VK", 30922 "tinymce/Env" 30923], function(VK, Env) { 30924 return function(editor) { 30925 var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0; 30926 30927 function getContent() { 30928 return editor.serializer.getTrimmedContent(); 30929 } 30930 30931 function setDirty(state) { 30932 editor.setDirty(state); 30933 } 30934 30935 function addNonTypingUndoLevel(e) { 30936 self.typing = false; 30937 self.add({}, e); 30938 } 30939 30940 // Add initial undo level when the editor is initialized 30941 editor.on('init', function() { 30942 self.add(); 30943 }); 30944 30945 // Get position before an execCommand is processed 30946 editor.on('BeforeExecCommand', function(e) { 30947 var cmd = e.command; 30948 30949 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 30950 self.beforeChange(); 30951 } 30952 }); 30953 30954 // Add undo level after an execCommand call was made 30955 editor.on('ExecCommand', function(e) { 30956 var cmd = e.command; 30957 30958 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') { 30959 addNonTypingUndoLevel(e); 30960 } 30961 }); 30962
30963 editor.on('ObjectResizeStart Cut', function() { 30964 self.beforeChange(); 30965 }); 30966 30967 editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel); 30968 editor.on('DragEnd', addNonTypingUndoLevel); 30969 30970 editor.on('KeyUp', function(e) { 30971 var keyCode = e.keyCode; 30972 30973 // If key is prevented then don't add undo level 30974 // This would happen on keyboard shortcuts for example 30975 if (e.isDefaultPrevented()) { 30976 return; 30977 } 30978 30979 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) { 30980 addNonTypingUndoLevel(); 30981 editor.nodeChanged(); 30982 } 30983 30984 if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) { 30985 editor.nodeChanged(); 30986 } 30987 30988 // Fire a TypingUndo event on the first character entered 30989 if (isFirstTypedCharacter && self.typing) { 30990 // Make it dirty if the content was changed after typing the first character 30991 if (!editor.isDirty()) { 30992 setDirty(data[0] && getContent() != data[0].content); 30993 30994 // Fire initial change event 30995 if (editor.isDirty()) { 30996 editor.fire('change', {level: data[0], lastLevel: null}); 30997 } 30998 } 30999 31000 editor.fire('TypingUndo'); 31001 isFirstTypedCharacter = false; 31002 editor.nodeChanged(); 31003 } 31004 }); 31005 31006 editor.on('KeyDown', function(e) { 31007 var keyCode = e.keyCode; 31008 31009 // If key is prevented then don't add undo level 31010 // This would happen on keyboard shortcuts for example 31011 if (e.isDefaultPrevented()) { 31012 return; 31013 } 31014 31015 // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter 31016 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) { 31017 if (self.typing) { 31018 addNonTypingUndoLevel(e); 31019 } 31020 31021 return; 31022 } 31023 31024 // If key isn't Ctrl+Alt/AltGr 31025 var modKey = (e.ctrlKey && !e.altKey) || e.metaKey; 31026 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) { 31027 self.beforeChange(); 31028 self.typing = true; 31029 self.add({}, e); 31030 isFirstTypedCharacter = true; 31031 } 31032 }); 31033 31034 editor.on('MouseDown', function(e) { 31035 if (self.typing) { 31036 addNonTypingUndoLevel(e); 31037 } 31038 }); 31039 31040 // Add keyboard shortcuts for undo/redo keys 31041 editor.addShortcut('meta+z', '', 'Undo'); 31042 editor.addShortcut('meta+y,meta+shift+z', '', 'Redo'); 31043 31044 editor.on('AddUndo Undo Redo ClearUndos', function(e) { 31045 if (!e.isDefaultPrevented()) { 31046 editor.nodeChanged(); 31047 } 31048 }); 31049 31050 /*eslint consistent-this:0 */ 31051 self = { 31052 // Explode for debugging reasons 31053 data: data, 31054 31055 /** 31056 * State if the user is currently typing or not. This will add a typing operation into one undo 31057 * level instead of one new level for each keystroke. 31058 * 31059 * @field {Boolean} typing 31060 */ 31061 typing: false, 31062 31063 /** 31064 * Stores away a bookmark to be used when performing an undo action so that the selection is before 31065 * the change has been made. 31066 * 31067 * @method beforeChange 31068 */ 31069 beforeChange: function() { 31070 if (!locks) { 31071 beforeBookmark = editor.selection.getBookmark(2, true); 31072 } 31073 }, 31074 31075 /** 31076 * Adds a new undo level/snapshot to the undo list. 31077 * 31078 * @method add 31079 * @param {Object} level Optional undo level object to add. 31080 * @param {DOMEvent} event Optional event responsible for the creation of the undo level. 31081 * @return {Object} Undo level that got added or null it a level wasn't needed. 31082 */ 31083 add: function(level, event) { 31084 var i, settings = editor.settings, lastLevel; 31085 31086 level = level || {}; 31087 level.content = getContent(); 31088 31089 if (locks || editor.removed) { 31090 return null; 31091 } 31092 31093 lastLevel = data[index]; 31094 if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) { 31095 return null; 31096 } 31097 31098 // Add undo level if needed 31099 if (lastLevel && lastLevel.content == level.content) { 31100 return null; 31101 } 31102 31103 // Set before bookmark on previous level 31104 if (data[index]) { 31105 data[index].beforeBookmark = beforeBookmark; 31106 } 31107 31108 // Time to compress 31109 if (settings.custom_undo_redo_levels) { 31110 if (data.length > settings.custom_undo_redo_levels) { 31111 for (i = 0; i < data.length - 1; i++) { 31112 data[i] = data[i + 1]; 31113 } 31114 31115 data.length--; 31116 index = data.length; 31117 } 31118 } 31119 31120 // Get a non intrusive normalized bookmark 31121 level.bookmark = editor.selection.getBookmark(2, true); 31122 31123 // Crop array if needed 31124 if (index < data.length - 1) { 31125 data.length = index + 1; 31126 } 31127 31128 data.push(level); 31129 index = data.length - 1; 31130 31131 var args = {level: level, lastLevel: lastLevel, originalEvent: event}; 31132 31133 editor.fire('AddUndo', args); 31134 31135 if (index > 0) { 31136 setDirty(true); 31137 editor.fire('change', args); 31138 } 31139 31140 return level; 31141 }, 31142 31143 /** 31144 * Undoes the last action. 31145 * 31146 * @method undo 31147 * @return {Object} Undo level or null if no undo was performed. 31148 */
31149 undo: function() { 31150 var level; 31151 31152 if (self.typing) { 31153 self.add(); 31154 self.typing = false; 31155 } 31156 31157 if (index > 0) { 31158 level = data[--index]; 31159 31160 editor.setContent(level.content, {format: 'raw'}); 31161 editor.selection.moveToBookmark(level.beforeBookmark); 31162 setDirty(true); 31163 31164 editor.fire('undo', {level: level}); 31165 } 31166 31167 return level; 31168 }, 31169 31170 /** 31171 * Redoes the last action. 31172 * 31173 * @method redo 31174 * @return {Object} Redo level or null if no redo was performed. 31175 */ 31176 redo: function() { 31177 var level; 31178 31179 if (index < data.length - 1) { 31180 level = data[++index]; 31181 31182 editor.setContent(level.content, {format: 'raw'}); 31183 editor.selection.moveToBookmark(level.bookmark); 31184 setDirty(true); 31185 31186 editor.fire('redo', {level: level}); 31187 } 31188 31189 return level; 31190 }, 31191 31192 /** 31193 * Removes all undo levels. 31194 * 31195 * @method clear 31196 */ 31197 clear: function() { 31198 data = []; 31199 index = 0; 31200 self.typing = false; 31201 editor.fire('ClearUndos'); 31202 }, 31203 31204 /** 31205 * Returns true/false if the undo manager has any undo levels. 31206 * 31207 * @method hasUndo 31208 * @return {Boolean} true/false if the undo manager has any undo levels. 31209 */ 31210 hasUndo: function() { 31211 // Has undo levels or typing and content isn't the same as the initial level 31212 return index > 0 || (self.typing && data[0] && getContent() != data[0].content); 31213 }, 31214 31215 /** 31216 * Returns true/false if the undo manager has any redo levels. 31217 * 31218 * @method hasRedo 31219 * @return {Boolean} true/false if the undo manager has any redo levels. 31220 */ 31221 hasRedo: function() { 31222 return index < data.length - 1 && !this.typing; 31223 }, 31224 31225 /** 31226 * Executes the specified function in an undo translation. The selection 31227 * before the modification will be stored to the undo stack and if the DOM changes 31228 * it will add a new undo level. Any methods within the translation that adds undo levels will 31229 * be ignored. So a translation can include calls to execCommand or editor.insertContent. 31230 * 31231 * @method transact 31232 * @param {function} callback Function to execute dom manipulation logic in. 31233 */ 31234 transact: function(callback) { 31235 self.beforeChange(); 31236 31237 try { 31238 locks++; 31239 callback(); 31240 } finally { 31241 locks--; 31242 } 31243 31244 self.add(); 31245 } 31246 }; 31247 31248 return self; 31249 }; 31250}); 31251 31252// Included from: js/tinymce/classes/EnterKey.js 31253 31254/** 31255 * EnterKey.js 31256 * 31257 * Released under LGPL License. 31258 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 31259 * 31260 * License: http://www.tinymce.com/license 31261 * Contributing: http://www.tinymce.com/contributing 31262 */ 31263 31264/** 31265 * Contains logic for handling the enter key to split/generate block elements. 31266 * 31267 * @private 31268 * @class tinymce.EnterKey 31269 */ 31270define("tinymce/EnterKey", [ 31271 "tinymce/dom/TreeWalker", 31272 "tinymce/dom/RangeUtils", 31273 "tinymce/Env" 31274], function(TreeWalker, RangeUtils, Env) { 31275 var isIE = Env.ie && Env.ie < 11; 31276 31277 return function(editor) { 31278 var dom = editor.dom, selection = editor.selection, settings = editor.settings; 31279 var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(), 31280 moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements(); 31281 31282 function handleEnterKey(evt) { 31283 var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey, 31284 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer; 31285 31286 // Returns true if the block can be split into two blocks or not 31287 function canSplitBlock(node) { 31288 return node && 31289 dom.isBlock(node) && 31290 !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && 31291 !/^(fixed|absolute)/i.test(node.style.position) && 31292 dom.getContentEditable(node) !== "true"; 31293 } 31294 31295 function isTableCell(node) { 31296 return node && /^(TD|TH|CAPTION)$/.test(node.nodeName); 31297 } 31298 31299 // Renders empty block on IE 31300 function renderBlockOnIE(block) { 31301 var oldRng; 31302 31303 if (dom.isBlock(block)) { 31304 oldRng = selection.getRng(); 31305 block.appendChild(dom.create('span', null, '\u00a0')); 31306 selection.select(block); 31307 block.lastChild.outerHTML = ''; 31308 selection.setRng(oldRng); 31309 } 31310 } 31311 31312 // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>
31312x</p> 31313 function trimInlineElementsOnLeftSideOfBlock(block) { 31314 var node = block, firstChilds = [], i; 31315 31316 if (!node) { 31317 return; 31318 } 31319 31320 // Find inner most first child ex: <p><i><b>*</b></i></p> 31321 while ((node = node.firstChild)) { 31322 if (dom.isBlock(node)) { 31323 return; 31324 } 31325 31326 if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) { 31327 firstChilds.push(node); 31328 } 31329 } 31330 31331 i = firstChilds.length; 31332 while (i--) { 31333 node = firstChilds[i]; 31334 if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) { 31335 dom.remove(node); 31336 } else { 31337 // Remove <a> </a> see #5381 31338 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') { 31339 dom.remove(node); 31340 } 31341 } 31342 } 31343 } 31344 31345 // Moves the caret to a suitable position within the root for example in the first non 31346 // pure whitespace text node or before an image 31347 function moveToCaretPosition(root) { 31348 var walker, node, rng, lastNode = root, tempElm; 31349 function firstNonWhiteSpaceNodeSibling(node) { 31350 while (node) { 31351 if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) { 31352 return node; 31353 } 31354 31355 node = node.nextSibling; 31356 } 31357 } 31358 31359 if (!root) { 31360 return; 31361 } 31362 31363 // Old IE versions doesn't properly render blocks with br elements in them 31364 // For example <p><br></p> wont be rendered correctly in a contentEditable area 31365 // until you remove the br producing <p></p> 31366 if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) { 31367 if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') { 31368 dom.remove(parentBlock.firstChild); 31369 } 31370 } 31371 31372 if (/^(LI|DT|DD)$/.test(root.nodeName)) { 31373 var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild); 31374 31375 if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) { 31376 root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild); 31377 } 31378 } 31379 31380 rng = dom.createRng(); 31381 31382 // Normalize whitespace to remove empty text nodes. Fix for: #6904 31383 // Gecko will be able to place the caret in empty text nodes but it won't render propery 31384 // Older IE versions will sometimes crash so for now ignore all IE versions 31385 if (!Env.ie) { 31386 root.normalize(); 31387 } 31388 31389 if (root.hasChildNodes()) { 31390 walker = new TreeWalker(root, root); 31391 31392 while ((node = walker.current())) { 31393 if (node.nodeType == 3) { 31394 rng.setStart(node, 0); 31395 rng.setEnd(node, 0); 31396 break; 31397 } 31398 31399 if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) { 31400 rng.setStartBefore(node); 31401 rng.setEndBefore(node); 31402 break; 31403 } 31404 31405 lastNode = node; 31406 node = walker.next(); 31407 } 31408 31409 if (!node) { 31410 rng.setStart(lastNode, 0); 31411 rng.setEnd(lastNode, 0); 31412 } 31413 } else { 31414 if (root.nodeName == 'BR') { 31415 if (root.nextSibling && dom.isBlock(root.nextSibling)) { 31416 // Trick on older IE versions to render the caret before the BR between two lists 31417 if (!documentMode || documentMode < 9) { 31418 tempElm = dom.create('br'); 31419 root.parentNode.insertBefore(tempElm, root); 31420 } 31421 31422 rng.setStartBefore(root); 31423 rng.setEndBefore(root); 31424 } else { 31425 rng.setStartAfter(root); 31426 rng.setEndAfter(root); 31427 } 31428 } else { 31429 rng.setStart(root, 0); 31430 rng.setEnd(root, 0); 31431 } 31432 } 31433 31434 selection.setRng(rng); 31435 31436 // Remove tempElm created for old IE:s 31437 dom.remove(tempElm); 31438 selection.scrollIntoView(root); 31439 } 31440 31441 function setForcedBlockAttrs(node) { 31442 var forcedRootBlockName = settings.forced_root_block; 31443 31444 if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) { 31445 dom.setAttribs(node, settings.forced_root_block_attrs); 31446 } 31447 } 31448 31449 function emptyBlock(elm) { 31450 // BR is needed in empty blocks on non IE browsers 31451 elm.innerHTML = !isIE ? '<br data-mce-bogus="1">' : ''; 31452 } 31453 31454 // Creates a new block element by cloning the current one or creating a new one if the name is specified 31455 // This function will also copy any text formatting from the parent block and add it to the new one 31456 function createNewBlock(name) {
31457 var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements(); 31458 31459 if (name || parentBlockName == "TABLE") { 31460 block = dom.create(name || newBlockName); 31461 setForcedBlockAttrs(block); 31462 } else { 31463 block = parentBlock.cloneNode(false); 31464 } 31465 31466 caretNode = block; 31467 31468 // Clone any parent styles 31469 if (settings.keep_styles !== false) { 31470 do { 31471 if (textInlineElements[node.nodeName]) { 31472 // Never clone a caret containers 31473 if (node.id == '_mce_caret') { 31474 continue; 31475 } 31476 31477 clonedNode = node.cloneNode(false); 31478 dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique 31479 31480 if (block.hasChildNodes()) { 31481 clonedNode.appendChild(block.firstChild); 31482 block.appendChild(clonedNode); 31483 } else { 31484 caretNode = clonedNode; 31485 block.appendChild(clonedNode); 31486 } 31487 } 31488 } while ((node = node.parentNode)); 31489 } 31490 31491 // BR is needed in empty blocks on non IE browsers 31492 if (!isIE) { 31493 caretNode.innerHTML = '<br data-mce-bogus="1">'; 31494 } 31495 31496 return block; 31497 } 31498 31499 // Returns true/false if the caret is at the start/end of the parent block element 31500 function isCaretAtStartOrEndOfBlock(start) { 31501 var walker, node, name; 31502 31503 // Caret is in the middle of a text node like "a|b" 31504 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) { 31505 return false; 31506 } 31507 31508 // If after the last element in block node edge case for #5091 31509 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) { 31510 return true; 31511 } 31512 31513 // If the caret if before the first element in parentBlock 31514 if (start && container.nodeType == 1 && container == parentBlock.firstChild) { 31515 return true; 31516 } 31517 31518 // Caret can be before/after a table 31519 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) { 31520 return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start); 31521 } 31522 31523 // Walk the DOM and look for text nodes or non empty elements 31524 walker = new TreeWalker(container, parentBlock); 31525 31526 // If caret is in beginning or end of a text block then jump to the next/previous node 31527 if (container.nodeType == 3) { 31528 if (start && offset === 0) { 31529 walker.prev(); 31530 } else if (!start && offset == container.nodeValue.length) { 31531 walker.next(); 31532 } 31533 } 31534 31535 while ((node = walker.current())) { 31536 if (node.nodeType === 1) { 31537 // Ignore bogus elements 31538 if (!node.getAttribute('data-mce-bogus')) { 31539 // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p> 31540 name = node.nodeName.toLowerCase(); 31541 if (nonEmptyElementsMap[name] && name !== 'br') { 31542 return false; 31543 } 31544 } 31545 } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) { 31546 return false; 31547 } 31548 31549 if (start) { 31550 walker.prev(); 31551 } else { 31552 walker.next(); 31553 } 31554 } 31555 31556 return true; 31557 } 31558 31559 // Wraps any text nodes or inline elements in the specified forced root block name 31560 function wrapSelfAndSiblingsInDefaultBlock(container, offset) { 31561 var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P'; 31562 31563 // Not in a block element or in a table cell or caption 31564 parentBlock = dom.getParent(container, dom.isBlock); 31565 if (!parentBlock || !canSplitBlock(parentBlock)) { 31566 parentBlock = parentBlock || editableRoot; 31567 31568 if (parentBlock == editor.getBody() || isTableCell(parentBlock)) { 31569 rootBlockName = parentBlock.nodeName.toLowerCase(); 31570 } else { 31571 rootBlockName = parentBlock.parentNode.nodeName.toLowerCase(); 31572 } 31573 31574 if (!parentBlock.hasChildNodes()) { 31575 newBlock = dom.create(blockName); 31576 setForcedBlockAttrs(newBlock); 31577 parentBlock.appendChild(newBlock); 31578 rng.setStart(newBlock, 0); 31579 rng.setEnd(newBlock, 0); 31580 return newBlock; 31581 } 31582 31583 // Find parent that is the first child of parentBlock 31584 node = container; 31585 while (node.parentNode != parentBlock) { 31586 node = node.parentNode; 31587 } 31588 31589 // Loop left to find start node start wrapping at 31590 while (node && !dom.isBlock(node)) { 31591 startNode = node; 31592 node = node.previousSibling; 31593 } 31594 31595 if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) { 31596 newBlock = dom.create(blockName); 31597 setForcedBlockAttrs(newBlock); 31598 startNode.parentNode.insertBefore(newBlock, startNode); 31599 31600 // Start wrapping until we hit a block 31601 node = startNode;
31602 while (node && !dom.isBlock(node)) { 31603 next = node.nextSibling; 31604 newBlock.appendChild(node); 31605 node = next; 31606 } 31607 31608 // Restore range to it's past location 31609 rng.setStart(container, offset); 31610 rng.setEnd(container, offset); 31611 } 31612 } 31613 31614 return container; 31615 } 31616 31617 // Inserts a block or br before/after or in the middle of a split list of the LI is empty 31618 function handleEmptyListItem() { 31619 function isFirstOrLastLi(first) { 31620 var node = containerBlock[first ? 'firstChild' : 'lastChild']; 31621 31622 // Find first/last element since there might be whitespace there 31623 while (node) { 31624 if (node.nodeType == 1) { 31625 break; 31626 } 31627 31628 node = node[first ? 'nextSibling' : 'previousSibling']; 31629 } 31630 31631 return node === parentBlock; 31632 } 31633 31634 function getContainerBlock() { 31635 var containerBlockParent = containerBlock.parentNode; 31636 31637 if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) { 31638 return containerBlockParent; 31639 } 31640 31641 return containerBlock; 31642 } 31643 31644 if (containerBlock == editor.getBody()) { 31645 return; 31646 } 31647 31648 // Check if we are in an nested list 31649 var containerBlockParentName = containerBlock.parentNode.nodeName; 31650 if (/^(OL|UL|LI)$/.test(containerBlockParentName)) { 31651 newBlockName = 'LI'; 31652 } 31653 31654 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR'); 31655 31656 if (isFirstOrLastLi(true) && isFirstOrLastLi()) { 31657 if (containerBlockParentName == 'LI') { 31658 // Nested list is inside a LI 31659 dom.insertAfter(newBlock, getContainerBlock()); 31660 } else { 31661 // Is first and last list item then replace the OL/UL with a text block 31662 dom.replace(newBlock, containerBlock); 31663 } 31664 } else if (isFirstOrLastLi(true)) { 31665 if (containerBlockParentName == 'LI') { 31666 // List nested in an LI then move the list to a new sibling LI 31667 dom.insertAfter(newBlock, getContainerBlock()); 31668 newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed 31669 newBlock.appendChild(containerBlock); 31670 } else { 31671 // First LI in list then remove LI and add text block before list 31672 containerBlock.parentNode.insertBefore(newBlock, containerBlock); 31673 } 31674 } else if (isFirstOrLastLi()) { 31675 // Last LI in list then remove LI and add text block after list 31676 dom.insertAfter(newBlock, getContainerBlock()); 31677 renderBlockOnIE(newBlock); 31678 } else { 31679 // Middle LI in list the split the list and insert a text block in the middle 31680 // Extract after fragment and insert it after the current block 31681 containerBlock = getContainerBlock(); 31682 tmpRng = rng.cloneRange(); 31683 tmpRng.setStartAfter(parentBlock); 31684 tmpRng.setEndAfter(containerBlock); 31685 fragment = tmpRng.extractContents(); 31686 31687 if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') { 31688 newBlock = fragment.firstChild; 31689 dom.insertAfter(fragment, containerBlock); 31690 } else { 31691 dom.insertAfter(fragment, containerBlock); 31692 dom.insertAfter(newBlock, containerBlock); 31693 } 31694 } 31695 31696 dom.remove(parentBlock); 31697 moveToCaretPosition(newBlock); 31698 undoManager.add(); 31699 } 31700 31701 // Inserts a BR element if the forced_root_block option is set to false or empty string 31702 function insertBr() { 31703 editor.execCommand("InsertLineBreak", false, evt); 31704 } 31705 31706 // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element 31707 function trimLeadingLineBreaks(node) { 31708 do { 31709 if (node.nodeType === 3) { 31710 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, ''); 31711 } 31712 31713 node = node.firstChild; 31714 } while (node); 31715 } 31716 31717 function getEditableRoot(node) { 31718 var root = dom.getRoot(), parent, editableRoot; 31719 31720 // Get all parents until we hit a non editable parent or the root 31721 parent = node; 31722 while (parent !== root && dom.getContentEditable(parent) !== "false") { 31723 if (dom.getContentEditable(parent) === "true") { 31724 editableRoot = parent; 31725 } 31726 31727 parent = parent.parentNode; 31728 } 31729 31730 return parent !== root ? editableRoot : root; 31731 } 31732 31733 // Adds a BR at the end of blocks that only contains an IMG or INPUT since 31734 // these might be floated and then they won't expand the block 31735 function addBrToBlockIfNeeded(block) { 31736 var lastChild; 31737 31738 // IE will render the blocks correctly other browsers needs a BR 31739 if (!isIE) { 31740 block.normalize(); // Remove empty text nodes that got left behind by the extract 31741 31742 // Check if the block is empty or contains a floated last child 31743 lastChild = block.lastChild; 31744 if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) { 31745 dom.add(block, 'br'); 31746 } 31747 } 31748 } 31749 31750 function insertNewBlockAfter() { 31751 // 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 31752 if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') { 31753 newBlock = createNewBlock(newBlockName); 31754 } else { 31755 newBlock = createNewBlock(); 31756 } 31757 31758 // Split the current container block element if enter is pressed inside an empty inner block element 31759 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) { 31760 // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P 31761 newBlock = dom.split(containerBlock, parentBlock); 31762 } else { 31763 dom.insertAfter(newBlock, parentBlock); 31764 } 31765 31766 moveToCaretPosition(newBlock); 31767 } 31768 31769 rng = selection.getRng(true); 31770 31771 // Event is blocked by some other handler for example the lists plugin 31772 if (evt.isDefaultPrevented()) { 31773 return; 31774 } 31775 31776 // Delete any selected contents 31777 if (!rng.collapsed) { 31778 editor.execCommand('Delete'); 31779 return; 31780 } 31781 31782 // Setup range items and newBlockName 31783 new RangeUtils(dom).normalize(rng); 31784 container = rng.startContainer; 31785 offset = rng.startOffset; 31786 newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block; 31787 newBlockName = newBlockName ? newBlockName.toUpperCase() : ''; 31788 documentMode = dom.doc.documentMode; 31789 shiftKey = evt.shiftKey; 31790 31791 // Resolve node index 31792 if (container.nodeType == 1 && container.hasChildNodes()) { 31793 isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 31794 31795 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 31796 if (isAfterLastNodeInContainer && container.nodeType == 3) { 31797 offset = container.nodeValue.length; 31798 } else { 31799 offset = 0; 31800 } 31801 } 31802 31803 // Get editable root node, normally the body element but sometimes a div or span 31804 editableRoot = getEditableRoot(container); 31805 31806 // If there is no editable root then enter is done inside a contentEditable false element 31807 if (!editableRoot) { 31808 return; 31809 } 31810 31811 undoManager.beforeChange(); 31812 31813 // If editable root isn't block nor the root of the editor
31814 if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) { 31815 if (!newBlockName || shiftKey) { 31816 insertBr(); 31817 } 31818 31819 return; 31820 } 31821 31822 // Wrap the current node and it's sibling in a default block if it's needed. 31823 // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td> 31824 // This won't happen if root blocks are disabled or the shiftKey is pressed 31825 if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) { 31826 container = wrapSelfAndSiblingsInDefaultBlock(container, offset); 31827 } 31828 31829 // Find parent block and setup empty block paddings 31830 parentBlock = dom.getParent(container, dom.isBlock); 31831 containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 31832 31833 // Setup block names 31834 parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 31835 containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 31836 31837 // Enter inside block contained within a LI then split or insert before/after LI 31838 if (containerBlockName == 'LI' && !evt.ctrlKey) { 31839 parentBlock = containerBlock; 31840 parentBlockName = containerBlockName; 31841 } 31842 31843 // Handle enter in list item 31844 if (/^(LI|DT|DD)$/.test(parentBlockName)) { 31845 if (!newBlockName && shiftKey) { 31846 insertBr(); 31847 return; 31848 } 31849 31850 // Handle enter inside an empty list item 31851 if (dom.isEmpty(parentBlock)) { 31852 handleEmptyListItem(); 31853 return; 31854 } 31855 } 31856 31857 // Don't split PRE tags but insert a BR instead easier when writing code samples etc 31858 if (parentBlockName == 'PRE' && settings.br_in_pre !== false) { 31859 if (!shiftKey) { 31860 insertBr(); 31861 return; 31862 } 31863 } else { 31864 // If no root block is configured then insert a BR by default or if the shiftKey is pressed 31865 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) { 31866 insertBr(); 31867 return; 31868 } 31869 } 31870 31871 // If parent block is root then never insert new blocks 31872 if (newBlockName && parentBlock === editor.getBody()) { 31873 return; 31874 } 31875 31876 // Default block name if it's not configured 31877 newBlockName = newBlockName || 'P'; 31878 31879 // Insert new block before/after the parent block depending on caret location 31880 if (isCaretAtStartOrEndOfBlock()) { 31881 insertNewBlockAfter(); 31882 } else if (isCaretAtStartOrEndOfBlock(true)) { 31883 // Insert new block before 31884 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock); 31885 renderBlockOnIE(newBlock); 31886 moveToCaretPosition(parentBlock); 31887 } else { 31888 // Extract after fragment and insert it after the current block 31889 tmpRng = rng.cloneRange(); 31890 tmpRng.setEndAfter(parentBlock); 31891 fragment = tmpRng.extractContents(); 31892 trimLeadingLineBreaks(fragment); 31893 newBlock = fragment.firstChild; 31894 dom.insertAfter(fragment, parentBlock); 31895 trimInlineElementsOnLeftSideOfBlock(newBlock); 31896 addBrToBlockIfNeeded(parentBlock); 31897 31898 if (dom.isEmpty(parentBlock)) { 31899 emptyBlock(parentBlock); 31900 } 31901 31902 newBlock.normalize(); 31903 31904 // New block might become empty if it's <p><b>a |</b></p> 31905 if (dom.isEmpty(newBlock)) { 31906 dom.remove(newBlock); 31907 insertNewBlockAfter(); 31908 } else { 31909 moveToCaretPosition(newBlock); 31910 } 31911 } 31912 31913 dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique 31914 31915 // Allow custom handling of new blocks 31916 editor.fire('NewBlock', {newBlock: newBlock}); 31917 31918 undoManager.add(); 31919 } 31920 31921 editor.on('keydown', function(evt) { 31922 if (evt.keyCode == 13) { 31923 if (handleEnterKey(evt) !== false) { 31924 evt.preventDefault(); 31925 } 31926 } 31927 }); 31928 }; 31929}); 31930 31931// Included from: js/tinymce/classes/ForceBlocks.js 31932 31933/** 31934 * ForceBlocks.js 31935 * 31936 * Released under LGPL License. 31937 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 31938 * 31939 * License: http://www.tinymce.com/license 31940 * Contributing: http://www.tinymce.com/contributing 31941 */ 31942 31943/** 31944 * Makes sure that everything gets wrapped in paragraphs. 31945 * 31946 * @private 31947 * @class tinymce.ForceBlocks 31948 */ 31949define("tinymce/ForceBlocks", [], function() { 31950 return function(editor) { 31951 var settings = editor.settings, dom = editor.dom, selection = editor.selection; 31952 var schema = editor.schema, blockElements = schema.getBlockElements(); 31953 31954 function addRootBlocks() { 31955 var node = selection.getStart(), rootNode = editor.getBody(), rng; 31956 var startContainer, startOffset, endContainer, endOffset, rootBlockNode; 31957 var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection; 31958 var tmpRng, rootNodeName, forcedRootBlock; 31959 31960 forcedRootBlock = settings.forced_root_block; 31961 31962 if (!node || node.nodeType !== 1 || !forcedRootBlock) { 31963 return; 31964 } 31965 31966 // Check if node is wrapped in block 31967 while (node && node != rootNode) { 31968 if (blockElements[node.nodeName]) { 31969 return; 31970 } 31971 31972 node = node.parentNode; 31973 } 31974 31975 // Get current selection 31976 rng = selection.getRng(); 31977 if (rng.setStart) { 31978 startContainer = rng.startContainer; 31979 startOffset = rng.startOffset; 31980 endContainer = rng.endContainer; 31981 endOffset = rng.endOffset; 31982 31983 try { 31984 restoreSelection = editor.getDoc().activeElement === rootNode; 31985 } catch (ex) { 31986 // IE throws unspecified error here sometimes 31987 } 31988 } else { 31989 // Force control range into text range 31990 if (rng.item) { 31991 node = rng.item(0); 31992 rng = editor.getDoc().body.createTextRange(); 31993 rng.moveToElementText(node); 31994 } 31995 31996 restoreSelection = rng.parentElement().ownerDocument === editor.getDoc(); 31997 tmpRng = rng.duplicate(); 31998 tmpRng.collapse(true); 31999 startOffset = tmpRng.move('character', offset) * -1; 32000 32001 if (!tmpRng.collapsed) { 32002 tmpRng = rng.duplicate(); 32003 tmpRng.collapse(false); 32004 endOffset = (tmpRng.move('character', offset) * -1) - startOffset; 32005 } 32006 } 32007 32008 // Wrap non block elements and text nodes 32009 node = rootNode.firstChild; 32010 rootNodeName = rootNode.nodeName.toLowerCase(); 32011 while (node) { 32012 // TODO: Break this up, too complex 32013 if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) && 32014 schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) { 32015 // Remove empty text nodes 32016 if (node.nodeType === 3 && node.nodeValue.length === 0) { 32017 tempNode = node; 32018 node = node.nextSibling; 32019 dom.remove(tempNode); 32020 continue; 32021 } 32022 32023 if (!rootBlockNode) { 32024 rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs); 32025 node.parentNode.insertBefore(rootBlockNode, node); 32026 wrapped = true; 32027 } 32028 32029 tempNode = node; 32030 node = node.nextSibling; 32031 rootBlockNode.appendChild(tempNode); 32032 } else { 32033 rootBlockNode = null; 32034 node = node.nextSibling; 32035 } 32036 } 32037 32038 if (wrapped && restoreSelection) { 32039 if (rng.setStart) { 32040 rng.setStart(startContainer, startOffset); 32041 rng.setEnd(endContainer, endOffset); 32042 selection.setRng(rng); 32043 } else { 32044 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode 32045 try { 32046 rng = editor.getDoc().body.createTextRange(); 32047 rng.moveToElementText(rootNode); 32048 rng.collapse(true); 32049 rng.moveStart('character', startOffset); 32050 32051 if (endOffset > 0) { 32052 rng.moveEnd('character', endOffset); 32053 } 32054 32055 rng.select(); 32056 } catch (ex) { 32057 // Ignore 32058 } 32059 } 32060 32061 editor.nodeChanged(); 32062 } 32063 } 32064 32065 // Force root blocks 32066 if (settings.forced_root_block) { 32067 editor.on('NodeChange', addRootBlocks); 32068 } 32069 }; 32070}); 32071 32072// Included from: js/tinymce/classes/EditorCommands.js 32073 32074/** 32075 * EditorCommands.js 32076 * 32077 * Released under LGPL License. 32078 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 32079 * 32080 * License: http://www.tinymce.com/license 32081 * Contributing: http://www.tinymce.com/contributing 32082 */ 32083 32084/** 32085 * This class enables you to add custom editor commands and it contains 32086 * overrides for native browser commands to address various bugs and issues. 32087 * 32088 * @class tinymce.EditorCommands 32089 */ 32090define("tinymce/EditorCommands", [ 32091 "tinymce/html/Serializer", 32092 "tinymce/Env", 32093 "tinymce/util/Tools", 32094 "tinymce/dom/ElementUtils", 32095 "tinymce/dom/RangeUtils", 32096 "tinymce/dom/TreeWalker" 32097], function(Serializer, Env, Tools, ElementUtils, RangeUtils, TreeWalker) {
32098 // Added for compression purposes 32099 var each = Tools.each, extend = Tools.extend; 32100 var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode; 32101 var isIE = Env.ie, isOldIE = Env.ie && Env.ie < 11; 32102 var TRUE = true, FALSE = false; 32103 32104 return function(editor) { 32105 var dom, selection, formatter, 32106 commands = {state: {}, exec: {}, value: {}}, 32107 settings = editor.settings, 32108 bookmark; 32109 32110 editor.on('PreInit', function() { 32111 dom = editor.dom; 32112 selection = editor.selection; 32113 settings = editor.settings; 32114 formatter = editor.formatter; 32115 }); 32116 32117 /** 32118 * Executes the specified command. 32119 * 32120 * @method execCommand 32121 * @param {String} command Command to execute. 32122 * @param {Boolean} ui Optional user interface state. 32123 * @param {Object} value Optional value for command. 32124 * @param {Object} args Optional extra arguments to the execCommand. 32125 * @return {Boolean} true/false if the command was found or not. 32126 */ 32127 function execCommand(command, ui, value, args) { 32128 var func, customCommand, state = 0; 32129 32130 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) { 32131 editor.focus(); 32132 } 32133 32134 args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value}); 32135 if (args.isDefaultPrevented()) { 32136 return false; 32137 } 32138 32139 customCommand = command.toLowerCase(); 32140 if ((func = commands.exec[customCommand])) { 32141 func(customCommand, ui, value); 32142 editor.fire('ExecCommand', {command: command, ui: ui, value: value}); 32143 return true; 32144 } 32145 32146 // Plugin commands 32147 each(editor.plugins, function(p) { 32148 if (p.execCommand && p.execCommand(command, ui, value)) { 32149 editor.fire('ExecCommand', {command: command, ui: ui, value: value}); 32150 state = true; 32151 return false; 32152 } 32153 }); 32154 32155 if (state) { 32156 return state; 32157 } 32158 32159 // Theme commands 32160 if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) { 32161 editor.fire('ExecCommand', {command: command, ui: ui, value: value}); 32162 return true; 32163 } 32164 32165 // Browser commands 32166 try { 32167 state = editor.getDoc().execCommand(command, ui, value); 32168 } catch (ex) { 32169 // Ignore old IE errors 32170 } 32171 32172 if (state) { 32173 editor.fire('ExecCommand', {command: command, ui: ui, value: value}); 32174 return true; 32175 } 32176 32177 return false; 32178 } 32179 32180 /** 32181 * Queries the current state for a command for example if the current selection is "bold". 32182 * 32183 * @method queryCommandState 32184 * @param {String} command Command to check the state of. 32185 * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found. 32186 */ 32187 function queryCommandState(command) { 32188 var func; 32189 32190 // Is hidden then return undefined 32191 if (editor._isHidden()) { 32192 return; 32193 } 32194 32195 command = command.toLowerCase(); 32196 if ((func = commands.state[command])) { 32197 return func(command); 32198 } 32199 32200 // Browser commands 32201 try { 32202 return editor.getDoc().queryCommandState(command); 32203 } catch (ex) { 32204 // Fails sometimes see bug: 1896577 32205 } 32206 32207 return false; 32208 } 32209 32210 /** 32211 * Queries the command value for example the current fontsize. 32212 * 32213 * @method queryCommandValue 32214 * @param {String} command Command to check the value of. 32215 * @return {Object} Command value of false if it's not found. 32216 */ 32217 function queryCommandValue(command) { 32218 var func; 32219 32220 // Is hidden then return undefined 32221 if (editor._isHidden()) { 32222 return; 32223 } 32224 32225 command = command.toLowerCase(); 32226 if ((func = commands.value[command])) { 32227 return func(command); 32228 } 32229 32230 // Browser commands 32231 try { 32232 return editor.getDoc().queryCommandValue(command); 32233 } catch (ex) { 32234 // Fails sometimes see bug: 1896577 32235 } 32236 } 32237 32238 /** 32239 * Adds commands to the command collection. 32240 * 32241 * @method addCommands 32242 * @param {Object} command_list Name/value collection with commands to add, the names can also be c
32242omma separated. 32243 * @param {String} type Optional type to add, defaults to exec. Can be value or state as well. 32244 */ 32245 function addCommands(command_list, type) { 32246 type = type || 'exec'; 32247 32248 each(command_list, function(callback, command) { 32249 each(command.toLowerCase().split(','), function(command) { 32250 commands[type][command] = callback; 32251 }); 32252 }); 32253 } 32254 32255 function addCommand(command, callback, scope) { 32256 command = command.toLowerCase(); 32257 commands.exec[command] = function(command, ui, value, args) { 32258 return callback.call(scope || editor, ui, value, args); 32259 }; 32260 } 32261 32262 /** 32263 * Returns true/false if the command is supported or not. 32264 * 32265 * @method queryCommandSupported 32266 * @param {String} command Command that we check support for. 32267 * @return {Boolean} true/false if the command is supported or not. 32268 */ 32269 function queryCommandSupported(command) { 32270 command = command.toLowerCase(); 32271 32272 if (commands.exec[command]) { 32273 return true; 32274 } 32275 32276 // Browser commands 32277 try { 32278 return editor.getDoc().queryCommandSupported(command); 32279 } catch (ex) { 32280 // Fails sometimes see bug: 1896577 32281 } 32282 32283 return false; 32284 } 32285 32286 function addQueryStateHandler(command, callback, scope) { 32287 command = command.toLowerCase(); 32288 commands.state[command] = function() { 32289 return callback.call(scope || editor); 32290 }; 32291 } 32292 32293 function addQueryValueHandler(command, callback, scope) { 32294 command = command.toLowerCase(); 32295 commands.value[command] = function() { 32296 return callback.call(scope || editor); 32297 }; 32298 } 32299 32300 function hasCustomCommand(command) { 32301 command = command.toLowerCase(); 32302 return !!commands.exec[command]; 32303 } 32304 32305 // Expose public methods 32306 extend(this, { 32307 execCommand: execCommand, 32308 queryCommandState: queryCommandState, 32309 queryCommandValue: queryCommandValue, 32310 queryCommandSupported: queryCommandSupported, 32311 addCommands: addCommands, 32312 addCommand: addCommand, 32313 addQueryStateHandler: addQueryStateHandler, 32314 addQueryValueHandler: addQueryValueHandler, 32315 hasCustomCommand: hasCustomCommand 32316 }); 32317 32318 // Private methods 32319 32320 function execNativeCommand(command, ui, value) { 32321 if (ui === undefined) { 32322 ui = FALSE; 32323 } 32324 32325 if (value === undefined) { 32326 value = null; 32327 } 32328 32329 return editor.getDoc().execCommand(command, ui, value); 32330 } 32331 32332 function isFormatMatch(name) { 32333 return formatter.match(name); 32334 } 32335 32336 function toggleFormat(name, value) { 32337 formatter.toggle(name, value ? {value: value} : undefined); 32338 editor.nodeChanged(); 32339 } 32340 32341 function storeSelection(type) { 32342 bookmark = selection.getBookmark(type); 32343 } 32344 32345 function restoreSelection() { 32346 selection.moveToBookmark(bookmark); 32347 } 32348 32349 // Add execCommand overrides 32350 addCommands({ 32351 // Ignore these, added for compatibility 32352 'mceResetDesignMode,mceBeginUndoLevel': function() {}, 32353 32354 // Add undo manager logic 32355 'mceEndUndoLevel,mceAddUndoLevel': function() { 32356 editor.undoManager.add(); 32357 }, 32358 32359 'Cut,Copy,Paste': function(command) { 32360 var doc = editor.getDoc(), failed; 32361 32362 // Try executing the native command 32363 try { 32364 execNativeCommand(command); 32365 } catch (ex) { 32366 // Command failed 32367 failed = TRUE; 32368 } 32369 32370 // Present alert message about clipboard access not being available 32371 if (failed || !doc.queryCommandSupported(command)) { 32372 var msg = editor.translate( 32373 "Your browser doesn't support direct access to the clipboard. " + 32374 "Please use the Ctrl+X/C/V keyboard shortcuts instead." 32375 ); 32376 32377 if (Env.mac) { 32378 msg = msg.replace(/Ctrl\+/g, '\u2318+'); 32379 } 32380 32381 editor.notificationManager.open({text: msg, type: 'error'}); 32382 } 32383 }, 32384 32385 // Override unlink command 32386 unlink: function() { 32387 if (selection.isCollapsed()) { 32388 var elm = selection.getNode(); 32389 if (elm.tagName == 'A') { 32390 editor.dom.remove(elm, true); 32391 } 32392 32393 return; 32394 } 32395 32396 formatter.remove("link"); 32397 }, 32398 32399 // Override justify commands to use the text formatter engine 32400 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) { 32401 var align = command.substring(7); 32402 32403 if (align == 'full') {
32404 align = 'justify'; 32405 } 32406 32407 // Remove all other alignments first 32408 each('left,center,right,justify'.split(','), function(name) { 32409 if (align != name) { 32410 formatter.remove('align' + name); 32411 } 32412 }); 32413 32414 if (align != 'none') { 32415 toggleFormat('align' + align); 32416 } 32417 }, 32418 32419 // Override list commands to fix WebKit bug 32420 'InsertUnorderedList,InsertOrderedList': function(command) { 32421 var listElm, listParent; 32422 32423 execNativeCommand(command); 32424 32425 // WebKit produces lists within block elements so we need to split them 32426 // we will replace the native list creation logic to custom logic later on 32427 // TODO: Remove this when the list creation logic is removed 32428 listElm = dom.getParent(selection.getNode(), 'ol,ul'); 32429 if (listElm) { 32430 listParent = listElm.parentNode; 32431 32432 // If list is within a text block then split that block 32433 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) { 32434 storeSelection(); 32435 dom.split(listParent, listElm); 32436 restoreSelection(); 32437 } 32438 } 32439 }, 32440 32441 // Override commands to use the text formatter engine 32442 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 32443 toggleFormat(command); 32444 }, 32445 32446 // Override commands to use the text formatter engine 32447 'ForeColor,HiliteColor,FontName': function(command, ui, value) { 32448 toggleFormat(command, value); 32449 }, 32450 32451 FontSize: function(command, ui, value) { 32452 var fontClasses, fontSizes; 32453 32454 // Convert font size 1-7 to styles 32455 if (value >= 1 && value <= 7) { 32456 fontSizes = explode(settings.font_size_style_values); 32457 fontClasses = explode(settings.font_size_classes); 32458 32459 if (fontClasses) { 32460 value = fontClasses[value - 1] || value; 32461 } else { 32462 value = fontSizes[value - 1] || value; 32463 } 32464 } 32465 32466 toggleFormat(command, value); 32467 }, 32468 32469 RemoveFormat: function(command) { 32470 formatter.remove(command); 32471 }, 32472 32473 mceBlockQuote: function() { 32474 toggleFormat('blockquote'); 32475 }, 32476 32477 FormatBlock: function(command, ui, value) { 32478 return toggleFormat(value || 'p'); 32479 }, 32480 32481 mceCleanup: function() { 32482 var bookmark = selection.getBookmark(); 32483 32484 editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE}); 32485 32486 selection.moveToBookmark(bookmark); 32487 }, 32488 32489 mceRemoveNode: function(command, ui, value) { 32490 var node = value || selection.getNode(); 32491 32492 // Make sure that the body node isn't removed 32493 if (node != editor.getBody()) { 32494 storeSelection(); 32495 editor.dom.remove(node, TRUE); 32496 restoreSelection(); 32497 } 32498 }, 32499 32500 mceSelectNodeDepth: function(command, ui, value) { 32501 var counter = 0; 32502 32503 dom.getParent(selection.getNode(), function(node) { 32504 if (node.nodeType == 1 && counter++ == value) { 32505 selection.select(node); 32506 return FALSE; 32507 } 32508 }, editor.getBody()); 32509 }, 32510 32511 mceSelectNode: function(command, ui, value) { 32512 selection.select(value); 32513 }, 32514 32515 mceInsertContent: function(command, ui, value) { 32516 var parser, serializer, parentNode, rootNode, fragment, args; 32517 var marker, rng, node, node2, bookmarkHtml, merge, data; 32518 var textInlineElements = editor.schema.getTextInlineElements(); 32519 32520 function trimOrPaddLeftRight(html) { 32521 var rng, container, offset; 32522 32523 rng = selection.getRng(true); 32524 container = rng.startContainer; 32525 offset = rng.startOffset; 32526 32527 function hasSiblingText(siblingName) { 32528 return container[siblingName] && container[siblingName].nodeType == 3; 32529 } 32530 32531 if (container.nodeType == 3) { 32532 if (offset > 0) { 32533 html = html.replace(/^ /, ' '); 32534 } else if (!hasSiblingText('previousSibling')) { 32535 html = html.replace(/^ /, ' '); 32536 } 32537 32538 if (offset < container.length) { 32539 html = html.replace(/ (<br>|)$/, ' '); 32540 } else if (!hasSiblingText('nextSibling')) { 32541 html = html.replace(/( | )(<br>|)$/, ' '); 32542 } 32543 } 32544 32545 return html; 32546 } 32547 32548 // Removes from a [b] c -> a c -> a c 32549 function trimNbspAfterDeleteAndPaddValue() { 32550 var rng, container, offset; 32551 32552 rng = selection.getRng(true); 32553 container = rng.startContainer; 32554 offset = rng.startOffset; 32555 32556 if (container.nodeType == 3 && rng.collapsed) { 32557 if (container.data[offset] === '\u00a0') { 32558 container.deleteData(offset, 1); 32559 32560 if (!/[\u00a0| ]$/.test(value)) { 32561 value += ' '; 32562 } 32563 } else if (container.data[offset - 1] === '\u00a0') { 32564 container.deleteData(offset - 1, 1); 32565 32566 if (!/[\u00a0| ]$/.test(value)) { 32567 value = ' ' + value; 32568 } 32569 } 32570 } 32571 } 32572 32573 function markInlineFormatElements(fragment) { 32574 if (merge) { 32575 for (node = fragment.firstChild; node; node = node.walk(true)) { 32576 if (textInlineElements[node.name]) { 32577 node.attr('data-mce-new', "true"); 32578 } 32579 } 32580 } 32581 } 32582 32583 function reduceInlineTextElements() { 32584 if (merge) { 32585 var root = editor.getBody(), elementUtils = new ElementUtils(dom); 32586 32587 each(dom.select('*[data-mce-new]'), function(node) { 32588 node.removeAttribute('data-mce-new'); 32589 32590 for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) { 32591 if (elementUtils.compare(testNode, node)) { 32592 dom.remove(node, true); 32593 } 32594 } 32595 }); 32596 } 32597 } 32598 32599 function moveSelectionToMarker(marker) { 32600 var parentEditableFalseElm; 32601 32602 function getContentEditableFalseParent(node) { 32603 var root = editor.getBody(); 32604 32605 for (; node && node !== root; node = node.parentNode) { 32606 if (editor.dom.getContentEditable(node) === 'false') { 32607 return node; 32608 } 32609 } 32610 32611 return null; 32612 } 32613 32614 if (!marker) { 32615 return; 32616 } 32617 32618 selection.scrollIntoView(marker); 32619
32620 // If marker is in cE=false then move selection to that element instead 32621 parentEditableFalseElm = getContentEditableFalseParent(marker); 32622 if (parentEditableFalseElm) { 32623 dom.remove(marker); 32624 selection.select(parentEditableFalseElm); 32625 return; 32626 } 32627 32628 // Move selection before marker and remove it 32629 rng = dom.createRng(); 32630 32631 // If previous sibling is a text node set the selection to the end of that node 32632 node = marker.previousSibling; 32633 if (node && node.nodeType == 3) { 32634 rng.setStart(node, node.nodeValue.length); 32635 32636 // TODO: Why can't we normalize on IE 32637 if (!isIE) { 32638 node2 = marker.nextSibling; 32639 if (node2 && node2.nodeType == 3) { 32640 node.appendData(node2.data); 32641 node2.parentNode.removeChild(node2); 32642 } 32643 } 32644 } else { 32645 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node 32646 rng.setStartBefore(marker); 32647 rng.setEndBefore(marker); 32648 } 32649 32650 // Remove the marker node and set the new range 32651 dom.remove(marker); 32652 selection.setRng(rng); 32653 } 32654 32655 if (typeof value != 'string') { 32656 merge = value.merge; 32657 data = value.data; 32658 value = value.content; 32659 } 32660 32661 // Check for whitespace before/after value 32662 if (/^ | $/.test(value)) { 32663 value = trimOrPaddLeftRight(value); 32664 } 32665 32666 // Setup parser and serializer 32667 parser = editor.parser; 32668 serializer = new Serializer({ 32669 validate: settings.validate 32670 }, editor.schema); 32671 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">​</span>'; 32672 32673 // Run beforeSetContent handlers on the HTML to be inserted 32674 args = {content: value, format: 'html', selection: true}; 32675 editor.fire('BeforeSetContent', args); 32676 value = args.content; 32677 32678 // Add caret at end of contents if it's missing 32679 if (value.indexOf('{$caret}') == -1) { 32680 value += '{$caret}'; 32681 } 32682 32683 // Replace the caret marker with a span bookmark element 32684 value = value.replace(/\{\$caret\}/, bookmarkHtml); 32685 32686 // If selection is at <body>|<p></p> then move it into <body><p>|</p> 32687 rng = selection.getRng(); 32688 var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null); 32689 var body = editor.getBody(); 32690 if (caretElement === body && selection.isCollapsed()) { 32691 if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) { 32692 rng = dom.createRng(); 32693 rng.setStart(body.firstChild, 0); 32694 rng.setEnd(body.firstChild, 0); 32695 selection.setRng(rng); 32696 } 32697 } 32698 32699 // Insert node maker where we will insert the new HTML and get it's parent 32700 if (!selection.isCollapsed()) { 32701 // Fix for #2595 seems that delete removes one extra character on 32702 // WebKit for some odd reason if you double click select a word 32703 editor.selection.setRng(editor.selection.getRng()); 32704 editor.getDoc().execCommand('Delete', false, null); 32705 trimNbspAfterDeleteAndPaddValue(); 32706 } 32707 32708 parentNode = selection.getNode(); 32709 32710 // Parse the fragment within the context of the parent node 32711 var parserArgs = {context: parentNode.nodeName.toLowerCase(), data: data}; 32712 fragment = parser.parse(value, parserArgs); 32713 32714 markInlineFormatElements(fragment); 32715 32716 // Move the caret to a more suitable location 32717 node = fragment.lastChild; 32718 if (node.attr('id') == 'mce_marker') { 32719 marker = node; 32720 32721 for (node = node.prev; node; node = node.walk(true)) { 32722 if (node.type == 3 || !dom.isBlock(node.name)) { 32723 if (editor.schema.isValidChild(node.parent.name, 'span')) { 32724 node.parent.insert(marker, node, node.name === 'br'); 32725 } 32726 break; 32727 } 32728 } 32729 } 32730 32731 editor._selectionOverrides.showBlockCaretContainer(parentNode); 32732 32733 // If parser says valid we can insert the contents into that parent 32734 if (!parserArgs.invalid) { 32735 value = serializer.serialize(fragment); 32736 32737 // Check if parent is empty or only has one BR element then set the innerHTML of that parent 32738 node = parentNode.firstChild; 32739 node2 = parentNode.lastChild; 32740 if (!node || (node === node2 && node.nodeName === 'BR')) { 32741 dom.setHTML(parentNode, value); 32742 } else { 32743 selection.setContent(value); 32744 } 32745 } else {
32746 // If the fragment was invalid within that context then we need 32747 // to parse and process the parent it's inserted into 32748 32749 // Insert bookmark node and get the parent 32750 selection.setContent(bookmarkHtml); 32751 parentNode = selection.getNode(); 32752 rootNode = editor.getBody(); 32753 32754 // Opera will return the document node when selection is in root 32755 if (parentNode.nodeType == 9) { 32756 parentNode = node = rootNode; 32757 } else { 32758 node = parentNode; 32759 } 32760 32761 // Find the ancestor just before the root element 32762 while (node !== rootNode) { 32763 parentNode = node; 32764 node = node.parentNode; 32765 } 32766 32767 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that 32768 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode); 32769 value = serializer.serialize( 32770 parser.parse( 32771 // Need to replace by using a function since $ in the contents would otherwise be a problem 32772 value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() { 32773 return serializer.serialize(fragment); 32774 }) 32775 ) 32776 ); 32777 32778 // Set the inner/outer HTML depending on if we are in the root or not 32779 if (parentNode == rootNode) { 32780 dom.setHTML(rootNode, value); 32781 } else { 32782 dom.setOuterHTML(parentNode, value); 32783 } 32784 } 32785 32786 reduceInlineTextElements(); 32787 moveSelectionToMarker(dom.get('mce_marker')); 32788 editor.fire('SetContent', args); 32789 editor.addVisual(); 32790 }, 32791 32792 mceInsertRawHTML: function(command, ui, value) { 32793 selection.setContent('tiny_mce_marker'); 32794 editor.setContent( 32795 editor.getContent().replace(/tiny_mce_marker/g, function() { 32796 return value; 32797 }) 32798 ); 32799 }, 32800 32801 mceToggleFormat: function(command, ui, value) { 32802 toggleFormat(value); 32803 }, 32804 32805 mceSetContent: function(command, ui, value) { 32806 editor.setContent(value); 32807 }, 32808 32809 'Indent,Outdent': function(command) { 32810 var intentValue, indentUnit, value; 32811 32812 // Setup indent level 32813 intentValue = settings.indentation; 32814 indentUnit = /[a-z%]+$/i.exec(intentValue); 32815 intentValue = parseInt(intentValue, 10); 32816 32817 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) { 32818 // If forced_root_blocks is set to false we don't have a block to indent so lets create a div 32819 if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) { 32820 formatter.apply('div'); 32821 } 32822 32823 each(selection.getSelectedBlocks(), function(element) { 32824 if (dom.getContentEditable(element) === "false") { 32825 return; 32826 } 32827 32828 if (element.nodeName != "LI") { 32829 var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding'; 32830 32831 indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left'; 32832 32833 if (command == 'outdent') { 32834 value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue); 32835 dom.setStyle(element, indentStyleName, value ? value + indentUnit : ''); 32836 } else { 32837 value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit; 32838 dom.setStyle(element, indentStyleName, value); 32839 } 32840 } 32841 }); 32842 } else { 32843 execNativeCommand(command); 32844 } 32845 }, 32846 32847 mceRepaint: function() { 32848 }, 32849 32850 InsertHorizontalRule: function() { 32851 editor.execCommand('mceInsertContent', false, '<hr />'); 32852 }, 32853 32854 mceToggleVisualAid: function() { 32855 editor.hasVisual = !editor.hasVisual; 32856 editor.addVisual(); 32857 }, 32858 32859 mceReplaceContent: function(command, ui, value) { 32860 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'}))); 32861 }, 32862 32863 mceInsertLink: function(command, ui, value) { 32864 var anchor; 32865 32866 if (typeof value == 'string') { 32867 value = {href: value}; 32868 } 32869 32870 anchor = dom.getParent(selection.getNode(), 'a'); 32871 32872 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. 32873 value.href = value.href.replace(' ', '%20'); 32874 32875 // Remove existing links if there could be child links or that the href isn't specified 32876 if (!anchor || !value.href) { 32877 formatter.remove('link'); 32878 } 32879 32880 // Apply new link to selection 32881 if (value.href) { 32882 formatter.apply('link', value, anchor); 32883 } 32884 }, 32885 32886 selectAll: function() { 32887 var root = dom.getRoot(), rng; 32888 32889 if (selection.getRng().setStart) { 32890 rng = dom.createRng(); 32891 rng.setStart(root, 0); 32892 rng.setEnd(root, root.childNodes.length); 32893 selection.setRng(rng); 32894 } else {
32895 // IE will render it's own root level block elements and sometimes 32896 // even put font elements in them when the user starts typing. So we need to 32897 // move the selection to a more suitable element from this: 32898 // <body>|<p></p></body> to this: <body><p>|</p></body> 32899 rng = selection.getRng(); 32900 if (!rng.item) { 32901 rng.moveToElementText(root); 32902 rng.select(); 32903 } 32904 } 32905 }, 32906 32907 "delete": function() { 32908 execNativeCommand("Delete"); 32909 32910 // Check if body is empty after the delete call if so then set the contents 32911 // to an empty string and move the caret to any block produced by that operation 32912 // this fixes the issue with root blocks not being properly produced after a delete call on IE 32913 var body = editor.getBody(); 32914 32915 if (dom.isEmpty(body)) { 32916 editor.setContent(''); 32917 32918 if (body.firstChild && dom.isBlock(body.firstChild)) { 32919 editor.selection.setCursorLocation(body.firstChild, 0); 32920 } else { 32921 editor.selection.setCursorLocation(body, 0); 32922 } 32923 } 32924 }, 32925 32926 mceNewDocument: function() { 32927 editor.setContent(''); 32928 }, 32929 32930 InsertLineBreak: function(command, ui, value) { 32931 // We load the current event in from EnterKey.js when appropriate to heed 32932 // certain event-specific variations such as ctrl-enter in a list 32933 var evt = value; 32934 var brElm, extraBr, marker; 32935 var rng = selection.getRng(true); 32936 new RangeUtils(dom).normalize(rng); 32937 32938 var offset = rng.startOffset; 32939 var container = rng.startContainer; 32940 32941 // Resolve node index 32942 if (container.nodeType == 1 && container.hasChildNodes()) { 32943 var isAfterLastNodeInContainer = offset > container.childNodes.length - 1; 32944 32945 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container; 32946 if (isAfterLastNodeInContainer && container.nodeType == 3) { 32947 offset = container.nodeValue.length; 32948 } else { 32949 offset = 0; 32950 } 32951 } 32952 32953 var parentBlock = dom.getParent(container, dom.isBlock); 32954 var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 32955 var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null; 32956 var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5 32957 32958 // Enter inside block contained within a LI then split or insert before/after LI 32959 var isControlKey = evt && evt.ctrlKey; 32960 if (containerBlockName == 'LI' && !isControlKey) { 32961 parentBlock = containerBlock; 32962 parentBlockName = containerBlockName; 32963 } 32964 32965 // Walks the parent block to the right and look for BR elements 32966 function hasRightSideContent() { 32967 var walker = new TreeWalker(container, parentBlock), node; 32968 var nonEmptyElementsMap = editor.schema.getNonEmptyElements(); 32969 32970 while ((node = walker.next())) { 32971 if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) { 32972 return true; 32973 } 32974 } 32975 } 32976 32977 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) { 32978 // Insert extra BR element at the end block elements 32979 if (!isOldIE && !hasRightSideContent()) { 32980 brElm = dom.create('br'); 32981 rng.insertNode(brElm); 32982 rng.setStartAfter(brElm); 32983 rng.setEndAfter(brElm); 32984 extraBr = true; 32985 } 32986 } 32987 32988 brElm = dom.create('br'); 32989 rng.insertNode(brElm); 32990 32991 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it 32992 var documentMode = dom.doc.documentMode; 32993 if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) { 32994 brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm); 32995 } 32996 32997 // Insert temp marker and scroll to that 32998 marker = dom.create('span', {}, ' '); 32999 brElm.parentNode.insertBefore(marker, brElm); 33000 selection.scrollIntoView(marker); 33001 dom.remove(marker); 33002 33003 if (!extraBr) { 33004 rng.setStartAfter(brElm); 33005 rng.setEndAfter(brElm); 33006 } else { 33007 rng.setStartBefore(brElm); 33008 rng.setEndBefore(brElm); 33009 } 33010 33011 selection.setRng(rng); 33012 editor.undoManager.add(); 33013 33014 return TRUE; 33015 } 33016 }); 33017 33018 // Add queryCommandState overrides 33019 addCommands({ 33020 // Override justify commands 33021 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) { 33022 var name = 'align' + command.substring(7); 33023 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks(); 33024 var matches = map(nodes, function(node) { 33025 return !!formatter.matchNode(node, name); 33026 }); 33027 return inArray(matches, TRUE) !== -1; 33028 }, 33029 33030 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) { 33031 return isFormatMatch(command); 33032 }, 33033 33034 mceBlockQuote: function() { 33035 return isFormatMatch('blockquote'); 33036 }, 33037 33038 Outdent: function() { 33039 var node; 33040 33041 if (settings.inline_styles) { 33042 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 33043 return TRUE; 33044 } 33045 33046 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) { 33047 return TRUE; 33048 } 33049 } 33050 33051 return ( 33052 queryCommandState('InsertUnorderedList') || 33053 queryCommandState('InsertOrderedList') || 33054 (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE')) 33055 ); 33056 }, 33057 33058 'InsertUnorderedList,InsertOrderedList': function(command) { 33059 var list = dom.getParent(selection.getNode(), 'ul,ol'); 33060 33061 return list && 33062 ( 33063 command === 'insertunorderedlist' && list.tagName === 'UL' || 33064 command === 'insertorderedlist' && list.tagName === 'OL' 33065 ); 33066 } 33067 }, 'state'); 33068 33069 // Add queryCommandValue overrides 33070 addCommands({ 33071 'FontSize,FontName': function(command) { 33072 var value = 0, parent; 33073 33074 if ((parent = dom.getParent(selection.getNode(), 'span'))) { 33075 if (command == 'fontsize') { 33076 value = parent.style.fontSize; 33077 } else { 33078 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase(); 33079 } 33080 } 33081 33082 return value; 33083 } 33084 }, 'value'); 33085 33086 // Add undo manager logic 33087 addCommands({ 33088 Undo: function() { 33089 editor.undoManager.undo(); 33090 }, 33091 33092 Redo: function() { 33093 editor.undoManager.redo(); 33094 } 33095 }); 33096 }; 33097}); 33098 33099// Included from: js/tinymce/classes/util/URI.js 33100 33101/** 33102 * URI.js 33103 * 33104 * Released under LGPL License. 33105 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 33106 * 33107 * License: http://www.tinymce.com/license 33108 * Contributing: http://www.tinymce.com/contributing 33109 */ 33110 33111/** 33112 * This class handles parsing, modification and serialization of URI/URL strings. 33113 * @class tinymce.util.URI 33114 */ 33115define("tinymce/util/URI", [ 33116 "tinymce/util/Tools" 33117], function(Tools) { 33118 var each = Tools.each, trim = Tools.trim; 33119 var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' '); 33120 var DEFAULT_PORTS = { 33121 'ftp': 21, 33122 'http': 80, 33123 'https': 443, 33124 'mailto': 25 33125 }; 33126 33127 /** 33128 * Constructs a new URI instance. 33129 * 33130 * @constructor 33131 * @method URI 33132 * @param {String} url URI string to parse. 33133 * @param {Object} settings Optional settings object. 33134 */ 33135 function URI(url, settings) { 33136 var self = this, baseUri, base_url; 33137 33138 url = trim(url); 33139 settings = self.settings = settings || {}; 33140 baseUri = settings.base_uri; 33141 33142 // Strange app protocol that isn't http/https or local anchor 33143 // For example: mailto,skype,tel etc. 33144 if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) { 33145 self.source = url; 33146 return; 33147 } 33148 33149 var isProtocolRelative = url.indexOf('//') === 0; 33150
33151 // Absolute path with no host, fake host and protocol 33152 if (url.indexOf('/') === 0 && !isProtocolRelative) { 33153 url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url; 33154 } 33155 33156 // Relative path http:// or protocol relative //path 33157 if (!/^[\w\-]*:?\/\//.test(url)) { 33158 base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory; 33159 if (settings.base_uri.protocol === "") { 33160 url = '//mce_host' + self.toAbsPath(base_url, url); 33161 } else { 33162 url = /([^#?]*)([#?]?.*)/.exec(url); 33163 url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2]; 33164 } 33165 } 33166 33167 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) 33168 url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something 33169 33170 /*jshint maxlen: 255 */ 33171 /*eslint max-len: 0 */ 33172 url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url); 33173 33174 each(queryParts, function(v, i) { 33175 var part = url[i]; 33176 33177 // Zope 3 workaround, they use @@something 33178 if (part) { 33179 part = part.replace(/\(mce_at\)/g, '@@'); 33180 } 33181 33182 self[v] = part; 33183 }); 33184 33185 if (baseUri) { 33186 if (!self.protocol) { 33187 self.protocol = baseUri.protocol; 33188 } 33189 33190 if (!self.userInfo) { 33191 self.userInfo = baseUri.userInfo; 33192 } 33193 33194 if (!self.port && self.host === 'mce_host') { 33195 self.port = baseUri.port; 33196 } 33197 33198 if (!self.host || self.host === 'mce_host') { 33199 self.host = baseUri.host; 33200 } 33201 33202 self.source = ''; 33203 } 33204 33205 if (isProtocolRelative) { 33206 self.protocol = ''; 33207 } 33208 33209 //t.path = t.path || '/'; 33210 } 33211 33212 URI.prototype = { 33213 /** 33214 * Sets the internal path part of the URI. 33215 * 33216 * @method setPath 33217 * @param {string} path Path string to set. 33218 */ 33219 setPath: function(path) { 33220 var self = this; 33221 33222 path = /^(.*?)\/?(\w+)?$/.exec(path); 33223 33224 // Update path parts 33225 self.path = path[0]; 33226 self.directory = path[1]; 33227 self.file = path[2]; 33228 33229 // Rebuild source 33230 self.source = ''; 33231 self.getURI(); 33232 }, 33233 33234 /** 33235 * Converts the specified URI into a relative URI based on the current URI instance location. 33236 * 33237 * @method toRelative 33238 * @param {String} uri URI to convert into a relative path/URI. 33239 * @return {String} Relative URI from the point specified in the current URI instance. 33240 * @example 33241 * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm 33242 * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm'); 33243 */ 33244 toRelative: function(uri) { 33245 var self = this, output; 33246 33247 if (uri === "./") { 33248 return uri; 33249 } 33250 33251 uri = new URI(uri, {base_uri: self}); 33252 33253 // Not on same domain/port or protocol 33254 if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port || 33255 (self.protocol != uri.protocol && uri.protocol !== "")) { 33256 return uri.getURI(); 33257 } 33258 33259 var tu = self.getURI(), uu = uri.getURI(); 33260 33261 // Allow usage of the base_uri when relative_urls = true 33262 if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) { 33263 return tu; 33264 } 33265 33266 output = self.toRelPath(self.path, uri.path); 33267 33268 // Add query 33269 if (uri.query) { 33270 output += '?' + uri.query; 33271 } 33272 33273 // Add anchor 33274 if (uri.anchor) { 33275 output += '#' + uri.anchor; 33276 } 33277 33278 return output; 33279 }, 33280 33281 /** 33282 * Converts the specified URI into a absolute URI based on the current URI instance location. 33283 * 33284 * @method toAbsolute 33285 * @param {String} uri URI to convert into a relative path/URI. 33286 * @param {Boolean} noHost No host and protocol prefix. 33287 * @return {String} Absolute URI from the point specified in the current URI instance. 33288 * @example 33289 * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm 33290 * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm'); 33291 */ 33292 toAbsolute: function(uri, noHost) { 33293 uri = new URI(uri, {base_uri: this}); 33294 33295 return uri.getURI(noHost && this.isSameOrigin(uri)); 33296 }, 33297 33298 /** 33299 * Determine whether the given URI has the same origin as this URI. Based on RFC-6454. 33300 * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they 33301 * won't match, if the port specifications differ. 33302 * 33303 * @method isSameOrigin 33304 * @param {tinymce.util.URI} uri Uri instance to compare. 33305 * @returns {Boolean} True if the origins are the same. 33306 */ 33307 isSameOrigin: function(uri) { 33308 if (this.host == uri.host && this.protocol == uri.protocol) { 33309 if (this.port == uri.port) { 33310 return true; 33311 } 33312 33313 var defaultPort = DEFAULT_PORTS[this.protocol]; 33314 if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) { 33315 return true; 33316 } 33317 } 33318 33319 return false;
33320 }, 33321 33322 /** 33323 * Converts a absolute path into a relative path. 33324 * 33325 * @method toRelPath 33326 * @param {String} base Base point to convert the path from. 33327 * @param {String} path Absolute path to convert into a relative path. 33328 */ 33329 toRelPath: function(base, path) { 33330 var items, breakPoint = 0, out = '', i, l; 33331 33332 // Split the paths 33333 base = base.substring(0, base.lastIndexOf('/')); 33334 base = base.split('/'); 33335 items = path.split('/'); 33336 33337 if (base.length >= items.length) { 33338 for (i = 0, l = base.length; i < l; i++) { 33339 if (i >= items.length || base[i] != items[i]) { 33340 breakPoint = i + 1; 33341 break; 33342 } 33343 } 33344 } 33345 33346 if (base.length < items.length) { 33347 for (i = 0, l = items.length; i < l; i++) { 33348 if (i >= base.length || base[i] != items[i]) { 33349 breakPoint = i + 1; 33350 break; 33351 } 33352 } 33353 } 33354 33355 if (breakPoint === 1) { 33356 return path; 33357 } 33358 33359 for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) { 33360 out += "../"; 33361 } 33362 33363 for (i = breakPoint - 1, l = items.length; i < l; i++) { 33364 if (i != breakPoint - 1) { 33365 out += "/" + items[i]; 33366 } else { 33367 out += items[i]; 33368 } 33369 } 33370 33371 return out; 33372 }, 33373 33374 /** 33375 * Converts a relative path into a absolute path. 33376 * 33377 * @method toAbsPath 33378 * @param {String} base Base point to convert the path from. 33379 * @param {String} path Relative path to convert into an absolute path. 33380 */ 33381 toAbsPath: function(base, path) { 33382 var i, nb = 0, o = [], tr, outPath; 33383 33384 // Split paths 33385 tr = /\/$/.test(path) ? '/' : ''; 33386 base = base.split('/'); 33387 path = path.split('/'); 33388 33389 // Remove empty chunks 33390 each(base, function(k) { 33391 if (k) { 33392 o.push(k); 33393 } 33394 }); 33395 33396 base = o; 33397 33398 // Merge relURLParts chunks 33399 for (i = path.length - 1, o = []; i >= 0; i--) { 33400 // Ignore empty or . 33401 if (path[i].length === 0 || path[i] === ".") { 33402 continue; 33403 } 33404 33405 // Is parent 33406 if (path[i] === '..') { 33407 nb++; 33408 continue; 33409 } 33410 33411 // Move up 33412 if (nb > 0) { 33413 nb--; 33414 continue; 33415 } 33416 33417 o.push(path[i]); 33418 } 33419 33420 i = base.length - nb; 33421 33422 // If /a/b/c or / 33423 if (i <= 0) { 33424 outPath = o.reverse().join('/'); 33425 } else { 33426 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/'); 33427 } 33428 33429 // Add front / if it's needed 33430 if (outPath.indexOf('/') !== 0) { 33431 outPath = '/' + outPath; 33432 } 33433 33434 // Add traling / if it's needed 33435 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) { 33436 outPath += tr; 33437 } 33438 33439 return outPath; 33440 }, 33441 33442 /** 33443 * Returns the full URI of the internal structure. 33444 * 33445 * @method getURI 33446 * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false. 33447 */ 33448 getURI: function(noProtoHost) { 33449 var s, self = this; 33450 33451 // Rebuild source 33452 if (!self.source || noProtoHost) { 33453 s = ''; 33454 33455 if (!noProtoHost) { 33456 if (self.protocol) { 33457 s += self.protocol + '://'; 33458 } else { 33459 s += '//'; 33460 } 33461 33462 if (self.userInfo) { 33463 s += self.userInfo + '@'; 33464 } 33465 33466 if (self.host) { 33467 s += self.host; 33468 } 33469 33470 if (self.port) { 33471 s += ':' + self.port; 33472 } 33473 } 33474 33475 if (self.path) { 33476 s += self.path; 33477 } 33478 33479 if (self.query) { 33480 s += '?' + self.query; 33481 } 33482 33483 if (self.anchor) { 33484 s += '#' + self.anchor; 33485 } 33486 33487 self.source = s; 33488 } 33489 33490 return self.source; 33491 } 33492 }; 33493 33494 URI.parseDataUri = function(uri) { 33495 var type, matches; 33496 33497 uri = decodeURIComponent(uri).split(','); 33498 33499 matches = /data:([^;]+)/.exec(uri[0]); 33500 if (matches) { 33501 type = matches[1]; 33502 } 33503 33504 return { 33505 type: type, 33506 data: uri[1] 33507 }; 33508 }; 33509 33510 return URI; 33511}); 33512 33513// Included from: js/tinymce/classes/util/Class.js 33514 33515/** 33516 * Class.js 33517 * 33518 * Released under LGPL License. 33519 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 33520 * 33521 * License: http://www.tinymce.com/license 33522 * Contributing: http://www.tinymce.com/contributing 33523 */ 33524 33525/** 33526 * This utilitiy class is used for easier inheritance. 33527 * 33528 * Features: 33529 * * Exposed super functions: this._super(); 33530 * * Mixins 33531 * * Dummy functions 33532 * * Property functions: var value = object.value(); and object.value(newValue); 33533 * * Static functions 33534 * * Defaults settings 33535 */ 33536define("tinymce/util/Class", [ 33537 "tinymce/util/Tools" 33538], function(Tools) { 33539 var each = Tools.each, extend = Tools.extend; 33540 33541 var extendClass, initializing; 33542 33543 function Class() { 33544 } 33545 33546 // Provides classical inheritance, based on code made by John Resig 33547 Class.extend = extendClass = function(prop) { 33548 var self = this, _super = self.prototype, prototype, name, member; 33549 33550 // The dummy class constructor 33551 function Class() { 33552 var i, mixins, mixin, self = this; 33553 33554 // All construction is actually done in the init method 33555 if (!initializing) { 33556 // Run class constuctor 33557 if (self.init) {
33558 self.init.apply(self, arguments); 33559 } 33560 33561 // Run mixin constructors 33562 mixins = self.Mixins; 33563 if (mixins) { 33564 i = mixins.length; 33565 while (i--) { 33566 mixin = mixins[i]; 33567 if (mixin.init) { 33568 mixin.init.apply(self, arguments); 33569 } 33570 } 33571 } 33572 } 33573 } 33574 33575 // Dummy function, needs to be extended in order to provide functionality 33576 function dummy() { 33577 return this; 33578 } 33579 33580 // Creates a overloaded method for the class 33581 // this enables you to use this._super(); to call the super function 33582 function createMethod(name, fn) { 33583 return function() { 33584 var self = this, tmp = self._super, ret; 33585 33586 self._super = _super[name]; 33587 ret = fn.apply(self, arguments); 33588 self._super = tmp; 33589 33590 return ret; 33591 }; 33592 } 33593 33594 // Instantiate a base class (but only create the instance, 33595 // don't run the init constructor) 33596 initializing = true; 33597 33598 /*eslint new-cap:0 */ 33599 prototype = new self(); 33600 initializing = false; 33601 33602 // Add mixins 33603 if (prop.Mixins) { 33604 each(prop.Mixins, function(mixin) { 33605 mixin = mixin; 33606 33607 for (var name in mixin) { 33608 if (name !== "init") { 33609 prop[name] = mixin[name]; 33610 } 33611 } 33612 }); 33613 33614 if (_super.Mixins) { 33615 prop.Mixins = _super.Mixins.concat(prop.Mixins); 33616 } 33617 } 33618 33619 // Generate dummy methods 33620 if (prop.Methods) { 33621 each(prop.Methods.split(','), function(name) { 33622 prop[name] = dummy; 33623 }); 33624 } 33625 33626 // Generate property methods 33627 if (prop.Properties) { 33628 each(prop.Properties.split(','), function(name) { 33629 var fieldName = '_' + name; 33630 33631 prop[name] = function(value) { 33632 var self = this, undef; 33633 33634 // Set value 33635 if (value !== undef) { 33636 self[fieldName] = value; 33637 33638 return self; 33639 } 33640 33641 // Get value 33642 return self[fieldName]; 33643 }; 33644 }); 33645 } 33646 33647 // Static functions 33648 if (prop.Statics) { 33649 each(prop.Statics, function(func, name) { 33650 Class[name] = func; 33651 }); 33652 } 33653 33654 // Default settings 33655 if (prop.Defaults && _super.Defaults) { 33656 prop.Defaults = extend({}, _super.Defaults, prop.Defaults); 33657 } 33658 33659 // Copy the properties over onto the new prototype 33660 for (name in prop) { 33661 member = prop[name]; 33662 33663 if (typeof member == "function" && _super[name]) { 33664 prototype[name] = createMethod(name, member); 33665 } else { 33666 prototype[name] = member; 33667 } 33668 } 33669 33670 // Populate our constructed prototype object 33671 Class.prototype = prototype; 33672 33673 // Enforce the constructor to be what we expect 33674 Class.constructor = Class; 33675 33676 // And make this class extendible 33677 Class.extend = extendClass; 33678 33679 return Class; 33680 }; 33681 33682 return Class; 33683}); 33684 33685// Included from: js/tinymce/classes/util/EventDispatcher.js 33686 33687/** 33688 * EventDispatcher.js 33689 * 33690 * Released under LGPL License. 33691 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 33692 * 33693 * License: http://www.tinymce.com/license 33694 * Contributing: http://www.tinymce.com/contributing 33695 */ 33696 33697/** 33698 * This class lets you add/remove and fire events by name on the specified scope. This makes 33699 * it easy to add event listener logic to any class. 33700 * 33701 * @class tinymce.util.EventDispatcher 33702 * @example 33703 * var eventDispatcher = new EventDispatcher(); 33704 * 33705 * eventDispatcher.on('click', function() {console.log('data');}); 33706 * eventDispatcher.fire('click', {data: 123}); 33707 */ 33708define("tinymce/util/EventDispatcher", [ 33709 "tinymce/util/Tools" 33710], function(Tools) { 33711 var nativeEvents = Tools.makeMap( 33712 "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " + 33713 "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " + 33714 "draggesture dragdrop drop drag submit " + 33715 "compositionstart compositionend compositionupdate touchstart touchend", 33716 ' ' 33717 ); 33718 33719 function Dispatcher(settings) { 33720 var self = this, scope, bindings = {}, toggleEvent; 33721 33722 function returnFalse() { 33723 return false; 33724 } 33725 33726 function returnTrue() { 33727 return true; 33728 } 33729 33730 settings = settings || {}; 33731 scope = settings.scope || self; 33732 toggleEvent = settings.toggleEvent || returnFalse; 33733 33734 /** 33735 * Fires the specified event by name. 33736 * 33737 * @method fire 33738 * @param {String} name Name of the event to fire. 33739 * @param {Object?} args Event arguments. 33740 * @return {Object} Event args instance passed in. 33741 * @example 33742 * instance.fire('event', {...}); 33743 */ 33744 function fire(name, args) { 33745 var handlers, i, l, callback; 33746 33747 name = name.toLowerCase(); 33748 args = args || {}; 33749 args.type = name; 33750 33751 // Setup target is there isn't one 33752 if (!args.target) { 33753 args.target = scope; 33754 } 33755 33756 // Add event delegation methods if they are missing 33757 if (!args.preventDefault) { 33758 // Add preventDefault method 33759 args.preventDefault = function() { 33760 args.isDefaultPrevented = returnTrue; 33761 }; 33762 33763 // Add stopPropagation 33764 args.stopPropagation = function() { 33765 args.isPropagationStopped = returnTrue; 33766 }; 33767 33768 // Add stopImmediatePropagation 33769 args.stopImmediatePropagation = function() { 33770 args.isImmediatePropagationStopped = returnTrue; 33771 }; 33772 33773 // Add event delegation states 33774 args.isDefaultPrevented = returnFalse; 33775 args.isPropagationStopped = returnFalse; 33776 args.isImmediatePropagationStopped = returnFalse; 33777 } 33778 33779 if (settings.beforeFire) { 33780 settings.beforeFire(args); 33781 } 33782 33783 handlers = bindings[name]; 33784 if (handlers) { 33785 for (i = 0, l = handlers.length; i < l; i++) { 33786 callback = handlers[i]; 33787 33788 // Unbind handlers marked with "once" 33789 if (callback.once) { 33790 off(name, callback.func); 33791 } 33792 33793 // Stop immediate propagation if needed 33794 if (args.isImmediatePropagationStopped()) { 33795 args.stopPropagation(); 33796 return args; 33797 } 33798 33799 // If callback returns false then prevent default and stop all propagation 33800 if (callback.func.call(scope, args) === false) { 33801 args.preventDefault(); 33802 return args; 33803 } 33804 } 33805 } 33806 33807 return args; 33808 } 33809 33810 /** 33811 * Binds an event listener to a specific event by name. 33812 * 33813 * @method on 33814 * @param {String} name Event name or space separated list of events to bind. 33815 * @param {callback} callback Callback to be executed when the event occurs. 33816 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 33817 * @return {Object} Current class instance. 33818 * @example 33819 * instance.on('event', function(e) { 33820 * // Callback logic 33821 * }); 33822 */ 33823 function on(name, callback, prepend, extra) { 33824 var handlers, names, i; 33825 33826 if (callback === false) { 33827 callback = returnFalse; 33828 } 33829 33830 if (callback) { 33831 callback = { 33832 func: callback 33833 }; 33834 33835 if (extra) { 33836 Tools.extend(callback, extra); 33837 } 33838 33839 names = name.toLowerCase().split(' '); 33840 i = names.length; 33841 while (i--) { 33842 name = names[i]; 33843 handlers = bindings[name]; 33844 if (!handlers) { 33845 handlers = bindings[name] = []; 33846 toggleEvent(name, true); 33847 } 33848 33849 if (prepend) { 33850 handlers.unshift(callback); 33851 } else { 33852 handlers.push(callback); 33853 } 33854 } 33855 } 33856 33857 return self; 33858 } 33859 33860 /** 33861 * Unbinds an event listener to a specific event by name. 33862 * 33863 * @method off 33864 * @param {String?} name Name of the event to unbind. 33865 * @param {callback?} callback Callback to unbind. 33866 * @return {Object} Current class instance. 33867 * @example 33868 * // Unbind specific callback 33869 * instance.off('event', handler); 33870 * 33871 * // Unbind all listeners by name 33872 * instance.off('event'); 33873 * 33874 * // Unbind all events 33875 * instance.off(); 33876 */ 33877 function off(name, callback) { 33878 var i, handlers, bindingName, names, hi; 33879 33880 if (name) { 33881 names = name.toLowerCase().split(' '); 33882 i = names.length; 33883 while (i--) { 33884 name = names[i]; 33885 handlers = bindings[name]; 33886 33887 // Unbind all handlers 33888 if (!name) { 33889 for (bindingName in bindings) { 33890 toggleEvent(bindingName, false); 33891 delete bindings[bindingName]; 33892 } 33893 33894 return self; 33895 } 33896 33897 if (handlers) { 33898 // Unbind all by name 33899 if (!callback) { 33900 handlers.length = 0; 33901 } else { 33902 // Unbind specific ones 33903 hi = handlers.length; 33904 while (hi--) { 33905 if (handlers[hi].func === callback) { 33906 handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1)); 33907 bindings[name] = handlers; 33908 } 33909 } 33910 } 33911 33912 if (!handlers.length) { 33913 toggleEvent(name, false); 33914 delete bindings[name]; 33915 } 33916 } 33917 } 33918 } else { 33919 for (name in bindings) { 33920 toggleEvent(name, false); 33921 } 33922 33923 bindings = {}; 33924 } 33925 33926 return self; 33927 } 33928 33929 /** 33930 * Binds an event listener to a specific event by name 33931 * and automatically unbind the event once the callback fires. 33932 * 33933 * @method once 33934 * @param {String} name Event name or space separated list of events to bind. 33935 * @param {callback} callback Callback to be executed when the event occurs. 33936 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 33937 * @return {Object} Current class instance. 33938 * @example 33939 * instance.once('event', function(e) { 33940 * // Callback logic 33941 * }); 33942 */ 33943 function once(name, callback, prepend) { 33944 return on(name, callback, prepend, {once: true}); 33945 } 33946 33947 /** 33948 * Returns true/false if the dispatcher has a event of the specified name. 33949 * 33950 * @method has 33951 * @param {String} name Name of the event to check for. 33952 * @return {Boolean} true/false if the event exists or not. 33953 */ 33954 function has(name) { 33955 name = name.toLowerCase(); 33956 return !(!bindings[name] || bindings[name].length === 0); 33957 } 33958 33959 // Expose 33960 self.fire = fire; 33961 self.on = on; 33962 self.off = off; 33963 self.once = once; 33964 self.has = has; 33965 } 33966 33967 /** 33968 * Returns true/false if the specified event name is a native browser event or not. 33969 * 33970 * @method isNative 33971 * @param {String} name Name to check if it's native. 33972 * @return {Boolean} true/false if the event is native or not. 33973 * @static 33974 */ 33975 Dispatcher.isNative = function(name) { 33976 return !!nativeEvents[name.toLowerCase()]; 33977 }; 33978 33979 return Dispatcher; 33980}); 33981 33982// Included from: js/tinymce/classes/data/Binding.js 33983 33984/** 33985 * Binding.js 33986 * 33987 * Released under LGPL License. 33988 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 33989 * 33990 * License: http://www.tinymce.com/license 33991 * Contributing: http://www.tinymce.com/contributing 33992 */ 33993 33994/** 33995 * This class gets dynamically extended to provide a binding between two models. This makes it possible to
33996 * sync the state of two properties in two models by a layer of abstraction. 33997 * 33998 * @private 33999 * @class tinymce.data.Binding 34000 */ 34001define("tinymce/data/Binding", [], function() { 34002 /** 34003 * Constructs a new bidning. 34004 * 34005 * @constructor 34006 * @method Binding 34007 * @param {Object} settings Settings to the binding. 34008 */ 34009 function Binding(settings) { 34010 this.create = settings.create; 34011 } 34012 34013 /** 34014 * Creates a binding for a property on a model. 34015 * 34016 * @method create 34017 * @param {tinymce.data.ObservableObject} model Model to create binding to. 34018 * @param {String} name Name of property to bind. 34019 * @return {tinymce.data.Binding} Binding instance. 34020 */ 34021 Binding.create = function(model, name) { 34022 return new Binding({ 34023 create: function(otherModel, otherName) { 34024 var bindings; 34025 34026 function fromSelfToOther(e) { 34027 otherModel.set(otherName, e.value); 34028 } 34029 34030 function fromOtherToSelf(e) { 34031 model.set(name, e.value); 34032 } 34033 34034 otherModel.on('change:' + otherName, fromOtherToSelf); 34035 model.on('change:' + name, fromSelfToOther); 34036 34037 // Keep track of the bindings 34038 bindings = otherModel._bindings; 34039 34040 if (!bindings) { 34041 bindings = otherModel._bindings = []; 34042 34043 otherModel.on('destroy', function() { 34044 var i = bindings.length; 34045 34046 while (i--) { 34047 bindings[i](); 34048 } 34049 }); 34050 } 34051 34052 bindings.push(function() { 34053 model.off('change:' + name, fromSelfToOther); 34054 }); 34055 34056 return model.get(name); 34057 } 34058 }); 34059 }; 34060 34061 return Binding; 34062}); 34063 34064// Included from: js/tinymce/classes/util/Observable.js 34065 34066/** 34067 * Observable.js 34068 * 34069 * Released under LGPL License. 34070 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 34071 * 34072 * License: http://www.tinymce.com/license 34073 * Contributing: http://www.tinymce.com/contributing 34074 */ 34075 34076/** 34077 * This mixin will add event binding logic to classes. 34078 * 34079 * @mixin tinymce.util.Observable 34080 */ 34081define("tinymce/util/Observable", [ 34082 "tinymce/util/EventDispatcher" 34083], function(EventDispatcher) { 34084 function getEventDispatcher(obj) { 34085 if (!obj._eventDispatcher) { 34086 obj._eventDispatcher = new EventDispatcher({ 34087 scope: obj, 34088 toggleEvent: function(name, state) { 34089 if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) { 34090 obj.toggleNativeEvent(name, state); 34091 } 34092 } 34093 }); 34094 } 34095 34096 return obj._eventDispatcher; 34097 } 34098 34099 return { 34100 /** 34101 * Fires the specified event by name. 34102 * 34103 * @method fire 34104 * @param {String} name Name of the event to fire. 34105 * @param {Object?} args Event arguments. 34106 * @param {Boolean?} bubble True/false if the event is to be bubbled. 34107 * @return {Object} Event args instance passed in. 34108 * @example 34109 * instance.fire('event', {...}); 34110 */ 34111 fire: function(name, args, bubble) { 34112 var self = this; 34113 34114 // Prevent all events except the remove event after the instance has been removed 34115 if (self.removed && name !== "remove") { 34116 return args; 34117 } 34118 34119 args = getEventDispatcher(self).fire(name, args, bubble); 34120 34121 // Bubble event up to parents 34122 if (bubble !== false && self.parent) { 34123 var parent = self.parent(); 34124 while (parent && !args.isPropagationStopped()) { 34125 parent.fire(name, args, false); 34126 parent = parent.parent(); 34127 } 34128 } 34129 34130 return args; 34131 }, 34132 34133 /** 34134 * Binds an event listener to a specific event by name. 34135 * 34136 * @method on 34137 * @param {String} name Event name or space separated list of events to bind. 34138 * @param {callback} callback Callback to be executed when the event occurs. 34139 * @param {Boolean} first Optional flag if the event should be prepended. Use this with care. 34140 * @return {Object} Current class instance. 34141 * @example 34142 * instance.on('event', function(e) { 34143 * // Callback logic 34144 * }); 34145 */ 34146 on: function(name, callback, prepend) { 34147 return getEventDispatcher(this).on(name, callback, prepend); 34148 }, 34149 34150 /** 34151 * Unbinds an event listener to a specific event by name. 34152 * 34153 * @method off 34154 * @param {String?} name Name of the event to unbind. 34155 * @param {callback?} callback Callback to unbind. 34156 * @return {Object} Current class instance. 34157 * @example 34158 * // Unbind specific callback 34159 * instance.off('event', handler); 34160 * 34161 * // Unbind all listeners by name 34162 * instance.off('event'); 34163 * 34164 * // Unbind all events 34165 * instance.off(); 34166 */ 34167 off: function(name, callback) { 34168 return getEventDispatcher(this).off(name, callback); 34169 }, 34170 34171 /** 34172 * Bind the event callback and once it fires the callback is removed. 34173 * 34174 * @method once 34175 * @param {String} name Name of the event to bind. 34176 * @param {callback} callback Callback to bind only once. 34177 * @return {Object} Current class instance. 34178 */ 34179 once: function(name, callback) { 34180 return getEventDispatcher(this).once(name, callback); 34181 }, 34182 34183 /** 34184 * Returns true/false if the object has a event of the specified name. 34185 * 34186 * @method hasEventListeners 34187 * @param {String} name Name of the event to check for. 34188 * @return {Boolean} true/false if the event exists or not. 34189 */ 34190 hasEventListeners: function(name) { 34191 return getEventDispatcher(this).has(name); 34192 } 34193 }; 34194}); 34195 34196// Included from: js/tinymce/classes/data/ObservableObject.js 34197 34198/** 34199 * ObservableObject.js 34200 * 34201 * Released under LGPL License. 34202 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 34203 * 34204 * License: http://www.tinymce.com/license 34205 * Contributing: http://www.tinymce.com/contributing 34206 */ 34207 34208/** 34209 * This class is a object that is observable when properties changes a change event gets emitted. 34210 * 34211 * @private 34212 * @class tinymce.data.ObservableObject 34213 */ 34214define("tinymce/data/ObservableObject", [ 34215 "tinymce/data/Binding", 34216 "tinymce/util/Observable", 34217 "tinymce/util/Class",
34218 "tinymce/util/Tools" 34219], function(Binding, Observable, Class, Tools) { 34220 function isNode(node) { 34221 return node.nodeType > 0; 34222 } 34223 34224 // Todo: Maybe this should be shallow compare since it might be huge object references 34225 function isEqual(a, b) { 34226 var k, checked; 34227 34228 // Strict equals 34229 if (a === b) { 34230 return true; 34231 } 34232 34233 // Compare null 34234 if (a === null || b === null) { 34235 return a === b; 34236 } 34237 34238 // Compare number, boolean, string, undefined 34239 if (typeof a !== "object" || typeof b !== "object") { 34240 return a === b; 34241 } 34242 34243 // Compare arrays 34244 if (Tools.isArray(b)) { 34245 if (a.length !== b.length) { 34246 return false; 34247 } 34248 34249 k = a.length; 34250 while (k--) { 34251 if (!isEqual(a[k], b[k])) { 34252 return false; 34253 } 34254 } 34255 } 34256 34257 // Shallow compare nodes 34258 if (isNode(a) || isNode(b)) { 34259 return a === b; 34260 } 34261 34262 // Compare objects 34263 checked = {}; 34264 for (k in b) { 34265 if (!isEqual(a[k], b[k])) { 34266 return false; 34267 } 34268 34269 checked[k] = true; 34270 } 34271 34272 for (k in a) { 34273 if (!checked[k] && !isEqual(a[k], b[k])) { 34274 return false; 34275 } 34276 } 34277 34278 return true; 34279 } 34280 34281 return Class.extend({ 34282 Mixins: [Observable], 34283 34284 /** 34285 * Constructs a new observable object instance. 34286 * 34287 * @constructor 34288 * @param {Object} data Initial data for the object. 34289 */ 34290 init: function(data) { 34291 var name, value; 34292 34293 data = data || {}; 34294 34295 for (name in data) { 34296 value = data[name]; 34297 34298 if (value instanceof Binding) { 34299 data[name] = value.create(this, name); 34300 } 34301 } 34302 34303 this.data = data; 34304 }, 34305 34306 /** 34307 * Sets a property on the value this will call 34308 * observers if the value is a change from the current value. 34309 * 34310 * @method set 34311 * @param {String/object} name Name of the property to set or a object of items to set. 34312 * @param {Object} value Value to set for the property. 34313 * @return {tinymce.data.ObservableObject} Observable object instance. 34314 */ 34315 set: function(name, value) { 34316 var key, args, oldValue = this.data[name]; 34317 34318 if (value instanceof Binding) { 34319 value = value.create(this, name); 34320 } 34321 34322 if (typeof name === "object") { 34323 for (key in name) { 34324 this.set(key, name[key]); 34325 } 34326 34327 return this; 34328 } 34329 34330 if (!isEqual(oldValue, value)) { 34331 this.data[name] = value; 34332 34333 args = { 34334 target: this, 34335 name: name, 34336 value: value, 34337 oldValue: oldValue 34338 }; 34339 34340 this.fire('change:' + name, args); 34341 this.fire('change', args); 34342 } 34343 34344 return this; 34345 }, 34346 34347 /** 34348 * Gets a property by name. 34349 * 34350 * @method get 34351 * @param {String} name Name of the property to get. 34352 * @return {Object} Object value of propery. 34353 */ 34354 get: function(name) { 34355 return this.data[name]; 34356 }, 34357 34358 /** 34359 * Returns true/false if the specified property exists. 34360 * 34361 * @method has 34362 * @param {String} name Name of the property to check for. 34363 * @return {Boolean} true/false if the item exists. 34364 */ 34365 has: function(name) { 34366 return name in this.data; 34367 }, 34368 34369 /** 34370 * Returns a dynamic property binding for the specified property name. This makes 34371 * it possible to sync the state of two properties in two ObservableObject instances. 34372 * 34373 * @method bind 34374 * @param {String} name Name of the property to sync with the property it's inserted to. 34375 * @return {tinymce.data.Binding} Data binding instance. 34376 */ 34377 bind: function(name) { 34378 return Binding.create(this, name); 34379 }, 34380 34381 /** 34382 * Destroys the observable object and fires the "destroy" 34383 * event and clean up any internal resources. 34384 * 34385 * @method destroy 34386 */ 34387 destroy: function() { 34388 this.fire('destroy'); 34389 } 34390 }); 34391}); 34392 34393// Included from: js/tinymce/classes/ui/Selector.js 34394 34395/** 34396 * Selector.js 34397 * 34398 * Released under LGPL License. 34399 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 34400 * 34401 * License: http://www.tinymce.com/license 34402 * Contributing: http://www.tinymce.com/contributing 34403 */ 34404 34405/*eslint no-nested-ternary:0 */ 34406 34407/** 34408 * Selector engine, enables you to select controls by using CSS like expressions. 34409 * We currently only support basic CSS expressions to reduce the size of the core 34410 * and the ones we support should be enough for most cases. 34411 * 34412 * @example 34413 * Supported expressions: 34414 * element 34415 * element#name 34416 * element.class 34417 * element[attr] 34418 * element[attr*=value] 34419 * element[attr~=value] 34420 * element[attr!=value] 34421 * element[attr^=value] 34422 * element[attr$=value] 34423 * element:<state> 34424 * element:not(<expression>) 34425 * element:first 34426 * element:last 34427 * element:odd 34428 * element:even 34429 * element element 34430 * element > element 34431 * 34432 * @class tinymce.ui.Selector 34433 */
34434define("tinymce/ui/Selector", [ 34435 "tinymce/util/Class" 34436], function(Class) { 34437 "use strict"; 34438 34439 /** 34440 * Produces an array with a unique set of objects. It will not compare the values 34441 * but the references of the objects. 34442 * 34443 * @private 34444 * @method unqiue 34445 * @param {Array} array Array to make into an array with unique items. 34446 * @return {Array} Array with unique items. 34447 */ 34448 function unique(array) { 34449 var uniqueItems = [], i = array.length, item; 34450 34451 while (i--) { 34452 item = array[i]; 34453 34454 if (!item.__checked) { 34455 uniqueItems.push(item); 34456 item.__checked = 1; 34457 } 34458 } 34459 34460 i = uniqueItems.length; 34461 while (i--) { 34462 delete uniqueItems[i].__checked; 34463 } 34464 34465 return uniqueItems; 34466 } 34467 34468 var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i; 34469 34470 /*jshint maxlen:255 */ 34471 /*eslint max-len:0 */ 34472 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 34473 whiteSpace = /^\s*|\s*$/g, 34474 Collection; 34475 34476 var Selector = Class.extend({ 34477 /** 34478 * Constructs a new Selector instance. 34479 * 34480 * @constructor 34481 * @method init 34482 * @param {String} selector CSS like selector expression. 34483 */ 34484 init: function(selector) { 34485 var match = this.match; 34486 34487 function compileNameFilter(name) { 34488 if (name) { 34489 name = name.toLowerCase(); 34490 34491 return function(item) { 34492 return name === '*' || item.type === name; 34493 }; 34494 } 34495 } 34496 34497 function compileIdFilter(id) { 34498 if (id) { 34499 return function(item) { 34500 return item._name === id; 34501 }; 34502 } 34503 } 34504 34505 function compileClassesFilter(classes) { 34506 if (classes) { 34507 classes = classes.split('.'); 34508 34509 return function(item) { 34510 var i = classes.length; 34511 34512 while (i--) { 34513 if (!item.classes.contains(classes[i])) { 34514 return false; 34515 } 34516 } 34517 34518 return true; 34519 }; 34520 } 34521 } 34522 34523 function compileAttrFilter(name, cmp, check) { 34524 if (name) { 34525 return function(item) { 34526 var value = item[name] ? item[name]() : ''; 34527 34528 return !cmp ? !!check : 34529 cmp === "=" ? value === check : 34530 cmp === "*=" ? value.indexOf(check) >= 0 : 34531 cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 : 34532 cmp === "!=" ? value != check : 34533 cmp === "^=" ? value.indexOf(check) === 0 : 34534 cmp === "$=" ? value.substr(value.length - check.length) === check : 34535 false; 34536 }; 34537 } 34538 } 34539 34540 function compilePsuedoFilter(name) { 34541 var notSelectors; 34542 34543 if (name) { 34544 name = /(?:not\((.+)\))|(.+)/i.exec(name); 34545 34546 if (!name[1]) { 34547 name = name[2]; 34548 34549 return function(item, index, length) { 34550 return name === 'first' ? index === 0 : 34551 name === 'last' ? index === length - 1 : 34552 name === 'even' ? index % 2 === 0 : 34553 name === 'odd' ? index % 2 === 1 : 34554 item[name] ? item[name]() : 34555 false; 34556 }; 34557 } 34558 34559 // Compile not expression 34560 notSelectors = parseChunks(name[1], []); 34561 34562 return function(item) { 34563 return !match(item, notSelectors); 34564 }; 34565 } 34566 } 34567 34568 function compile(selector, filters, direct) { 34569 var parts; 34570 34571 function add(filter) { 34572 if (filter) { 34573 filters.push(filter); 34574 } 34575 } 34576 34577 // Parse expression into parts 34578 parts = expression.exec(selector.replace(whiteSpace, '')); 34579 34580 add(compileNameFilter(parts[1])); 34581 add(compileIdFilter(parts[2])); 34582 add(compileClassesFilter(parts[3])); 34583 add(compileAttrFilter(parts[4], parts[5], parts[6])); 34584 add(compilePsuedoFilter(parts[7])); 34585 34586 // Mark the filter with pseudo for performance 34587 filters.pseudo = !!parts[7]; 34588 filters.direct = direct; 34589 34590 return filters; 34591 } 34592 34593 // Parser logic based on Sizzle by John Resig 34594 function parseChunks(selector, selectors) { 34595 var parts = [], extra, matches, i; 34596 34597 do { 34598 chunker.exec(""); 34599 matches = chunker.exec(selector); 34600 34601 if (matches) { 34602 selector = matches[3]; 34603 parts.push(matches[1]); 34604 34605 if (matches[2]) { 34606 extra = matches[3]; 34607 break; 34608 } 34609 } 34610 } while (matches); 34611 34612 if (extra) { 34613 parseChunks(extra, selectors); 34614 } 34615 34616 selector = []; 34617 for (i = 0; i < parts.length; i++) { 34618 if (parts[i] != '>') { 34619 selector.push(compile(parts[i], [], parts[i - 1] === '>')); 34620 } 34621 } 34622 34623 selectors.push(selector); 34624 34625 return selectors; 34626 } 34627 34628 this._selectors = parseChunks(selector, []); 34629 }, 34630 34631 /** 34632 * Returns true/false if the selector matches the specified control. 34633 * 34634 * @method match 34635 * @param {tinymce.ui.Control} control Control to match against the selector. 34636 * @param {Array} selectors Optional array of selectors, mostly used internally. 34637 * @return {Boolean} true/false state if the control matches or not. 34638 */ 34639 match: function(control, selectors) { 34640 var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item; 34641 34642 selectors = selectors || this._selectors; 34643 for (i = 0, l = selectors.length; i < l; i++) { 34644 selector = selectors[i]; 34645 sl = selector.length; 34646 item = control; 34647 count = 0; 34648 34649 for (si = sl - 1; si >= 0; si--) { 34650 filters = selector[si]; 34651 34652 while (item) { 34653 // Find the index and length since a pseudo filter like :first needs it 34654 if (filters.pseudo) { 34655 siblings = item.parent().items(); 34656 index = length = siblings.length; 34657 while (index--) { 34658 if (siblings[index] === item) { 34659 break; 34660 } 34661 } 34662 } 34663 34664 for (fi = 0, fl = filters.length; fi < fl; fi++) { 34665 if (!filters[fi](item, index, length)) { 34666 fi = fl + 1; 34667 break; 34668 } 34669 } 34670 34671 if (fi === fl) { 34672 count++; 34673 break; 34674 } else { 34675 // If it didn't match the right most expression then 34676 // break since it's no point looking at the parents 34677 if (si === sl - 1) { 34678 break; 34679 } 34680 } 34681 34682 item = item.parent(); 34683 } 34684 } 34685 34686 // If we found all selectors then return true otherwise continue looking 34687 if (count === sl) { 34688 return true; 34689 } 34690 } 34691 34692 return false;
34693 }, 34694 34695 /** 34696 * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container. 34697 * 34698 * @method find 34699 * @param {tinymce.ui.Control} container Container to look for items in. 34700 * @return {tinymce.ui.Collection} Collection with matched elements. 34701 */ 34702 find: function(container) { 34703 var matches = [], i, l, selectors = this._selectors; 34704 34705 function collect(items, selector, index) { 34706 var i, l, fi, fl, item, filters = selector[index]; 34707 34708 for (i = 0, l = items.length; i < l; i++) { 34709 item = items[i]; 34710 34711 // Run each filter against the item 34712 for (fi = 0, fl = filters.length; fi < fl; fi++) { 34713 if (!filters[fi](item, i, l)) { 34714 fi = fl + 1; 34715 break; 34716 } 34717 } 34718 34719 // All filters matched the item 34720 if (fi === fl) { 34721 // Matched item is on the last expression like: panel toolbar [button] 34722 if (index == selector.length - 1) { 34723 matches.push(item); 34724 } else { 34725 // Collect next expression type 34726 if (item.items) { 34727 collect(item.items(), selector, index + 1); 34728 } 34729 } 34730 } else if (filters.direct) { 34731 return; 34732 } 34733 34734 // Collect child items 34735 if (item.items) { 34736 collect(item.items(), selector, index); 34737 } 34738 } 34739 } 34740 34741 if (container.items) { 34742 for (i = 0, l = selectors.length; i < l; i++) { 34743 collect(container.items(), selectors[i], 0); 34744 } 34745 34746 // Unique the matches if needed 34747 if (l > 1) { 34748 matches = unique(matches); 34749 } 34750 } 34751 34752 // Fix for circular reference 34753 if (!Collection) { 34754 // TODO: Fix me! 34755 Collection = Selector.Collection; 34756 } 34757 34758 return new Collection(matches); 34759 } 34760 }); 34761 34762 return Selector; 34763}); 34764 34765// Included from: js/tinymce/classes/ui/Collection.js 34766 34767/** 34768 * Collection.js 34769 * 34770 * Released under LGPL License. 34771 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 34772 * 34773 * License: http://www.tinymce.com/license 34774 * Contributing: http://www.tinymce.com/contributing 34775 */ 34776 34777/** 34778 * Control collection, this class contains control instances and it enables you to 34779 * perform actions on all the contained items. This is very similar to how jQuery works. 34780 * 34781 * @example 34782 * someCollection.show().disabled(true); 34783 * 34784 * @class tinymce.ui.Collection 34785 */ 34786define("tinymce/ui/Collection", [ 34787 "tinymce/util/Tools", 34788 "tinymce/ui/Selector", 34789 "tinymce/util/Class" 34790], function(Tools, Selector, Class) { 34791 "use strict"; 34792 34793 var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice; 34794 34795 proto = { 34796 /** 34797 * Current number of contained control instances. 34798 * 34799 * @field length 34800 * @type Number 34801 */ 34802 length: 0, 34803 34804 /** 34805 * Constructor for the collection. 34806 * 34807 * @constructor 34808 * @method init 34809 * @param {Array} items Optional array with items to add. 34810 */ 34811 init: function(items) { 34812 if (items) { 34813 this.add(items); 34814 } 34815 }, 34816 34817 /** 34818 * Adds new items to the control collection. 34819 * 34820 * @method add 34821 * @param {Array} items Array if items to add to collection. 34822 * @return {tinymce.ui.Collection} Current collection instance. 34823 */ 34824 add: function(items) { 34825 var self = this; 34826 34827 // Force single item into array 34828 if (!Tools.isArray(items)) { 34829 if (items instanceof Collection) { 34830 self.add(items.toArray()); 34831 } else { 34832 push.call(self, items); 34833 } 34834 } else { 34835 push.apply(self, items); 34836 } 34837 34838 return self; 34839 }, 34840 34841 /** 34842 * Sets the contents of the collection. This will remove any existing items 34843 * and replace them with the ones specified in the input array. 34844 * 34845 * @method set 34846 * @param {Array} items Array with items to set into the Collection. 34847 * @return {tinymce.ui.Collection} Collection instance. 34848 */ 34849 set: function(items) { 34850 var self = this, len = self.length, i; 34851 34852 self.length = 0; 34853 self.add(items); 34854 34855 // Remove old entries 34856 for (i = self.length; i < len; i++) { 34857 delete self[i]; 34858 } 34859 34860 return self; 34861 }, 34862 34863 /** 34864 * Filters the collection item based on the specified selector expression or selector function. 34865 * 34866 * @method filter 34867 * @param {String} selector Selector expression to filter items by. 34868 * @return {tinymce.ui.Collection} Collection containing the filtered items. 34869 */ 34870 filter: function(selector) { 34871 var self = this, i, l, matches = [], item, match; 34872 34873 // Compile string into selector expression 34874 if (typeof selector === "string") { 34875 selector = new Selector(selector); 34876 34877 match = function(item) { 34878 return selector.match(item); 34879 }; 34880 } else { 34881 // Use selector as matching function 34882 match = selector; 34883 } 34884 34885 for (i = 0, l = self.length; i < l; i++) { 34886 item = self[i]; 34887 34888 if (match(item)) { 34889 matches.push(item); 34890 } 34891 } 34892 34893 return new Collection(matches); 34894 }, 34895 34896 /** 34897 * Slices the items within the collection. 34898 * 34899 * @method slice 34900 * @param {Number} index Index to slice at. 34901 * @param {Number} len Optional length to slice. 34902 * @return {tinymce.ui.Collection} Current collection. 34903 */ 34904 slice: function() { 34905 return new Collection(slice.apply(this, arguments)); 34906 }, 34907 34908 /** 34909 * Makes the current collection equal to the specified index. 34910 * 34911 * @method eq 34912 * @param {Number} index Index of the item to set the collection to. 34913 * @return {tinymce.ui.Collection} Current collection. 34914 */ 34915 eq: function(index) { 34916 return index === -1 ? this.slice(index) : this.slice(index, +index + 1); 34917 }, 34918 34919 /** 34920 * Executes the specified callback on each item in collection. 34921 * 34922 * @method each 34923 * @param {function} callback Callback to execute for each item in collection. 34924 * @return {tinymce.ui.Collection} Current collection instance. 34925 */ 34926 each: function(callback) { 34927 Tools.each(this, callback); 34928 34929 return this; 34930 }, 34931 34932 /** 34933 * Returns an JavaScript array object of the contents inside the collection. 34934 * 34935 * @method toArray 34936 * @return {Array} Array with all items from collection. 34937 */ 34938 toArray: function() { 34939 return Tools.toArray(this); 34940 }, 34941 34942 /** 34943 * Finds the index of the specified control or return -1 if it isn't in the collection. 34944 * 34945 * @method indexOf 34946 * @param {Control} ctrl Control instance to look for. 34947 * @return {Number} Index of the specified control or -1. 34948 */ 34949 indexOf: function(ctrl) { 34950 var self = this, i = self.length; 34951 34952 while (i--) { 34953 if (self[i] === ctrl) { 34954 break; 34955 } 34956 } 34957 34958 return i; 34959 }, 34960 34961 /** 34962 * Returns a new collection of the contents in reverse order. 34963 * 34964 * @method reverse 34965 * @return {tinymce.ui.Collection} Collection instance with reversed items. 34966 */ 34967 reverse: function() { 34968 return new Collection(Tools.toArray(this).reverse()); 34969 }, 34970 34971 /** 34972 * Returns true/false if the class exists or not. 34973 * 34974 * @method hasClass 34975 * @param {String} cls Class to check for. 34976 * @return {Boolean} true/false state if the class exists or not. 34977 */ 34978 hasClass: function(cls) { 34979 return this[0] ? this[0].classes.contains(cls) : false;
34980 }, 34981 34982 /** 34983 * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>); 34984 * 34985 * @method prop 34986 * @param {String} name Property name to get/set. 34987 * @param {Object} value Optional object value to set. 34988 * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation. 34989 */ 34990 prop: function(name, value) { 34991 var self = this, undef, item; 34992 34993 if (value !== undef) { 34994 self.each(function(item) { 34995 if (item[name]) { 34996 item[name](value); 34997 } 34998 }); 34999 35000 return self; 35001 } 35002 35003 item = self[0]; 35004 35005 if (item && item[name]) { 35006 return item[name](); 35007 } 35008 }, 35009 35010 /** 35011 * Executes the specific function name with optional arguments an all items in collection if it exists. 35012 * 35013 * @example collection.exec("myMethod", arg1, arg2, arg3); 35014 * @method exec 35015 * @param {String} name Name of the function to execute. 35016 * @param {Object} ... Multiple arguments to pass to each function. 35017 * @return {tinymce.ui.Collection} Current collection. 35018 */ 35019 exec: function(name) { 35020 var self = this, args = Tools.toArray(arguments).slice(1); 35021 35022 self.each(function(item) { 35023 if (item[name]) { 35024 item[name].apply(item, args); 35025 } 35026 }); 35027 35028 return self; 35029 }, 35030 35031 /** 35032 * Remove all items from collection and DOM. 35033 * 35034 * @method remove 35035 * @return {tinymce.ui.Collection} Current collection. 35036 */ 35037 remove: function() { 35038 var i = this.length; 35039 35040 while (i--) { 35041 this[i].remove(); 35042 } 35043 35044 return this; 35045 }, 35046 35047 /** 35048 * Adds a class to all items in the collection. 35049 * 35050 * @method addClass 35051 * @param {String} cls Class to add to each item. 35052 * @return {tinymce.ui.Collection} Current collection instance. 35053 */ 35054 addClass: function(cls) { 35055 return this.each(function(item) { 35056 item.classes.add(cls); 35057 }); 35058 }, 35059 35060 /** 35061 * Removes the specified class from all items in collection. 35062 * 35063 * @method removeClass 35064 * @param {String} cls Class to remove from each item. 35065 * @return {tinymce.ui.Collection} Current collection instance. 35066 */ 35067 removeClass: function(cls) { 35068 return this.each(function(item) { 35069 item.classes.remove(cls); 35070 }); 35071 } 35072 35073 /** 35074 * Fires the specified event by name and arguments on the control. This will execute all 35075 * bound event handlers. 35076 * 35077 * @method fire 35078 * @param {String} name Name of the event to fire. 35079 * @param {Object} args Optional arguments to pass to the event. 35080 * @return {tinymce.ui.Collection} Current collection instance. 35081 */ 35082 // fire: function(event, args) {}, -- Generated by code below 35083 35084 /** 35085 * Binds a callback to the specified event. This event can both be 35086 * native browser events like "click" or custom ones like PostRender. 35087 * 35088 * The callback function will have two parameters the first one being the control that received the event 35089 * the second one will be the event object either the browsers native event object or a custom JS object. 35090 * 35091 * @method on 35092 * @param {String} name Name of the event to bind. For example "click". 35093 * @param {String/function} callback Callback function to execute ones the event occurs. 35094 * @return {tinymce.ui.Collection} Current collection instance. 35095 */ 35096 // on: function(name, callback) {}, -- Generated by code below 35097 35098 /** 35099 * Unbinds the specified event and optionally a specific callback. If you omit the name 35100 * parameter all event handlers will be removed. If you omit the callback all event handles 35101 * by the specified name will be removed. 35102 * 35103 * @method off 35104 * @param {String} name Optional name for the event to unbind. 35105 * @param {function} callback Optional callback function to unbind. 35106 * @return {tinymce.ui.Collection} Current collection instance. 35107 */ 35108 // off: function(name, callback) {}, -- Generated by code below 35109 35110 /** 35111 * Shows the items in the current collection. 35112 * 35113 * @method show 35114 * @return {tinymce.ui.Collection} Current collection instance. 35115 */ 35116 // show: function() {}, -- Generated by code below 35117 35118 /** 35119 * Hides the items in the current collection. 35120 * 35121 * @method hide 35122 * @return {tinymce.ui.Collection} Current collection instance. 35123 */ 35124 // hide: function() {}, -- Generated by code below 35125 35126 /** 35127 * Sets/gets the text contents of the items in the current collection. 35128 * 35129 * @method text 35130 * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation. 35131 */ 35132 // text: function(value) {}, -- Generated by code below 35133 35134 /** 35135 * Sets/gets the name contents of the items in the current collection. 35136 *
35137 * @method name 35138 * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation. 35139 */ 35140 // name: function(value) {}, -- Generated by code below 35141 35142 /** 35143 * Sets/gets the disabled state on the items in the current collection. 35144 * 35145 * @method disabled 35146 * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation. 35147 */ 35148 // disabled: function(state) {}, -- Generated by code below 35149 35150 /** 35151 * Sets/gets the active state on the items in the current collection. 35152 * 35153 * @method active 35154 * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation. 35155 */ 35156 // active: function(state) {}, -- Generated by code below 35157 35158 /** 35159 * Sets/gets the selected state on the items in the current collection. 35160 * 35161 * @method selected 35162 * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation. 35163 */ 35164 // selected: function(state) {}, -- Generated by code below 35165 35166 /** 35167 * Sets/gets the selected state on the items in the current collection. 35168 * 35169 * @method visible 35170 * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation. 35171 */ 35172 // visible: function(state) {}, -- Generated by code below 35173 }; 35174 35175 // Extend tinymce.ui.Collection prototype with some generated control specific methods 35176 Tools.each('fire on off show hide append prepend before after reflow'.split(' '), function(name) { 35177 proto[name] = function() { 35178 var args = Tools.toArray(arguments); 35179 35180 this.each(function(ctrl) { 35181 if (name in ctrl) { 35182 ctrl[name].apply(ctrl, args); 35183 } 35184 }); 35185 35186 return this; 35187 }; 35188 }); 35189 35190 // Extend tinymce.ui.Collection prototype with some property methods 35191 Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) { 35192 proto[name] = function(value) { 35193 return this.prop(name, value); 35194 }; 35195 }); 35196 35197 // Create class based on the new prototype 35198 Collection = Class.extend(proto); 35199 35200 // Stick Collection into Selector to prevent circual references 35201 Selector.Collection = Collection; 35202 35203 return Collection; 35204}); 35205 35206// Included from: js/tinymce/classes/ui/DomUtils.js 35207 35208/** 35209 * DomUtils.js 35210 * 35211 * Released under LGPL License. 35212 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 35213 * 35214 * License: http://www.tinymce.com/license 35215 * Contributing: http://www.tinymce.com/contributing 35216 */ 35217 35218/** 35219 * Private UI DomUtils proxy. 35220 * 35221 * @private 35222 * @class tinymce.ui.DomUtils 35223 */ 35224define("tinymce/ui/DomUtils", [ 35225 "tinymce/util/Tools", 35226 "tinymce/dom/DOMUtils" 35227], function(Tools, DOMUtils) { 35228 "use strict"; 35229 35230 var count = 0; 35231 35232 return { 35233 id: function() { 35234 return 'mceu_' + (count++); 35235 }, 35236 35237 createFragment: function(html) { 35238 return DOMUtils.DOM.createFragment(html); 35239 }, 35240 35241 getWindowSize: function() { 35242 return DOMUtils.DOM.getViewPort(); 35243 }, 35244 35245 getSize: function(elm) { 35246 var width, height; 35247 35248 if (elm.getBoundingClientRect) { 35249 var rect = elm.getBoundingClientRect(); 35250 35251 width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth); 35252 height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight); 35253 } else { 35254 width = elm.offsetWidth; 35255 height = elm.offsetHeight; 35256 } 35257 35258 return {width: width, height: height}; 35259 }, 35260 35261 getPos: function(elm, root) { 35262 return DOMUtils.DOM.getPos(elm, root); 35263 }, 35264 35265 getViewPort: function(win) { 35266 return DOMUtils.DOM.getViewPort(win); 35267 }, 35268 35269 get: function(id) { 35270 return document.getElementById(id); 35271 }, 35272 35273 addClass: function(elm, cls) { 35274 return DOMUtils.DOM.addClass(elm, cls); 35275 }, 35276 35277 removeClass: function(elm, cls) { 35278 return DOMUtils.DOM.removeClass(elm, cls); 35279 }, 35280 35281 hasClass: function(elm, cls) { 35282 return DOMUtils.DOM.hasClass(elm, cls); 35283 }, 35284 35285 toggleClass: function(elm, cls, state) { 35286 return DOMUtils.DOM.toggleClass(elm, cls, state); 35287 }, 35288 35289 css: function(elm, name, value) { 35290 return DOMUtils.DOM.setStyle(elm, name, value); 35291 }, 35292 35293 getRuntimeStyle: function(elm, name) { 35294 return DOMUtils.DOM.getStyle(elm, name, true); 35295 }, 35296 35297 on: function(target, name, callback, scope) { 35298 return DOMUtils.DOM.bind(target, name, callback, scope); 35299 }, 35300 35301 off: function(target, name, callback) { 35302 return DOMUtils.DOM.unbind(target, name, callback); 35303 }, 35304 35305 fire: function(target, name, args) { 35306 return DOMUtils.DOM.fire(target, name, args); 35307 }, 35308 35309 innerHtml: function(elm, html) { 35310 // Workaround for <div> in <p> bug on IE 8 #6178 35311 DOMUtils.DOM.setHTML(elm, html); 35312 } 35313 }; 35314}); 35315 35316// Included from: js/tinymce/classes/ui/BoxUtils.js 35317 35318/** 35319 * BoxUtils.js 35320 * 35321 * Released under LGPL License. 35322 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 35323 * 35324 * License: http://www.tinymce.com/license 35325 * Contributing: http://www.tinymce.com/contributing 35326 */ 35327 35328/** 35329 * Utility class for box parsing and measuring. 35330 * 35331 * @private 35332 * @class tinymce.ui.BoxUtils 35333 */
35334define("tinymce/ui/BoxUtils", [ 35335], function() { 35336 "use strict"; 35337 35338 return { 35339 /** 35340 * Parses the specified box value. A box value contains 1-4 properties in clockwise order. 35341 * 35342 * @method parseBox 35343 * @param {String/Number} value Box value "0 1 2 3" or "0" etc. 35344 * @return {Object} Object with top/right/bottom/left properties. 35345 * @private 35346 */ 35347 parseBox: function(value) { 35348 var len, radix = 10; 35349 35350 if (!value) { 35351 return; 35352 } 35353 35354 if (typeof value === "number") { 35355 value = value || 0; 35356 35357 return { 35358 top: value, 35359 left: value, 35360 bottom: value, 35361 right: value 35362 }; 35363 } 35364 35365 value = value.split(' '); 35366 len = value.length; 35367 35368 if (len === 1) { 35369 value[1] = value[2] = value[3] = value[0]; 35370 } else if (len === 2) { 35371 value[2] = value[0]; 35372 value[3] = value[1]; 35373 } else if (len === 3) { 35374 value[3] = value[1]; 35375 } 35376 35377 return { 35378 top: parseInt(value[0], radix) || 0, 35379 right: parseInt(value[1], radix) || 0, 35380 bottom: parseInt(value[2], radix) || 0, 35381 left: parseInt(value[3], radix) || 0 35382 }; 35383 }, 35384 35385 measureBox: function(elm, prefix) { 35386 function getStyle(name) { 35387 var defaultView = document.defaultView; 35388 35389 if (defaultView) { 35390 // Remove camelcase 35391 name = name.replace(/[A-Z]/g, function(a) { 35392 return '-' + a; 35393 }); 35394 35395 return defaultView.getComputedStyle(elm, null).getPropertyValue(name); 35396 } 35397 35398 return elm.currentStyle[name]; 35399 } 35400 35401 function getSide(name) { 35402 var val = parseFloat(getStyle(name), 10); 35403 35404 return isNaN(val) ? 0 : val; 35405 } 35406 35407 return { 35408 top: getSide(prefix + "TopWidth"), 35409 right: getSide(prefix + "RightWidth"), 35410 bottom: getSide(prefix + "BottomWidth"), 35411 left: getSide(prefix + "LeftWidth") 35412 }; 35413 } 35414 }; 35415}); 35416 35417// Included from: js/tinymce/classes/ui/ClassList.js 35418 35419/** 35420 * ClassList.js 35421 * 35422 * Released under LGPL License. 35423 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 35424 * 35425 * License: http://www.tinymce.com/license 35426 * Contributing: http://www.tinymce.com/contributing 35427 */ 35428 35429/** 35430 * Handles adding and removal of classes. 35431 * 35432 * @private 35433 * @class tinymce.ui.ClassList 35434 */ 35435define("tinymce/ui/ClassList", [ 35436 "tinymce/util/Tools" 35437], function(Tools) { 35438 "use strict"; 35439 35440 function noop() { 35441 } 35442 35443 /** 35444 * Constructs a new class list the specified onchange 35445 * callback will be executed when the class list gets modifed. 35446 * 35447 * @constructor ClassList 35448 * @param {function} onchange Onchange callback to be executed. 35449 */ 35450 function ClassList(onchange) { 35451 this.cls = []; 35452 this.cls._map = {}; 35453 this.onchange = onchange || noop; 35454 this.prefix = ''; 35455 } 35456 35457 Tools.extend(ClassList.prototype, { 35458 /** 35459 * Adds a new class to the class list. 35460 * 35461 * @method add 35462 * @param {String} cls Class to be added. 35463 * @return {tinymce.ui.ClassList} Current class list instance. 35464 */ 35465 add: function(cls) { 35466 if (cls && !this.contains(cls)) { 35467 this.cls._map[cls] = true; 35468 this.cls.push(cls); 35469 this._change(); 35470 } 35471 35472 return this; 35473 }, 35474 35475 /** 35476 * Removes the specified class from the class list. 35477 * 35478 * @method remove 35479 * @param {String} cls Class to be removed. 35480 * @return {tinymce.ui.ClassList} Current class list instance. 35481 */ 35482 remove: function(cls) { 35483 if (this.contains(cls)) { 35484 for (var i = 0; i < this.cls.length; i++) { 35485 if (this.cls[i] === cls) { 35486 break; 35487 } 35488 } 35489 35490 this.cls.splice(i, 1); 35491 delete this.cls._map[cls]; 35492 this._change(); 35493 } 35494 35495 return this; 35496 }, 35497 35498 /** 35499 * Toggles a class in the class list. 35500 * 35501 * @method toggle 35502 * @param {String} cls Class to be added/removed. 35503 * @param {Boolean} state Optional state if it should be added/removed. 35504 * @return {tinymce.ui.ClassList} Current class list instance. 35505 */ 35506 toggle: function(cls, state) { 35507 var curState = this.contains(cls); 35508 35509 if (curState !== state) { 35510 if (curState) { 35511 this.remove(cls); 35512 } else { 35513 this.add(cls); 35514 } 35515 35516 this._change(); 35517 } 35518 35519 return this; 35520 }, 35521 35522 /** 35523 * Returns true if the class list has the specified class. 35524 * 35525 * @method contains 35526 * @param {String} cls Class to look for. 35527 * @return {Boolean} true/false if the class exists or not. 35528 */ 35529 contains: function(cls) { 35530 return !!this.cls._map[cls]; 35531 }, 35532 35533 /** 35534 * Returns a space separated list of classes. 35535 * 35536 * @method toString 35537 * @return {String} Space separated list of classes. 35538 */ 35539 35540 _change: function() { 35541 delete this.clsValue; 35542 this.onchange.call(this); 35543 } 35544 }); 35545 35546 // IE 8 compatibility 35547 ClassList.prototype.toString = function() { 35548 var value; 35549 35550 if (this.clsValue) { 35551 return this.clsValue; 35552 } 35553 35554 value = ''; 35555 for (var i = 0; i < this.cls.length; i++) { 35556 if (i > 0) { 35557 value += ' '; 35558 } 35559 35560 value += this.prefix + this.cls[i]; 35561 } 35562 35563 return value; 35564 }; 35565 35566 return ClassList; 35567}); 35568 35569// Included from: js/tinymce/classes/ui/ReflowQueue.js 35570 35571/** 35572 * ReflowQueue.js 35573 * 35574 * Released under LGPL License. 35575 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 35576 * 35577 * License: http://www.tinymce.com/license 35578 * Contributing: http://www.tinymce.com/contributing 35579 */ 35580 35581/** 35582 * This class will automatically reflow controls on the next animation frame within a few milliseconds on older browsers. 35583 * If the user manually reflows then the automatic reflow will be cancelled. This class is used internally when various control states 35584 * changes that triggers a reflow. 35585 * 35586 * @class tinymce.ui.ReflowQueue
35587 * @static 35588 */ 35589define("tinymce/ui/ReflowQueue", [ 35590 "tinymce/util/Delay" 35591], function(Delay) { 35592 var dirtyCtrls = {}, animationFrameRequested; 35593 35594 return { 35595 /** 35596 * Adds a control to the next automatic reflow call. This is the control that had a state 35597 * change for example if the control was hidden/shown. 35598 * 35599 * @method add 35600 * @param {tinymce.ui.Control} ctrl Control to add to queue. 35601 */ 35602 add: function(ctrl) { 35603 var parent = ctrl.parent(); 35604 35605 if (parent) { 35606 if (!parent._layout || parent._layout.isNative()) { 35607 return; 35608 } 35609 35610 if (!dirtyCtrls[parent._id]) { 35611 dirtyCtrls[parent._id] = parent; 35612 } 35613 35614 if (!animationFrameRequested) { 35615 animationFrameRequested = true; 35616 35617 Delay.requestAnimationFrame(function() { 35618 var id, ctrl; 35619 35620 animationFrameRequested = false; 35621 35622 for (id in dirtyCtrls) { 35623 ctrl = dirtyCtrls[id]; 35624 35625 if (ctrl.state.get('rendered')) { 35626 ctrl.reflow(); 35627 } 35628 } 35629 35630 dirtyCtrls = {}; 35631 }, document.body); 35632 } 35633 } 35634 }, 35635 35636 /** 35637 * Removes the specified control from the automatic reflow. This will happen when for example the user 35638 * manually triggers a reflow. 35639 * 35640 * @method remove 35641 * @param {tinymce.ui.Control} ctrl Control to remove from queue. 35642 */ 35643 remove: function(ctrl) { 35644 if (dirtyCtrls[ctrl._id]) { 35645 delete dirtyCtrls[ctrl._id]; 35646 } 35647 } 35648 }; 35649}); 35650 35651// Included from: js/tinymce/classes/ui/Control.js 35652 35653/** 35654 * Control.js 35655 * 35656 * Released under LGPL License. 35657 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 35658 * 35659 * License: http://www.tinymce.com/license 35660 * Contributing: http://www.tinymce.com/contributing 35661 */ 35662 35663/*eslint consistent-this:0 */ 35664 35665/** 35666 * This is the base class for all controls and containers. All UI control instances inherit 35667 * from this one as it has the base logic needed by all of them. 35668 * 35669 * @class tinymce.ui.Control 35670 */ 35671define("tinymce/ui/Control", [ 35672 "tinymce/util/Class", 35673 "tinymce/util/Tools", 35674 "tinymce/util/EventDispatcher", 35675 "tinymce/data/ObservableObject", 35676 "tinymce/ui/Collection", 35677 "tinymce/ui/DomUtils", 35678 "tinymce/dom/DomQuery", 35679 "tinymce/ui/BoxUtils", 35680 "tinymce/ui/ClassList", 35681 "tinymce/ui/ReflowQueue" 35682], function(Class, Tools, EventDispatcher, ObservableObject, Collection, DomUtils, $, BoxUtils, ClassList, ReflowQueue) { 35683 "use strict"; 35684 35685 var hasMouseWheelEventSupport = "onmousewheel" in document; 35686 var hasWheelEventSupport = false; 35687 var classPrefix = "mce-"; 35688 var Control, idCounter = 0; 35689 35690 var proto = { 35691 Statics: { 35692 classPrefix: classPrefix 35693 }, 35694 35695 isRtl: function() { 35696 return Control.rtl; 35697 }, 35698 35699 /** 35700 * Class/id prefix to use for all controls. 35701 * 35702 * @final 35703 * @field {String} classPrefix 35704 */ 35705 classPrefix: classPrefix, 35706 35707 /** 35708 * Constructs a new control instance with the specified settings. 35709 * 35710 * @constructor 35711 * @param {Object} settings Name/value object with settings. 35712 * @setting {String} style Style CSS properties to add. 35713 * @setting {String} border Border box values example: 1 1 1 1 35714 * @setting {String} padding Padding box values example: 1 1 1 1 35715 * @setting {String} margin Margin box values example: 1 1 1 1 35716 * @setting {Number} minWidth Minimal width for the control. 35717 * @setting {Number} minHeight Minimal height for the control. 35718 * @setting {String} classes Space separated list of classes to add. 35719 * @setting {String} role WAI-ARIA role to use for control. 35720 * @setting {Boolean} hidden Is the control hidden by default. 35721 * @setting {Boolean} disabled Is the control disabled by default. 35722 * @setting {String} name Name of the control instance. 35723 */ 35724 init: function(settings) { 35725 var self = this, classes, defaultClasses; 35726 35727 function applyClasses(classes) { 35728 var i; 35729 35730 classes = classes.split(' '); 35731 for (i = 0; i < classes.length; i++) { 35732 self.classes.add(classes[i]); 35733 } 35734 } 35735 35736 self.settings = settings = Tools.extend({}, self.Defaults, settings); 35737 35738 // Initial states 35739 self._id = settings.id || ('mceu_' + (idCounter++)); 35740 self._aria = {role: settings.role}; 35741 self._elmCache = {}; 35742 self.$ = $; 35743 35744 self.state = new ObservableObject({ 35745 visible: true, 35746 active: false, 35747 disabled: false, 35748 value: '' 35749 }); 35750 35751 self.data = new ObservableObject(settings.data); 35752 35753 self.classes = new ClassList(function() { 35754 if (self.state.get('rendered')) { 35755 self.getEl().className = this.toString(); 35756 } 35757 }); 35758 self.classes.prefix = self.classPrefix; 35759 35760 // Setup classes 35761 classes = settings.classes; 35762 if (classes) { 35763 if (self.Defaults) { 35764 defaultClasses = self.Defaults.classes; 35765 35766 if (defaultClasses && classes != defaultClasses) { 35767 applyClasses(defaultClasses); 35768 } 35769 } 35770 35771 applyClasses(classes); 35772 } 35773 35774 Tools.each('title text name visible disabled active value'.split(' '), function(name) { 35775 if (name in settings) { 35776 self[name](settings[name]); 35777 } 35778 }); 35779 35780 self.on('click', function() { 35781 if (self.disabled()) { 35782 return false;
35783 } 35784 }); 35785 35786 /** 35787 * Name/value object with settings for the current control. 35788 * 35789 * @field {Object} settings 35790 */ 35791 self.settings = settings; 35792 35793 self.borderBox = BoxUtils.parseBox(settings.border); 35794 self.paddingBox = BoxUtils.parseBox(settings.padding); 35795 self.marginBox = BoxUtils.parseBox(settings.margin); 35796 35797 if (settings.hidden) { 35798 self.hide(); 35799 } 35800 }, 35801 35802 // Will generate getter/setter methods for these properties 35803 Properties: 'parent,name', 35804 35805 /** 35806 * Returns the root element to render controls into. 35807 * 35808 * @method getContainerElm 35809 * @return {Element} HTML DOM element to render into. 35810 */ 35811 getContainerElm: function() { 35812 return document.body; 35813 }, 35814 35815 /** 35816 * Returns a control instance for the current DOM element. 35817 * 35818 * @method getParentCtrl 35819 * @param {Element} elm HTML dom element to get parent control from. 35820 * @return {tinymce.ui.Control} Control instance or undefined. 35821 */ 35822 getParentCtrl: function(elm) { 35823 var ctrl, lookup = this.getRoot().controlIdLookup; 35824 35825 while (elm && lookup) { 35826 ctrl = lookup[elm.id]; 35827 if (ctrl) { 35828 break; 35829 } 35830 35831 elm = elm.parentNode; 35832 } 35833 35834 return ctrl; 35835 }, 35836 35837 /** 35838 * Initializes the current controls layout rect. 35839 * This will be executed by the layout managers to determine the 35840 * default minWidth/minHeight etc. 35841 * 35842 * @method initLayoutRect 35843 * @return {Object} Layout rect instance. 35844 */ 35845 initLayoutRect: function() { 35846 var self = this, settings = self.settings, borderBox, layoutRect; 35847 var elm = self.getEl(), width, height, minWidth, minHeight, autoResize; 35848 var startMinWidth, startMinHeight, initialSize; 35849 35850 // Measure the current element 35851 borderBox = self.borderBox = self.borderBox || BoxUtils.measureBox(elm, 'border'); 35852 self.paddingBox = self.paddingBox || BoxUtils.measureBox(elm, 'padding'); 35853 self.marginBox = self.marginBox || BoxUtils.measureBox(elm, 'margin'); 35854 initialSize = DomUtils.getSize(elm); 35855 35856 // Setup minWidth/minHeight and width/height 35857 startMinWidth = settings.minWidth; 35858 startMinHeight = settings.minHeight; 35859 minWidth = startMinWidth || initialSize.width; 35860 minHeight = startMinHeight || initialSize.height; 35861 width = settings.width; 35862 height = settings.height; 35863 autoResize = settings.autoResize; 35864 autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height; 35865 35866 width = width || minWidth; 35867 height = height || minHeight; 35868 35869 var deltaW = borderBox.left + borderBox.right; 35870 var deltaH = borderBox.top + borderBox.bottom; 35871 35872 var maxW = settings.maxWidth || 0xFFFF; 35873 var maxH = settings.maxHeight || 0xFFFF; 35874 35875 // Setup initial layout rect 35876 self._layoutRect = layoutRect = { 35877 x: settings.x || 0, 35878 y: settings.y || 0, 35879 w: width, 35880 h: height, 35881 deltaW: deltaW, 35882 deltaH: deltaH, 35883 contentW: width - deltaW, 35884 contentH: height - deltaH, 35885 innerW: width - deltaW, 35886 innerH: height - deltaH, 35887 startMinWidth: startMinWidth || 0, 35888 startMinHeight: startMinHeight || 0, 35889 minW: Math.min(minWidth, maxW), 35890 minH: Math.min(minHeight, maxH), 35891 maxW: maxW, 35892 maxH: maxH, 35893 autoResize: autoResize, 35894 scrollW: 0 35895 }; 35896 35897 self._lastLayoutRect = {}; 35898 35899 return layoutRect; 35900 }, 35901 35902 /** 35903 * Getter/setter for the current layout rect. 35904 * 35905 * @method layoutRect 35906 * @param {Object} [newRect] Optional new layout rect. 35907 * @return {tinymce.ui.Control/Object} Current control or rect object. 35908 */ 35909 layoutRect: function(newRect) { 35910 var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls; 35911 35912 // Initialize default layout rect 35913 if (!curRect) { 35914 curRect = self.initLayoutRect(); 35915 } 35916 35917 // Set new rect values 35918 if (newRect) { 35919 // Calc deltas between inner and outer sizes 35920 deltaWidth = curRect.deltaW; 35921 deltaHeight = curRect.deltaH; 35922 35923 // Set x position 35924 if (newRect.x !== undef) { 35925 curRect.x = newRect.x; 35926 } 35927 35928 // Set y position 35929 if (newRect.y !== undef) { 35930 curRect.y = newRect.y; 35931 } 35932 35933 // Set minW 35934 if (newRect.minW !== undef) { 35935 curRect.minW = newRect.minW; 35936 } 35937 35938 // Set minH 35939 if (newRect.minH !== undef) { 35940 curRect.minH = newRect.minH; 35941 } 35942 35943 // Set new width and calculate inner width 35944 size = newRect.w; 35945 if (size !== undef) { 35946 size = size < curRect.minW ? curRect.minW : size; 35947 size = size > curRect.maxW ? curRect.maxW : size; 35948 curRect.w = size; 35949 curRect.innerW = size - deltaWidth; 35950 } 35951 35952 // Set new height and calculate inner height 35953 size = newRect.h; 35954 if (size !== undef) { 35955 size = size < curRect.minH ? curRect.minH : size; 35956 size = size > curRect.maxH ? curRect.maxH : size; 35957 curRect.h = size; 35958 curRect.innerH = size - deltaHeight; 35959 } 35960 35961 // Set new inner width and calculate width 35962 size = newRect.innerW; 35963 if (size !== undef) { 35964 size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size; 35965 size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size; 35966 curRect.innerW = size; 35967 curRect.w = size + deltaWidth; 35968 } 35969 35970 // Set new height and calculate inner height 35971 size = newRect.innerH; 35972 if (size !== undef) { 35973 size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size; 35974 size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size; 35975 curRect.innerH = size; 35976 curRect.h = size + deltaHeight; 35977 } 35978 35979 // Set new contentW 35980 if (newRect.contentW !== undef) { 35981 curRect.contentW = newRect.contentW; 35982 } 35983 35984 // Set new contentH 35985 if (newRect.contentH !== undef) { 35986 curRect.contentH = newRect.contentH; 35987 } 35988 35989 // Compare last layout rect with the current one to see if we need to repaint or not 35990 lastLayoutRect = self._lastLayoutRect; 35991 if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y || 35992 lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) { 35993 repaintControls = Control.repaintControls; 35994 35995 if (repaintControls) { 35996 if (repaintControls.map && !repaintControls.map[self._id]) { 35997 repaintControls.push(self); 35998 repaintControls.map[self._id] = true; 35999 } 36000 } 36001 36002 lastLayoutRect.x = curRect.x; 36003 lastLayoutRect.y = curRect.y; 36004 lastLayoutRect.w = curRect.w; 36005 lastLayoutRect.h = curRect.h; 36006 } 36007 36008 return self; 36009 } 36010 36011 return curRect; 36012 }, 36013 36014 /** 36015 * Repaints the control after a layout operation. 36016 * 36017 * @method repaint 36018 */ 36019 repaint: function() { 36020 var self = this, style, bodyStyle, bodyElm, rect, borderBox; 36021 var borderW, borderH, lastRepaintRect, round, value; 36022 36023 // Use Math.round on all values on IE < 9
36024 round = !document.createRange ? Math.round : function(value) { 36025 return value; 36026 }; 36027 36028 style = self.getEl().style; 36029 rect = self._layoutRect; 36030 lastRepaintRect = self._lastRepaintRect || {}; 36031 36032 borderBox = self.borderBox; 36033 borderW = borderBox.left + borderBox.right; 36034 borderH = borderBox.top + borderBox.bottom; 36035 36036 if (rect.x !== lastRepaintRect.x) { 36037 style.left = round(rect.x) + 'px'; 36038 lastRepaintRect.x = rect.x; 36039 } 36040 36041 if (rect.y !== lastRepaintRect.y) { 36042 style.top = round(rect.y) + 'px'; 36043 lastRepaintRect.y = rect.y; 36044 } 36045 36046 if (rect.w !== lastRepaintRect.w) { 36047 value = round(rect.w - borderW); 36048 style.width = (value >= 0 ? value : 0) + 'px'; 36049 lastRepaintRect.w = rect.w; 36050 } 36051 36052 if (rect.h !== lastRepaintRect.h) { 36053 value = round(rect.h - borderH); 36054 style.height = (value >= 0 ? value : 0) + 'px'; 36055 lastRepaintRect.h = rect.h; 36056 } 36057 36058 // Update body if needed 36059 if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) { 36060 value = round(rect.innerW); 36061 36062 bodyElm = self.getEl('body'); 36063 if (bodyElm) { 36064 bodyStyle = bodyElm.style; 36065 bodyStyle.width = (value >= 0 ? value : 0) + 'px'; 36066 } 36067 36068 lastRepaintRect.innerW = rect.innerW; 36069 } 36070 36071 if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) { 36072 value = round(rect.innerH); 36073 36074 bodyElm = bodyElm || self.getEl('body'); 36075 if (bodyElm) { 36076 bodyStyle = bodyStyle || bodyElm.style; 36077 bodyStyle.height = (value >= 0 ? value : 0) + 'px'; 36078 } 36079 36080 lastRepaintRect.innerH = rect.innerH; 36081 } 36082 36083 self._lastRepaintRect = lastRepaintRect; 36084 self.fire('repaint', {}, false); 36085 }, 36086 36087 /** 36088 * Binds a callback to the specified event. This event can both be 36089 * native browser events like "click" or custom ones like PostRender. 36090 * 36091 * The callback function will be passed a DOM event like object that enables yout do stop propagation. 36092 * 36093 * @method on 36094 * @param {String} name Name of the event to bind. For example "click". 36095 * @param {String/function} callback Callback function to execute ones the event occurs. 36096 * @return {tinymce.ui.Control} Current control object. 36097 */ 36098 on: function(name, callback) { 36099 var self = this; 36100 36101 function resolveCallbackName(name) { 36102 var callback, scope; 36103 36104 if (typeof name != 'string') { 36105 return name; 36106 } 36107 36108 return function(e) { 36109 if (!callback) { 36110 self.parentsAndSelf().each(function(ctrl) { 36111 var callbacks = ctrl.settings.callbacks; 36112 36113 if (callbacks && (callback = callbacks[name])) { 36114 scope = ctrl; 36115 return false; 36116 } 36117 }); 36118 } 36119 36120 if (!callback) { 36121 e.action = name; 36122 this.fire('execute', e); 36123 return; 36124 } 36125 36126 return callback.call(scope, e); 36127 }; 36128 } 36129 36130 getEventDispatcher(self).on(name, resolveCallbackName(callback)); 36131 36132 return self; 36133 }, 36134 36135 /** 36136 * Unbinds the specified event and optionally a specific callback. If you omit the name 36137 * parameter all event handlers will be removed. If you omit the callback all event handles 36138 * by the specified name will be removed. 36139 * 36140 * @method off 36141 * @param {String} [name] Name for the event to unbind. 36142 * @param {function} [callback] Callback function to unbind. 36143 * @return {tinymce.ui.Control} Current control object. 36144 */ 36145 off: function(name, callback) { 36146 getEventDispatcher(this).off(name, callback); 36147 return this; 36148 }, 36149 36150 /** 36151 * Fires the specified event by name and arguments on the control. This will execute all 36152 * bound event handlers. 36153 * 36154 * @method fire 36155 * @param {String} name Name of the event to fire. 36156 * @param {Object} [args] Arguments to pass to the event. 36157 * @param {Boolean} [bubble] Value to control bubbling. Defaults to true. 36158 * @return {Object} Current arguments object. 36159 */ 36160 fire: function(name, args, bubble) { 36161 var self = this; 36162 36163 args = args || {}; 36164 36165 if (!args.control) { 36166 args.control = self; 36167 } 36168 36169 args = getEventDispatcher(self).fire(name, args); 36170 36171 // Bubble event up to parents 36172 if (bubble !== false && self.parent) { 36173 var parent = self.parent(); 36174 while (parent && !args.isPropagationStopped()) { 36175 parent.fire(name, args, false); 36176 parent = parent.parent(); 36177 } 36178 } 36179 36180 return args; 36181 }, 36182 36183 /** 36184 * Returns true/false if the specified event has any listeners. 36185 * 36186 * @method hasEventListeners 36187 * @param {String} name Name of the event to check for. 36188 * @return {Boolean} True/false state if the event has listeners. 36189 */ 36190 hasEventListeners: function(name) { 36191 return getEventDispatcher(this).has(name); 36192 }, 36193 36194 /** 36195 * Returns a control collection with all parent controls. 36196 * 36197 * @method parents 36198 * @param {String} selector Optional selector expression to find parents. 36199 * @return {tinymce.ui.Collection} Collection with all parent controls. 36200 */ 36201 parents: function(selector) { 36202 var self = this, ctrl, parents = new Collection(); 36203 36204 // Add each parent to collection 36205 for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) { 36206 parents.add(ctrl); 36207 } 36208 36209 // Filter away everything that doesn't match the selector 36210 if (selector) { 36211 parents = parents.filter(selector); 36212 } 36213 36214 return parents; 36215 }, 36216 36217 /** 36218 * Returns the current control and it's parents. 36219 * 36220 * @method parentsAndSelf 36221 * @param {String} selector Optional selector expression to find parents. 36222 * @return {tinymce.ui.Collection} Collection with all parent controls. 36223 */ 36224 parentsAndSelf: function(selector) { 36225 return new Collection(this).add(this.parents(selector)); 36226 }, 36227 36228 /** 36229 * Returns the control next to the current control. 36230 * 36231 * @method next 36232 * @return {tinymce.ui.Control} Next control instance. 36233 */ 36234 next: function() { 36235 var parentControls = this.parent().items(); 36236 36237 return parentControls[parentControls.indexOf(this) + 1]; 36238 }, 36239 36240 /** 36241 * Returns the control previous to the current control. 36242 * 36243 * @method prev 36244 * @return {tinymce.ui.Control} Previous control instance. 36245 */ 36246 prev: function() { 36247 var parentControls = this.parent().items(); 36248 36249 return parentControls[parentControls.indexOf(this) - 1]; 36250 }, 36251 36252 /** 36253 * Sets the inner HTML of the control element. 36254 * 36255 * @method innerHtml 36256 * @param {String} html Html string to set as inner html. 36257 * @return {tinymce.ui.Control} Current control object. 36258 */ 36259 innerHtml: function(html) { 36260 this.$el.html(html); 36261 return this; 36262 }, 36263 36264 /** 36265 * Returns the control DOM element or sub element. 36266 * 36267 * @method getEl 36268 * @param {String} [suffix] Suffix to get element by. 36269 * @return {Element} HTML DOM element for the current control or it's children. 36270 */ 36271 getEl: function(suffix) { 36272 var id = suffix ? this._id + '-' + suffix : this._id; 36273 36274 if (!this._elmCache[id]) { 36275 this._elmCache[id] = $('#' + id)[0]; 36276 } 36277 36278 return this._elmCache[id]; 36279 }, 36280 36281 /** 36282 * Sets the visible state to true. 36283 * 36284 * @method show 36285 * @return {tinymce.ui.Control} Current control instance. 36286 */ 36287 show: function() { 36288 return this.visible(true); 36289 }, 36290 36291 /** 36292 * Sets the visible state to false. 36293 * 36294 * @method hide 36295 * @return {tinymce.ui.Control} Current control instance. 36296 */ 36297 hide: function() { 36298 return this.visible(false); 36299 }, 36300 36301 /** 36302 * Focuses the current control. 36303 *
36304 * @method focus 36305 * @return {tinymce.ui.Control} Current control instance. 36306 */ 36307 focus: function() { 36308 try { 36309 this.getEl().focus(); 36310 } catch (ex) { 36311 // Ignore IE error 36312 } 36313 36314 return this; 36315 }, 36316 36317 /** 36318 * Blurs the current control. 36319 * 36320 * @method blur 36321 * @return {tinymce.ui.Control} Current control instance. 36322 */ 36323 blur: function() { 36324 this.getEl().blur(); 36325 36326 return this; 36327 }, 36328 36329 /** 36330 * Sets the specified aria property. 36331 * 36332 * @method aria 36333 * @param {String} name Name of the aria property to set. 36334 * @param {String} value Value of the aria property. 36335 * @return {tinymce.ui.Control} Current control instance. 36336 */ 36337 aria: function(name, value) { 36338 var self = this, elm = self.getEl(self.ariaTarget); 36339 36340 if (typeof value === "undefined") { 36341 return self._aria[name]; 36342 } 36343 36344 self._aria[name] = value; 36345 36346 if (self.state.get('rendered')) { 36347 elm.setAttribute(name == 'role' ? name : 'aria-' + name, value); 36348 } 36349 36350 return self; 36351 }, 36352 36353 /** 36354 * Encodes the specified string with HTML entities. It will also 36355 * translate the string to different languages. 36356 * 36357 * @method encode 36358 * @param {String/Object/Array} text Text to entity encode. 36359 * @param {Boolean} [translate=true] False if the contents shouldn't be translated. 36360 * @return {String} Encoded and possible traslated string. 36361 */ 36362 encode: function(text, translate) { 36363 if (translate !== false) { 36364 text = this.translate(text); 36365 } 36366 36367 return (text || '').replace(/[&<>"]/g, function(match) { 36368 return '&#' + match.charCodeAt(0) + ';'; 36369 }); 36370 }, 36371 36372 /** 36373 * Returns the translated string. 36374 * 36375 * @method translate 36376 * @param {String} text Text to translate. 36377 * @return {String} Translated string or the same as the input. 36378 */ 36379 translate: function(text) { 36380 return Control.translate ? Control.translate(text) : text; 36381 }, 36382 36383 /** 36384 * Adds items before the current control. 36385 * 36386 * @method before 36387 * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control. 36388 * @return {tinymce.ui.Control} Current control instance. 36389 */ 36390 before: function(items) { 36391 var self = this, parent = self.parent(); 36392 36393 if (parent) { 36394 parent.insert(items, parent.items().indexOf(self), true); 36395 } 36396 36397 return self; 36398 }, 36399 36400 /** 36401 * Adds items after the current control. 36402 * 36403 * @method after 36404 * @param {Array/tinymce.ui.Collection} items Array of items to append after this control. 36405 * @return {tinymce.ui.Control} Current control instance. 36406 */ 36407 after: function(items) { 36408 var self = this, parent = self.parent(); 36409 36410 if (parent) { 36411 parent.insert(items, parent.items().indexOf(self)); 36412 } 36413 36414 return self; 36415 }, 36416 36417 /** 36418 * Removes the current control from DOM and from UI collections. 36419 * 36420 * @method remove 36421 * @return {tinymce.ui.Control} Current control instance. 36422 */ 36423 remove: function() { 36424 var self = this, elm = self.getEl(), parent = self.parent(), newItems, i; 36425 36426 if (self.items) { 36427 var controls = self.items().toArray(); 36428 i = controls.length; 36429 while (i--) { 36430 controls[i].remove(); 36431 } 36432 } 36433 36434 if (parent && parent.items) { 36435 newItems = []; 36436 36437 parent.items().each(function(item) { 36438 if (item !== self) { 36439 newItems.push(item); 36440 } 36441 }); 36442 36443 parent.items().set(newItems); 36444 parent._lastRect = null; 36445 } 36446 36447 if (self._eventsRoot && self._eventsRoot == self) { 36448 $(elm).off(); 36449 } 36450 36451 var lookup = self.getRoot().controlIdLookup; 36452 if (lookup) { 36453 delete lookup[self._id]; 36454 } 36455 36456 if (elm && elm.parentNode) { 36457 elm.parentNode.removeChild(elm); 36458 } 36459 36460 self.state.set('rendered', false); 36461 self.state.destroy(); 36462 36463 self.fire('remove'); 36464 36465 return self; 36466 }, 36467 36468 /** 36469 * Renders the control before the specified element. 36470 * 36471 * @method renderBefore 36472 * @param {Element} elm Element to render before. 36473 * @return {tinymce.ui.Control} Current control instance. 36474 */ 36475 renderBefore: function(elm) { 36476 $(elm).before(this.renderHtml()); 36477 this.postRender(); 36478 return this; 36479 }, 36480 36481 /** 36482 * Renders the control to the specified element. 36483 * 36484 * @method renderBefore 36485 * @param {Element} elm Element to render to. 36486 * @return {tinymce.ui.Control} Current control instance. 36487 */ 36488 renderTo: function(elm) { 36489 $(elm || this.getContainerElm()).append(this.renderHtml()); 36490 this.postRender(); 36491 return this; 36492 }, 36493 36494 preRender: function() { 36495 }, 36496 36497 render: function() { 36498 }, 36499 36500 renderHtml: function() { 36501 return '<div id="' + this._id + '" class="' + this.classes + '"></div>'; 36502 }, 36503 36504 /** 36505 * Post render method. Called after the control has been rendered to the target. 36506 * 36507 * @method postRender 36508 * @return {tinymce.ui.Control} Current control instance. 36509 */ 36510 postRender: function() { 36511 var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot; 36512 36513 self.$el = $(self.getEl()); 36514 self.state.set('rendered', true); 36515 36516 // Bind on<event> settings 36517 for (name in settings) { 36518 if (name.indexOf("on") === 0) { 36519 self.on(name.substr(2), settings[name]); 36520 } 36521 } 36522 36523 if (self._eventsRoot) { 36524 for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) { 36525 parentEventsRoot = parent._eventsRoot; 36526 } 36527 36528 if (parentEventsRoot) { 36529 for (name in parentEventsRoot._nativeEvents) { 36530 self._nativeEvents[name] = true; 36531 } 36532 } 36533 } 36534 36535 bindPendingEvents(self); 36536 36537 if (settings.style) { 36538 elm = self.getEl(); 36539 if (elm) { 36540 elm.setAttribute('style', settings.style); 36541 elm.style.cssText = settings.style; 36542 } 36543 } 36544 36545 if (self.settings.border) { 36546 box = self.borderBox; 36547 self.$el.css({ 36548 'border-top-width': box.top, 36549 'border-right-width': box.right, 36550 'border-bottom-width': box.bottom, 36551 'border-left-width': box.left 36552 }); 36553 } 36554 36555 // Add instance to lookup 36556 var root = self.getRoot(); 36557 if (!root.controlIdLookup) { 36558 root.controlIdLookup = {}; 36559 } 36560 36561 root.controlIdLookup[self._id] = self; 36562 36563 for (var key in self._aria) { 36564 self.aria(key, self._aria[key]); 36565 } 36566 36567 if (self.state.get('visible') === false) { 36568 self.getEl().style.display = 'none'; 36569 } 36570 36571 self.bindStates(); 36572 36573 self.state.on('change:visible', function(e) { 36574 var state = e.value, parentCtrl; 36575 36576 if (self.state.get('rendered')) { 36577 self.getEl().style.display = state === false ? 'none' : ''; 36578 36579 // Need to force a reflow here on IE 8 36580 self.getEl().getBoundingClientRect(); 36581 } 36582 36583 // Parent container needs to reflow 36584 parentCtrl = self.parent(); 36585 if (parentCtrl) { 36586 parentCtrl._lastRect = null; 36587 } 36588 36589 self.fire(state ? 'show' : 'hide'); 36590 36591 ReflowQueue.add(self); 36592 }); 36593 36594 self.fire('postrender', {}, false); 36595 }, 36596 36597 bindStates: function() { 36598 }, 36599 36600 /** 36601 * Scrolls the current control into view. 36602 * 36603 * @method scrollIntoView 36604 * @param {String} align Alignment in view top|center|bottom. 36605 * @return {tinymce.ui.Control} Current control instance. 36606 */ 36607 scrollIntoView: function(align) { 36608 function getOffset(elm, rootElm) { 36609 var x, y, parent = elm; 36610 36611 x = y = 0; 36612 while (parent && parent != rootElm && parent.nodeType) { 36613 x += parent.offsetLeft || 0; 36614 y += parent.offsetTop || 0; 36615 parent = parent.offsetParent; 36616 } 36617 36618 return {x: x, y: y}; 36619 } 36620 36621 var elm = this.getEl(), parentElm = elm.parentNode; 36622 var x, y, width, height, parentWidth, parentHeight; 36623 var pos = getOffset(elm, parentElm); 36624 36625 x = pos.x; 36626 y = pos.y; 36627 width = elm.offsetWidth; 36628 height = elm.offsetHeight; 36629 parentWidth = parentElm.clientWidth; 36630 parentHeight = parentElm.clientHeight; 36631
36632 if (align == "end") { 36633 x -= parentWidth - width; 36634 y -= parentHeight - height; 36635 } else if (align == "center") { 36636 x -= (parentWidth / 2) - (width / 2); 36637 y -= (parentHeight / 2) - (height / 2); 36638 } 36639 36640 parentElm.scrollLeft = x; 36641 parentElm.scrollTop = y; 36642 36643 return this; 36644 }, 36645 36646 getRoot: function() { 36647 var ctrl = this, rootControl, parents = []; 36648 36649 while (ctrl) { 36650 if (ctrl.rootControl) { 36651 rootControl = ctrl.rootControl; 36652 break; 36653 } 36654 36655 parents.push(ctrl); 36656 rootControl = ctrl; 36657 ctrl = ctrl.parent(); 36658 } 36659 36660 if (!rootControl) { 36661 rootControl = this; 36662 } 36663 36664 var i = parents.length; 36665 while (i--) { 36666 parents[i].rootControl = rootControl; 36667 } 36668 36669 return rootControl; 36670 }, 36671 36672 /** 36673 * Reflows the current control and it's parents. 36674 * This should be used after you for example append children to the current control so 36675 * that the layout managers know that they need to reposition everything. 36676 * 36677 * @example 36678 * container.append({type: 'button', text: 'My button'}).reflow(); 36679 * 36680 * @method reflow 36681 * @return {tinymce.ui.Control} Current control instance. 36682 */ 36683 reflow: function() { 36684 ReflowQueue.remove(this); 36685 36686 var parent = this.parent(); 36687 if (parent._layout && !parent._layout.isNative()) { 36688 parent.reflow(); 36689 } 36690 36691 return this; 36692 } 36693 36694 /** 36695 * Sets/gets the parent container for the control. 36696 * 36697 * @method parent 36698 * @param {tinymce.ui.Container} parent Optional parent to set. 36699 * @return {tinymce.ui.Control} Parent control or the current control on a set action. 36700 */ 36701 // parent: function(parent) {} -- Generated 36702 36703 /** 36704 * Sets/gets the text for the control. 36705 * 36706 * @method text 36707 * @param {String} value Value to set to control. 36708 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 36709 */ 36710 // text: function(value) {} -- Generated 36711 36712 /** 36713 * Sets/gets the disabled state on the control. 36714 * 36715 * @method disabled 36716 * @param {Boolean} state Value to set to control. 36717 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 36718 */ 36719 // disabled: function(state) {} -- Generated 36720 36721 /** 36722 * Sets/gets the active for the control. 36723 * 36724 * @method active 36725 * @param {Boolean} state Value to set to control. 36726 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 36727 */ 36728 // active: function(state) {} -- Generated 36729 36730 /** 36731 * Sets/gets the name for the control. 36732 * 36733 * @method name 36734 * @param {String} value Value to set to control. 36735 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 36736 */ 36737 // name: function(value) {} -- Generated 36738 36739 /** 36740 * Sets/gets the title for the control. 36741 * 36742 * @method title 36743 * @param {String} value Value to set to control. 36744 * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get. 36745 */ 36746 // title: function(value) {} -- Generated 36747 36748 /** 36749 * Sets/gets the visible for the control. 36750 * 36751 * @method visible 36752 * @param {Boolean} state Value to set to control. 36753 * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get. 36754 */ 36755 // visible: function(value) {} -- Generated 36756 }; 36757 36758 /** 36759 * Setup state properties. 36760 */ 36761 Tools.each('text title visible disabled active value'.split(' '), function(name) { 36762 proto[name] = function(value) { 36763 if (arguments.length === 0) { 36764 return this.state.get(name); 36765 } 36766 36767 if (typeof value != "undefined") { 36768 this.state.set(name, value); 36769 } 36770 36771 return this; 36772 }; 36773 }); 36774 36775 Control = Class.extend(proto); 36776 36777 function getEventDispatcher(obj) { 36778 if (!obj._eventDispatcher) { 36779 obj._eventDispatcher = new EventDispatcher({ 36780 scope: obj, 36781 toggleEvent: function(name, state) { 36782 if (state && EventDispatcher.isNative(name)) { 36783 if (!obj._nativeEvents) { 36784 obj._nativeEvents = {}; 36785 } 36786 36787 obj._nativeEvents[name] = true; 36788 36789 if (obj.state.get('rendered')) { 36790 bindPendingEvents(obj); 36791 } 36792 } 36793 } 36794 }); 36795 } 36796 36797 return obj._eventDispatcher; 36798 } 36799 36800 function bindPendingEvents(eventCtrl) { 36801 var i, l, parents, eventRootCtrl, nativeEvents, name; 36802 36803 function delegate(e) { 36804 var control = eventCtrl.getParentCtrl(e.target); 36805 36806 if (control) { 36807 control.fire(e.type, e); 36808 } 36809 } 36810 36811 function mouseLeaveHandler() { 36812 var ctrl = eventRootCtrl._lastHoverCtrl; 36813 36814 if (ctrl) { 36815 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 36816 36817 ctrl.parents().each(function(ctrl) { 36818 ctrl.fire("mouseleave", {target: ctrl.getEl()}); 36819 }); 36820 36821 eventRootCtrl._lastHoverCtrl = null; 36822 } 36823 } 36824 36825 function mouseEnterHandler(e) { 36826 var ctrl = eventCtrl.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents; 36827 36828 // Over on a new control 36829 if (ctrl !== lastCtrl) { 36830 eventRootCtrl._lastHoverCtrl = ctrl; 36831 36832 parents = ctrl.parents().toArray().reverse(); 36833 parents.push(ctrl); 36834 36835 if (lastCtrl) { 36836 lastParents = lastCtrl.parents().toArray().reverse(); 36837 lastParents.push(lastCtrl); 36838 36839 for (idx = 0; idx < lastParents.length; idx++) { 36840 if (parents[idx] !== lastParents[idx]) { 36841 break; 36842 } 36843 } 36844 36845 for (i = lastParents.length - 1; i >= idx; i--) { 36846 lastCtrl = lastParents[i]; 36847 lastCtrl.fire("mouseleave", { 36848 target: lastCtrl.getEl() 36849 }); 36850 } 36851 } 36852 36853 for (i = idx; i < parents.length; i++) { 36854 ctrl = parents[i]; 36855 ctrl.fire("mouseenter", { 36856 target: ctrl.getEl() 36857 }); 36858 } 36859 } 36860 } 36861 36862 function fixWheelEvent(e) { 36863 e.preventDefault(); 36864 36865 if (e.type == "mousewheel") { 36866 e.deltaY = -1 / 40 * e.wheelDelta; 36867 36868 if (e.wheelDeltaX) { 36869 e.deltaX = -1 / 40 * e.wheelDeltaX; 36870 } 36871 } else { 36872 e.deltaX = 0; 36873 e.deltaY = e.detail; 36874 } 36875 36876 e = eventCtrl.fire("wheel", e); 36877 } 36878 36879 nativeEvents = eventCtrl._nativeEvents; 36880 if (nativeEvents) { 36881 // Find event root element if it exists 36882 parents = eventCtrl.parents().toArray(); 36883 parents.unshift(eventCtrl); 36884 for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) { 36885 eventRootCtrl = parents[i]._eventsRoot; 36886 } 36887 36888 // Event root wasn't found the use the root control 36889 if (!eventRootCtrl) { 36890 eventRootCtrl = parents[parents.length - 1] || eventCtrl; 36891 } 36892 36893 // Set the eventsRoot property on children that didn't have it 36894 eventCtrl._eventsRoot = eventRootCtrl; 36895 for (l = i, i = 0; i < l; i++) { 36896 parents[i]._eventsRoot = eventRootCtrl; 36897 } 36898 36899 var eventRootDelegates = eventRootCtrl._delegates; 36900 if (!eventRootDelegates) { 36901 eventRootDelegates = eventRootCtrl._delegates = {}; 36902 } 36903 36904 // Bind native event delegates 36905 for (name in nativeEvents) { 36906 if (!nativeEvents) { 36907 return false;
36908 } 36909 36910 if (name === "wheel" && !hasWheelEventSupport) { 36911 if (hasMouseWheelEventSupport) { 36912 $(eventCtrl.getEl()).on("mousewheel", fixWheelEvent); 36913 } else { 36914 $(eventCtrl.getEl()).on("DOMMouseScroll", fixWheelEvent); 36915 } 36916 36917 continue; 36918 } 36919 36920 // Special treatment for mousenter/mouseleave since these doesn't bubble 36921 if (name === "mouseenter" || name === "mouseleave") { 36922 // Fake mousenter/mouseleave 36923 if (!eventRootCtrl._hasMouseEnter) { 36924 $(eventRootCtrl.getEl()).on("mouseleave", mouseLeaveHandler).on("mouseover", mouseEnterHandler); 36925 eventRootCtrl._hasMouseEnter = 1; 36926 } 36927 } else if (!eventRootDelegates[name]) { 36928 $(eventRootCtrl.getEl()).on(name, delegate); 36929 eventRootDelegates[name] = true; 36930 } 36931 36932 // Remove the event once it's bound 36933 nativeEvents[name] = false; 36934 } 36935 } 36936 } 36937 36938 return Control; 36939}); 36940 36941// Included from: js/tinymce/classes/ui/Factory.js 36942 36943/** 36944 * Factory.js 36945 * 36946 * Released under LGPL License. 36947 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 36948 * 36949 * License: http://www.tinymce.com/license 36950 * Contributing: http://www.tinymce.com/contributing 36951 */ 36952 36953/*global tinymce:true */ 36954 36955/** 36956 * This class is a factory for control instances. This enables you 36957 * to create instances of controls without having to require the UI controls directly. 36958 * 36959 * It also allow you to override or add new control types. 36960 * 36961 * @class tinymce.ui.Factory 36962 */ 36963define("tinymce/ui/Factory", [], function() { 36964 "use strict"; 36965 36966 var types = {}, namespaceInit; 36967 36968 return { 36969 /** 36970 * Adds a new control instance type to the factory. 36971 * 36972 * @method add 36973 * @param {String} type Type name for example "button". 36974 * @param {function} typeClass Class type function. 36975 */ 36976 add: function(type, typeClass) { 36977 types[type.toLowerCase()] = typeClass; 36978 }, 36979 36980 /** 36981 * Returns true/false if the specified type exists or not. 36982 * 36983 * @method has 36984 * @param {String} type Type to look for. 36985 * @return {Boolean} true/false if the control by name exists. 36986 */ 36987 has: function(type) { 36988 return !!types[type.toLowerCase()]; 36989 }, 36990 36991 /** 36992 * Creates a new control instance based on the settings provided. The instance created will be 36993 * based on the specified type property it can also create whole structures of components out of 36994 * the specified JSON object. 36995 * 36996 * @example 36997 * tinymce.ui.Factory.create({ 36998 * type: 'button', 36999 * text: 'Hello world!' 37000 * }); 37001 * 37002 * @method create 37003 * @param {Object/String} settings Name/Value object with items used to create the type. 37004 * @return {tinymce.ui.Control} Control instance based on the specified type. 37005 */ 37006 create: function(type, settings) { 37007 var ControlType, name, namespace; 37008 37009 // Build type lookup 37010 if (!namespaceInit) { 37011 namespace = tinymce.ui; 37012 37013 for (name in namespace) { 37014 types[name.toLowerCase()] = namespace[name]; 37015 } 37016 37017 namespaceInit = true; 37018 } 37019 37020 // If string is specified then use it as the type 37021 if (typeof type == 'string') { 37022 settings = settings || {}; 37023 settings.type = type; 37024 } else { 37025 settings = type; 37026 type = settings.type; 37027 } 37028 37029 // Find control type 37030 type = type.toLowerCase(); 37031 ControlType = types[type]; 37032 37033 // #if debug 37034 37035 if (!ControlType) { 37036 throw new Error("Could not find control by type: " + type); 37037 } 37038 37039 // #endif 37040 37041 ControlType = new ControlType(settings); 37042 ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine 37043 37044 return ControlType; 37045 } 37046 }; 37047}); 37048 37049// Included from: js/tinymce/classes/ui/KeyboardNavigation.js 37050 37051/** 37052 * KeyboardNavigation.js 37053 * 37054 * Released under LGPL License. 37055 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 37056 * 37057 * License: http://www.tinymce.com/license 37058 * Contributing: http://www.tinymce.com/contributing 37059 */ 37060 37061/** 37062 * This class handles keyboard navigation of controls and elements. 37063 * 37064 * @class tinymce.ui.KeyboardNavigation 37065 */ 37066define("tinymce/ui/KeyboardNavigation", [ 37067], function() { 37068 "use strict"; 37069 37070 /** 37071 * This class handles all keyboard navigation for WAI-ARIA support. Each root container 37072 * gets an instance of this class. 37073 * 37074 * @constructor 37075 */ 37076 return function(settings) { 37077 var root = settings.root, focusedElement, focusedControl; 37078 37079 function isElement(node) { 37080 return node && node.nodeType === 1; 37081 } 37082 37083 try { 37084 focusedElement = document.activeElement; 37085 } catch (ex) { 37086 // IE sometimes fails to return a proper element 37087 focusedElement = document.body; 37088 } 37089 37090 focusedControl = root.getParentCtrl(focusedElement); 37091 37092 /** 37093 * Returns the currently focused elements wai aria role of the currently 37094 * focused element or specified element. 37095 * 37096 * @private 37097 * @param {Element} elm Optional element to get role from. 37098 * @return {String} Role of specified element. 37099 */ 37100 function getRole(elm) { 37101 elm = elm || focusedElement; 37102 37103 if (isElement(elm)) { 37104 return elm.getAttribute('role'); 37105 } 37106 37107 return null; 37108 } 37109 37110 /** 37111 * Returns the wai role of the parent element of the currently 37112 * focused element or specified element. 37113 * 37114 * @private 37115 * @param {Element} elm Optional element to get parent role from. 37116 * @return {String} Role of the first parent that has a role. 37117 */ 37118 function getParentRole(elm) { 37119 var role, parent = elm || focusedElement; 37120 37121 while ((parent = parent.parentNode)) { 37122 if ((role = getRole(parent))) { 37123 return role; 37124 } 37125 } 37126 } 37127 37128 /** 37129 * Returns a wai aria property by name for example aria-selected. 37130 * 37131 * @private 37132 * @param {String} name Name of the aria property to get for example "disabled". 37133 * @return {String} Aria property value. 37134 */ 37135 function getAriaProp(name) { 37136 var elm = focusedElement; 37137 37138 if (isElement(elm)) { 37139 return elm.getAttribute('aria-' + name); 37140 } 37141 } 37142 37143 /** 37144 * Is the element a text input element or not. 37145 * 37146 * @private 37147 * @param {Element} elm Element to check if it's an text input element or not. 37148 * @return {Boolean} True/false if the element is a text element or not. 37149 */ 37150 function isTextInputElement(elm) { 37151 var tagName = elm.tagName.toUpperCase(); 37152 37153 // Notice: since type can be "email" etc we don't check the type 37154 // So all input elements gets treated as text input elements 37155 return tagName == "INPUT" || tagName == "TEXTAREA"; 37156 } 37157 37158 /** 37159 * Returns true/false if the specified element can be focused or not. 37160 * 37161 * @private 37162 * @param {Element} elm DOM element to check if it can be focused or not. 37163 * @return {Boolean} True/false if the element can have focus. 37164 */ 37165 function canFocus(elm) { 37166 if (isTextInputElement(elm) && !elm.hidden) { 37167 return true; 37168 } 37169 37170 if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) { 37171 return true; 37172 } 37173 37174 return false;
37175 } 37176 37177 /** 37178 * Returns an array of focusable visible elements within the specified container element. 37179 * 37180 * @private 37181 * @param {Element} elm DOM element to find focusable elements within. 37182 * @return {Array} Array of focusable elements. 37183 */ 37184 function getFocusElements(elm) { 37185 var elements = []; 37186 37187 function collect(elm) { 37188 if (elm.nodeType != 1 || elm.style.display == 'none') { 37189 return; 37190 } 37191 37192 if (canFocus(elm)) { 37193 elements.push(elm); 37194 } 37195 37196 for (var i = 0; i < elm.childNodes.length; i++) { 37197 collect(elm.childNodes[i]); 37198 } 37199 } 37200 37201 collect(elm || root.getEl()); 37202 37203 return elements; 37204 } 37205 37206 /** 37207 * Returns the navigation root control for the specified control. The navigation root 37208 * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group. 37209 * It will look for parents of the specified target control or the currently focused control if this option is omitted. 37210 * 37211 * @private 37212 * @param {tinymce.ui.Control} targetControl Optional target control to find root of. 37213 * @return {tinymce.ui.Control} Navigation root control. 37214 */ 37215 function getNavigationRoot(targetControl) { 37216 var navigationRoot, controls; 37217 37218 targetControl = targetControl || focusedControl;
37219 controls = targetControl.parents().toArray(); 37220 controls.unshift(targetControl); 37221 37222 for (var i = 0; i < controls.length; i++) { 37223 navigationRoot = controls[i]; 37224 37225 if (navigationRoot.settings.ariaRoot) { 37226 break; 37227 } 37228 } 37229 37230 return navigationRoot; 37231 } 37232 37233 /** 37234 * Focuses the first item in the specified targetControl element or the last aria index if the 37235 * navigation root has the ariaRemember option enabled. 37236 * 37237 * @private 37238 * @param {tinymce.ui.Control} targetControl Target control to focus the first item in. 37239 */ 37240 function focusFirst(targetControl) { 37241 var navigationRoot = getNavigationRoot(targetControl); 37242 var focusElements = getFocusElements(navigationRoot.getEl()); 37243 37244 if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) { 37245 moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements); 37246 } else { 37247 moveFocusToIndex(0, focusElements); 37248 } 37249 } 37250 37251 /** 37252 * Moves the focus to the specified index within the elements list. 37253 * This will scope the index to the size of the element list if it changed. 37254 * 37255 * @private 37256 * @param {Number} idx Specified index to move to. 37257 * @param {Array} elements Array with dom elements to move focus within. 37258 * @return {Number} Input index or a changed index if it was out of range. 37259 */ 37260 function moveFocusToIndex(idx, elements) { 37261 if (idx < 0) { 37262 idx = elements.length - 1; 37263 } else if (idx >= elements.length) { 37264 idx = 0; 37265 } 37266 37267 if (elements[idx]) { 37268 elements[idx].focus(); 37269 } 37270 37271 return idx; 37272 } 37273 37274 /** 37275 * Moves the focus forwards or backwards. 37276 * 37277 * @private 37278 * @param {Number} dir Direction to move in positive means forward, negative means backwards. 37279 * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements. 37280 */ 37281 function moveFocus(dir, elements) { 37282 var idx = -1, navigationRoot = getNavigationRoot(); 37283 37284 elements = elements || getFocusElements(navigationRoot.getEl()); 37285 37286 for (var i = 0; i < elements.length; i++) { 37287 if (elements[i] === focusedElement) { 37288 idx = i; 37289 } 37290 } 37291 37292 idx += dir; 37293 navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements); 37294 } 37295 37296 /** 37297 * Moves the focus to the left this is called by the left key. 37298 * 37299 * @private 37300 */ 37301 function left() { 37302 var parentRole = getParentRole(); 37303 37304 if (parentRole == "tablist") { 37305 moveFocus(-1, getFocusElements(focusedElement.parentNode)); 37306 } else if (focusedControl.parent().submenu) { 37307 cancel(); 37308 } else { 37309 moveFocus(-1); 37310 } 37311 } 37312 37313 /** 37314 * Moves the focus to the right this is called by the right key. 37315 * 37316 * @private 37317 */ 37318 function right() { 37319 var role = getRole(), parentRole = getParentRole(); 37320 37321 if (parentRole == "tablist") { 37322 moveFocus(1, getFocusElements(focusedElement.parentNode)); 37323 } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) { 37324 enter(); 37325 } else { 37326 moveFocus(1); 37327 } 37328 } 37329 37330 /** 37331 * Moves the focus to the up this is called by the up key. 37332 * 37333 * @private 37334 */ 37335 function up() { 37336 moveFocus(-1); 37337 } 37338 37339 /** 37340 * Moves the focus to the up this is called by the down key. 37341 * 37342 * @private 37343 */ 37344 function down() { 37345 var role = getRole(), parentRole = getParentRole(); 37346 37347 if (role == "menuitem" && parentRole == "menubar") { 37348 enter(); 37349 } else if (role == "button" && getAriaProp('haspopup')) { 37350 enter({key: 'down'}); 37351 } else { 37352 moveFocus(1); 37353 } 37354 } 37355 37356 /** 37357 * Moves the focus to the next item or previous item depending on shift key. 37358 * 37359 * @private 37360 * @param {DOMEvent} e DOM event object. 37361 */ 37362 function tab(e) { 37363 var parentRole = getParentRole(); 37364 37365 if (parentRole == "tablist") { 37366 var elm = getFocusElements(focusedControl.getEl('body'))[0]; 37367 37368 if (elm) { 37369 elm.focus(); 37370 } 37371 } else { 37372 moveFocus(e.shiftKey ? -1 : 1); 37373 } 37374 } 37375 37376 /** 37377 * Calls the cancel event on the currently focused control. This is normally done using the Esc key. 37378 * 37379 * @private 37380 */ 37381 function cancel() { 37382 focusedControl.fire('cancel'); 37383 } 37384 37385 /** 37386 * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys. 37387 * 37388 * @private 37389 * @param {Object} aria Optional aria data to pass along with the enter event. 37390 */ 37391 function enter(aria) { 37392 aria = aria || {};
37393 focusedControl.fire('click', {target: focusedElement, aria: aria}); 37394 } 37395 37396 root.on('keydown', function(e) { 37397 function handleNonTabOrEscEvent(e, handler) { 37398 // Ignore non tab keys for text elements 37399 if (isTextInputElement(focusedElement)) { 37400 return; 37401 } 37402 37403 if (handler(e) !== false) { 37404 e.preventDefault(); 37405 } 37406 } 37407 37408 if (e.isDefaultPrevented()) { 37409 return; 37410 } 37411 37412 switch (e.keyCode) { 37413 case 37: // DOM_VK_LEFT 37414 handleNonTabOrEscEvent(e, left); 37415 break; 37416 37417 case 39: // DOM_VK_RIGHT 37418 handleNonTabOrEscEvent(e, right); 37419 break; 37420 37421 case 38: // DOM_VK_UP 37422 handleNonTabOrEscEvent(e, up); 37423 break; 37424 37425 case 40: // DOM_VK_DOWN 37426 handleNonTabOrEscEvent(e, down); 37427 break; 37428 37429 case 27: // DOM_VK_ESCAPE 37430 cancel(); 37431 break; 37432 37433 case 14: // DOM_VK_ENTER 37434 case 13: // DOM_VK_RETURN 37435 case 32: // DOM_VK_SPACE 37436 handleNonTabOrEscEvent(e, enter); 37437 break; 37438 37439 case 9: // DOM_VK_TAB 37440 if (tab(e) !== false) { 37441 e.preventDefault(); 37442 } 37443 break; 37444 } 37445 }); 37446 37447 root.on('focusin', function(e) { 37448 focusedElement = e.target; 37449 focusedControl = e.control; 37450 }); 37451 37452 return { 37453 focusFirst: focusFirst 37454 }; 37455 }; 37456}); 37457 37458// Included from: js/tinymce/classes/ui/Container.js 37459 37460/** 37461 * Container.js 37462 * 37463 * Released under LGPL License. 37464 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 37465 * 37466 * License: http://www.tinymce.com/license 37467 * Contributing: http://www.tinymce.com/contributing 37468 */ 37469 37470/** 37471 * Container control. This is extended by all controls that can have 37472 * children such as panels etc. You can also use this class directly as an 37473 * generic container instance. The container doesn't have any specific role or style. 37474 * 37475 * @-x-less Container.less 37476 * @class tinymce.ui.Container 37477 * @extends tinymce.ui.Control 37478 */ 37479define("tinymce/ui/Container", [ 37480 "tinymce/ui/Control", 37481 "tinymce/ui/Collection", 37482 "tinymce/ui/Selector", 37483 "tinymce/ui/Factory", 37484 "tinymce/ui/KeyboardNavigation", 37485 "tinymce/util/Tools", 37486 "tinymce/dom/DomQuery", 37487 "tinymce/ui/ClassList", 37488 "tinymce/ui/ReflowQueue" 37489], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, $, ClassList, ReflowQueue) { 37490 "use strict"; 37491 37492 var selectorCache = {}; 37493 37494 return Control.extend({ 37495 /** 37496 * Constructs a new control instance with the specified settings. 37497 * 37498 * @constructor 37499 * @param {Object} settings Name/value object with settings. 37500 * @setting {Array} items Items to add to container in JSON format or control instances. 37501 * @setting {String} layout Layout manager by name to use. 37502 * @setting {Object} defaults Default settings to apply to all items. 37503 */ 37504 init: function(settings) { 37505 var self = this; 37506 37507 self._super(settings); 37508 settings = self.settings; 37509 37510 if (settings.fixed) { 37511 self.state.set('fixed', true); 37512 } 37513 37514 self._items = new Collection(); 37515 37516 if (self.isRtl()) { 37517 self.classes.add('rtl'); 37518 } 37519 37520 self.bodyClasses = new ClassList(function() { 37521 if (self.state.get('rendered')) { 37522 self.getEl('body').className = this.toString(); 37523 } 37524 }); 37525 self.bodyClasses.prefix = self.classPrefix; 37526 37527 self.classes.add('container'); 37528 self.bodyClasses.add('container-body'); 37529 37530 if (settings.containerCls) { 37531 self.classes.add(settings.containerCls); 37532 } 37533 37534 self._layout = Factory.create((settings.layout || '') + 'layout'); 37535 37536 if (self.settings.items) { 37537 self.add(self.settings.items); 37538 } else { 37539 self.add(self.render()); 37540 } 37541 37542 // TODO: Fix this! 37543 self._hasBody = true; 37544 }, 37545 37546 /** 37547 * Returns a collection of child items that the container currently have. 37548 * 37549 * @method items 37550 * @return {tinymce.ui.Collection} Control collection direct child controls. 37551 */ 37552 items: function() { 37553 return this._items; 37554 }, 37555 37556 /** 37557 * Find child controls by selector. 37558 * 37559 * @method find 37560 * @param {String} selector Selector CSS pattern to find children by. 37561 * @return {tinymce.ui.Collection} Control collection with child controls. 37562 */ 37563 find: function(selector) { 37564 selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector); 37565 37566 return selector.find(this); 37567 }, 37568 37569 /** 37570 * Adds one or many items to the current container. This will create instances of 37571 * the object representations if needed. 37572 * 37573 * @method add 37574 * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container. 37575 * @return {tinymce.ui.Collection} Current collection control. 37576 */ 37577 add: function(items) { 37578 var self = this; 37579 37580 self.items().add(self.create(items)).parent(self); 37581 37582 return self; 37583 }, 37584 37585 /** 37586 * Focuses the current container instance. This will look 37587 * for the first control in the container and focus that. 37588 *
37589 * @method focus 37590 * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not. 37591 * @return {tinymce.ui.Collection} Current instance. 37592 */ 37593 focus: function(keyboard) { 37594 var self = this, focusCtrl, keyboardNav, items; 37595 37596 if (keyboard) { 37597 keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav; 37598 37599 if (keyboardNav) { 37600 keyboardNav.focusFirst(self); 37601 return; 37602 } 37603 } 37604 37605 items = self.find('*'); 37606 37607 // TODO: Figure out a better way to auto focus alert dialog buttons 37608 if (self.statusbar) { 37609 items.add(self.statusbar.items()); 37610 } 37611 37612 items.each(function(ctrl) { 37613 if (ctrl.settings.autofocus) { 37614 focusCtrl = null; 37615 return false; 37616 } 37617 37618 if (ctrl.canFocus) { 37619 focusCtrl = focusCtrl || ctrl; 37620 } 37621 }); 37622 37623 if (focusCtrl) { 37624 focusCtrl.focus(); 37625 } 37626 37627 return self; 37628 }, 37629 37630 /** 37631 * Replaces the specified child control with a new control. 37632 * 37633 * @method replace 37634 * @param {tinymce.ui.Control} oldItem Old item to be replaced. 37635 * @param {tinymce.ui.Control} newItem New item to be inserted. 37636 */ 37637 replace: function(oldItem, newItem) { 37638 var ctrlElm, items = this.items(), i = items.length; 37639 37640 // Replace the item in collection 37641 while (i--) { 37642 if (items[i] === oldItem) { 37643 items[i] = newItem; 37644 break; 37645 } 37646 } 37647 37648 if (i >= 0) { 37649 // Remove new item from DOM 37650 ctrlElm = newItem.getEl(); 37651 if (ctrlElm) { 37652 ctrlElm.parentNode.removeChild(ctrlElm); 37653 } 37654 37655 // Remove old item from DOM 37656 ctrlElm = oldItem.getEl(); 37657 if (ctrlElm) { 37658 ctrlElm.parentNode.removeChild(ctrlElm); 37659 } 37660 } 37661 37662 // Adopt the item 37663 newItem.parent(this); 37664 }, 37665 37666 /** 37667 * Creates the specified items. If any of the items is plain JSON style objects 37668 * it will convert these into real tinymce.ui.Control instances. 37669 * 37670 * @method create 37671 * @param {Array} items Array of items to convert into control instances. 37672 * @return {Array} Array with control instances. 37673 */ 37674 create: function(items) { 37675 var self = this, settings, ctrlItems = []; 37676 37677 // Non array structure, then force it into an array 37678 if (!Tools.isArray(items)) { 37679 items = [items]; 37680 } 37681 37682 // Add default type to each child control 37683 Tools.each(items, function(item) { 37684 if (item) { 37685 // Construct item if needed 37686 if (!(item instanceof Control)) { 37687 // Name only then convert it to an object 37688 if (typeof item == "string") { 37689 item = {type: item}; 37690 } 37691 37692 // Create control instance based on input settings and default settings 37693 settings = Tools.extend({}, self.settings.defaults, item); 37694 item.type = settings.type = settings.type || item.type || self.settings.defaultType || 37695 (settings.defaults ? settings.defaults.type : null); 37696 item = Factory.create(settings); 37697 } 37698 37699 ctrlItems.push(item); 37700 } 37701 }); 37702 37703 return ctrlItems; 37704 }, 37705 37706 /** 37707 * Renders new control instances. 37708 * 37709 * @private 37710 */ 37711 renderNew: function() { 37712 var self = this; 37713 37714 // Render any new items 37715 self.items().each(function(ctrl, index) { 37716 var containerElm; 37717 37718 ctrl.parent(self); 37719 37720 if (!ctrl.state.get('rendered')) { 37721 containerElm = self.getEl('body'); 37722 37723 // Insert or append the item 37724 if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) { 37725 $(containerElm.childNodes[index]).before(ctrl.renderHtml()); 37726 } else { 37727 $(containerElm).append(ctrl.renderHtml()); 37728 } 37729 37730 ctrl.postRender(); 37731 ReflowQueue.add(ctrl); 37732 } 37733 }); 37734 37735 self._layout.applyClasses(self.items().filter(':visible')); 37736 self._lastRect = null; 37737 37738 return self; 37739 }, 37740 37741 /** 37742 * Appends new instances to the current container. 37743 * 37744 * @method append 37745 * @param {Array/tinymce.ui.Collection} items Array if controls to append. 37746 * @return {tinymce.ui.Container} Current container instance. 37747 */ 37748 append: function(items) { 37749 return this.add(items).renderNew(); 37750 }, 37751 37752 /** 37753 * Prepends new instances to the current container. 37754 * 37755 * @method prepend 37756 * @param {Array/tinymce.ui.Collection} items Array if controls to prepend. 37757 * @return {tinymce.ui.Container} Current container instance. 37758 */ 37759 prepend: function(items) { 37760 var self = this; 37761 37762 self.items().set(self.create(items).concat(self.items().toArray())); 37763 37764 return self.renderNew(); 37765 }, 37766 37767 /** 37768 * Inserts an control at a specific index. 37769 * 37770 * @method insert 37771 * @param {Array/tinymce.ui.Collection} items Array if controls to insert. 37772 * @param {Number} index Index to insert controls at. 37773 * @param {Boolean} [before=false] Inserts controls before the index. 37774 */ 37775 insert: function(items, index, before) { 37776 var self = this, curItems, beforeItems, afterItems; 37777 37778 items = self.create(items); 37779 curItems = self.items(); 37780 37781 if (!before && index < curItems.length - 1) { 37782 index += 1; 37783 } 37784 37785 if (index >= 0 && index < curItems.length) { 37786 beforeItems = curItems.slice(0, index).toArray(); 37787 afterItems = curItems.slice(index).toArray(); 37788 curItems.set(beforeItems.concat(items, afterItems)); 37789 } 37790 37791 return self.renderNew(); 37792 }, 37793 37794 /** 37795 * Populates the form fields from the specified JSON data object. 37796 * 37797 * Control items in the form that matches the data will have it's value set. 37798 * 37799 * @method fromJSON 37800 * @param {Object} data JSON data object to set control values by. 37801 * @return {tinymce.ui.Container} Current form instance. 37802 */ 37803 fromJSON: function(data) { 37804 var self = this; 37805 37806 for (var name in data) { 37807 self.find('#' + name).value(data[name]); 37808 } 37809 37810 return self; 37811 }, 37812 37813 /** 37814 * Serializes the form into a JSON object by getting all items 37815 * that has a name and a value. 37816 * 37817 * @method toJSON 37818 * @return {Object} JSON object with form data. 37819 */ 37820 toJSON: function() { 37821 var self = this, data = {}; 37822 37823 self.find('*').each(function(ctrl) { 37824 var name = ctrl.name(), value = ctrl.value(); 37825 37826 if (name && typeof value != "undefined") { 37827 data[name] = value; 37828 } 37829 }); 37830 37831 return data; 37832 }, 37833 37834 /** 37835 * Renders the control as a HTML string. 37836 * 37837 * @method renderHtml 37838 * @return {String} HTML representing the control. 37839 */ 37840 renderHtml: function() { 37841 var self = this, layout = self._layout, role = this.settings.role; 37842 37843 self.preRender(); 37844 layout.preRender(self); 37845 37846 return ( 37847 '<div id="' + self._id + '" class="' + self.classes + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>
37847' + 37848 '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + 37849 (self.settings.html || '') + layout.renderHtml(self) + 37850 '</div>' + 37851 '</div>' 37852 ); 37853 }, 37854 37855 /** 37856 * Post render method. Called after the control has been rendered to the target. 37857 * 37858 * @method postRender 37859 * @return {tinymce.ui.Container} Current combobox instance. 37860 */ 37861 postRender: function() { 37862 var self = this, box; 37863 37864 self.items().exec('postRender'); 37865 self._super(); 37866 37867 self._layout.postRender(self); 37868 self.state.set('rendered', true); 37869 37870 if (self.settings.style) { 37871 self.$el.css(self.settings.style); 37872 } 37873 37874 if (self.settings.border) { 37875 box = self.borderBox; 37876 self.$el.css({ 37877 'border-top-width': box.top, 37878 'border-right-width': box.right, 37879 'border-bottom-width': box.bottom, 37880 'border-left-width': box.left 37881 }); 37882 } 37883 37884 if (!self.parent()) { 37885 self.keyboardNav = new KeyboardNavigation({ 37886 root: self 37887 }); 37888 } 37889 37890 return self; 37891 }, 37892 37893 /** 37894 * Initializes the current controls layout rect. 37895 * This will be executed by the layout managers to determine the 37896 * default minWidth/minHeight etc. 37897 * 37898 * @method initLayoutRect 37899 * @return {Object} Layout rect instance. 37900 */ 37901 initLayoutRect: function() { 37902 var self = this, layoutRect = self._super(); 37903 37904 // Recalc container size by asking layout manager 37905 self._layout.recalc(self); 37906 37907 return layoutRect; 37908 }, 37909 37910 /** 37911 * Recalculates the positions of the controls in the current container. 37912 * This is invoked by the reflow method and shouldn't be called directly. 37913 * 37914 * @method recalc 37915 */ 37916 recalc: function() { 37917 var self = this, rect = self._layoutRect, lastRect = self._lastRect; 37918 37919 if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) { 37920 self._layout.recalc(self); 37921 rect = self.layoutRect(); 37922 self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h}; 37923 return true; 37924 } 37925 }, 37926 37927 /** 37928 * Reflows the current container and it's children and possible parents. 37929 * This should be used after you for example append children to the current control so 37930 * that the layout managers know that they need to reposition everything. 37931 * 37932 * @example 37933 * container.append({type: 'button', text: 'My button'}).reflow(); 37934 * 37935 * @method reflow 37936 * @return {tinymce.ui.Container} Current container instance. 37937 */ 37938 reflow: function() { 37939 var i; 37940 37941 ReflowQueue.remove(this); 37942 37943 if (this.visible()) { 37944 Control.repaintControls = []; 37945 Control.repaintControls.map = {}; 37946 37947 this.recalc(); 37948 i = Control.repaintControls.length; 37949 37950 while (i--) { 37951 Control.repaintControls[i].repaint(); 37952 } 37953 37954 // TODO: Fix me! 37955 if (this.settings.layout !== "flow" && this.settings.layout !== "stack") { 37956 this.repaint(); 37957 } 37958 37959 Control.repaintControls = []; 37960 } 37961 37962 return this; 37963 } 37964 }); 37965}); 37966 37967// Included from: js/tinymce/classes/ui/DragHelper.js 37968 37969/** 37970 * DragHelper.js 37971 * 37972 * Released under LGPL License. 37973 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 37974 * 37975 * License: http://www.tinymce.com/license 37976 * Contributing: http://www.tinymce.com/contributing 37977 */ 37978 37979/** 37980 * Drag/drop helper class. 37981 * 37982 * @example 37983 * var dragHelper = new tinymce.ui.DragHelper('mydiv', { 37984 * start: function(evt) { 37985 * }, 37986 * 37987 * drag: function(evt) { 37988 * }, 37989 * 37990 * end: function(evt) { 37991 * } 37992 * }); 37993 * 37994 * @class tinymce.ui.DragHelper 37995 */ 37996define("tinymce/ui/DragHelper", [ 37997 "tinymce/dom/DomQuery" 37998], function($) { 37999 "use strict"; 38000 38001 function getDocumentSize(doc) { 38002 var documentElement, body, scrollWidth, clientWidth; 38003 var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max; 38004 38005 documentElement = doc.documentElement; 38006 body = doc.body; 38007 38008 scrollWidth = max(documentElement.scrollWidth, body.scrollWidth); 38009 clientWidth = max(documentElement.clientWidth, body.clientWidth); 38010 offsetWidth = max(documentElement.offsetWidth, body.offsetWidth); 38011 38012 scrollHeight = max(documentElement.scrollHeight, body.scrollHeight); 38013 clientHeight = max(documentElement.clientHeight, body.clientHeight); 38014 offsetHeight = max(documentElement.offsetHeight, body.offsetHeight); 38015 38016 return { 38017 width: scrollWidth < offsetWidth ? clientWidth : scrollWidth, 38018 height: scrollHeight < offsetHeight ? clientHeight : scrollHeight 38019 }; 38020 } 38021 38022 function updateWithTouchData(e) { 38023 var keys, i; 38024 38025 if (e.changedTouches) { 38026 keys = "screenX screenY pageX pageY clientX clientY".split(' '); 38027 for (i = 0; i < keys.length; i++) { 38028 e[keys[i]] = e.changedTouches[0][keys[i]]; 38029 } 38030 } 38031 } 38032 38033 return function(id, settings) { 38034 var $eventOverlay, doc = settings.document || document, downButton, start, stop, drag, startX, startY; 38035 38036 settings = settings || {}; 38037 38038 function getHandleElm() { 38039 return doc.getElementById(settings.handle || id); 38040 } 38041 38042 start = function(e) { 38043 var docSize = getDocumentSize(doc), handleElm, cursor; 38044 38045 updateWithTouchData(e); 38046 38047 e.preventDefault(); 38048 downButton = e.button; 38049 handleElm = getHandleElm(); 38050 startX = e.screenX; 38051 startY = e.screenY; 38052 38053 // Grab cursor from handle so we can place it on overlay 38054 if (window.getComputedStyle) { 38055 cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor"); 38056 } else { 38057 cursor = handleElm.runtimeStyle.cursor; 38058 } 38059 38060 $eventOverlay = $('<div>').css({ 38061 position: "absolute", 38062 top: 0, left: 0,
38063 width: docSize.width, 38064 height: docSize.height, 38065 zIndex: 0x7FFFFFFF, 38066 opacity: 0.0001, 38067 cursor: cursor 38068 }).appendTo(doc.body); 38069 38070 $(doc).on('mousemove touchmove', drag).on('mouseup touchend', stop); 38071 38072 settings.start(e); 38073 }; 38074 38075 drag = function(e) { 38076 updateWithTouchData(e); 38077 38078 if (e.button !== downButton) { 38079 return stop(e); 38080 } 38081 38082 e.deltaX = e.screenX - startX; 38083 e.deltaY = e.screenY - startY; 38084 38085 e.preventDefault(); 38086 settings.drag(e); 38087 }; 38088 38089 stop = function(e) { 38090 updateWithTouchData(e); 38091 38092 $(doc).off('mousemove touchmove', drag).off('mouseup touchend', stop); 38093 38094 $eventOverlay.remove(); 38095 38096 if (settings.stop) { 38097 settings.stop(e); 38098 } 38099 }; 38100 38101 /** 38102 * Destroys the drag/drop helper instance. 38103 * 38104 * @method destroy 38105 */ 38106 this.destroy = function() { 38107 $(getHandleElm()).off(); 38108 }; 38109 38110 $(getHandleElm()).on('mousedown touchstart', start); 38111 }; 38112}); 38113 38114// Included from: js/tinymce/classes/ui/Scrollable.js 38115 38116/** 38117 * Scrollable.js 38118 * 38119 * Released under LGPL License. 38120 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 38121 * 38122 * License: http://www.tinymce.com/license 38123 * Contributing: http://www.tinymce.com/contributing 38124 */ 38125 38126/** 38127 * This mixin makes controls scrollable using custom scrollbars. 38128 * 38129 * @-x-less Scrollable.less 38130 * @mixin tinymce.ui.Scrollable 38131 */ 38132define("tinymce/ui/Scrollable", [ 38133 "tinymce/dom/DomQuery", 38134 "tinymce/ui/DragHelper" 38135], function($, DragHelper) { 38136 "use strict"; 38137 38138 return { 38139 init: function() { 38140 var self = this; 38141 self.on('repaint', self.renderScroll); 38142 }, 38143 38144 renderScroll: function() { 38145 var self = this, margin = 2; 38146 38147 function repaintScroll() { 38148 var hasScrollH, hasScrollV, bodyElm; 38149 38150 function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) { 38151 var containerElm, scrollBarElm, scrollThumbElm; 38152 var containerSize, scrollSize, ratio, rect; 38153 var posNameLower, sizeNameLower; 38154 38155 scrollBarElm = self.getEl('scroll' + axisName); 38156 if (scrollBarElm) { 38157 posNameLower = posName.toLowerCase(); 38158 sizeNameLower = sizeName.toLowerCase(); 38159 38160 $(self.getEl('absend')).css(posNameLower, self.layoutRect()[contentSizeName] - 1); 38161 38162 if (!hasScroll) { 38163 $(scrollBarElm).css('display', 'none'); 38164 return; 38165 } 38166 38167 $(scrollBarElm).css('display', 'block'); 38168 containerElm = self.getEl('body'); 38169 scrollThumbElm = self.getEl('scroll' + axisName + "t"); 38170 containerSize = containerElm["client" + sizeName] - (margin * 2); 38171 containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0; 38172 scrollSize = containerElm["scroll" + sizeName]; 38173 ratio = containerSize / scrollSize; 38174 38175 rect = {}; 38176 rect[posNameLower] = containerElm["offset" + posName] + margin; 38177 rect[sizeNameLower] = containerSize; 38178 $(scrollBarElm).css(rect); 38179 38180 rect = {}; 38181 rect[posNameLower] = containerElm["scroll" + posName] * ratio; 38182 rect[sizeNameLower] = containerSize * ratio; 38183 $(scrollThumbElm).css(rect); 38184 } 38185 } 38186 38187 bodyElm = self.getEl('body'); 38188 hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth; 38189 hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight; 38190 38191 repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height"); 38192 repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width"); 38193 } 38194 38195 function addScroll() { 38196 function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) { 38197 var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix; 38198 38199 $(self.getEl()).append( 38200 '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' + 38201 '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' + 38202 '</div>' 38203 ); 38204 38205 self.draghelper = new DragHelper(axisId + 't', { 38206 start: function() { 38207 scrollStart = self.getEl('body')["scroll" + posName]; 38208 $('#' + axisId).addClass(prefix + 'active'); 38209 }, 38210 38211 drag: function(e) { 38212 var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect(); 38213 38214 hasScrollH = layoutRect.contentW > layoutRect.innerW; 38215 hasScrollV = layoutRect.contentH > layoutRect.innerH; 38216 containerSize = self.getEl('body')["client" + sizeName] - (margin * 2); 38217 containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0; 38218 38219 ratio = containerSize / self.getEl('body')["scroll" + sizeName]; 38220 self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio); 38221 }, 38222 38223 stop: function() { 38224 $('#' + axisId).removeClass(prefix + 'active'); 38225 } 38226 }); 38227 } 38228 38229 self.classes.add('scroll'); 38230 38231 addScrollAxis("v", "Top", "Height", "Y", "Width"); 38232 addScrollAxis("h", "Left", "Width", "X", "Height"); 38233 } 38234 38235 if (self.settings.autoScroll) { 38236 if (!self._hasScroll) { 38237 self._hasScroll = true; 38238 addScroll(); 38239 38240 self.on('wheel', function(e) { 38241 var bodyEl = self.getEl('body'); 38242 38243 bodyEl.scrollLeft += (e.deltaX || 0) * 10; 38244 bodyEl.scrollTop += e.deltaY * 10; 38245 38246 repaintScroll(); 38247 }); 38248 38249 $(self.getEl('body')).on("scroll", repaintScroll); 38250 } 38251 38252 repaintScroll(); 38253 } 38254 } 38255 }; 38256}); 38257 38258// Included from: js/tinymce/classes/ui/Panel.js 38259 38260/**
38261 * Panel.js 38262 * 38263 * Released under LGPL License. 38264 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 38265 * 38266 * License: http://www.tinymce.com/license 38267 * Contributing: http://www.tinymce.com/contributing 38268 */ 38269 38270/** 38271 * Creates a new panel. 38272 * 38273 * @-x-less Panel.less 38274 * @class tinymce.ui.Panel 38275 * @extends tinymce.ui.Container 38276 * @mixes tinymce.ui.Scrollable 38277 */ 38278define("tinymce/ui/Panel", [ 38279 "tinymce/ui/Container", 38280 "tinymce/ui/Scrollable" 38281], function(Container, Scrollable) { 38282 "use strict"; 38283 38284 return Container.extend({ 38285 Defaults: { 38286 layout: 'fit', 38287 containerCls: 'panel' 38288 }, 38289 38290 Mixins: [Scrollable], 38291 38292 /** 38293 * Renders the control as a HTML string. 38294 * 38295 * @method renderHtml 38296 * @return {String} HTML representing the control. 38297 */ 38298 renderHtml: function() { 38299 var self = this, layout = self._layout, innerHtml = self.settings.html; 38300 38301 self.preRender(); 38302 layout.preRender(self); 38303 38304 if (typeof innerHtml == "undefined") { 38305 innerHtml = ( 38306 '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + 38307 layout.renderHtml(self) + 38308 '</div>' 38309 ); 38310 } else { 38311 if (typeof innerHtml == 'function') { 38312 innerHtml = innerHtml.call(self); 38313 } 38314 38315 self._hasBody = false; 38316 } 38317 38318 return ( 38319 '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1" role="group">' + 38320 (self._preBodyHtml || '') + 38321 innerHtml + 38322 '</div>' 38323 ); 38324 } 38325 }); 38326}); 38327 38328// Included from: js/tinymce/classes/ui/Movable.js 38329 38330/** 38331 * Movable.js 38332 * 38333 * Released under LGPL License. 38334 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 38335 * 38336 * License: http://www.tinymce.com/license 38337 * Contributing: http://www.tinymce.com/contributing 38338 */ 38339 38340/** 38341 * Movable mixin. Makes controls movable absolute and relative to other elements. 38342 * 38343 * @mixin tinymce.ui.Movable 38344 */ 38345define("tinymce/ui/Movable", [ 38346 "tinymce/ui/DomUtils" 38347], function(DomUtils) { 38348 "use strict"; 38349 38350 function calculateRelativePosition(ctrl, targetElm, rel) { 38351 var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size; 38352 38353 viewport = DomUtils.getViewPort(); 38354 38355 // Get pos of target 38356 pos = DomUtils.getPos(targetElm); 38357 x = pos.x; 38358 y = pos.y; 38359 38360 if (ctrl.state.get('fixed') && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') { 38361 x -= viewport.x; 38362 y -= viewport.y; 38363 } 38364 38365 // Get size of self 38366 ctrlElm = ctrl.getEl(); 38367 size = DomUtils.getSize(ctrlElm); 38368 selfW = size.width; 38369 selfH = size.height; 38370 38371 // Get size of target 38372 size = DomUtils.getSize(targetElm); 38373 targetW = size.width; 38374 targetH = size.height; 38375 38376 // Parse align string 38377 rel = (rel || '').split(''); 38378 38379 // Target corners 38380 if (rel[0] === 'b') { 38381 y += targetH; 38382 } 38383 38384 if (rel[1] === 'r') { 38385 x += targetW; 38386 } 38387 38388 if (rel[0] === 'c') { 38389 y += Math.round(targetH / 2); 38390 } 38391 38392 if (rel[1] === 'c') { 38393 x += Math.round(targetW / 2); 38394 } 38395 38396 // Self corners 38397 if (rel[3] === 'b') { 38398 y -= selfH; 38399 } 38400 38401 if (rel[4] === 'r') { 38402 x -= selfW; 38403 } 38404 38405 if (rel[3] === 'c') { 38406 y -= Math.round(selfH / 2); 38407 } 38408 38409 if (rel[4] === 'c') { 38410 x -= Math.round(selfW / 2); 38411 } 38412 38413 return { 38414 x: x, 38415 y: y, 38416 w: selfW, 38417 h: selfH 38418 }; 38419 } 38420 38421 return { 38422 /** 38423 * Tests various positions to get the most suitable one. 38424 * 38425 * @method testMoveRel 38426 * @param {DOMElement} elm Element to position against. 38427 * @param {Array} rels Array with relative positions. 38428 * @return {String} Best suitable relative position. 38429 */ 38430 testMoveRel: function(elm, rels) { 38431 var viewPortRect = DomUtils.getViewPort(); 38432 38433 for (var i = 0; i < rels.length; i++) { 38434 var pos = calculateRelativePosition(this, elm, rels[i]); 38435 38436 if (this.state.get('fixed')) { 38437 if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) { 38438 return rels[i]; 38439 } 38440 } else { 38441 if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x && 38442 pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) { 38443 return rels[i]; 38444 } 38445 } 38446 } 38447 38448 return rels[0]; 38449 }, 38450 38451 /** 38452 * Move relative to the specified element. 38453 * 38454 * @method moveRel 38455 * @param {Element} elm Element to move relative to. 38456 * @param {String} rel Relative mode. For example: br-tl. 38457 * @return {tinymce.ui.Control} Current control instance. 38458 */ 38459 moveRel: function(elm, rel) { 38460 if (typeof rel != 'string') { 38461 rel = this.testMoveRel(elm, rel); 38462 } 38463 38464 var pos = calculateRelativePosition(this, elm, rel); 38465 return this.moveTo(pos.x, pos.y); 38466 }, 38467 38468 /** 38469 * Move by a relative x, y values. 38470 * 38471 * @method moveBy 38472 * @param {Number} dx Relative x position. 38473 * @param {Number} dy Relative y position. 38474 * @return {tinymce.ui.Control} Current control instance. 38475 */ 38476 moveBy: function(dx, dy) { 38477 var self = this, rect = self.layoutRect(); 38478 38479 self.moveTo(rect.x + dx, rect.y + dy); 38480 38481 return self; 38482 }, 38483 38484 /** 38485 * Move to absolute position. 38486 * 38487 * @method moveTo 38488 * @param {Number} x Absolute x position. 38489 * @param {Number} y Absolute y position. 38490 * @return {tinymce.ui.Control} Current control instance. 38491 */ 38492 moveTo: function(x, y) { 38493 var self = this; 38494 38495 // TODO: Move this to some global class 38496 function constrain(value, max, size) { 38497 if (value < 0) { 38498 return 0; 38499 } 38500 38501 if (value + size > max) { 38502 value = max - size; 38503 return value < 0 ? 0 : value; 38504 } 38505 38506 return value; 38507 } 38508 38509 if (self.settings.constrainToViewport) { 38510 var viewPortRect = DomUtils.getViewPort(window); 38511 var layoutRect = self.layoutRect(); 38512 38513 x = constrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w); 38514 y = constrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h); 38515 } 38516 38517 if (self.state.get('rendered')) { 38518 self.layoutRect({x: x, y: y}).repaint(); 38519 } else { 38520 self.settings.x = x; 38521 self.settings.y = y; 38522 } 38523 38524 self.fire('move', {x: x, y: y}); 38525 38526 return self; 38527 } 38528 }; 38529}); 38530 38531// Included from: js/tinymce/classes/ui/Resizable.js 38532 38533/** 38534 * Resizable.js 38535 * 38536 * Released under LGPL License. 38537 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 38538 * 38539 * License: http://www.tinymce.com/license 38540 * Contributing: http://www.tinymce.com/contributing 38541 */ 38542 38543/** 38544 * Resizable mixin. Enables controls to be resized. 38545 * 38546 * @mixin tinymce.ui.Resizable 38547 */
38548define("tinymce/ui/Resizable", [ 38549 "tinymce/ui/DomUtils" 38550], function(DomUtils) { 38551 "use strict"; 38552 38553 return { 38554 /** 38555 * Resizes the control to contents. 38556 * 38557 * @method resizeToContent 38558 */ 38559 resizeToContent: function() { 38560 this._layoutRect.autoResize = true; 38561 this._lastRect = null; 38562 this.reflow(); 38563 }, 38564 38565 /** 38566 * Resizes the control to a specific width/height. 38567 * 38568 * @method resizeTo 38569 * @param {Number} w Control width. 38570 * @param {Number} h Control height. 38571 * @return {tinymce.ui.Control} Current control instance. 38572 */ 38573 resizeTo: function(w, h) { 38574 // TODO: Fix hack 38575 if (w <= 1 || h <= 1) { 38576 var rect = DomUtils.getWindowSize(); 38577 38578 w = w <= 1 ? w * rect.w : w; 38579 h = h <= 1 ? h * rect.h : h; 38580 } 38581 38582 this._layoutRect.autoResize = false; 38583 return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow(); 38584 }, 38585 38586 /** 38587 * Resizes the control to a specific relative width/height. 38588 * 38589 * @method resizeBy 38590 * @param {Number} dw Relative control width. 38591 * @param {Number} dh Relative control height. 38592 * @return {tinymce.ui.Control} Current control instance. 38593 */ 38594 resizeBy: function(dw, dh) { 38595 var self = this, rect = self.layoutRect(); 38596 38597 return self.resizeTo(rect.w + dw, rect.h + dh); 38598 } 38599 }; 38600}); 38601 38602// Included from: js/tinymce/classes/ui/FloatPanel.js 38603 38604/** 38605 * FloatPanel.js 38606 * 38607 * Released under LGPL License. 38608 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 38609 * 38610 * License: http://www.tinymce.com/license 38611 * Contributing: http://www.tinymce.com/contributing 38612 */ 38613 38614/** 38615 * This class creates a floating panel. 38616 * 38617 * @-x-less FloatPanel.less 38618 * @class tinymce.ui.FloatPanel 38619 * @extends tinymce.ui.Panel 38620 * @mixes tinymce.ui.Movable 38621 * @mixes tinymce.ui.Resizable 38622 */ 38623define("tinymce/ui/FloatPanel", [ 38624 "tinymce/ui/Panel", 38625 "tinymce/ui/Movable", 38626 "tinymce/ui/Resizable", 38627 "tinymce/ui/DomUtils", 38628 "tinymce/dom/DomQuery", 38629 "tinymce/util/Delay" 38630], function(Panel, Movable, Resizable, DomUtils, $, Delay) { 38631 "use strict"; 38632 38633 var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = []; 38634 var zOrder = [], hasModal; 38635 38636 function isChildOf(ctrl, parent) { 38637 while (ctrl) { 38638 if (ctrl == parent) { 38639 return true; 38640 } 38641 38642 ctrl = ctrl.parent(); 38643 } 38644 } 38645 38646 function skipOrHidePanels(e) { 38647 // Hide any float panel when a click/focus out is out side that float panel and the 38648 // float panels direct parent for example a click on a menu button 38649 var i = visiblePanels.length; 38650 38651 while (i--) { 38652 var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target); 38653 38654 if (panel.settings.autohide) { 38655 if (clickCtrl) { 38656 if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) { 38657 continue; 38658 } 38659 } 38660 38661 e = panel.fire('autohide', {target: e.target}); 38662 if (!e.isDefaultPrevented()) { 38663 panel.hide(); 38664 } 38665 } 38666 } 38667 } 38668 38669 function bindDocumentClickHandler() { 38670 38671 if (!documentClickHandler) { 38672 documentClickHandler = function(e) { 38673 // Gecko fires click event and in the wrong order on Mac so lets normalize 38674 if (e.button == 2) { 38675 return; 38676 } 38677 38678 skipOrHidePanels(e); 38679 }; 38680 38681 $(document).on('click touchstart', documentClickHandler); 38682 } 38683 } 38684 38685 function bindDocumentScrollHandler() { 38686 if (!documentScrollHandler) { 38687 documentScrollHandler = function() { 38688 var i; 38689 38690 i = visiblePanels.length; 38691 while (i--) { 38692 repositionPanel(visiblePanels[i]); 38693 } 38694 }; 38695 38696 $(window).on('scroll', documentScrollHandler); 38697 } 38698 } 38699 38700 function bindWindowResizeHandler() { 38701 if (!windowResizeHandler) { 38702 var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight; 38703 38704 windowResizeHandler = function() { 38705 // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized 38706 if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) { 38707 clientWidth = docElm.clientWidth; 38708 clientHeight = docElm.clientHeight; 38709 FloatPanel.hideAll(); 38710 } 38711 }; 38712 38713 $(window).on('resize', windowResizeHandler); 38714 } 38715 } 38716 38717 /** 38718 * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will 38719 * also reposition all child panels of the current panel. 38720 */ 38721 function repositionPanel(panel) { 38722 var scrollY = DomUtils.getViewPort().y; 38723 38724 function toggleFixedChildPanels(fixed, deltaY) { 38725 var parent; 38726 38727 for (var i = 0; i < visiblePanels.length; i++) { 38728 if (visiblePanels[i] != panel) { 38729 parent = visiblePanels[i].parent(); 38730 38731 while (parent && (parent = parent.parent())) { 38732 if (parent == panel) {
38733 visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint(); 38734 } 38735 } 38736 } 38737 } 38738 } 38739 38740 if (panel.settings.autofix) { 38741 if (!panel.state.get('fixed')) { 38742 panel._autoFixY = panel.layoutRect().y; 38743 38744 if (panel._autoFixY < scrollY) { 38745 panel.fixed(true).layoutRect({y: 0}).repaint(); 38746 toggleFixedChildPanels(true, scrollY - panel._autoFixY); 38747 } 38748 } else { 38749 if (panel._autoFixY > scrollY) { 38750 panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint(); 38751 toggleFixedChildPanels(false, panel._autoFixY - scrollY); 38752 } 38753 } 38754 } 38755 } 38756 38757 function addRemove(add, ctrl) { 38758 var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal; 38759 38760 if (add) { 38761 zOrder.push(ctrl); 38762 } else { 38763 i = zOrder.length; 38764 38765 while (i--) { 38766 if (zOrder[i] === ctrl) { 38767 zOrder.splice(i, 1); 38768 } 38769 } 38770 } 38771 38772 if (zOrder.length) { 38773 for (i = 0; i < zOrder.length; i++) { 38774 if (zOrder[i].modal) { 38775 zIndex++; 38776 topModal = zOrder[i]; 38777 } 38778 38779 zOrder[i].getEl().style.zIndex = zIndex; 38780 zOrder[i].zIndex = zIndex; 38781 zIndex++; 38782 } 38783 } 38784 38785 var modalBlockEl = document.getElementById(ctrl.classPrefix + 'modal-block'); 38786 38787 if (topModal) { 38788 $(modalBlockEl).css('z-index', topModal.zIndex - 1); 38789 } else if (modalBlockEl) { 38790 modalBlockEl.parentNode.removeChild(modalBlockEl); 38791 hasModal = false; 38792 } 38793 38794 FloatPanel.currentZIndex = zIndex; 38795 } 38796 38797 var FloatPanel = Panel.extend({ 38798 Mixins: [Movable, Resizable], 38799 38800 /** 38801 * Constructs a new control instance with the specified settings. 38802 * 38803 * @constructor 38804 * @param {Object} settings Name/value object with settings. 38805 * @setting {Boolean} autohide Automatically hide the panel. 38806 */ 38807 init: function(settings) { 38808 var self = this; 38809 38810 self._super(settings); 38811 self._eventsRoot = self; 38812 38813 self.classes.add('floatpanel'); 38814 38815 // Hide floatpanes on click out side the root button 38816 if (settings.autohide) { 38817 bindDocumentClickHandler(); 38818 bindWindowResizeHandler(); 38819 visiblePanels.push(self); 38820 } 38821 38822 if (settings.autofix) { 38823 bindDocumentScrollHandler(); 38824 38825 self.on('move', function() { 38826 repositionPanel(this); 38827 }); 38828 } 38829 38830 self.on('postrender show', function(e) { 38831 if (e.control == self) { 38832 var $modalBlockEl, prefix = self.classPrefix; 38833 38834 if (self.modal && !hasModal) { 38835 $modalBlockEl = $('#' + prefix + 'modal-block'); 38836 if (!$modalBlockEl[0]) { 38837 $modalBlockEl = $( 38838 '<div id="' + prefix + 'modal-block" class="' + prefix + 'reset ' + prefix + 'fade"></div>' 38839 ).appendTo(self.getContainerElm()); 38840 } 38841 38842 Delay.setTimeout(function() { 38843 $modalBlockEl.addClass(prefix + 'in'); 38844 $(self.getEl()).addClass(prefix + 'in'); 38845 }); 38846 38847 hasModal = true; 38848 } 38849 38850 addRemove(true, self); 38851 } 38852 }); 38853 38854 self.on('show', function() { 38855 self.parents().each(function(ctrl) { 38856 if (ctrl.state.get('fixed')) { 38857 self.fixed(true); 38858 return false; 38859 } 38860 }); 38861 }); 38862 38863 if (settings.popover) { 38864 self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>'; 38865 self.classes.add('popover').add('bottom').add(self.isRtl() ? 'end' : 'start'); 38866 } 38867 }, 38868 38869 fixed: function(state) { 38870 var self = this; 38871 38872 if (self.state.get('fixed') != state) { 38873 if (self.state.get('rendered')) { 38874 var viewport = DomUtils.getViewPort(); 38875 38876 if (state) { 38877 self.layoutRect().y -= viewport.y; 38878 } else { 38879 self.layoutRect().y += viewport.y; 38880 } 38881 } 38882 38883 self.classes.toggle('fixed', state); 38884 self.state.set('fixed', state); 38885 } 38886 38887 return self; 38888 }, 38889 38890 /** 38891 * Shows the current float panel. 38892 * 38893 * @method show 38894 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 38895 */ 38896 show: function() { 38897 var self = this, i, state = self._super(); 38898 38899 i = visiblePanels.length; 38900 while (i--) { 38901 if (visiblePanels[i] === self) { 38902 break; 38903 } 38904 } 38905 38906 if (i === -1) { 38907 visiblePanels.push(self); 38908 } 38909 38910 return state; 38911 }, 38912 38913 /** 38914 * Hides the current float panel. 38915 * 38916 * @method hide 38917 * @return {tinymce.ui.FloatPanel} Current floatpanel instance. 38918 */ 38919 hide: function() { 38920 removeVisiblePanel(this); 38921 addRemove(false, this); 38922 38923 return this._super(); 38924 }, 38925 38926 /** 38927 * Hide all visible float panels with he autohide setting enabled. This is for 38928 * manually hiding floating menus or panels. 38929 * 38930 * @method hideAll 38931 */ 38932 hideAll: function() { 38933 FloatPanel.hideAll(); 38934 }, 38935 38936 /** 38937 * Closes the float panel. This will remove the float panel from page and fire the close event. 38938 * 38939 * @method close 38940 */ 38941 close: function() { 38942 var self = this; 38943 38944 if (!self.fire('close').isDefaultPrevented()) { 38945 self.remove(); 38946 addRemove(false, self); 38947 } 38948 38949 return self; 38950 }, 38951 38952 /** 38953 * Removes the float panel from page. 38954 * 38955 * @method remove 38956 */ 38957 remove: function() { 38958 removeVisiblePanel(this); 38959 this._super(); 38960 }, 38961 38962 postRender: function() { 38963 var self = this; 38964 38965 if (self.settings.bodyRole) { 38966 this.getEl('body').setAttribute('role', self.settings.bodyRole); 38967 } 38968 38969 return self._super(); 38970 } 38971 }); 38972 38973 /** 38974 * Hide all visible float panels with he autohide setting enabled. This is for 38975 * manually hiding floating menus or panels. 38976 * 38977 * @static 38978 * @method hideAll 38979 */ 38980 FloatPanel.hideAll = function() { 38981 var i = visiblePanels.length; 38982 38983 while (i--) { 38984 var panel = visiblePanels[i]; 38985 38986 if (panel && panel.settings.autohide) { 38987 panel.hide(); 38988 visiblePanels.splice(i, 1); 38989 } 38990 } 38991 }; 38992 38993 function removeVisiblePanel(panel) { 38994 var i; 38995 38996 i = visiblePanels.length; 38997 while (i--) { 38998 if (visiblePanels[i] === panel) { 38999 visiblePanels.splice(i, 1); 39000 } 39001 } 39002 39003 i = zOrder.length; 39004 while (i--) { 39005 if (zOrder[i] === panel) { 39006 zOrder.splice(i, 1); 39007 } 39008 } 39009 } 39010 39011 return FloatPanel; 39012}); 39013 39014// Included from: js/tinymce/classes/ui/Window.js 39015 39016/** 39017 * Window.js 39018 * 39019 * Released under LGPL License. 39020 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 39021 * 39022 * License: http://www.tinymce.com/license 39023 * Contributing: http://www.tinymce.com/contributing 39024 */ 39025 39026/** 39027 * Creates a new window. 39028 * 39029 * @-x-less Window.less 39030 * @class tinymce.ui.Window 39031 * @extends tinymce.ui.FloatPanel 39032 */
39033define("tinymce/ui/Window", [ 39034 "tinymce/ui/FloatPanel", 39035 "tinymce/ui/Panel", 39036 "tinymce/ui/DomUtils", 39037 "tinymce/dom/DomQuery", 39038 "tinymce/ui/DragHelper", 39039 "tinymce/ui/BoxUtils", 39040 "tinymce/Env", 39041 "tinymce/util/Delay" 39042], function(FloatPanel, Panel, DomUtils, $, DragHelper, BoxUtils, Env, Delay) { 39043 "use strict"; 39044 39045 var windows = [], oldMetaValue = ''; 39046 39047 function toggleFullScreenState(state) { 39048 var noScaleMetaValue = 'width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0', 39049 viewport = $("meta[name=viewport]")[0], 39050 contentValue; 39051 39052 if (Env.overrideViewPort === false) { 39053 return; 39054 } 39055 39056 if (!viewport) { 39057 viewport = document.createElement('meta'); 39058 viewport.setAttribute('name', 'viewport'); 39059 document.getElementsByTagName('head')[0].appendChild(viewport); 39060 } 39061 39062 contentValue = viewport.getAttribute('content'); 39063 if (contentValue && typeof oldMetaValue != 'undefined') { 39064 oldMetaValue = contentValue; 39065 } 39066 39067 viewport.setAttribute('content', state ? noScaleMetaValue : oldMetaValue); 39068 } 39069 39070 function toggleBodyFullScreenClasses(classPrefix) { 39071 for (var i = 0; i < windows.length; i++) { 39072 if (windows[i]._fullscreen) { 39073 return; 39074 } 39075 } 39076 39077 $([document.documentElement, document.body]).removeClass(classPrefix + 'fullscreen'); 39078 } 39079 39080 function handleWindowResize() { 39081 var lastSize = { 39082 w: window.innerWidth, 39083 h: window.innerHeight 39084 }; 39085 39086 Delay.setInterval(function() { 39087 var w = window.innerWidth, 39088 h = window.innerHeight; 39089 39090 if (lastSize.w != w || lastSize.h != h) { 39091 lastSize = { 39092 w: w, 39093 h: h 39094 }; 39095 39096 $(window).trigger('resize'); 39097 } 39098 }, 100); 39099 39100 function reposition() { 39101 var i, rect = DomUtils.getWindowSize(), layoutRect; 39102 39103 for (i = 0; i < windows.length; i++) { 39104 layoutRect = windows[i].layoutRect(); 39105 39106 windows[i].moveTo( 39107 windows[i].settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2), 39108 windows[i].settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2) 39109 ); 39110 } 39111 } 39112 39113 $(window).on('resize', reposition); 39114 } 39115 39116 var Window = FloatPanel.extend({ 39117 modal: true, 39118 39119 Defaults: { 39120 border: 1, 39121 layout: 'flex', 39122 containerCls: 'panel', 39123 role: 'dialog', 39124 callbacks: { 39125 submit: function() { 39126 this.fire('submit', {data: this.toJSON()}); 39127 }, 39128 39129 close: function() { 39130 this.close(); 39131 } 39132 } 39133 }, 39134 39135 /** 39136 * Constructs a instance with the specified settings. 39137 * 39138 * @constructor 39139 * @param {Object} settings Name/value object with settings. 39140 */ 39141 init: function(settings) { 39142 var self = this; 39143 39144 self._super(settings); 39145 39146 if (self.isRtl()) { 39147 self.classes.add('rtl'); 39148 } 39149 39150 self.classes.add('window'); 39151 self.bodyClasses.add('window-body'); 39152 self.state.set('fixed', true); 39153 39154 // Create statusbar 39155 if (settings.buttons) { 39156 self.statusbar = new Panel({ 39157 layout: 'flex', 39158 border: '1 0 0 0', 39159 spacing: 3, 39160 padding: 10, 39161 align: 'center', 39162 pack: self.isRtl() ? 'start' : 'end', 39163 defaults: { 39164 type: 'button' 39165 }, 39166 items: settings.buttons 39167 }); 39168 39169 self.statusbar.classes.add('foot'); 39170 self.statusbar.parent(self); 39171 } 39172 39173 self.on('click', function(e) { 39174 if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { 39175 self.close(); 39176 } 39177 }); 39178 39179 self.on('cancel', function() { 39180 self.close(); 39181 }); 39182 39183 self.aria('describedby', self.describedBy || self._id + '-none'); 39184 self.aria('label', settings.title); 39185 self._fullscreen = false; 39186 }, 39187 39188 /** 39189 * Recalculates the positions of the controls in the current container. 39190 * This is invoked by the reflow method and shouldn't be called directly. 39191 * 39192 * @method recalc 39193 */ 39194 recalc: function() { 39195 var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc; 39196 39197 if (self._fullscreen) { 39198 self.layoutRect(DomUtils.getWindowSize()); 39199 self.layoutRect().contentH = self.layoutRect().innerH; 39200 } 39201 39202 self._super(); 39203 39204 layoutRect = self.layoutRect(); 39205 39206 // Resize window based on title width 39207 if (self.settings.title && !self._fullscreen) { 39208 width = layoutRect.headerW; 39209 if (width > layoutRect.w) { 39210 x = layoutRect.x - Math.max(0, width / 2); 39211 self.layoutRect({w: width, x: x}); 39212 needsRecalc = true; 39213 } 39214 } 39215 39216 // Resize window based on statusbar width 39217 if (statusbar) { 39218 statusbar.layoutRect({w: self.layoutRect().innerW}).recalc(); 39219 39220 width = statusbar.layoutRect().minW + layoutRect.deltaW; 39221 if (width > layoutRect.w) { 39222 x = layoutRect.x - Math.max(0, width - layoutRect.w); 39223 self.layoutRect({w: width, x: x}); 39224 needsRecalc = true; 39225 } 39226 } 39227 39228 // Recalc body and disable auto resize 39229 if (needsRecalc) { 39230 self.recalc(); 39231 } 39232 }, 39233 39234 /** 39235 * Initializes the current controls layout rect. 39236 * This will be executed by the layout managers to determine the 39237 * default minWidth/minHeight etc. 39238 * 39239 * @method initLayoutRect 39240 * @return {Object} Layout rect instance. 39241 */ 39242 initLayoutRect: function() { 39243 var self = this, layoutRect = self._super(), deltaH = 0, headEl; 39244 39245 // Reserve vertical space for title 39246 if (self.settings.title && !self._fullscreen) { 39247 headEl = self.getEl('head'); 39248 39249 var size = DomUtils.getSize(headEl); 39250 39251 layoutRect.headerW = size.width; 39252 layoutRect.headerH = size.height; 39253 39254 deltaH += layoutRect.headerH; 39255 } 39256 39257 // Reserve vertical space for statusbar 39258 if (self.statusbar) { 39259 deltaH += self.statusbar.layoutRect().h; 39260 } 39261 39262 layoutRect.deltaH += deltaH; 39263 layoutRect.minH += deltaH; 39264 //layoutRect.innerH -= deltaH; 39265 layoutRect.h += deltaH; 39266 39267 var rect = DomUtils.getWindowSize(); 39268 39269 layoutRect.x = self.settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2); 39270 layoutRect.y = self.settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2); 39271 39272 return layoutRect; 39273 }, 39274 39275 /** 39276 * Renders the control as a HTML string. 39277 * 39278 * @method renderHtml 39279 * @return {String} HTML representing the control. 39280 */ 39281 renderHtml: function() { 39282 var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; 39283 var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html; 39284 39285 self.preRender(); 39286 layout.preRender(self); 39287 39288 if (settings.title) { 39289 headerHtml = ( 39290 '<div id="' + id + '-head" class="' + prefix + 'window-head">' + 39291 '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' + 39292 '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' + 39293 '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' + 39294 '</div>' 39295 ); 39296 } 39297 39298 if (settings.url) { 39299 html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>'; 39300 } 39301 39302 if (typeof html == "undefined") { 39303 html = layout.renderHtml(self); 39304 } 39305 39306 if (self.statusbar) { 39307 footerHtml = self.statusbar.renderHtml(); 39308 } 39309 39310 return ( 39311 '<div id="' + id + '" class="' + self.classes + '" hidefocus="1">' + 39312 '<div class="' + self.classPrefix + 'reset" role="application">' + 39313 headerHtml + 39314 '<div id="' + id + '-body" class="' + self.bodyClasses + '">' + 39315 html + 39316 '</div>' + 39317 footerHtml + 39318 '</div>' + 39319 '</div>' 39320 ); 39321 }, 39322 39323 /** 39324 * Switches the window fullscreen mode. 39325 * 39326 * @method fullscreen 39327 * @param {Boolean} state True/false state. 39328 * @return {tinymce.ui.Window} Current window instance. 39329 */ 39330 fullscreen: function(state) { 39331 var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect; 39332 39333 if (state != self._fullscreen) { 39334 $(window).on('resize', function() { 39335 var time; 39336 39337 if (self._fullscreen) { 39338 // Time the layout time if it's to slow use a timeout to not hog the CPU 39339 if (!slowRendering) { 39340 time = new Date().getTime(); 39341 39342 var rect = DomUtils.getWindowSize(); 39343 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 39344 39345 if ((new Date().getTime()) - time > 50) { 39346 slowRendering = true; 39347 } 39348 } else { 39349 if (!self._timer) { 39350 self._timer = Delay.setTimeout(function() { 39351 var rect = DomUtils.getWindowSize(); 39352 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 39353 39354 self._timer = 0; 39355 }, 50); 39356 } 39357 } 39358 } 39359 }); 39360 39361 layoutRect = self.layoutRect(); 39362 self._fullscreen = state; 39363 39364 if (!state) { 39365 self.borderBox = BoxUtils.parseBox(self.settings.border); 39366 self.getEl('head').style.display = ''; 39367 layoutRect.deltaH += layoutRect.headerH; 39368 $([documentElement, document.body]).removeClass(prefix + 'fullscreen'); 39369 self.classes.remove('fullscreen'); 39370 self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); 39371 } else { 39372 self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h}; 39373 39374 self.borderBox = BoxUtils.parseBox('0'); 39375 self.getEl('head').style.display = 'none'; 39376 layoutRect.deltaH -= layoutRect.headerH + 2; 39377 $([documentElement, document.body]).addClass(prefix + 'fullscreen'); 39378 self.classes.add('fullscreen'); 39379 39380 var rect = DomUtils.getWindowSize(); 39381 self.moveTo(0, 0).resizeTo(rect.w, rect.h); 39382 } 39383 } 39384 39385 return self.reflow(); 39386 }, 39387 39388 /** 39389 * Called after the control has been rendered. 39390 * 39391 * @method postRender 39392 */ 39393 postRender: function() { 39394 var self = this, startPos; 39395 39396 setTimeout(function() { 39397 self.classes.add('in'); 39398 }, 0); 39399 39400 self._super(); 39401 39402 if (self.statusbar) { 39403 self.statusbar.postRender(); 39404 } 39405 39406 self.focus(); 39407 39408 this.dragHelper = new DragHelper(self._id + '-dragh', { 39409 start: function() { 39410 startPos = { 39411 x: self.layoutRect().x, 39412 y: self.layoutRect().y 39413 }; 39414 }, 39415 39416 drag: function(e) { 39417 self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); 39418 } 39419 }); 39420 39421 self.on('submit', function(e) { 39422 if (!e.isDefaultPrevented()) { 39423 self.close(); 39424 } 39425 }); 39426 39427 windows.push(self); 39428 toggleFullScreenState(true); 39429 }, 39430 39431 /** 39432 * Fires a submit event with the serialized form. 39433 * 39434 * @method submit 39435 * @return {Object} Event arguments object. 39436 */ 39437 submit: function() { 39438 return this.fire('submit', {data: this.toJSON()}); 39439 }, 39440 39441 /** 39442 * Removes the current control from DOM and from UI collections. 39443 * 39444 * @method remove 39445 * @return {tinymce.ui.Control} Current control instance. 39446 */ 39447 remove: function() { 39448 var self = this, i; 39449 39450 self.dragHelper.destroy(); 39451 self._super(); 39452 39453 if (self.statusbar) { 39454 this.statusbar.remove(); 39455 } 39456 39457 i = windows.length; 39458 while (i--) { 39459 if (windows[i] === self) { 39460 windows.splice(i, 1); 39461 } 39462 } 39463 39464 toggleFullScreenState(windows.length > 0); 39465 toggleBodyFullScreenClasses(self.classPrefix); 39466 }, 39467 39468 /** 39469 * Returns the contentWindow object of the iframe if it exists. 39470 * 39471 * @method getContentWindow 39472 * @return {Window} window object or null. 39473 */ 39474 getContentWindow: function() { 39475 var ifr = this.getEl().getElementsByTagName('iframe')[0]; 39476 return ifr ? ifr.contentWindow : null; 39477 } 39478 }); 39479 39480 if (!Env.desktop) { 39481 handleWindowResize(); 39482 } 39483 39484 return Window; 39485}); 39486 39487// Included from: js/tinymce/classes/ui/MessageBox.js 39488 39489/** 39490 * MessageBox.js 39491 * 39492 * Released under LGPL License. 39493 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 39494 * 39495 * License: http://www.tinymce.com/license 39496 * Contributing: http://www.tinymce.com/contributing 39497 */ 39498 39499/** 39500 * This class is used to create MessageBoxes like alerts/confirms etc. 39501 * 39502 * @class tinymce.ui.MessageBox 39503 * @extends tinymce.ui.FloatPanel 39504 */
39505define("tinymce/ui/MessageBox", [ 39506 "tinymce/ui/Window" 39507], function(Window) { 39508 "use strict"; 39509 39510 var MessageBox = Window.extend({ 39511 /** 39512 * Constructs a instance with the specified settings. 39513 * 39514 * @constructor 39515 * @param {Object} settings Name/value object with settings. 39516 */ 39517 init: function(settings) { 39518 settings = { 39519 border: 1, 39520 padding: 20, 39521 layout: 'flex', 39522 pack: "center", 39523 align: "center", 39524 containerCls: 'panel', 39525 autoScroll: true, 39526 buttons: {type: "button", text: "Ok", action: "ok"}, 39527 items: { 39528 type: "label", 39529 multiline: true, 39530 maxWidth: 500, 39531 maxHeight: 200 39532 } 39533 }; 39534 39535 this._super(settings); 39536 }, 39537 39538 Statics: { 39539 /** 39540 * Ok buttons constant. 39541 * 39542 * @static 39543 * @final 39544 * @field {Number} OK 39545 */ 39546 OK: 1, 39547 39548 /** 39549 * Ok/cancel buttons constant. 39550 * 39551 * @static 39552 * @final 39553 * @field {Number} OK_CANCEL 39554 */ 39555 OK_CANCEL: 2, 39556 39557 /** 39558 * yes/no buttons constant. 39559 * 39560 * @static 39561 * @final 39562 * @field {Number} YES_NO 39563 */ 39564 YES_NO: 3, 39565 39566 /** 39567 * yes/no/cancel buttons constant. 39568 * 39569 * @static 39570 * @final 39571 * @field {Number} YES_NO_CANCEL 39572 */ 39573 YES_NO_CANCEL: 4, 39574 39575 /** 39576 * Constructs a new message box and renders it to the body element. 39577 * 39578 * @static 39579 * @method msgBox 39580 * @param {Object} settings Name/value object with settings. 39581 */ 39582 msgBox: function(settings) { 39583 var buttons, callback = settings.callback || function() {}; 39584 39585 function createButton(text, status, primary) { 39586 return { 39587 type: "button", 39588 text: text, 39589 subtype: primary ? 'primary' : '', 39590 onClick: function(e) { 39591 e.control.parents()[1].close(); 39592 callback(status); 39593 } 39594 }; 39595 } 39596 39597 switch (settings.buttons) { 39598 case MessageBox.OK_CANCEL: 39599 buttons = [ 39600 createButton('Ok', true, true), 39601 createButton('Cancel', false) 39602 ]; 39603 break; 39604 39605 case MessageBox.YES_NO: 39606 case MessageBox.YES_NO_CANCEL: 39607 buttons = [ 39608 createButton('Yes', 1, true), 39609 createButton('No', 0) 39610 ]; 39611 39612 if (settings.buttons == MessageBox.YES_NO_CANCEL) { 39613 buttons.push(createButton('Cancel', -1)); 39614 } 39615 break; 39616 39617 default: 39618 buttons = [ 39619 createButton('Ok', true, true) 39620 ]; 39621 break; 39622 } 39623 39624 return new Window({ 39625 padding: 20, 39626 x: settings.x, 39627 y: settings.y, 39628 minWidth: 300, 39629 minHeight: 100, 39630 layout: "flex", 39631 pack: "center", 39632 align: "center", 39633 buttons: buttons, 39634 title: settings.title, 39635 role: 'alertdialog', 39636 items: { 39637 type: "label", 39638 multiline: true, 39639 maxWidth: 500, 39640 maxHeight: 200, 39641 text: settings.text 39642 }, 39643 onPostRender: function() { 39644 this.aria('describedby', this.items()[0]._id); 39645 }, 39646 onClose: settings.onClose, 39647 onCancel: function() { 39648 callback(false); 39649 } 39650 }).renderTo(document.body).reflow(); 39651 }, 39652 39653 /** 39654 * Creates a new alert dialog. 39655 * 39656 * @method alert 39657 * @param {Object} settings Settings for the alert dialog. 39658 * @param {function} [callback] Callback to execute when the user makes a choice. 39659 */ 39660 alert: function(settings, callback) { 39661 if (typeof settings == "string") { 39662 settings = {text: settings}; 39663 } 39664 39665 settings.callback = callback; 39666 return MessageBox.msgBox(settings); 39667 }, 39668 39669 /** 39670 * Creates a new confirm dialog. 39671 * 39672 * @method confirm 39673 * @param {Object} settings Settings for the confirm dialog. 39674 * @param {function} [callback] Callback to execute when the user makes a choice. 39675 */ 39676 confirm: function(settings, callback) { 39677 if (typeof settings == "string") { 39678 settings = {text: settings}; 39679 } 39680 39681 settings.callback = callback; 39682 settings.buttons = MessageBox.OK_CANCEL; 39683 39684 return MessageBox.msgBox(settings); 39685 } 39686 } 39687 }); 39688 39689 return MessageBox; 39690}); 39691 39692// Included from: js/tinymce/classes/WindowManager.js 39693 39694/** 39695 * WindowManager.js 39696 * 39697 * Released under LGPL License. 39698 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 39699 * 39700 * License: http://www.tinymce.com/license 39701 * Contributing: http://www.tinymce.com/contributing 39702 */ 39703 39704/** 39705 * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs. 39706 * 39707 * @class tinymce.WindowManager 39708 * @example 39709 * // Opens a new dialog with the file.htm file and the size 320x240 39710 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 39711 * tinymce.activeEditor.windowManager.open({ 39712 * url: 'file.htm', 39713 * width: 320, 39714 * height: 240 39715 * }, { 39716 * custom_param: 1 39717 * }); 39718 * 39719 * // Displays an alert box using the active editors window manager instance 39720 * tinymce.activeEditor.windowManager.alert('Hello world!'); 39721 * 39722 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 39723 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 39724 * if (s) 39725 * tinymce.activeEditor.windowManager.alert("Ok"); 39726 * else 39727 * tinymce.activeEditor.windowManager.alert("Cancel"); 39728 * }); 39729 */ 39730define("tinymce/WindowManager", [
39731 "tinymce/ui/Window", 39732 "tinymce/ui/MessageBox" 39733], function(Window, MessageBox) { 39734 return function(editor) { 39735 var self = this, windows = []; 39736 39737 function getTopMostWindow() { 39738 if (windows.length) { 39739 return windows[windows.length - 1]; 39740 } 39741 } 39742 39743 self.windows = windows; 39744 39745 editor.on('remove', function() { 39746 var i = windows.length; 39747 39748 while (i--) { 39749 windows[i].close(); 39750 } 39751 }); 39752 39753 /** 39754 * Opens a new window. 39755 * 39756 * @method open 39757 * @param {Object} args Optional name/value settings collection contains things like width/height/url etc. 39758 * @param {Object} params Options like title, file, width, height etc. 39759 * @option {String} title Window title. 39760 * @option {String} file URL of the file to open in the window. 39761 * @option {Number} width Width in pixels. 39762 * @option {Number} height Height in pixels. 39763 * @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content 39764 * larger than the popup size specified). 39765 */ 39766 self.open = function(args, params) { 39767 var win; 39768 39769 editor.editorManager.setActive(editor); 39770 39771 args.title = args.title || ' '; 39772 39773 // Handle URL 39774 args.url = args.url || args.file; // Legacy 39775 if (args.url) { 39776 args.width = parseInt(args.width || 320, 10); 39777 args.height = parseInt(args.height || 240, 10); 39778 } 39779 39780 // Handle body 39781 if (args.body) { 39782 args.items = { 39783 defaults: args.defaults, 39784 type: args.bodyType || 'form', 39785 items: args.body 39786 }; 39787 } 39788 39789 if (!args.url && !args.buttons) { 39790 args.buttons = [ 39791 {text: 'Ok', subtype: 'primary', onclick: function() { 39792 win.find('form')[0].submit(); 39793 }}, 39794 39795 {text: 'Cancel', onclick: function() { 39796 win.close(); 39797 }} 39798 ]; 39799 } 39800 39801 win = new Window(args); 39802 windows.push(win); 39803 39804 win.on('close', function() { 39805 var i = windows.length; 39806 39807 while (i--) { 39808 if (windows[i] === win) { 39809 windows.splice(i, 1); 39810 } 39811 } 39812 39813 if (!windows.length) { 39814 editor.focus(); 39815 } 39816 }); 39817 39818 // Handle data 39819 if (args.data) { 39820 win.on('postRender', function() { 39821 this.find('*').each(function(ctrl) { 39822 var name = ctrl.name(); 39823 39824 if (name in args.data) { 39825 ctrl.value(args.data[name]); 39826 } 39827 }); 39828 }); 39829 } 39830 39831 // store args and parameters 39832 win.features = args || {}; 39833 win.params = params || {}; 39834 39835 // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog 39836 if (windows.length === 1) { 39837 editor.nodeChanged(); 39838 } 39839 39840 return win.renderTo().reflow(); 39841 }; 39842 39843 /** 39844 * Creates a alert dialog. Please don't use the blocking behavior of this 39845 * native version use the callback method instead then it can be extended. 39846 * 39847 * @method alert 39848 * @param {String} message Text to display in the new alert dialog. 39849 * @param {function} callback Callback function to be executed after the user has selected ok. 39850 * @param {Object} scope Optional scope to execute the callback in. 39851 * @example 39852 * // Displays an alert box using the active editors window manager instance 39853 * tinymce.activeEditor.windowManager.alert('Hello world!'); 39854 */ 39855 self.alert = function(message, callback, scope) { 39856 MessageBox.alert(message, function() { 39857 if (callback) { 39858 callback.call(scope || this); 39859 } else { 39860 editor.focus(); 39861 } 39862 }); 39863 }; 39864 39865 /** 39866 * Creates a confirm dialog. Please don't use the blocking behavior of this 39867 * native version use the callback method instead then it can be extended. 39868 * 39869 * @method confirm 39870 * @param {String} message Text to display in the new confirm dialog. 39871 * @param {function} callback Callback function to be executed after the user has selected ok or cancel. 39872 * @param {Object} scope Optional scope to execute the callback in. 39873 * @example 39874 * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm 39875 * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) { 39876 * if (s) 39877 * tinymce.activeEditor.windowManager.alert("Ok"); 39878 * else 39879 * tinymce.activeEditor.windowManager.alert("Cancel"); 39880 * }); 39881 */ 39882 self.confirm = function(message, callback, scope) { 39883 MessageBox.confirm(message, function(state) { 39884 callback.call(scope || this, state); 39885 }); 39886 }; 39887 39888 /** 39889 * Closes the top most window. 39890 * 39891 * @method close 39892 */ 39893 self.close = function() {
39894 if (getTopMostWindow()) { 39895 getTopMostWindow().close(); 39896 } 39897 }; 39898 39899 /** 39900 * Returns the params of the last window open call. This can be used in iframe based 39901 * dialog to get params passed from the tinymce plugin. 39902 * 39903 * @example 39904 * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams(); 39905 * 39906 * @method getParams 39907 * @return {Object} Name/value object with parameters passed from windowManager.open call. 39908 */ 39909 self.getParams = function() { 39910 return getTopMostWindow() ? getTopMostWindow().params : null; 39911 }; 39912 39913 /** 39914 * Sets the params of the last opened window. 39915 * 39916 * @method setParams 39917 * @param {Object} params Params object to set for the last opened window. 39918 */ 39919 self.setParams = function(params) { 39920 if (getTopMostWindow()) { 39921 getTopMostWindow().params = params; 39922 } 39923 }; 39924 39925 /** 39926 * Returns the currently opened window objects. 39927 * 39928 * @method getWindows 39929 * @return {Array} Array of the currently opened windows. 39930 */ 39931 self.getWindows = function() { 39932 return windows; 39933 }; 39934 }; 39935}); 39936 39937// Included from: js/tinymce/classes/ui/Tooltip.js 39938 39939/** 39940 * Tooltip.js 39941 * 39942 * Released under LGPL License. 39943 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 39944 * 39945 * License: http://www.tinymce.com/license 39946 * Contributing: http://www.tinymce.com/contributing 39947 */ 39948 39949/** 39950 * Creates a tooltip instance. 39951 * 39952 * @-x-less ToolTip.less 39953 * @class tinymce.ui.ToolTip 39954 * @extends tinymce.ui.Control 39955 * @mixes tinymce.ui.Movable 39956 */ 39957define("tinymce/ui/Tooltip", [ 39958 "tinymce/ui/Control", 39959 "tinymce/ui/Movable" 39960], function(Control, Movable) { 39961 return Control.extend({ 39962 Mixins: [Movable], 39963 39964 Defaults: { 39965 classes: 'widget tooltip tooltip-n' 39966 }, 39967 39968 /** 39969 * Renders the control as a HTML string. 39970 * 39971 * @method renderHtml 39972 * @return {String} HTML representing the control. 39973 */ 39974 renderHtml: function() { 39975 var self = this, prefix = self.classPrefix; 39976 39977 return ( 39978 '<div id="' + self._id + '" class="' + self.classes + '" role="presentation">' + 39979 '<div class="' + prefix + 'tooltip-arrow"></div>' + 39980 '<div class="' + prefix + 'tooltip-inner">' + self.encode(self.state.get('text')) + '</div>' + 39981 '</div>' 39982 ); 39983 }, 39984 39985 bindStates: function() { 39986 var self = this; 39987 39988 self.state.on('change:text', function(e) { 39989 self.getEl().lastChild.innerHTML = self.encode(e.value); 39990 }); 39991 39992 return self._super(); 39993 }, 39994 39995 /** 39996 * Repaints the control after a layout operation. 39997 * 39998 * @method repaint 39999 */ 40000 repaint: function() { 40001 var self = this, style, rect; 40002 40003 style = self.getEl().style; 40004 rect = self._layoutRect; 40005 40006 style.left = rect.x + 'px'; 40007 style.top = rect.y + 'px'; 40008 style.zIndex = 0xFFFF + 0xFFFF; 40009 } 40010 }); 40011}); 40012 40013// Included from: js/tinymce/classes/ui/Widget.js 40014 40015/** 40016 * Widget.js 40017 * 40018 * Released under LGPL License. 40019 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40020 * 40021 * License: http://www.tinymce.com/license 40022 * Contributing: http://www.tinymce.com/contributing 40023 */ 40024 40025/** 40026 * Widget base class a widget is a control that has a tooltip and s
40026ome basic states. 40027 * 40028 * @class tinymce.ui.Widget 40029 * @extends tinymce.ui.Control 40030 */ 40031define("tinymce/ui/Widget", [ 40032 "tinymce/ui/Control", 40033 "tinymce/ui/Tooltip" 40034], function(Control, Tooltip) { 40035 "use strict"; 40036 40037 var tooltip; 40038 40039 var Widget = Control.extend({ 40040 /** 40041 * Constructs a instance with the specified settings. 40042 * 40043 * @constructor 40044 * @param {Object} settings Name/value object with settings. 40045 * @setting {String} tooltip Tooltip text to display when hovering. 40046 * @setting {Boolean} autofocus True if the control should be focused when rendered. 40047 * @setting {String} text Text to display inside widget. 40048 */ 40049 init: function(settings) { 40050 var self = this; 40051 40052 self._super(settings); 40053 settings = self.settings; 40054 self.canFocus = true; 40055 40056 if (settings.tooltip && Widget.tooltips !== false) { 40057 self.on('mouseenter', function(e) { 40058 var tooltip = self.tooltip().moveTo(-0xFFFF); 40059 40060 if (e.control == self) { 40061 var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']); 40062 40063 tooltip.classes.toggle('tooltip-n', rel == 'bc-tc'); 40064 tooltip.classes.toggle('tooltip-nw', rel == 'bc-tl'); 40065 tooltip.classes.toggle('tooltip-ne', rel == 'bc-tr'); 40066 40067 tooltip.moveRel(self.getEl(), rel); 40068 } else { 40069 tooltip.hide(); 40070 } 40071 }); 40072 40073 self.on('mouseleave mousedown click', function() { 40074 self.tooltip().hide(); 40075 }); 40076 } 40077 40078 self.aria('label', settings.ariaLabel || settings.tooltip); 40079 }, 40080 40081 /** 40082 * Returns the current tooltip instance. 40083 * 40084 * @method tooltip 40085 * @return {tinymce.ui.Tooltip} Tooltip instance. 40086 */ 40087 tooltip: function() { 40088 if (!tooltip) { 40089 tooltip = new Tooltip({type: 'tooltip'}); 40090 tooltip.renderTo(); 40091 } 40092 40093 return tooltip; 40094 }, 40095 40096 /** 40097 * Called after the control has been rendered. 40098 * 40099 * @method postRender 40100 */ 40101 postRender: function() { 40102 var self = this, settings = self.settings; 40103 40104 self._super(); 40105 40106 if (!self.parent() && (settings.width || settings.height)) { 40107 self.initLayoutRect(); 40108 self.repaint(); 40109 } 40110 40111 if (settings.autofocus) { 40112 self.focus(); 40113 } 40114 }, 40115 40116 bindStates: function() { 40117 var self = this; 40118 40119 function disable(state) { 40120 self.aria('disabled', state); 40121 self.classes.toggle('disabled', state); 40122 } 40123 40124 function active(state) { 40125 self.aria('pressed', state); 40126 self.classes.toggle('active', state); 40127 } 40128 40129 self.state.on('change:disabled', function(e) { 40130 disable(e.value); 40131 }); 40132 40133 self.state.on('change:active', function(e) { 40134 active(e.value); 40135 }); 40136 40137 if (self.state.get('disabled')) { 40138 disable(true); 40139 } 40140 40141 if (self.state.get('active')) { 40142 active(true); 40143 } 40144 40145 return self._super(); 40146 }, 40147 40148 /** 40149 * Removes the current control from DOM and from UI collections. 40150 * 40151 * @method remove 40152 * @return {tinymce.ui.Control} Current control instance. 40153 */ 40154 remove: function() { 40155 this._super(); 40156 40157 if (tooltip) { 40158 tooltip.remove(); 40159 tooltip = null; 40160 } 40161 } 40162 }); 40163 40164 return Widget; 40165}); 40166 40167// Included from: js/tinymce/classes/ui/Progress.js 40168 40169/** 40170 * Progress.js 40171 * 40172 * Released under LGPL License. 40173 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40174 * 40175 * License: http://www.tinymce.com/license 40176 * Contributing: http://www.tinymce.com/contributing 40177 */ 40178 40179/** 40180 * Progress control. 40181 * 40182 * @-x-less Progress.less 40183 * @class tinymce.ui.Progress 40184 * @extends tinymce.ui.Control 40185 */ 40186define("tinymce/ui/Progress", [ 40187 "tinymce/ui/Widget" 40188], function(Widget) { 40189 "use strict"; 40190 40191 return Widget.extend({ 40192 Defaults: { 40193 value: 0 40194 }, 40195 40196 init: function(settings) { 40197 var self = this; 40198 40199 self._super(settings); 40200 self.classes.add('progress'); 40201 40202 if (!self.settings.filter) { 40203 self.settings.filter = function(value) { 40204 return Math.round(value); 40205 }; 40206 } 40207 }, 40208 40209 renderHtml: function() { 40210 var self = this, id = self._id, prefix = this.classPrefix; 40211 40212 return ( 40213 '<div id="' + id + '" class="' + self.classes + '">' + 40214 '<div class="' + prefix + 'bar-container">' + 40215 '<div class="' + prefix + 'bar"></div>' + 40216 '</div>' + 40217 '<div class="' + prefix + 'text">0%</div>' + 40218 '</div>' 40219 ); 40220 }, 40221 40222 postRender: function() { 40223 var self = this; 40224 40225 self._super(); 40226 self.value(self.settings.value); 40227 40228 return self; 40229 }, 40230 40231 bindStates: function() { 40232 var self = this; 40233 40234 function setValue(value) { 40235 value = self.settings.filter(value); 40236 self.getEl().lastChild.innerHTML = value + '%'; 40237 self.getEl().firstChild.firstChild.style.width = value + '%'; 40238 } 40239 40240 self.state.on('change:value', function(e) { 40241 setValue(e.value); 40242 }); 40243 40244 setValue(self.state.get('value')); 40245 40246 return self._super(); 40247 } 40248 }); 40249}); 40250 40251// Included from: js/tinymce/classes/ui/Notification.js 40252 40253/** 40254 * Notification.js 40255 * 40256 * Released under LGPL License. 40257 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40258 * 40259 * License: http://www.tinymce.com/license 40260 * Contributing: http://www.tinymce.com/contributing 40261 */ 40262 40263/** 40264 * Creates a notification instance. 40265 * 40266 * @-x-less Notification.less 40267 * @class tinymce.ui.Notification 40268 * @extends tinymce.ui.Container 40269 * @mixes tinymce.ui.Movable 40270 */
40271define("tinymce/ui/Notification", [ 40272 "tinymce/ui/Control", 40273 "tinymce/ui/Movable", 40274 "tinymce/ui/Progress" 40275], function(Control, Movable, Progress) { 40276 return Control.extend({ 40277 Mixins: [Movable], 40278 40279 Defaults: { 40280 classes: 'widget notification' 40281 }, 40282 40283 init: function(settings) { 40284 var self = this; 40285 40286 self._super(settings); 40287 40288 if (settings.text) { 40289 self.text(settings.text); 40290 } 40291 40292 if (settings.icon) { 40293 self.icon = settings.icon; 40294 } 40295 40296 if (settings.color) { 40297 self.color = settings.color; 40298 } 40299 40300 if (settings.type) { 40301 self.classes.add('notification-' + settings.type); 40302 } 40303 40304 if (settings.timeout && (settings.timeout < 0 || settings.timeout > 0) && !settings.closeButton) { 40305 self.closeButton = false; 40306 } else { 40307 self.classes.add('has-close'); 40308 self.closeButton = true; 40309 } 40310 40311 if (settings.progressBar) { 40312 self.progressBar = new Progress(); 40313 } 40314 40315 self.on('click', function(e) { 40316 if (e.target.className.indexOf(self.classPrefix + 'close') != -1) { 40317 self.close(); 40318 } 40319 }); 40320 }, 40321 40322 /** 40323 * Renders the control as a HTML string. 40324 * 40325 * @method renderHtml 40326 * @return {String} HTML representing the control. 40327 */ 40328 renderHtml: function() { 40329 var self = this, prefix = self.classPrefix, icon = '', closeButton = '', progressBar = '', notificationStyle = ''; 40330 40331 if (self.icon) { 40332 icon = '<i class="' + prefix + 'ico' + ' ' + prefix + 'i-' + self.icon + '"></i>'; 40333 } 40334 40335 if (self.color) { 40336 notificationStyle = ' style="background-color: ' + self.color + '"'; 40337 } 40338 40339 if (self.closeButton) { 40340 closeButton = '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>'; 40341 } 40342 40343 if (self.progressBar) { 40344 progressBar = self.progressBar.renderHtml(); 40345 } 40346 40347 return ( 40348 '<div id="' + self._id + '" class="' + self.classes + '"' + notificationStyle + ' role="presentation">' + 40349 icon + 40350 '<div class="' + prefix + 'notification-inner">' + self.state.get('text') + '</div>' + 40351 progressBar + 40352 closeButton + 40353 '</div>' 40354 ); 40355 }, 40356 40357 bindStates: function() { 40358 var self = this; 40359 40360 self.state.on('change:text', function(e) { 40361 self.getEl().childNodes[1].innerHTML = e.value; 40362 }); 40363 if (self.progressBar) { 40364 self.progressBar.bindStates(); 40365 } 40366 return self._super(); 40367 }, 40368 40369 close: function() { 40370 var self = this; 40371 40372 if (!self.fire('close').isDefaultPrevented()) { 40373 self.remove(); 40374 } 40375 40376 return self; 40377 }, 40378 40379 /** 40380 * Repaints the control after a layout operation. 40381 * 40382 * @method repaint 40383 */ 40384 repaint: function() { 40385 var self = this, style, rect; 40386 40387 style = self.getEl().style; 40388 rect = self._layoutRect; 40389 40390 style.left = rect.x + 'px'; 40391 style.top = rect.y + 'px'; 40392 style.zIndex = 0xFFFF + 0xFFFF; 40393 } 40394 }); 40395}); 40396 40397// Included from: js/tinymce/classes/NotificationManager.js 40398 40399/** 40400 * NotificationManager.js 40401 * 40402 * Released under LGPL License. 40403 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40404 * 40405 * License: http://www.tinymce.com/license 40406 * Contributing: http://www.tinymce.com/contributing 40407 */ 40408 40409/** 40410 * This class handles the creation of TinyMCE's notifications. 40411 * 40412 * @class tinymce.notificationManager 40413 * @example 40414 * // Opens a new notification of type "error" with text "An error occurred." 40415 * tinymce.activeEditor.notificationManager.open({ 40416 * text: 'An error occurred.', 40417 * type: 'error' 40418 * }); 40419 */ 40420define("tinymce/NotificationManager", [ 40421 "tinymce/ui/Notification", 40422 "tinymce/util/Delay" 40423], function(Notification, Delay) { 40424 return function(editor) { 40425 var self = this, notifications = []; 40426 40427 function getLastNotification() { 40428 if (notifications.length) { 40429 return notifications[notifications.length - 1]; 40430 } 40431 } 40432 40433 self.notifications = notifications; 40434 40435 function resizeWindowEvent() { 40436 Delay.requestAnimationFrame(function() { 40437 prePositionNotifications(); 40438 positionNotifications(); 40439 }); 40440 } 40441 40442 // Since the viewport will change based on the present notifications, we need to move them all to the 40443 // top left of the viewport to give an accurate size measurement so we can position them later. 40444 function prePositionNotifications() { 40445 for (var i = 0; i < notifications.length; i++) { 40446 notifications[i].moveTo(0, 0); 40447 } 40448 } 40449 40450 function positionNotifications() { 40451 if (notifications.length > 0) { 40452 var firstItem = notifications.slice(0, 1)[0]; 40453 var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer(); 40454 firstItem.moveRel(container, 'tc-tc'); 40455 if (notifications.length > 1) { 40456 for (var i = 1; i < notifications.length; i++) { 40457 notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc'); 40458 } 40459 } 40460 } 40461 } 40462 40463 editor.on('remove', function() { 40464 var i = notifications.length; 40465 40466 while (i--) { 40467 notifications[i].close(); 40468 } 40469 }); 40470 40471 editor.on('ResizeEditor', positionNotifications); 40472 editor.on('ResizeWindow', resizeWindowEvent); 40473 40474 /** 40475 * Opens a new notification. 40476 * 40477 * @method open 40478 * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc. 40479 */ 40480 self.open = function(args) { 40481 var notif; 40482 40483 editor.editorManager.setActive(editor); 40484
40485 notif = new Notification(args); 40486 notifications.push(notif); 40487 40488 //If we have a timeout value 40489 if (args.timeout > 0) { 40490 notif.timer = setTimeout(function() { 40491 notif.close(); 40492 }, args.timeout); 40493 } 40494 40495 notif.on('close', function() { 40496 var i = notifications.length; 40497 40498 if (notif.timer) { 40499 editor.getWin().clearTimeout(notif.timer); 40500 } 40501 40502 while (i--) { 40503 if (notifications[i] === notif) { 40504 notifications.splice(i, 1); 40505 } 40506 } 40507 40508 positionNotifications(); 40509 }); 40510 40511 notif.renderTo(); 40512 40513 positionNotifications(); 40514 40515 return notif; 40516 }; 40517 40518 /** 40519 * Closes the top most notification. 40520 * 40521 * @method close 40522 */ 40523 self.close = function() { 40524 if (getLastNotification()) { 40525 getLastNotification().close(); 40526 } 40527 }; 40528 40529 /** 40530 * Returns the currently opened notification objects. 40531 * 40532 * @method getNotifications 40533 * @return {Array} Array of the currently opened notifications. 40534 */ 40535 self.getNotifications = function() { 40536 return notifications; 40537 }; 40538 40539 //self.positionNotifications = positionNotifications; 40540 }; 40541}); 40542 40543// Included from: js/tinymce/classes/dom/NodePath.js 40544 40545/** 40546 * NodePath.js 40547 * 40548 * Released under LGPL License. 40549 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40550 * 40551 * License: http://www.tinymce.com/license 40552 * Contributing: http://www.tinymce.com/contributing 40553 */ 40554 40555/** 40556 * Handles paths of nodes within an element. 40557 * 40558 * @private 40559 * @class tinymce.dom.NodePath 40560 */ 40561define("tinymce/dom/NodePath", [ 40562 "tinymce/dom/DOMUtils" 40563], function(DOMUtils) { 40564 function create(rootNode, targetNode, normalized) { 40565 var path = []; 40566 40567 for (; targetNode && targetNode != rootNode; targetNode = targetNode.parentNode) { 40568 path.push(DOMUtils.nodeIndex(targetNode, normalized)); 40569 } 40570 40571 return path; 40572 } 40573 40574 function resolve(rootNode, path) { 40575 var i, node, children; 40576 40577 for (node = rootNode, i = path.length - 1; i >= 0; i--) { 40578 children = node.childNodes; 40579 40580 if (path[i] > children.length - 1) { 40581 return null; 40582 } 40583 40584 node = children[path[i]]; 40585 } 40586 40587 return node; 40588 } 40589 40590 return { 40591 create: create, 40592 resolve: resolve 40593 }; 40594}); 40595 40596// Included from: js/tinymce/classes/util/Quirks.js 40597 40598/** 40599 * Quirks.js 40600 * 40601 * Released under LGPL License. 40602 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 40603 * 40604 * License: http://www.tinymce.com/license 40605 * Contributing: http://www.tinymce.com/contributing 40606 * 40607 * @ignore-file 40608 */ 40609 40610/** 40611 * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes. 40612 * 40613 * @private 40614 * @class tinymce.util.Quirks 40615 */ 40616define("tinymce/util/Quirks", [ 40617 "tinymce/util/VK", 40618 "tinymce/dom/RangeUtils", 40619 "tinymce/dom/TreeWalker", 40620 "tinymce/dom/NodePath", 40621 "tinymce/html/Node", 40622 "tinymce/html/Entities", 40623 "tinymce/Env", 40624 "tinymce/util/Tools", 40625 "tinymce/util/Delay", 40626 "tinymce/caret/CaretContainer" 40627], function(VK, RangeUtils, TreeWalker, NodePath, Node, Entities, Env, Tools, Delay, CaretContainer) { 40628 return function(editor) { 40629 var each = Tools.each, $ = editor.$; 40630 var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, 40631 settings = editor.settings, parser = editor.parser, serializer = editor.serializer; 40632 var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit; 40633 var mceInternalUrlPrefix = 'data:text/mce-internal,'; 40634 var mceInternalDataType = isIE ? 'Text' : 'URL'; 40635 40636 /** 40637 * Executes a command with a specific state this can be to enable/disable browser editing features. 40638 */ 40639 function setEditorCommandState(cmd, state) { 40640 try { 40641 editor.getDoc().execCommand(cmd, false, state); 40642 } catch (ex) { 40643 // Ignore 40644 } 40645 } 40646 40647 /** 40648 * Returns current IE document mode. 40649 */ 40650 function getDocumentMode() { 40651 var documentMode = editor.getDoc().documentMode; 40652 40653 return documentMode ? documentMode : 6; 40654 } 40655 40656 /** 40657 * Returns true/false if the event is prevented or not. 40658 * 40659 * @private 40660 * @param {Event} e Event object. 40661 * @return {Boolean} true/false if the event is prevented or not. 40662 */ 40663 function isDefaultPrevented(e) { 40664 return e.isDefaultPrevented(); 40665 } 40666 40667 /** 40668 * Sets Text/URL data on the event's dataTransfer object to a special data:text/mce-internal url. 40669 * This is to workaround the inability to set custom contentType on IE and Safari. 40670 * The editor's selected content is encoded into this url so drag and drop between editors will work. 40671 * 40672 * @private 40673 * @param {DragEvent} e Event object 40674 */ 40675 function setMceInteralContent(e) { 40676 var selectionHtml, internalContent; 40677 40678 if (e.dataTransfer) { 40679 if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') { 40680 selection.select(e.target); 40681 } 40682 40683 selectionHtml = editor.selection.getContent(); 40684 40685 // Safari/IE doesn't support custom dataTransfer items so we can only use URL and Text 40686 if (selectionHtml.length > 0) { 40687 internalContent = mceInternalUrlPrefix + escape(editor.id) + ',' + escape(selectionHtml); 40688 e.dataTransfer.setData(mceInternalDataType, internalContent); 40689 } 40690 } 40691 } 40692 40693 /** 40694 * Gets content of special data:text/mce-internal url on the event's dataTransfer object. 40695 * This is to workaround the inability to set custom contentType on IE and Safari. 40696 * The editor's selected content is encoded into this url so drag and drop between editors will work. 40697 * 40698 * @private 40699 * @param {DragEvent} e Event object 40700 * @returns {String}
40700 mce-internal content 40701 */ 40702 function getMceInternalContent(e) { 40703 var internalContent; 40704 40705 if (e.dataTransfer) { 40706 internalContent = e.dataTransfer.getData(mceInternalDataType); 40707 40708 if (internalContent && internalContent.indexOf(mceInternalUrlPrefix) >= 0) { 40709 internalContent = internalContent.substr(mceInternalUrlPrefix.length).split(','); 40710 40711 return { 40712 id: unescape(internalContent[0]), 40713 html: unescape(internalContent[1]) 40714 }; 40715 } 40716 } 40717 40718 return null; 40719 } 40720 40721 /** 40722 * Inserts contents using the paste clipboard command if it's available if it isn't it will fallback 40723 * to the core command. 40724 * 40725 * @private 40726 * @param {String} content Content to insert at selection. 40727 */ 40728 function insertClipboardContents(content) { 40729 if (editor.queryCommandSupported('mceInsertClipboardContent')) { 40730 editor.execCommand('mceInsertClipboardContent', false, {content: content}); 40731 } else { 40732 editor.execCommand('mceInsertContent', false, content); 40733 } 40734 } 40735 40736 /** 40737 * Fixes a WebKit bug when deleting contents using backspace or delete key. 40738 * WebKit will produce a span element if you delete across two block elements. 40739 * 40740 * Example: 40741 * <h1>a</h1><p>|b</p> 40742 * 40743 * Will produce this on backspace: 40744 * <h1>a<span style="<all runtime styles>">b</span></p> 40745 * 40746 * This fixes the backspace to produce: 40747 * <h1>a|b</p> 40748 * 40749 * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784 40750 * 40751 * This fixes the following delete scenarios: 40752 * 1. Delete by pressing backspace key. 40753 * 2. Delete by pressing delete key. 40754 * 3. Delete by pressing backspace key with ctrl/cmd (Word delete). 40755 * 4. Delete by pressing delete key with ctrl/cmd (Word delete). 40756 * 5. Delete by drag/dropping contents inside the editor. 40757 * 6. Delete by using Cut Ctrl+X/Cmd+X. 40758 * 7. Delete by selecting contents and writing a character. 40759 * 40760 * This code is a ugly hack since writing full custom delete logic for just this bug 40761 * fix seemed like a huge task. I hope we can remove this before the year 2030. 40762 */ 40763 function cleanupStylesWhenDeleting() { 40764 var doc = editor.getDoc(), dom = editor.dom, selection = editor.selection; 40765 var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng; 40766 40767 // Add mini polyfill for older WebKits 40768 // TODO: Remove this when old Safari versions gets updated 40769 if (!MutationObserver) { 40770 olderWebKit = true; 40771 40772 MutationObserver = function() { 40773 var records = [], target; 40774 40775 function nodeInsert(e) { 40776 var target = e.relatedNode || e.target; 40777 records.push({target: target, addedNodes: [target]}); 40778 } 40779 40780 function attrModified(e) { 40781 var target = e.relatedNode || e.target; 40782 records.push({target: target, attributeName: e.attrName}); 40783 } 40784 40785 this.observe = function(node) { 40786 target = node; 40787 target.addEventListener('DOMSubtreeModified', nodeInsert, false); 40788 target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false); 40789 target.addEventListener('DOMNodeInserted', nodeInsert, false); 40790 target.addEventListener('DOMAttrModified', attrModified, false); 40791 }; 40792 40793 this.disconnect = function() { 40794 target.removeEventListener('DOMSubtreeModified', nodeInsert, false); 40795 target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false); 40796 target.removeEventListener('DOMNodeInserted', nodeInsert, false); 40797 target.removeEventListener('DOMAttrModified', attrModified, false); 40798 }; 40799 40800 this.takeRecords = function() { 40801 return records; 40802 }; 40803 }; 40804 } 40805 40806 function isTrailingBr(node) { 40807 var blockElements = dom.schema.getBlockElements(), rootNode = editor.getBody(); 40808 40809 if (node.nodeName != 'BR') { 40810 return false; 40811 } 40812 40813 for (node = node; node != rootNode && !blockElements[node.nodeName]; node = node.parentNode) { 40814 if (node.nextSibling) { 40815 return false; 40816 } 40817 } 40818 40819 return true; 40820 } 40821 40822 function isSiblingsIgnoreWhiteSpace(node1, node2) { 40823 var node; 40824 40825 for (node = node1.nextSibling; node && node != node2; node = node.nextSibling) { 40826 if (node.nodeType == 3 && $.trim(node.data).length === 0) { 40827 continue; 40828 } 40829
40830 if (node !== node2) { 40831 return false; 40832 } 40833 } 40834 40835 return node === node2; 40836 } 40837 40838 function findCaretNode(node, forward, startNode) { 40839 var walker, current, nonEmptyElements; 40840 40841 nonEmptyElements = dom.schema.getNonEmptyElements(); 40842 40843 walker = new TreeWalker(startNode || node, node); 40844 40845 while ((current = walker[forward ? 'next' : 'prev']())) { 40846 if (nonEmptyElements[current.nodeName] && !isTrailingBr(current)) { 40847 return current; 40848 } 40849 40850 if (current.nodeType == 3 && current.data.length > 0) { 40851 return current; 40852 } 40853 } 40854 } 40855 40856 function deleteRangeBetweenTextBlocks(rng) { 40857 var startBlock, endBlock, caretNodeBefore, caretNodeAfter, textBlockElements; 40858 40859 if (rng.collapsed) { 40860 return; 40861 } 40862 40863 startBlock = dom.getParent(RangeUtils.getNode(rng.startContainer, rng.startOffset), dom.isBlock); 40864 endBlock = dom.getParent(RangeUtils.getNode(rng.endContainer, rng.endOffset), dom.isBlock); 40865 textBlockElements = editor.schema.getTextBlockElements(); 40866 40867 if (startBlock == endBlock) { 40868 return; 40869 } 40870 40871 if (!textBlockElements[startBlock.nodeName] || !textBlockElements[endBlock.nodeName]) { 40872 return; 40873 } 40874 40875 if (dom.getContentEditable(startBlock) === "false" || dom.getContentEditable(endBlock) === "false") { 40876 return; 40877 } 40878 40879 rng.deleteContents(); 40880 40881 caretNodeBefore = findCaretNode(startBlock, false);
40882 caretNodeAfter = findCaretNode(endBlock, true); 40883 40884 if (!dom.isEmpty(endBlock)) { 40885 $(startBlock).append(endBlock.childNodes); 40886 } 40887 40888 $(endBlock).remove(); 40889 40890 if (caretNodeBefore) { 40891 if (caretNodeBefore.nodeType == 1) { 40892 if (caretNodeBefore.nodeName == "BR") { 40893 rng.setStartBefore(caretNodeBefore); 40894 rng.setEndBefore(caretNodeBefore); 40895 } else { 40896 rng.setStartAfter(caretNodeBefore); 40897 rng.setEndAfter(caretNodeBefore); 40898 } 40899 } else { 40900 rng.setStart(caretNodeBefore, caretNodeBefore.data.length); 40901 rng.setEnd(caretNodeBefore, caretNodeBefore.data.length); 40902 } 40903 } else if (caretNodeAfter) { 40904 if (caretNodeAfter.nodeType == 1) { 40905 rng.setStartBefore(caretNodeAfter); 40906 rng.setEndBefore(caretNodeAfter); 40907 } else { 40908 rng.setStart(caretNodeAfter, 0); 40909 rng.setEnd(caretNodeAfter, 0); 40910 } 40911 } 40912 40913 selection.setRng(rng); 40914 40915 return true; 40916 } 40917 40918 function expandBetweenBlocks(rng, isForward) { 40919 var caretNode, targetCaretNode, textBlock, targetTextBlock, container, offset; 40920 40921 if (!rng.collapsed) { 40922 return rng; 40923 } 40924 40925 container = rng.startContainer; 40926 offset = rng.startOffset; 40927 40928 if (container.nodeType == 3) { 40929 if (isForward) { 40930 if (offset < container.data.length) { 40931 return rng; 40932 } 40933 } else { 40934 if (offset > 0) { 40935 return rng; 40936 } 40937 } 40938 } 40939 40940 caretNode = RangeUtils.getNode(rng.startContainer, rng.startOffset); 40941 textBlock = dom.getParent(caretNode, dom.isBlock); 40942 targetCaretNode = findCaretNode(editor.getBody(), isForward, caretNode); 40943 targetTextBlock = dom.getParent(targetCaretNode, dom.isBlock); 40944 40945 if (!caretNode || !targetCaretNode) { 40946 return rng; 40947 } 40948 40949 if (targetTextBlock && textBlock != targetTextBlock) { 40950 if (!isForward) { 40951 if (!isSiblingsIgnoreWhiteSpace(targetTextBlock, textBlock)) { 40952 return rng; 40953 } 40954 40955 if (targetCaretNode.nodeType == 1) { 40956 if (targetCaretNode.nodeName == "BR") { 40957 rng.setStartBefore(targetCaretNode); 40958 } else { 40959 rng.setStartAfter(targetCaretNode); 40960 } 40961 } else { 40962 rng.setStart(targetCaretNode, targetCaretNode.data.length); 40963 } 40964 40965 if (caretNode.nodeType == 1) { 40966 rng.setEnd(caretNode, 0); 40967 } else { 40968 rng.setEndBefore(caretNode); 40969 } 40970 } else { 40971 if (!isSiblingsIgnoreWhiteSpace(textBlock, targetTextBlock)) { 40972 return rng; 40973 } 40974 40975 if (caretNode.nodeType == 1) { 40976 if (caretNode.nodeName == "BR") { 40977 rng.setStartBefore(caretNode); 40978 } else { 40979 rng.setStartAfter(caretNode); 40980 } 40981 } else { 40982 rng.setStart(caretNode, caretNode.data.length); 40983 } 40984 40985 if (targetCaretNode.nodeType == 1) { 40986 rng.setEnd(targetCaretNode, 0); 40987 } else { 40988 rng.setEndBefore(targetCaretNode); 40989 } 40990 } 40991 } 40992 40993 return rng; 40994 } 40995 40996 function handleTextBlockMergeDelete(isForward) { 40997 var rng = selection.getRng(); 40998 40999 rng = expandBetweenBlocks(rng, isForward); 41000 41001 if (deleteRangeBetweenTextBlocks(rng)) { 41002 return true; 41003 } 41004 } 41005 41006 /** 41007 * This retains the formatting if the last character is to be deleted. 41008 * 41009 * Backspace on this: <p><b><i>a|</i></b></p> would become <p>|</p> in WebKit. 41010 * With this patch: <p><b><i>|<br></i></b></p> 41011 */ 41012 function handleLastBlockCharacterDelete(isForward, rng) { 41013 var path, blockElm, newBlockElm, clonedBlockElm, sibling, 41014 container, offset, br, currentFormatNodes; 41015 41016 function cloneTextBlockWithFormats(blockElm, node) { 41017 currentFormatNodes = $(node).parents().filter(function(idx, node) { 41018 return !!editor.schema.getTextInlineElements()[node.nodeName]; 41019 }); 41020 41021 newBlockElm = blockElm.cloneNode(false); 41022 41023 currentFormatNodes = Tools.map(currentFormatNodes, function(formatNode) { 41024 formatNode = formatNode.cloneNode(false); 41025 41026 if (newBlockElm.hasChildNodes()) { 41027 formatNode.appendChild(newBlockElm.firstChild); 41028 newBlockElm.appendChild(formatNode); 41029 } else { 41030 newBlockElm.appendChild(formatNode); 41031 } 41032 41033 newBlockElm.appendChild(formatNode); 41034 41035 return formatNode; 41036 }); 41037 41038 if (currentFormatNodes.length) { 41039 br = dom.create('br'); 41040 currentFormatNodes[0].appendChild(br); 41041 dom.replace(newBlockElm, blockElm); 41042 41043 rng.setStartBefore(br); 41044 rng.setEndBefore(br); 41045 editor.selection.setRng(rng); 41046 41047 return br; 41048 } 41049 41050 return null; 41051 } 41052 41053 function isTextBlock(node) { 41054 return node && editor.schema.getTextBlockElements()[node.tagName]; 41055 } 41056 41057 if (!rng.collapsed) { 41058 return; 41059 } 41060 41061 container = rng.startContainer; 41062 offset = rng.startOffset; 41063 blockElm = dom.getParent(container, dom.isBlock); 41064 if (!isTextBlock(blockElm)) { 41065 return; 41066 } 41067 41068 if (container.nodeType == 1) { 41069 container = container.childNodes[offset]; 41070 if (container && container.tagName != 'BR') { 41071 return; 41072 } 41073 41074 if (isForward) { 41075 sibling = blockElm.nextSibling; 41076 } else { 41077 sibling = blockElm.previousSibling; 41078 } 41079 41080 if (dom.isEmpty(blockElm) && isTextBlock(sibling) && dom.isEmpty(sibling)) { 41081 if (cloneTextBlockWithFormats(blockElm, container)) { 41082 dom.remove(sibling); 41083 return true; 41084 } 41085 } 41086 } else if (container.nodeType == 3) {
41087 path = NodePath.create(blockElm, container); 41088 clonedBlockElm = blockElm.cloneNode(true); 41089 container = NodePath.resolve(clonedBlockElm, path); 41090 41091 if (isForward) { 41092 if (offset >= container.data.length) { 41093 return; 41094 } 41095 41096 container.deleteData(offset, 1); 41097 } else { 41098 if (offset <= 0) { 41099 return; 41100 } 41101 41102 container.deleteData(offset - 1, 1); 41103 } 41104 41105 if (dom.isEmpty(clonedBlockElm)) { 41106 return cloneTextBlockWithFormats(blockElm, container); 41107 } 41108 } 41109 } 41110 41111 function customDelete(isForward) { 41112 var mutationObserver, rng, caretElement; 41113 41114 if (handleTextBlockMergeDelete(isForward)) { 41115 return; 41116 } 41117 41118 Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) { 41119 // Mark existing spans 41120 if (elm.tagName == 'SPAN') { 41121 elm.setAttribute('mce-data-marked', 1); 41122 } 41123 41124 // Make sure all elements has a data-mce-style attribute 41125 if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) { 41126 editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style')); 41127 } 41128 }); 41129 41130 // Observe added nodes and style attribute changes 41131 mutationObserver = new MutationObserver(function() {}); 41132 mutationObserver.observe(editor.getDoc(), { 41133 childList: true, 41134 attributes: true, 41135 subtree: true, 41136 attributeFilter: ['style'] 41137 }); 41138 41139 editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null); 41140 41141 rng = editor.selection.getRng(); 41142 caretElement = rng.startContainer.parentNode; 41143 41144 Tools.each(mutationObserver.takeRecords(), function(record) { 41145 if (!dom.isChildOf(record.target, editor.getBody())) { 41146 return; 41147 } 41148 41149 // Restore style attribute to previous value 41150 if (record.attributeName == "style") { 41151 var oldValue = record.target.getAttribute('data-mce-style'); 41152 41153 if (oldValue) { 41154 record.target.setAttribute("style", oldValue); 41155 } else { 41156 record.target.removeAttribute("style"); 41157 } 41158 } 41159 41160 // Remove all spans that aren't marked and retain selection 41161 Tools.each(record.addedNodes, function(node) { 41162 if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) { 41163 var offset, container; 41164 41165 if (node == caretElement) { 41166 offset = rng.startOffset; 41167 container = node.firstChild; 41168 } 41169 41170 dom.remove(node, true); 41171 41172 if (container) { 41173 rng.setStart(container, offset); 41174 rng.setEnd(container, offset); 41175 editor.selection.setRng(rng); 41176 } 41177 } 41178 }); 41179 }); 41180 41181 mutationObserver.disconnect(); 41182 41183 // Remove any left over marks 41184 Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) { 41185 span.removeAttribute('mce-data-marked'); 41186 }); 41187 } 41188 41189 editor.on('keydown', function(e) { 41190 var isForward = e.keyCode == DELETE, isMetaOrCtrl = e.ctrlKey || e.metaKey; 41191 41192 if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) { 41193 var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset; 41194 41195 // Shift+Delete is cut 41196 if (isForward && e.shiftKey) { 41197 return; 41198 } 41199 41200 if (handleLastBlockCharacterDelete(isForward, rng)) { 41201 e.preventDefault(); 41202 return; 41203 } 41204 41205 // Ignore non meta delete in the where there is text before/after the caret 41206 if (!isMetaOrCtrl && rng.collapsed && container.nodeType == 3) { 41207 if (isForward ? offset < container.data.length : offset > 0) { 41208 return; 41209 } 41210 } 41211 41212 e.preventDefault(); 41213 41214 if (isMetaOrCtrl) { 41215 editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", e.metaKey ? "lineboundary" : "word"); 41216 } 41217 41218 customDelete(isForward); 41219 } 41220 }); 41221 41222 // Handle case where text is deleted by typing over 41223 editor.on('keypress', function(e) { 41224 if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) { 41225 var rng, currentFormatNodes, fragmentNode, blockParent, caretNode, charText; 41226 41227 rng = editor.selection.getRng(); 41228 charText = String.fromCharCode(e.charCode); 41229 e.preventDefault(); 41230 41231 // Keep track of current format nodes 41232 currentFormatNodes = $(rng.startContainer).parents().filter(function(idx, node) { 41233 return !!editor.schema.getTextInlineElements()[node.nodeName]; 41234 }); 41235 41236 customDelete(true); 41237 41238 // Check if the browser removed them 41239 currentFormatNodes = currentFormatNodes.filter(function(idx, node) { 41240 return !$.contains(editor.getBody(), node); 41241 }); 41242 41243 // Then re-add them 41244 if (currentFormatNodes.length) { 41245 fragmentNode = dom.createFragment(); 41246 41247 currentFormatNodes.each(function(idx, formatNode) { 41248 formatNode = formatNode.cloneNode(false); 41249 41250 if (fragmentNode.hasChildNodes()) { 41251 formatNode.appendChild(fragmentNode.firstChild); 41252 fragmentNode.appendChild(formatNode); 41253 } else {
41254 caretNode = formatNode; 41255 fragmentNode.appendChild(formatNode); 41256 } 41257 41258 fragmentNode.appendChild(formatNode); 41259 }); 41260 41261 caretNode.appendChild(editor.getDoc().createTextNode(charText)); 41262 41263 // Prevent edge case where older WebKit would add an extra BR element 41264 blockParent = dom.getParent(rng.startContainer, dom.isBlock); 41265 if (dom.isEmpty(blockParent)) { 41266 $(blockParent).empty().append(fragmentNode); 41267 } else { 41268 rng.insertNode(fragmentNode); 41269 } 41270 41271 rng.setStart(caretNode.firstChild, 1); 41272 rng.setEnd(caretNode.firstChild, 1); 41273 editor.selection.setRng(rng); 41274 } else { 41275 editor.selection.setContent(charText); 41276 } 41277 } 41278 }); 41279 41280 editor.addCommand('Delete', function() { 41281 customDelete(); 41282 }); 41283 41284 editor.addCommand('ForwardDelete', function() { 41285 customDelete(true); 41286 }); 41287 41288 // Older WebKits doesn't properly handle the clipboard so we can't add the rest 41289 if (olderWebKit) { 41290 return; 41291 } 41292 41293 editor.on('dragstart', function(e) { 41294 dragStartRng = selection.getRng(); 41295 setMceInteralContent(e); 41296 }); 41297 41298 editor.on('drop', function(e) { 41299 if (!isDefaultPrevented(e)) { 41300 var internalContent = getMceInternalContent(e); 41301 41302 if (internalContent) { 41303 e.preventDefault(); 41304 41305 // Safari has a weird issue where drag/dropping images sometimes 41306 // produces a green plus icon. When this happens the caretRangeFromPoint 41307 // will return "null" even though the x, y coordinate is correct. 41308 // But if we detach the insert from the drop event we will get a proper range 41309 Delay.setEditorTimeout(editor, function() { 41310 var pointRng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, doc); 41311 41312 if (dragStartRng) { 41313 selection.setRng(dragStartRng); 41314 dragStartRng = null; 41315 } 41316 41317 customDelete(); 41318 selection.setRng(pointRng); 41319 insertClipboardContents(internalContent.html); 41320 }); 41321 } 41322 } 41323 }); 41324 41325 editor.on('cut', function(e) { 41326 if (!isDefaultPrevented(e) && e.clipboardData && !editor.selection.isCollapsed()) { 41327 e.preventDefault(); 41328 e.clipboardData.clearData(); 41329 e.clipboardData.setData('text/html', editor.selection.getContent()); 41330 e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'})); 41331 41332 // Needed delay for https://code.google.com/p/chromium/issues/detail?id=363288#c3 41333 // Nested delete/forwardDelete not allowed on execCommand("cut") 41334 // This is ugly but not sure how to work around it otherwise 41335 Delay.setEditorTimeout(editor, function() { 41336 customDelete(true); 41337 }); 41338 } 41339 }); 41340 } 41341 41342 /** 41343 * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors. 41344 * 41345 * For example: 41346 * <p><b>|</b></p> 41347 * 41348 * Or: 41349 * <h1>|</h1> 41350 * 41351 * Or: 41352 * [<h1></h1>] 41353 */ 41354 function emptyEditorWhenDeleting() { 41355 function serializeRng(rng) { 41356 var body = dom.create("body"); 41357 var contents = rng.cloneContents(); 41358 body.appendChild(contents); 41359 return selection.serializer.serialize(body, {format: 'html'}); 41360 } 41361 41362 function allContentsSelected(rng) { 41363 if (!rng.setStart) { 41364 if (rng.item) { 41365 return false; 41366 } 41367 41368 var bodyRng = rng.duplicate(); 41369 bodyRng.moveToElementText(editor.getBody()); 41370 return RangeUtils.compareRanges(rng, bodyRng); 41371 } 41372 41373 var selection = serializeRng(rng); 41374 41375 var allRng = dom.createRng(); 41376 allRng.selectNode(editor.getBody()); 41377 41378 var allSelection = serializeRng(allRng); 41379 return selection === allSelection; 41380 } 41381 41382 editor.on('keydown', function(e) { 41383 var keyCode = e.keyCode, isCollapsed, body; 41384 41385 // Empty the editor if it's needed for example backspace at <p><b>|</b></p> 41386 if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) { 41387 isCollapsed = editor.selection.isCollapsed(); 41388 body = editor.getBody(); 41389 41390 // Selection is collapsed but the editor isn't empty 41391 if (isCollapsed && !dom.isEmpty(body)) { 41392 return; 41393 } 41394 41395 // Selection isn't collapsed but not all the contents is selected 41396 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) { 41397 return; 41398 } 41399 41400 // Manually empty the editor
41401 e.preventDefault(); 41402 editor.setContent(''); 41403 41404 if (body.firstChild && dom.isBlock(body.firstChild)) { 41405 editor.selection.setCursorLocation(body.firstChild, 0); 41406 } else { 41407 editor.selection.setCursorLocation(body, 0); 41408 } 41409 41410 editor.nodeChanged(); 41411 } 41412 }); 41413 } 41414 41415 /** 41416 * WebKit doesn't select all the nodes in the body when you press Ctrl+A. 41417 * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438 41418 * This selects the whole body so that backspace/delete logic will delete everything 41419 */ 41420 function selectAll() { 41421 editor.shortcuts.add('meta+a', null, 'SelectAll'); 41422 } 41423 41424 /** 41425 * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes. 41426 * The IME on Mac doesn't initialize when it doesn't fire a proper focus event. 41427 * 41428 * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until 41429 * you enter a character into the editor. 41430 * 41431 * It also happens when the first focus in made to the body. 41432 * 41433 * See: https://bugs.webkit.org/show_bug.cgi?id=83566 41434 */ 41435 function inputMethodFocus() { 41436 if (!editor.settings.content_editable) { 41437 // Case 1 IME doesn't initialize if you focus the document 41438 // Disabled since it was interferring with the cE=false logic 41439 // Also coultn't reproduce the issue on Safari 9 41440 /*dom.bind(editor.getDoc(), 'focusin', function() { 41441 selection.setRng(selection.getRng()); 41442 });*/ 41443 41444 // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event 41445 // Needs to be both down/up due to weird rendering bug on Chrome Windows 41446 dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) { 41447 var rng; 41448 41449 if (e.target == editor.getDoc().documentElement) { 41450 rng = selection.getRng(); 41451 editor.getBody().focus(); 41452 41453 if (e.type == 'mousedown') { 41454 if (CaretContainer.isCaretContainer(rng.startContainer)) { 41455 return; 41456 } 41457 41458 // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret 41459 selection.placeCaretAt(e.clientX, e.clientY); 41460 } else { 41461 selection.setRng(rng); 41462 } 41463 } 41464 }); 41465 } 41466 } 41467 41468 /** 41469 * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the 41470 * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is 41471 * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js 41472 * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other 41473 * browsers. 41474 * 41475 * It also fixes a bug on Firefox where it's impossible to delete HR elements. 41476 */ 41477 function removeHrOnBackspace() { 41478 editor.on('keydown', function(e) { 41479 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 41480 // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow 41481 if (!editor.getBody().getElementsByTagName('hr').length) { 41482 return; 41483 } 41484 41485 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 41486 var node = selection.getNode(); 41487 var previousSibling = node.previousSibling; 41488 41489 if (node.nodeName == 'HR') { 41490 dom.remove(node); 41491 e.preventDefault(); 41492 return; 41493 } 41494 41495 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") { 41496 dom.remove(previousSibling); 41497 e.preventDefault(); 41498 } 41499 } 41500 } 41501 }); 41502 } 41503 41504 /** 41505 * Firefox 3.x has an issue where the body element won't get proper focus if you click out 41506 * side it's rectangle. 41507 */ 41508 function focusBody() { 41509 // Fix for a focus bug in FF 3.x where the body element 41510 // wouldn't get proper focus if the user clicked on the HTML element 41511 if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4 41512 editor.on('mousedown', function(e) { 41513 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") { 41514 var body = editor.getBody(); 41515
41516 // Blur the body it's focused but not correctly focused 41517 body.blur(); 41518 41519 // Refocus the body after a little while 41520 Delay.setEditorTimeout(editor, function() { 41521 body.focus(); 41522 }); 41523 } 41524 }); 41525 } 41526 } 41527 41528 /** 41529 * WebKit has a bug where it isn't possible to select image, hr or anchor elements 41530 * by clicking on them so we need to fake that. 41531 */ 41532 function selectControlElements() { 41533 editor.on('click', function(e) { 41534 var target = e.target; 41535 41536 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250 41537 // WebKit can't even do simple things like selecting an image 41538 // Needs to be the setBaseAndExtend or it will fail to select floated images 41539 if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== "false") { 41540 e.preventDefault(); 41541 selection.getSel().setBaseAndExtent(target, 0, target, 1); 41542 editor.nodeChanged(); 41543 } 41544 41545 if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) { 41546 e.preventDefault(); 41547 selection.select(target); 41548 } 41549 }); 41550 } 41551 41552 /** 41553 * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements. 41554 * 41555 * Fixes do backspace/delete on this: 41556 * <p>bla[ck</p><p style="color:red">r]ed</p> 41557 * 41558 * Would become: 41559 * <p>bla|ed</p> 41560 * 41561 * Instead of: 41562 * <p style="color:red">bla|ed</p> 41563 */ 41564 function removeStylesWhenDeletingAcrossBlockElements() { 41565 function getAttributeApplyFunction() { 41566 var template = dom.getAttribs(selection.getStart().cloneNode(false)); 41567 41568 return function() { 41569 var target = selection.getStart(); 41570 41571 if (target !== editor.getBody()) { 41572 dom.setAttrib(target, "style", null); 41573 41574 each(template, function(attr) { 41575 target.setAttributeNode(attr.cloneNode(true)); 41576 }); 41577 } 41578 }; 41579 } 41580 41581 function isSelectionAcrossElements() { 41582 return !selection.isCollapsed() && 41583 dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock); 41584 } 41585 41586 editor.on('keypress', function(e) { 41587 var applyAttributes; 41588 41589 if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { 41590 applyAttributes = getAttributeApplyFunction(); 41591 editor.getDoc().execCommand('delete', false, null); 41592 applyAttributes(); 41593 e.preventDefault(); 41594 return false; 41595 } 41596 }); 41597 41598 dom.bind(editor.getDoc(), 'cut', function(e) { 41599 var applyAttributes; 41600 41601 if (!isDefaultPrevented(e) && isSelectionAcrossElements()) { 41602 applyAttributes = getAttributeApplyFunction(); 41603 41604 Delay.setEditorTimeout(editor, function() { 41605 applyAttributes(); 41606 }); 41607 } 41608 }); 41609 } 41610 41611 /** 41612 * Screen readers on IE needs to have the role application set on the body. 41613 */ 41614 function ensureBodyHasRoleApplication() { 41615 document.body.setAttribute("role", "application"); 41616 } 41617 41618 /** 41619 * Backspacing into a table behaves differently depending upon browser type. 41620 * Therefore, disable Backspace when cursor immediately follows a table. 41621 */ 41622 function disableBackspaceIntoATable() { 41623 editor.on('keydown', function(e) { 41624 if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) { 41625 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) { 41626 var previousSibling = selection.getNode().previousSibling; 41627 if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") { 41628 e.preventDefault(); 41629 return false; 41630 } 41631 } 41632 } 41633 }); 41634 } 41635 41636 /** 41637 * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this 41638 * logic adds a \n before the BR so that it will get rendered. 41639 */ 41640 function addNewLinesBeforeBrInPre() { 41641 // IE8+ rendering mode does the right thing with BR in PRE 41642 if (getDocumentMode() > 7) { 41643 return; 41644 } 41645 41646 // Enable display: none in area and add a specific class that hides all BR elements in PRE to 41647 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key 41648 setEditorCommandState('RespectVisibilityInDesign', true); 41649 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}'); 41650 dom.addClass(editor.getBody(), 'mceHideBrInPre'); 41651 41652 // Adds a \n before all BR elements in PRE to get them visual 41653 parser.addNodeFilter('pre', function(nodes) { 41654 var i = nodes.length, brNodes, j, brElm, sibling; 41655 41656 while (i--) { 41657 brNodes = nodes[i].getAll('br'); 41658 j = brNodes.length; 41659 while (j--) {
41660 brElm = brNodes[j]; 41661 41662 // Add \n before BR in PRE elements on older IE:s so the new lines get rendered 41663 sibling = brElm.prev; 41664 if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') { 41665 sibling.value += '\n'; 41666 } else { 41667 brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n'; 41668 } 41669 } 41670 } 41671 }); 41672 41673 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible 41674 serializer.addNodeFilter('pre', function(nodes) { 41675 var i = nodes.length, brNodes, j, brElm, sibling; 41676 41677 while (i--) { 41678 brNodes = nodes[i].getAll('br'); 41679 j = brNodes.length; 41680 while (j--) { 41681 brElm = brNodes[j]; 41682 sibling = brElm.prev; 41683 if (sibling && sibling.type == 3) { 41684 sibling.value = sibling.value.replace(/\r?\n$/, ''); 41685 } 41686 } 41687 } 41688 }); 41689 } 41690 41691 /** 41692 * Moves style width/height to attribute width/height when the user resizes an image on IE. 41693 */ 41694 function removePreSerializedStylesWhenSelectingControls() { 41695 dom.bind(editor.getBody(), 'mouseup', function() { 41696 var value, node = selection.getNode(); 41697 41698 // Moved styles to attributes on IMG eements 41699 if (node.nodeName == 'IMG') { 41700 // Convert style width to width attribute 41701 if ((value = dom.getStyle(node, 'width'))) { 41702 dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, '')); 41703 dom.setStyle(node, 'width', ''); 41704 } 41705 41706 // Convert style height to height attribute 41707 if ((value = dom.getStyle(node, 'height'))) { 41708 dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, '')); 41709 dom.setStyle(node, 'height', ''); 41710 } 41711 } 41712 }); 41713 } 41714 41715 /** 41716 * Removes a blockquote when backspace is pressed at the beginning of it. 41717 * 41718 * For example: 41719 * <blockquote><p>|x</p></blockquote> 41720 * 41721 * Becomes: 41722 * <p>|x</p> 41723 */ 41724 function removeBlockQuoteOnBackSpace() { 41725 // Add block quote deletion handler 41726 editor.on('keydown', function(e) { 41727 var rng, container, offset, root, parent; 41728 41729 if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) { 41730 return; 41731 } 41732 41733 rng = selection.getRng(); 41734 container = rng.startContainer; 41735 offset = rng.startOffset; 41736 root = dom.getRoot(); 41737 parent = container; 41738 41739 if (!rng.collapsed || offset !== 0) { 41740 return; 41741 } 41742 41743 while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) { 41744 parent = parent.parentNode; 41745 } 41746 41747 // Is the cursor at the beginning of a blockquote? 41748 if (parent.tagName === 'BLOCKQUOTE') { 41749 // Remove the blockquote 41750 editor.formatter.toggle('blockquote', null, parent); 41751 41752 // Move the caret to the beginning of container 41753 rng = dom.createRng(); 41754 rng.setStart(container, 0); 41755 rng.setEnd(container, 0); 41756 selection.setRng(rng); 41757 } 41758 }); 41759 } 41760 41761 /** 41762 * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc. 41763 */ 41764 function setGeckoEditingOptions() { 41765 function setOpts() { 41766 editor._refreshContentEditable(); 41767 41768 setEditorCommandState("StyleWithCSS", false); 41769 setEditorCommandState("enableInlineTableEditing", false); 41770 41771 if (!settings.object_resizing) { 41772 setEditorCommandState("enableObjectResizing", false); 41773 } 41774 } 41775 41776 if (!settings.readonly) { 41777 editor.on('BeforeExecCommand MouseDown', setOpts); 41778 } 41779 } 41780 41781 /** 41782 * Fixes a gecko link bug, when a link is placed at the end of block elements there is 41783 * no way to move the caret behind the link. This fix adds a bogus br element after the link. 41784 * 41785 * For example this: 41786 * <p><b><a href="#">x</a></b></p> 41787 * 41788 * Becomes this: 41789 * <p><b><a href="#">x</a></b><br></p> 41790 */ 41791 function addBrAfterLastLinks() { 41792 function fixLinks() { 41793 each(dom.select('a'), function(node) { 41794 var parentNode = node.parentNode, root = dom.getRoot(); 41795 41796 if (parentNode.lastChild === node) { 41797 while (parentNode && !dom.isBlock(parentNode)) { 41798 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) { 41799 return; 41800 } 41801 41802 parentNode = parentNode.parentNode; 41803 } 41804 41805 dom.add(parentNode, 'br', {'data-mce-bogus': 1}); 41806 } 41807 }); 41808 } 41809 41810 editor.on('SetContent ExecCommand', function(e) { 41811 if (e.type == "setcontent" || e.command === 'mceInsertLink') { 41812 fixLinks(); 41813 } 41814 }); 41815 } 41816 41817 /** 41818 * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by 41819 * default we want to change that behavior. 41820 */ 41821 function setDefaultBlockType() { 41822 if (settings.forced_root_block) { 41823 editor.on('init', function() { 41824 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block); 41825 }); 41826 } 41827 } 41828 41829 /** 41830 * Deletes the selected image on IE instead of navigating to previous page. 41831 */ 41832 function deleteControlItemOnBackSpace() {
41833 editor.on('keydown', function(e) { 41834 var rng; 41835 41836 if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) { 41837 rng = editor.getDoc().selection.createRange(); 41838 if (rng && rng.item) { 41839 e.preventDefault(); 41840 editor.undoManager.beforeChange(); 41841 dom.remove(rng.item(0)); 41842 editor.undoManager.add(); 41843 } 41844 } 41845 }); 41846 } 41847 41848 /** 41849 * IE10 doesn't properly render block elements with the right height until you add contents to them. 41850 * This fixes that by adding a padding-right to all empty text block elements. 41851 * See: https://connect.microsoft.com/IE/feedback/details/743881 41852 */ 41853 function renderEmptyBlocksFix() { 41854 var emptyBlocksCSS; 41855 41856 // IE10+ 41857 if (getDocumentMode() >= 10) { 41858 emptyBlocksCSS = ''; 41859 each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) { 41860 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty'; 41861 }); 41862 41863 editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}'); 41864 } 41865 } 41866 41867 /** 41868 * Old IE versions can't retain contents within noscript elements so this logic will store the contents 41869 * as a attribute and the insert that value as it's raw text when the DOM is serialized. 41870 */ 41871 function keepNoScriptContents() { 41872 if (getDocumentMode() < 9) { 41873 parser.addNodeFilter('noscript', function(nodes) { 41874 var i = nodes.length, node, textNode; 41875 41876 while (i--) { 41877 node = nodes[i]; 41878 textNode = node.firstChild; 41879 41880 if (textNode) { 41881 node.attr('data-mce-innertext', textNode.value); 41882 } 41883 } 41884 }); 41885 41886 serializer.addNodeFilter('noscript', function(nodes) { 41887 var i = nodes.length, node, textNode, value; 41888 41889 while (i--) { 41890 node = nodes[i]; 41891 textNode = nodes[i].firstChild; 41892 41893 if (textNode) { 41894 textNode.value = Entities.decode(textNode.value); 41895 } else { 41896 // Old IE can't retain noscript value so an attribute is used to store it 41897 value = node.attributes.map['data-mce-innertext']; 41898 if (value) { 41899 node.attr('data-mce-innertext', null); 41900 textNode = new Node('#text', 3); 41901 textNode.value = value; 41902 textNode.raw = true; 41903 node.append(textNode); 41904 } 41905 } 41906 } 41907 }); 41908 } 41909 } 41910 41911 /** 41912 * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode. 41913 */ 41914 function fixCaretSelectionOfDocumentElementOnIe() { 41915 var doc = dom.doc, body = doc.body, started, startRng, htmlElm; 41916 41917 // Return range from point or null if it failed 41918 function rngFromPoint(x, y) { 41919 var rng = body.createTextRange(); 41920 41921 try { 41922 rng.moveToPoint(x, y); 41923 } catch (ex) { 41924 // IE sometimes throws and exception, so lets just ignore it 41925 rng = null; 41926 } 41927 41928 return rng; 41929 } 41930 41931 // Fires while the selection is changing 41932 function selectionChange(e) { 41933 var pointRng; 41934 41935 // Check if the button is down or not 41936 if (e.button) { 41937 // Create range from mouse position 41938 pointRng = rngFromPoint(e.x, e.y); 41939 41940 if (pointRng) { 41941 // Check if pointRange is before/after selection then change the endPoint 41942 if (pointRng.compareEndPoints('StartToStart', startRng) > 0) { 41943 pointRng.setEndPoint('StartToStart', startRng); 41944 } else { 41945 pointRng.setEndPoint('EndToEnd', startRng); 41946 } 41947 41948 pointRng.select(); 41949 } 41950 } else { 41951 endSelection(); 41952 } 41953 } 41954 41955 // Removes listeners 41956 function endSelection() { 41957 var rng = doc.selection.createRange(); 41958 41959 // If the range is collapsed then use the last start range 41960 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) { 41961 startRng.select(); 41962 } 41963 41964 dom.unbind(doc, 'mouseup', endSelection); 41965 dom.unbind(doc, 'mousemove', selectionChange); 41966 startRng = started = 0; 41967 } 41968 41969 // Make HTML element unselectable since we are going to handle selection by hand 41970 doc.documentElement.unselectable = true; 41971 41972 // Detect when user selects outside BODY 41973 dom.bind(doc, 'mousedown contextmenu', function(e) { 41974 if (e.target.nodeName === 'HTML') { 41975 if (started) { 41976 endSelection(); 41977 } 41978 41979 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML 41980 htmlElm = doc.documentElement; 41981 if (htmlElm.scrollHeight > htmlElm.clientHeight) { 41982 return; 41983 } 41984 41985 started = 1; 41986 // Setup start position 41987 startRng = rngFromPoint(e.x, e.y); 41988 if (startRng) { 41989 // Listen for selection change events 41990 dom.bind(doc, 'mouseup', endSelection); 41991 dom.bind(doc, 'mousemove', selectionChange); 41992 41993 dom.getRoot().focus(); 41994 startRng.select(); 41995 } 41996 } 41997 }); 41998 } 41999 42000 /** 42001 * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b> 42002 * this fix will lean the caret right into the closest inline element. 42003 */ 42004 function normalizeSelection() { 42005 // 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
42006 editor.on('keyup focusin mouseup', function(e) { 42007 if (e.keyCode != 65 || !VK.metaKeyPressed(e)) { 42008 selection.normalize(); 42009 } 42010 }, true); 42011 } 42012 42013 /** 42014 * Forces Gecko to render a broken image icon if it fails to load an image. 42015 */ 42016 function showBrokenImageIcon() { 42017 editor.contentStyles.push( 42018 'img:-moz-broken {' + 42019 '-moz-force-broken-image-icon:1;' + 42020 'min-width:24px;' + 42021 'min-height:24px' + 42022 '}' 42023 ); 42024 } 42025 42026 /** 42027 * iOS has a bug where it's impossible to type if the document has a touchstart event 42028 * bound and the user touches the document while having the on screen keyboard visible. 42029 * 42030 * The touch event moves the focus to the parent document while having the caret inside the iframe 42031 * this fix moves the focus back into the iframe document. 42032 */ 42033 function restoreFocusOnKeyDown() { 42034 if (!editor.inline) { 42035 editor.on('keydown', function() { 42036 if (document.activeElement == document.body) { 42037 editor.getWin().focus(); 42038 } 42039 }); 42040 } 42041 } 42042 42043 /** 42044 * IE 11 has an annoying issue where you can't move focus into the editor 42045 * by clicking on the white area HTML element. We used to be able to to fix this with 42046 * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection 42047 * object it's not possible anymore. So we need to hack in a ungly CSS to force the 42048 * body to be at least 150px. If the user clicks the HTML element out side this 150px region 42049 * we simply move the focus into the first paragraph. Not ideal since you loose the 42050 * positioning of the caret but goot enough for most cases. 42051 */ 42052 function bodyHeight() { 42053 if (!editor.inline) { 42054 editor.contentStyles.push('body {min-height: 150px}'); 42055 editor.on('click', function(e) { 42056 var rng; 42057 42058 if (e.target.nodeName == 'HTML') { 42059 // Edge seems to only need focus if we set the range 42060 // the caret will become invisible and moved out of the iframe!! 42061 if (Env.ie > 11) { 42062 editor.getBody().focus(); 42063 return; 42064 } 42065 42066 // Need to store away non collapsed ranges since the focus call will mess that up see #7382 42067 rng = editor.selection.getRng(); 42068 editor.getBody().focus(); 42069 editor.selection.setRng(rng); 42070 editor.selection.normalize(); 42071 editor.nodeChanged(); 42072 } 42073 }); 42074 } 42075 } 42076 42077 /** 42078 * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow. 42079 * You might then loose all your work so we need to block that behavior and replace it with our own. 42080 */ 42081 function blockCmdArrowNavigation() { 42082 if (Env.mac) { 42083 editor.on('keydown', function(e) { 42084 if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode == 37 || e.keyCode == 39)) { 42085 e.preventDefault(); 42086 editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary'); 42087 } 42088 }); 42089 } 42090 } 42091 42092 /** 42093 * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin. 42094 */ 42095 function disableAutoUrlDetect() { 42096 setEditorCommandState("AutoUrlDetect", false); 42097 } 42098 42099 /** 42100 * iOS 7.1 introduced two new bugs: 42101 * 1) It's possible to open links within a contentEditable area by clicking on them. 42102 * 2) If you hold down the finger it will display the link/image touch callout menu. 42103 */ 42104 function tapLinksAndImages() { 42105 editor.on('click', function(e) { 42106 var elm = e.target; 42107 42108 do { 42109 if (elm.tagName === 'A') { 42110 e.preventDefault(); 42111 return; 42112 } 42113 } while ((elm = elm.parentNode)); 42114 }); 42115 42116 editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}'); 42117 } 42118 42119 /** 42120 * iOS Safari and possible other browsers have a bug where it won't fire 42121 * a click event when a contentEditable is focused. This function fakes click events 42122 * by using touchstart/touchend and measuring the time and distance travelled. 42123 */ 42124 /* 42125 function touchClickEvent() { 42126 editor.on('touchstart', function(e) { 42127 var elm, time, startTouch, changedTouches; 42128 42129 elm = e.target; 42130 time = new Date().getTime(); 42131 changedTouches = e.changedTouches; 42132 42133 if (!changedTouches || changedTouches.length > 1) { 42134 return; 42135 } 42136 42137 startTouch = changedTouches[0]; 42138 42139 editor.once('touchend', function(e) { 42140 var endTouch = e.changedTouches[0], args; 42141 42142 if (new Date().getTime() - time > 500) { 42143 return; 42144 } 42145 42146 if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) { 42147 return; 42148 } 42149 42150 if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) { 42151 return; 42152 } 42153 42154 args = { 42155 target: elm 42156 }; 42157 42158 each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) { 42159 args[key] = endTouch[key]; 42160 }); 42161
42162 args = editor.fire('click', args); 42163 42164 if (!args.isDefaultPrevented()) { 42165 // iOS WebKit can't place the caret properly once 42166 // you bind touch events so we need to do this manually 42167 // TODO: Expand to the closest word? Touble tap still works. 42168 editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY); 42169 editor.nodeChanged(); 42170 } 42171 }); 42172 }); 42173 } 42174 */ 42175 42176 /** 42177 * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element. 42178 * For example this: <form><button></form> 42179 */ 42180 function blockFormSubmitInsideEditor() { 42181 editor.on('init', function() { 42182 editor.dom.bind(editor.getBody(), 'submit', function(e) { 42183 e.preventDefault(); 42184 }); 42185 }); 42186 } 42187 42188 /** 42189 * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class. 42190 * 42191 * Scenario: 42192 * 1) Create a table 2x2. 42193 * 2) Select and copy cells A2-B2. 42194 * 3) Paste and it will add BR element to table cell. 42195 */ 42196 function removeAppleInterchangeBrs() { 42197 parser.addNodeFilter('br', function(nodes) { 42198 var i = nodes.length; 42199 42200 while (i--) { 42201 if (nodes[i].attr('class') == 'Apple-interchange-newline') { 42202 nodes[i].remove(); 42203 } 42204 } 42205 }); 42206 } 42207 42208 /** 42209 * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between 42210 * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors. 42211 */ 42212 function ieInternalDragAndDrop() { 42213 editor.on('dragstart', function(e) { 42214 setMceInteralContent(e); 42215 }); 42216 42217 editor.on('drop', function(e) { 42218 if (!isDefaultPrevented(e)) { 42219 var internalContent = getMceInternalContent(e); 42220 42221 if (internalContent && internalContent.id != editor.id) { 42222 e.preventDefault(); 42223 42224 var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc()); 42225 selection.setRng(rng); 42226 insertClipboardContents(internalContent.html); 42227 } 42228 } 42229 }); 42230 } 42231 42232 // All browsers 42233 removeBlockQuoteOnBackSpace(); 42234 emptyEditorWhenDeleting(); 42235 normalizeSelection(); 42236 42237 // WebKit 42238 if (isWebKit) { 42239 cleanupStylesWhenDeleting(); 42240 inputMethodFocus(); 42241 selectControlElements(); 42242 setDefaultBlockType(); 42243 blockFormSubmitInsideEditor(); 42244 disableBackspaceIntoATable(); 42245 removeAppleInterchangeBrs(); 42246 //touchClickEvent(); 42247 42248 // iOS 42249 if (Env.iOS) { 42250 restoreFocusOnKeyDown(); 42251 bodyHeight(); 42252 tapLinksAndImages(); 42253 } else { 42254 selectAll(); 42255 } 42256 } 42257 42258 // IE 42259 if (isIE && Env.ie < 11) { 42260 removeHrOnBackspace(); 42261 ensureBodyHasRoleApplication(); 42262 addNewLinesBeforeBrInPre(); 42263 removePreSerializedStylesWhenSelectingControls(); 42264 deleteControlItemOnBackSpace(); 42265 renderEmptyBlocksFix(); 42266 keepNoScriptContents(); 42267 fixCaretSelectionOfDocumentElementOnIe(); 42268 } 42269 42270 if (Env.ie >= 11) { 42271 bodyHeight(); 42272 disableBackspaceIntoATable(); 42273 } 42274 42275 if (Env.ie) { 42276 selectAll(); 42277 disableAutoUrlDetect(); 42278 ieInternalDragAndDrop(); 42279 } 42280 42281 // Gecko 42282 if (isGecko) { 42283 removeHrOnBackspace(); 42284 focusBody(); 42285 removeStylesWhenDeletingAcrossBlockElements(); 42286 setGeckoEditingOptions(); 42287 addBrAfterLastLinks(); 42288 showBrokenImageIcon(); 42289 blockCmdArrowNavigation(); 42290 disableBackspaceIntoATable(); 42291 } 42292 }; 42293}); 42294 42295// Included from: js/tinymce/classes/EditorObservable.js 42296 42297/** 42298 * EditorObservable.js 42299 * 42300 * Released under LGPL License. 42301 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 42302 * 42303 * License: http://www.tinymce.com/license 42304 * Contributing: http://www.tinymce.com/contributing 42305 */ 42306 42307/** 42308 * This mixin contains the event logic for the tinymce.Editor class. 42309 * 42310 * @mixin tinymce.EditorObservable 42311 * @extends tinymce.util.Observable 42312 */ 42313define("tinymce/EditorObservable", [ 42314 "tinymce/util/Observable", 42315 "tinymce/dom/DOMUtils", 42316 "tinymce/util/Tools" 42317], function(Observable, DOMUtils, Tools) { 42318 var DOM = DOMUtils.DOM, customEventRootDelegates; 42319 42320 /** 42321 * Returns the event target so for the specified event. Some events fire 42322 * only on document, some fire on documentElement etc. This also handles the 42323 * custom event root setting where it returns that element instead of the body. 42324 * 42325 * @private 42326 * @param {tinymce.Editor} editor Editor instance to get event target from. 42327 * @param {String} eventName Name of the event for example "click". 42328 * @return {Element/Document} HTML Element or document target to bind on. 42329 */ 42330 function getEventTarget(editor, eventName) { 42331 if (eventName == 'selectionchange') { 42332 return editor.getDoc(); 42333 } 42334 42335 // Need to bind mousedown/mouseup etc to document not body in iframe mode 42336 // Since the user might click on the HTML element not the BODY 42337 if (!editor.inline && /^mouse|click|contextmenu|drop|dragover|dragend/.test(eventName)) { 42338 return editor.getDoc().documentElement; 42339 } 42340 42341 // Bind to event root instead of body if it's defined 42342 if (editor.settings.event_root) { 42343 if (!editor.eventRoot) { 42344 editor.eventRoot = DOM.select(editor.settings.event_root)[0]; 42345 } 42346 42347 return editor.eventRoot; 42348 } 42349 42350 return editor.getBody(); 42351 } 42352 42353 /** 42354 * Binds a event delegate for the specified name this delegate will fire 42355 * the event to the editor dispatcher. 42356 * 42357 * @private 42358 * @param {tinymce.Editor} editor Editor instance to get event target from. 42359 * @param {String} eventName Name of the event for example "click". 42360 */ 42361 function bindEventDelegate(editor, eventName) { 42362 var eventRootElm = getEventTarget(editor, eventName), delegate; 42363 42364 function isListening(editor) { 42365 return !editor.hidden && !editor.readonly; 42366 } 42367 42368 if (!editor.delegates) { 42369 editor.delegates = {}; 42370 } 42371 42372 if (editor.delegates[eventName]) { 42373 return; 42374 } 42375 42376 if (editor.settings.event_root) { 42377 if (!customEventRootDelegates) { 42378 customEventRootDelegates = {}; 42379 editor.editorManager.on('removeEditor', function() { 42380 var name; 42381 42382 if (!editor.editorManager.activeEditor) { 42383 if (customEventRootDelegates) { 42384 for (name in customEventRootDelegates) { 42385 editor.dom.unbind(getEventTarget(editor, name)); 42386 } 42387 42388 customEventRootDelegates = null; 42389 } 42390 } 42391 }); 42392 } 42393 42394 if (customEventRootDelegates[eventName]) { 42395 return; 42396 } 42397 42398 delegate = function(e) { 42399 var target = e.target, editors = editor.editorManager.editors, i = editors.length; 42400 42401 while (i--) { 42402 var body = editors[i].getBody(); 42403 42404 if (body === target || DOM.isChildOf(target, body)) { 42405 if (isListening(editors[i])) { 42406 editors[i].fire(eventName, e); 42407 } 42408 } 42409 } 42410 }; 42411 42412 customEventRootDelegates[eventName] = delegate; 42413 DOM.bind(eventRootElm, eventName, delegate); 42414 } else { 42415 delegate = function(e) { 42416 if (isListening(editor)) { 42417 editor.fire(eventName, e); 42418 } 42419 }; 42420 42421 DOM.bind(eventRootElm, eventName, delegate); 42422 editor.delegates[eventName] = delegate; 42423 } 42424 } 42425 42426 var EditorObservable = { 42427 /** 42428 * Bind any pending event delegates. This gets executed after the target body/document is created. 42429 * 42430 * @private 42431 */ 42432 bindPendingEventDelegates: function() { 42433 var self = this; 42434 42435 Tools.each(self._pendingNativeEvents, function(name) { 42436 bindEventDelegate(self, name); 42437 }); 42438 }, 42439 42440 /** 42441 * Toggles a native event on/off this is called by the EventDispatcher when 42442 * the first native event handler is added and when the last native event handler is removed. 42443 * 42444 * @private 42445 */ 42446 toggleNativeEvent: function(name, state) { 42447 var self = this; 42448 42449 // Never bind focus/blur since the FocusManager fakes those 42450 if (name == "focus" || name == "blur") { 42451 return; 42452 } 42453 42454 if (state) { 42455 if (self.initialized) { 42456 bindEventDelegate(self, name); 42457 } else { 42458 if (!self._pendingNativeEvents) { 42459 self._pendingNativeEvents = [name]; 42460 } else { 42461 self._pendingNativeEvents.push(name); 42462 } 42463 } 42464 } else if (self.initialized) { 42465 self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); 42466 delete self.delegates[name]; 42467 } 42468 }, 42469 42470 /** 42471 * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc. 42472 * 42473 * @private 42474 */ 42475 unbindAllNativeEvents: function() { 42476 var self = this, name; 42477 42478 if (self.delegates) { 42479 for (name in self.delegates) { 42480 self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]); 42481 } 42482 42483 delete self.delegates; 42484 } 42485 42486 if (!self.inline) { 42487 self.getBody().onload = null; 42488 self.dom.unbind(self.getWin()); 42489 self.dom.unbind(self.getDoc()); 42490 } 42491 42492 self.dom.unbind(self.getBody()); 42493 self.dom.unbind(self.getContainer()); 42494 } 42495 }; 42496 42497 EditorObservable = Tools.extend({}, Observable, EditorObservable); 42498 42499 return EditorObservable; 42500}); 42501 42502// Included from: js/tinymce/classes/Mode.js 42503 42504/** 42505 * Mode.js 42506 * 42507 * Released under LGPL License. 42508 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 42509 * 42510 * License: http://www.tinymce.com/license 42511 * Contributing: http://www.tinymce.com/contributing 42512 */ 42513 42514/** 42515 * Mode switcher logic. 42516 * 42517 * @private 42518 * @class tinymce.Mode 42519 */ 42520define("tinymce/Mode", [], function() { 42521 function setEditorCommandState(editor, cmd, state) { 42522 try { 42523 editor.getDoc().execCommand(cmd, false, state); 42524 } catch (ex) { 42525 // Ignore 42526 } 42527 } 42528 42529 function setMode(editor, mode) {
42530 var currentMode = editor.readonly ? 'readonly' : 'design'; 42531 42532 if (mode == currentMode) { 42533 return; 42534 } 42535 42536 if (mode == 'readonly') { 42537 editor.selection.controlSelection.hideResizeRect(); 42538 editor.readonly = true; 42539 editor.getBody().contentEditable = false; 42540 } else { 42541 editor.readonly = false; 42542 editor.getBody().contentEditable = true; 42543 setEditorCommandState(editor, "StyleWithCSS", false); 42544 setEditorCommandState(editor, "enableInlineTableEditing", false); 42545 setEditorCommandState(editor, "enableObjectResizing", false); 42546 editor.focus(); 42547 editor.nodeChanged(); 42548 } 42549 42550 // Event is NOT preventable 42551 editor.fire('SwitchMode', {mode: mode}); 42552 } 42553 42554 return { 42555 setMode: setMode 42556 }; 42557}); 42558 42559// Included from: js/tinymce/classes/Shortcuts.js 42560 42561/** 42562 * Shortcuts.js 42563 * 42564 * Released under LGPL License. 42565 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 42566 * 42567 * License: http://www.tinymce.com/license 42568 * Contributing: http://www.tinymce.com/contributing 42569 */ 42570 42571/** 42572 * Contains all logic for handling of keyboard shortcuts. 42573 * 42574 * @class tinymce.Shortcuts 42575 * @example 42576 * editor.shortcuts.add('ctrl+a', function() {}); 42577 * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC 42578 * editor.shortcuts.add('ctrl+alt+a', function() {}); 42579 * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC 42580 */ 42581define("tinymce/Shortcuts", [ 42582 "tinymce/util/Tools", 42583 "tinymce/Env" 42584], function(Tools, Env) { 42585 var each = Tools.each, explode = Tools.explode; 42586 42587 var keyCodeLookup = { 42588 "f9": 120, 42589 "f10": 121, 42590 "f11": 122 42591 }; 42592 42593 var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access'); 42594 42595 return function(editor) { 42596 var self = this, shortcuts = {}; 42597 42598 function createShortcut(pattern, desc, cmdFunc, scope) { 42599 var id, key, shortcut; 42600 42601 shortcut = { 42602 func: cmdFunc, 42603 scope: scope || editor, 42604 desc: editor.translate(desc) 42605 }; 42606 42607 // Parse modifiers and keys ctrl+alt+b for example 42608 each(explode(pattern, '+'), function(value) { 42609 if (value in modifierNames) { 42610 shortcut[value] = true; 42611 } else { 42612 // Allow numeric keycodes like ctrl+219 for ctrl+[ 42613 if (/^[0-9]{2,}$/.test(value)) { 42614 shortcut.keyCode = parseInt(value, 10); 42615 } else { 42616 shortcut.charCode = value.charCodeAt(0); 42617 shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0); 42618 } 42619 } 42620 }); 42621 42622 // Generate unique id for modifier combination and set default state for unused modifiers 42623 id = [shortcut.keyCode]; 42624 for (key in modifierNames) { 42625 if (shortcut[key]) { 42626 id.push(key); 42627 } else { 42628 shortcut[key] = false; 42629 } 42630 } 42631 shortcut.id = id.join(','); 42632 42633 // Handle special access modifier differently depending on Mac/Win 42634 if (shortcut.access) { 42635 shortcut.alt = true; 42636 42637 if (Env.mac) { 42638 shortcut.ctrl = true; 42639 } else { 42640 shortcut.shift = true; 42641 } 42642 } 42643 42644 // Handle special meta modifier differently depending on Mac/Win 42645 if (shortcut.meta) { 42646 if (Env.mac) { 42647 shortcut.meta = true; 42648 } else { 42649 shortcut.ctrl = true; 42650 shortcut.meta = false; 42651 } 42652 } 42653 42654 return shortcut; 42655 } 42656 42657 editor.on('keyup keypress keydown', function(e) { 42658 if ((e.altKey || e.ctrlKey || e.metaKey) && !e.isDefaultPrevented()) { 42659 each(shortcuts, function(shortcut) { 42660 if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) { 42661 return; 42662 } 42663 42664 if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) { 42665 return; 42666 } 42667 42668 if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) { 42669 e.preventDefault(); 42670 42671 if (e.type == "keydown") { 42672 shortcut.func.call(shortcut.scope); 42673 } 42674 42675 return true; 42676 } 42677 }); 42678 } 42679 }); 42680 42681 /** 42682 * Adds a keyboard shortcut for some command or function. 42683 * 42684 * @method addShortcut 42685 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 42686 * @param {String} desc Text description for the command. 42687 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 42688 * @param {Object} scope Optional scope to execute the function in. 42689 * @return {Boolean} true/false state if the shortcut was added or not. 42690 */ 42691 self.add = function(pattern, desc, cmdFunc, scope) { 42692 var cmd; 42693 42694 cmd = cmdFunc; 42695 42696 if (typeof cmdFunc === 'string') { 42697 cmdFunc = function() { 42698 editor.execCommand(cmd, false, null); 42699 }; 42700 } else if (Tools.isArray(cmd)) { 42701 cmdFunc = function() { 42702 editor.execCommand(cmd[0], cmd[1], cmd[2]); 42703 }; 42704 } 42705 42706 each(explode(pattern.toLowerCase()), function(pattern) { 42707 var shortcut = createShortcut(pattern, desc, cmdFunc, scope); 42708 shortcuts[shortcut.id] = shortcut; 42709 }); 42710 42711 return true; 42712 }; 42713 42714 /** 42715 * Remove a keyboard shortcut by pattern. 42716 * 42717 * @method remove 42718 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 42719 * @return {Boolean} true/false state if the shortcut was removed or not. 42720 */ 42721 self.remove = function(pattern) { 42722 var shortcut = createShortcut(pattern); 42723 42724 if (shortcuts[shortcut.id]) { 42725 delete shortcuts[shortcut.id]; 42726 return true; 42727 } 42728 42729 return false;
42730 }; 42731 }; 42732}); 42733 42734// Included from: js/tinymce/classes/file/Uploader.js 42735 42736/** 42737 * Uploader.js 42738 * 42739 * Released under LGPL License. 42740 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 42741 * 42742 * License: http://www.tinymce.com/license 42743 * Contributing: http://www.tinymce.com/contributing 42744 */ 42745 42746/** 42747 * Upload blobs or blob infos to the specified URL or handler. 42748 * 42749 * @private 42750 * @class tinymce.file.Uploader 42751 * @example 42752 * var uploader = new Uploader({ 42753 * url: '/upload.php', 42754 * basePath: '/base/path', 42755 * credentials: true, 42756 * handler: function(data, success, failure) { 42757 * ... 42758 * } 42759 * }); 42760 * 42761 * uploader.upload(blobInfos).then(function(result) { 42762 * ... 42763 * }); 42764 */ 42765define("tinymce/file/Uploader", [ 42766 "tinymce/util/Promise", 42767 "tinymce/util/Tools", 42768 "tinymce/util/Fun" 42769], function(Promise, Tools, Fun) { 42770 return function(settings) { 42771 var cachedPromises = {}; 42772 42773 function fileName(blobInfo) { 42774 var ext, extensions; 42775 42776 extensions = { 42777 'image/jpeg': 'jpg', 42778 'image/jpg': 'jpg', 42779 'image/gif': 'gif', 42780 'image/png': 'png' 42781 }; 42782 42783 ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat'; 42784 42785 return blobInfo.id() + '.' + ext; 42786 } 42787 42788 function pathJoin(path1, path2) { 42789 if (path1) { 42790 return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, ''); 42791 } 42792 42793 return path2; 42794 } 42795 42796 function blobInfoToData(blobInfo) { 42797 return { 42798 id: blobInfo.id, 42799 blob: blobInfo.blob, 42800 base64: blobInfo.base64, 42801 filename: Fun.constant(fileName(blobInfo)) 42802 }; 42803 } 42804 42805 function defaultHandler(blobInfo, success, failure, openNotification) { 42806 var xhr, formData, notification; 42807 42808 xhr = new XMLHttpRequest(); 42809 xhr.open('POST', settings.url); 42810 xhr.withCredentials = settings.credentials; 42811 42812 notification = openNotification(); 42813 42814 xhr.upload.onprogress = function(e) { 42815 var percentLoaded = Math.round(e.loaded / e.total * 100); 42816 notification.progressBar.value(percentLoaded); 42817 }; 42818 42819 xhr.onload = function() { 42820 var json; 42821 42822 notification.close(); 42823 42824 if (xhr.status != 200) { 42825 failure("HTTP Error: " + xhr.status); 42826 return; 42827 } 42828 42829 json = JSON.parse(xhr.responseText); 42830 42831 if (!json || typeof json.location != "string") { 42832 failure("Invalid JSON: " + xhr.responseText); 42833 return; 42834 } 42835 42836 success(pathJoin(settings.basePath, json.location)); 42837 }; 42838 42839 formData = new FormData(); 42840 formData.append('file', blobInfo.blob(), fileName(blobInfo)); 42841 42842 xhr.send(formData); 42843 } 42844 42845 function noUpload() { 42846 return new Promise(function(resolve) { 42847 resolve([]); 42848 }); 42849 } 42850 42851 function interpretResult(promise) { 42852 return promise.then(function(result) { 42853 return result; 42854 })['catch'](function(error) { 42855 return error; 42856 }); 42857 } 42858 42859 function registerPromise(handler, id, blobInfo) { 42860 var response = handler(blobInfo); 42861 var promise = interpretResult(response); 42862 delete cachedPromises[id]; 42863 cachedPromises[id] = promise; 42864 return promise; 42865 } 42866 42867 function collectUploads(blobInfos, uploadBlobInfo) { 42868 return Tools.map(blobInfos, function(blobInfo) { 42869 var id = blobInfo.id(); 42870 return cachedPromises[id] ? cachedPromises[id] : registerPromise(uploadBlobInfo, id, blobInfo); 42871 }); 42872 } 42873 42874 function uploadBlobs(blobInfos, openNotification) { 42875 function uploadBlobInfo(blobInfo) { 42876 return new Promise(function(resolve) { 42877 var handler = settings.handler; 42878 42879 handler(blobInfoToData(blobInfo), function(url) { 42880 resolve({ 42881 url: url, 42882 blobInfo: blobInfo, 42883 status: true 42884 }); 42885 }, function(failure) { 42886 resolve({ 42887 url: '', 42888 blobInfo: blobInfo, 42889 status: false, 42890 error: failure 42891 }); 42892 }, openNotification); 42893 }); 42894 } 42895 42896 var promises = collectUploads(blobInfos, uploadBlobInfo); 42897 return Promise.all(promises); 42898 } 42899 42900 function upload(blobInfos, openNotification) { 42901 return (!settings.url && settings.handler === defaultHandler) ? noUpload() : uploadBlobs(blobInfos, openNotification); 42902 } 42903 42904 settings = Tools.extend({ 42905 credentials: false, 42906 // We are adding a notify argument to this (at the moment, until it doesn't work) 42907 handler: defaultHandler 42908 }, settings); 42909 42910 return { 42911 upload: upload 42912 }; 42913 }; 42914}); 42915 42916// Included from: js/tinymce/classes/file/Conversions.js 42917 42918/** 42919 * Conversions.js 42920 * 42921 * Released under LGPL License. 42922 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 42923 * 42924 * License: http://www.tinymce.com/license 42925 * Contributing: http://www.tinymce.com/contributing 42926 */ 42927 42928/** 42929 * Converts blob/uris back and forth. 42930 * 42931 * @private 42932 * @class tinymce.file.Conversions 42933 */ 42934define("tinymce/file/Conversions", [
42935 "tinymce/util/Promise" 42936], function(Promise) { 42937 function blobUriToBlob(url) { 42938 return new Promise(function(resolve) { 42939 var xhr = new XMLHttpRequest(); 42940 42941 xhr.open('GET', url, true); 42942 xhr.responseType = 'blob'; 42943 42944 xhr.onload = function() { 42945 if (this.status == 200) { 42946 resolve(this.response); 42947 } 42948 }; 42949 42950 xhr.send(); 42951 }); 42952 } 42953 42954 function parseDataUri(uri) { 42955 var type, matches; 42956 42957 uri = decodeURIComponent(uri).split(','); 42958 42959 matches = /data:([^;]+)/.exec(uri[0]); 42960 if (matches) { 42961 type = matches[1]; 42962 } 42963 42964 return { 42965 type: type, 42966 data: uri[1] 42967 }; 42968 } 42969 42970 function dataUriToBlob(uri) { 42971 return new Promise(function(resolve) { 42972 var str, arr, i; 42973 42974 uri = parseDataUri(uri); 42975 42976 // Might throw error if data isn't proper base64 42977 try { 42978 str = atob(uri.data); 42979 } catch (e) { 42980 resolve(new Blob([])); 42981 return; 42982 } 42983 42984 arr = new Uint8Array(str.length); 42985 42986 for (i = 0; i < arr.length; i++) { 42987 arr[i] = str.charCodeAt(i); 42988 } 42989 42990 resolve(new Blob([arr], {type: uri.type})); 42991 }); 42992 } 42993 42994 function uriToBlob(url) { 42995 if (url.indexOf('blob:') === 0) { 42996 return blobUriToBlob(url); 42997 } 42998 42999 if (url.indexOf('data:') === 0) { 43000 return dataUriToBlob(url); 43001 } 43002 43003 return null; 43004 } 43005 43006 function blobToDataUri(blob) { 43007 return new Promise(function(resolve) { 43008 var reader = new FileReader(); 43009 43010 reader.onloadend = function() { 43011 resolve(reader.result); 43012 }; 43013 43014 reader.readAsDataURL(blob); 43015 }); 43016 } 43017 43018 return { 43019 uriToBlob: uriToBlob, 43020 blobToDataUri: blobToDataUri, 43021 parseDataUri: parseDataUri 43022 }; 43023}); 43024 43025// Included from: js/tinymce/classes/file/ImageScanner.js 43026 43027/** 43028 * ImageScanner.js 43029 * 43030 * Released under LGPL License. 43031 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43032 * 43033 * License: http://www.tinymce.com/license 43034 * Contributing: http://www.tinymce.com/contributing 43035 */ 43036 43037/** 43038 * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris. 43039 * 43040 * @private 43041 * @class tinymce.file.ImageScanner 43042 */ 43043define("tinymce/file/ImageScanner", [ 43044 "tinymce/util/Promise", 43045 "tinymce/util/Arr", 43046 "tinymce/util/Fun", 43047 "tinymce/file/Conversions",
43048 "tinymce/Env" 43049], function(Promise, Arr, Fun, Conversions, Env) { 43050 var count = 0; 43051 43052 return function(blobCache) { 43053 var cachedPromises = {}; 43054 43055 function findAll(elm, predicate) { 43056 var images, promises; 43057 43058 function imageToBlobInfo(img, resolve) { 43059 var base64, blobInfo; 43060 43061 if (img.src.indexOf('blob:') === 0) { 43062 blobInfo = blobCache.getByUri(img.src); 43063 43064 if (blobInfo) { 43065 resolve({ 43066 image: img, 43067 blobInfo: blobInfo 43068 }); 43069 } 43070 43071 return; 43072 } 43073 43074 base64 = Conversions.parseDataUri(img.src).data; 43075 blobInfo = blobCache.findFirst(function(cachedBlobInfo) { 43076 return cachedBlobInfo.base64() === base64; 43077 }); 43078 43079 if (blobInfo) { 43080 resolve({ 43081 image: img, 43082 blobInfo: blobInfo 43083 }); 43084 } else { 43085 Conversions.uriToBlob(img.src).then(function(blob) { 43086 var blobInfoId = 'blobid' + (count++), 43087 blobInfo = blobCache.create(blobInfoId, blob, base64); 43088 43089 blobCache.add(blobInfo); 43090 43091 resolve({ 43092 image: img, 43093 blobInfo: blobInfo 43094 }); 43095 }); 43096 } 43097 } 43098 43099 if (!predicate) { 43100 predicate = Fun.constant(true); 43101 } 43102 43103 images = Arr.filter(elm.getElementsByTagName('img'), function(img) { 43104 var src = img.src; 43105 43106 if (!Env.fileApi) { 43107 return false; 43108 } 43109 43110 if (img.hasAttribute('data-mce-bogus')) { 43111 return false; 43112 } 43113 43114 if (img.hasAttribute('data-mce-placeholder')) { 43115 return false; 43116 } 43117 43118 if (!src || src == Env.transparentSrc) { 43119 return false; 43120 } 43121 43122 if (src.indexOf('blob:') === 0) { 43123 return true; 43124 } 43125 43126 if (src.indexOf('data:') === 0) { 43127 return predicate(img); 43128 } 43129 43130 return false; 43131 }); 43132 43133 promises = Arr.map(images, function(img) { 43134 var newPromise; 43135 43136 if (cachedPromises[img.src]) { 43137 // Since the cached promise will return the cached image 43138 // We need to wrap it and resolve with the actual image 43139 return new Promise(function(resolve) { 43140 cachedPromises[img.src].then(function(imageInfo) { 43141 resolve({ 43142 image: img, 43143 blobInfo: imageInfo.blobInfo 43144 }); 43145 }); 43146 }); 43147 } 43148 43149 newPromise = new Promise(function(resolve) { 43150 imageToBlobInfo(img, resolve); 43151 }).then(function(result) { 43152 delete cachedPromises[result.image.src]; 43153 return result; 43154 })['catch'](function(error) { 43155 delete cachedPromises[img.src]; 43156 return error; 43157 }); 43158 43159 cachedPromises[img.src] = newPromise; 43160 43161 return newPromise; 43162 }); 43163 43164 return Promise.all(promises); 43165 } 43166 43167 return { 43168 findAll: findAll 43169 }; 43170 }; 43171}); 43172 43173// Included from: js/tinymce/classes/file/BlobCache.js 43174 43175/** 43176 * BlobCache.js 43177 * 43178 * Released under LGPL License. 43179 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43180 * 43181 * License: http://www.tinymce.com/license 43182 * Contributing: http://www.tinymce.com/contributing 43183 */ 43184 43185/** 43186 * Hold blob info objects where a blob has extra internal information. 43187 * 43188 * @private 43189 * @class tinymce.file.BlobCache 43190 */ 43191define("tinymce/file/BlobCache", [ 43192 "tinymce/util/Arr", 43193 "tinymce/util/Fun" 43194], function(Arr, Fun) { 43195 return function() { 43196 var cache = [], constant = Fun.constant; 43197 43198 function create(id, blob, base64) { 43199 return { 43200 id: constant(id), 43201 blob: constant(blob),
43202 base64: constant(base64), 43203 blobUri: constant(URL.createObjectURL(blob)) 43204 }; 43205 } 43206 43207 function add(blobInfo) { 43208 if (!get(blobInfo.id())) { 43209 cache.push(blobInfo); 43210 } 43211 } 43212 43213 function get(id) { 43214 return findFirst(function(cachedBlobInfo) { 43215 return cachedBlobInfo.id() === id; 43216 }); 43217 } 43218 43219 function findFirst(predicate) { 43220 return Arr.filter(cache, predicate)[0]; 43221 } 43222 43223 function getByUri(blobUri) { 43224 return findFirst(function(blobInfo) { 43225 return blobInfo.blobUri() == blobUri; 43226 }); 43227 } 43228 43229 function destroy() { 43230 Arr.each(cache, function(cachedBlobInfo) { 43231 URL.revokeObjectURL(cachedBlobInfo.blobUri()); 43232 }); 43233 43234 cache = []; 43235 } 43236 43237 return { 43238 create: create, 43239 add: add, 43240 get: get, 43241 getByUri: getByUri, 43242 findFirst: findFirst, 43243 destroy: destroy 43244 }; 43245 }; 43246}); 43247 43248// Included from: js/tinymce/classes/EditorUpload.js 43249 43250/** 43251 * EditorUpload.js 43252 * 43253 * Released under LGPL License. 43254 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43255 * 43256 * License: http://www.tinymce.com/license 43257 * Contributing: http://www.tinymce.com/contributing 43258 */ 43259 43260/** 43261 * Handles image uploads, updates undo stack and patches over various internal functions. 43262 * 43263 * @private 43264 * @class tinymce.EditorUpload 43265 */ 43266define("tinymce/EditorUpload", [ 43267 "tinymce/util/Arr", 43268 "tinymce/file/Uploader", 43269 "tinymce/file/ImageScanner", 43270 "tinymce/file/BlobCache" 43271], function(Arr, Uploader, ImageScanner, BlobCache) { 43272 return function(editor) { 43273 var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings; 43274 43275 function aliveGuard(callback) { 43276 return function(result) { 43277 if (editor.selection) { 43278 return callback(result); 43279 } 43280 43281 return []; 43282 }; 43283 } 43284 43285 // Replaces strings without regexps to avoid FF regexp to big issue 43286 function replaceString(content, search, replace) { 43287 var index = 0; 43288 43289 do { 43290 index = content.indexOf(search, index); 43291 43292 if (index !== -1) { 43293 content = content.substring(0, index) + replace + content.substr(index + search.length); 43294 index += replace.length - search.length + 1; 43295 } 43296 } while (index !== -1); 43297 43298 return content; 43299 } 43300 43301 function replaceImageUrl(content, targetUrl, replacementUrl) { 43302 content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"'); 43303 content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"'); 43304 43305 return content; 43306 } 43307 43308 function replaceUrlInUndoStack(targetUrl, replacementUrl) { 43309 Arr.each(editor.undoManager.data, function(level) { 43310 level.content = replaceImageUrl(level.content, targetUrl, replacementUrl); 43311 }); 43312 } 43313 43314 function openNotification() { 43315 return editor.notificationManager.open({ 43316 text: editor.translate('Image uploading...'), 43317 type: 'info', 43318 timeout: -1, 43319 progressBar: true 43320 }); 43321 } 43322 43323 function uploadImages(callback) { 43324 if (!uploader) { 43325 uploader = new Uploader({ 43326 url: settings.images_upload_url, 43327 basePath: settings.images_upload_base_path, 43328 credentials: settings.images_upload_credentials, 43329 handler: settings.images_upload_handler 43330 }); 43331 } 43332 43333 return scanForImages().then(aliveGuard(function(imageInfos) { 43334 var blobInfos; 43335 43336 blobInfos = Arr.map(imageInfos, function(imageInfo) { 43337 return imageInfo.blobInfo; 43338 }); 43339 43340 return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) { 43341 result = Arr.map(result, function(uploadInfo, index) { 43342 var image = imageInfos[index].image; 43343 43344 replaceUrlInUndoStack(image.src, uploadInfo.url); 43345 43346 editor.$(image).attr({ 43347 src: uploadInfo.url, 43348 'data-mce-src': editor.convertURL(uploadInfo.url, 'src') 43349 }); 43350 43351 return { 43352 element: image, 43353 status: uploadInfo.status 43354 }; 43355 }); 43356 43357 if (callback) { 43358 callback(result); 43359 } 43360 43361 return result; 43362 })); 43363 })); 43364 } 43365 43366 function uploadImagesAuto(callback) { 43367 if (settings.automatic_uploads !== false) { 43368 return uploadImages(callback); 43369 } 43370 } 43371 43372 function scanForImages() { 43373 if (!imageScanner) { 43374 imageScanner = new ImageScanner(blobCache); 43375 } 43376 43377 return imageScanner.findAll(editor.getBody(), settings.images_dataimg_filter).then(aliveGuard(function(result) { 43378 Arr.each(result, function(resultItem) { 43379 replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri()); 43380 resultItem.image.src = resultItem.blobInfo.blobUri(); 43381 }); 43382 43383 return result; 43384 })); 43385 } 43386 43387 function destroy() { 43388 blobCache.destroy(); 43389 imageScanner = uploader = null; 43390 } 43391 43392 function replaceBlobWithBase64(content) { 43393 return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) { 43394 var blobInfo = blobCache.getByUri(blobUri); 43395 43396 if (!blobInfo) { 43397 blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) { 43398 return result || editor.editorUpload.blobCache.getByUri(blobUri); 43399 }, null); 43400 } 43401 43402 if (blobInfo) { 43403 return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"'; 43404 } 43405 43406 return match; 43407 }); 43408 } 43409 43410 editor.on('setContent', function() { 43411 if (editor.settings.automatic_uploads !== false) { 43412 uploadImagesAuto(); 43413 } else { 43414 scanForImages(); 43415 } 43416 }); 43417 43418 editor.on('RawSaveContent', function(e) { 43419 e.content = replaceBlobWithBase64(e.content); 43420 }); 43421 43422 editor.on('getContent', function(e) { 43423 if (e.source_view || e.format == 'raw') { 43424 return; 43425 } 43426 43427 e.content = replaceBlobWithBase64(e.content); 43428 }); 43429 43430 return { 43431 blobCache: blobCache, 43432 uploadImages: uploadImages, 43433 uploadImagesAuto: uploadImagesAuto, 43434 scanForImages: scanForImages, 43435 destroy: destroy 43436 }; 43437 }; 43438}); 43439 43440// Included from: js/tinymce/classes/caret/CaretUtils.js 43441 43442/** 43443 * CaretUtils.js 43444 * 43445 * Released under LGPL License. 43446 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43447 * 43448 * License: http://www.tinymce.com/license 43449 * Contributing: http://www.tinymce.com/contributing 43450 */ 43451 43452/** 43453 * Utility functions shared by the caret logic. 43454 * 43455 * @private 43456 * @class tinymce.caret.CaretUtils 43457 */ 43458define("tinymce/caret/CaretUtils", [ 43459 "tinymce/util/Fun", 43460 "tinymce/dom/TreeWalker", 43461 "tinymce/dom/NodeType", 43462 "tinymce/caret/CaretPosition", 43463 "tinymce/caret/CaretContainer", 43464 "tinymce/caret/CaretCandidate" 43465], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) { 43466 var isContentEditableTrue = NodeType.isContentEditableTrue, 43467 isContentEditableFalse = NodeType.isContentEditableFalse, 43468 isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'), 43469 isCaretContainer = CaretContainer.isCaretContainer, 43470 curry = Fun.curry, 43471 isElement = NodeType.isElement, 43472 isCaretCandidate = CaretCandidate.isCaretCandidate; 43473 43474 function isForwards(direction) { 43475 return direction > 0; 43476 } 43477 43478 function isBackwards(direction) { 43479 return direction < 0; 43480 } 43481 43482 function findNode(node, direction, predicateFn, rootNode, shallow) { 43483 var walker = new TreeWalker(node, rootNode); 43484 43485 if (isBackwards(direction)) { 43486 if (isContentEditableFalse(node)) { 43487 node = walker.prev(true); 43488 if (predicateFn(node)) { 43489 return node; 43490 } 43491 } 43492 43493 while ((node = walker.prev(shallow))) { 43494 if (predicateFn(node)) { 43495 return node; 43496 } 43497 } 43498 } 43499 43500 if (isForwards(direction)) { 43501 if (isContentEditableFalse(node)) { 43502 node = walker.next(true); 43503 if (predicateFn(node)) { 43504 return node; 43505 } 43506 } 43507 43508 while ((node = walker.next(shallow))) { 43509 if (predicateFn(node)) { 43510 return node; 43511 } 43512 } 43513 } 43514 43515 return null; 43516 } 43517 43518 function getEditingHost(node, rootNode) { 43519 for (node = node.parentNode; node && node != rootNode; node = node.parentNode) { 43520 if (isContentEditableTrue(node)) { 43521 return node; 43522 } 43523 } 43524 43525 return rootNode; 43526 } 43527 43528 function getParentBlock(node, rootNode) { 43529 while (node && node != rootNode) { 43530 if (isBlockLike(node)) { 43531 return node; 43532 } 43533 43534 node = node.parentNode; 43535 } 43536 43537 return null; 43538 } 43539 43540 function isInSameBlock(caretPosition1, caretPosition2, rootNode) { 43541 return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode); 43542 } 43543 43544 function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) { 43545 return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode); 43546 } 43547 43548 function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) { 43549 var container, offset; 43550 43551 if (!caretPosition) { 43552 return null; 43553 } 43554 43555 container = caretPosition.container(); 43556 offset = caretPosition.offset(); 43557 43558 if (!isElement(container)) { 43559 return null; 43560 } 43561 43562 return container.childNodes[offset + relativeOffset]; 43563 } 43564 43565 function beforeAfter(before, node) { 43566 var range = node.ownerDocument.createRange(); 43567 43568 if (before) { 43569 range.setStartBefore(node); 43570 range.setEndBefore(node); 43571 } else { 43572 range.setStartAfter(node); 43573 range.setEndAfter(node); 43574 } 43575 43576 return range; 43577 } 43578 43579 function isNodesInSameBlock(rootNode, node1, node2) { 43580 return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode); 43581 } 43582 43583 function lean(left, rootNode, node) { 43584 var sibling, siblingName; 43585 43586 if (left) { 43587 siblingName = 'previousSibling'; 43588 } else { 43589 siblingName = 'nextSibling'; 43590 } 43591 43592 while (node && node != rootNode) { 43593 sibling = node[siblingName]; 43594 43595 if (isCaretContainer(sibling)) { 43596 sibling = sibling[siblingName]; 43597 } 43598 43599 if (isContentEditableFalse(sibling)) { 43600 if (isNodesInSameBlock(rootNode, sibling, node)) { 43601 return sibling; 43602 } 43603 43604 break; 43605 } 43606 43607 if (isCaretCandidate(sibling)) { 43608 break; 43609 } 43610 43611 node = node.parentNode; 43612 } 43613 43614 return null; 43615 } 43616 43617 var before = curry(beforeAfter, true); 43618 var after = curry(beforeAfter, false); 43619 43620 function normalizeRange(direction, rootNode, range) { 43621 var node, container, offset, location; 43622 var leanLeft = curry(lean, true, rootNode); 43623 var leanRight = curry(lean, false, rootNode); 43624 43625 container = range.startContainer; 43626 offset = range.startOffset; 43627 43628 if (CaretContainer.isCaretContainerBlock(container)) { 43629 if (!isElement(container)) { 43630 container = container.parentNode; 43631 } 43632 43633 location = container.getAttribute('data-mce-caret'); 43634 43635 if (location == 'before') { 43636 node = container.nextSibling; 43637 if (isContentEditableFalse(node)) { 43638 return before(node); 43639 } 43640 } 43641 43642 if (location == 'after') { 43643 node = container.previousSibling; 43644 if (isContentEditableFalse(node)) { 43645 return after(node); 43646 } 43647 } 43648 } 43649 43650 if (!range.collapsed) { 43651 return range; 43652 } 43653 43654 if (NodeType.isText(container)) { 43655 if (isCaretContainer(container)) { 43656 if (direction === 1) { 43657 node = leanRight(container); 43658 if (node) { 43659 return before(node); 43660 } 43661 43662 node = leanLeft(container); 43663 if (node) { 43664 return after(node); 43665 } 43666 } 43667 43668 if (direction === -1) { 43669 node = leanLeft(container); 43670 if (node) { 43671 return after(node); 43672 } 43673 43674 node = leanRight(container); 43675 if (node) { 43676 return before(node); 43677 } 43678 } 43679 43680 return range; 43681 } 43682 43683 if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) { 43684 if (direction === 1) { 43685 node = leanRight(container); 43686 if (node) { 43687 return before(node); 43688 } 43689 } 43690 43691 return range; 43692 } 43693 43694 if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) { 43695 if (direction === -1) { 43696 node = leanLeft(container); 43697 if (node) { 43698 return after(node); 43699 } 43700 } 43701 43702 return range; 43703 } 43704 43705 if (offset === container.data.length) { 43706 node = leanRight(container); 43707 if (node) { 43708 return before(node); 43709 } 43710 43711 return range; 43712 } 43713 43714 if (offset === 0) { 43715 node = leanLeft(container); 43716 if (node) { 43717 return after(node); 43718 } 43719 43720 return range; 43721 } 43722 } 43723 43724 return range; 43725 } 43726 43727 function isNextToContentEditableFalse(relativeOffset, caretPosition) { 43728 return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition)); 43729 } 43730 43731 return { 43732 isForwards: isForwards, 43733 isBackwards: isBackwards, 43734 findNode: findNode, 43735 getEditingHost: getEditingHost, 43736 getParentBlock: getParentBlock, 43737 isInSameBlock: isInSameBlock, 43738 isInSameEditingHost: isInSameEditingHost, 43739 isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0), 43740 isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1), 43741 normalizeRange: normalizeRange 43742 }; 43743}); 43744 43745// Included from: js/tinymce/classes/caret/CaretWalker.js 43746 43747/** 43748 * CaretWalker.js 43749 * 43750 * Released under LGPL License. 43751 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43752 * 43753 * License: http://www.tinymce.com/license 43754 * Contributing: http://www.tinymce.com/contributing 43755 */ 43756 43757/** 43758 * This module contains logic for moving around a virtual caret in logical order within a DOM element. 43759 * 43760 * It ignores the most obvious invalid caret locations such as within a script element or within a 43761 * contentEditable=false element but it will return locations that isn't possible to render visually. 43762 * 43763 * @private 43764 * @class tinymce.caret.CaretWalker 43765 * @example 43766 * var caretWalker = new CaretWalker(rootElm); 43767 * 43768 * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range)); 43769 * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range)); 43770 */ 43771define("tinymce/caret/CaretWalker", [ 43772 "tinymce/dom/NodeType", 43773 "tinymce/caret/CaretCandidate", 43774 "tinymce/caret/CaretPosition", 43775 "tinymce/caret/CaretUtils", 43776 "tinymce/util/Arr", 43777 "tinymce/util/Fun"
43778], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) { 43779 var isContentEditableFalse = NodeType.isContentEditableFalse, 43780 isText = NodeType.isText, 43781 isElement = NodeType.isElement, 43782 isForwards = CaretUtils.isForwards, 43783 isBackwards = CaretUtils.isBackwards, 43784 isCaretCandidate = CaretCandidate.isCaretCandidate, 43785 isAtomic = CaretCandidate.isAtomic, 43786 isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate; 43787 43788 function getParents(node, rootNode) { 43789 var parents = []; 43790 43791 while (node && node != rootNode) { 43792 parents.push(node); 43793 node = node.parentNode; 43794 } 43795 43796 return parents; 43797 } 43798 43799 function nodeAtIndex(container, offset) { 43800 if (container.hasChildNodes() && offset < container.childNodes.length) { 43801 return container.childNodes[offset]; 43802 } 43803 43804 return null; 43805 } 43806 43807 function getCaretCandidatePosition(direction, node) { 43808 if (isForwards(direction)) { 43809 if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) { 43810 return CaretPosition.before(node); 43811 } 43812 43813 if (isText(node)) { 43814 return CaretPosition(node, 0); 43815 } 43816 } 43817 43818 if (isBackwards(direction)) { 43819 if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) { 43820 return CaretPosition.after(node); 43821 } 43822 43823 if (isText(node)) { 43824 return CaretPosition(node, node.data.length); 43825 } 43826 } 43827 43828 if (isBackwards(direction)) { 43829 return CaretPosition.after(node); 43830 } 43831 43832 return CaretPosition.before(node); 43833 } 43834 43835 function findCaretPosition(direction, startCaretPosition, rootNode) { 43836 var container, offset, node, nextNode, innerNode, 43837 rootContentEditableFalseElm, caretPosition; 43838 43839 if (!isElement(rootNode) || !startCaretPosition) { 43840 return null; 43841 } 43842 43843 caretPosition = startCaretPosition; 43844 container = caretPosition.container(); 43845 offset = caretPosition.offset(); 43846 43847 if (isText(container)) { 43848 if (isBackwards(direction) && offset > 0) { 43849 return CaretPosition(container, --offset); 43850 } 43851 43852 if (isForwards(direction) && offset < container.length) { 43853 return CaretPosition(container, ++offset); 43854 } 43855 43856 node = container; 43857 } else { 43858 if (isBackwards(direction) && offset > 0) { 43859 nextNode = nodeAtIndex(container, offset - 1); 43860 if (isCaretCandidate(nextNode)) { 43861 if (!isAtomic(nextNode)) { 43862 innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); 43863 if (innerNode) { 43864 if (isText(innerNode)) { 43865 return CaretPosition(innerNode, innerNode.data.length); 43866 } 43867 43868 return CaretPosition.after(innerNode); 43869 } 43870 } 43871 43872 if (isText(nextNode)) { 43873 return CaretPosition(nextNode, nextNode.data.length); 43874 } 43875 43876 return CaretPosition.before(nextNode); 43877 } 43878 } 43879 43880 if (isForwards(direction) && offset < container.childNodes.length) { 43881 nextNode = nodeAtIndex(container, offset); 43882 if (isCaretCandidate(nextNode)) { 43883 if (!isAtomic(nextNode)) { 43884 innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode); 43885 if (innerNode) { 43886 if (isText(innerNode)) { 43887 return CaretPosition(innerNode, 0); 43888 } 43889 43890 return CaretPosition.before(innerNode); 43891 } 43892 } 43893 43894 if (isText(nextNode)) { 43895 return CaretPosition(nextNode, 0); 43896 } 43897 43898 return CaretPosition.after(nextNode); 43899 } 43900 } 43901 43902 node = caretPosition.getNode(); 43903 } 43904 43905 if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) { 43906 node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true); 43907 if (isEditableCaretCandidate(node)) { 43908 return getCaretCandidatePosition(direction, node); 43909 } 43910 } 43911 43912 nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode); 43913 43914 rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse)); 43915 if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) { 43916 if (isForwards(direction)) { 43917 caretPosition = CaretPosition.after(rootContentEditableFalseElm); 43918 } else { 43919 caretPosition = CaretPosition.before(rootContentEditableFalseElm); 43920 } 43921 43922 return caretPosition; 43923 } 43924 43925 if (nextNode) { 43926 return getCaretCandidatePosition(direction, nextNode); 43927 } 43928 43929 return null; 43930 } 43931 43932 return function(rootNode) { 43933 return { 43934 /** 43935 * Returns the next logical caret position from the specificed input 43936 * caretPoisiton or null if there isn't any more positions left for example 43937 * at the end specified root element. 43938 * 43939 * @method next 43940 * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. 43941 * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. 43942 */ 43943 next: function(caretPosition) { 43944 return findCaretPosition(1, caretPosition, rootNode); 43945 }, 43946 43947 /** 43948 * Returns the previous logical caret position from the specificed input 43949 * caretPoisiton or null if there isn't any more positions left for example 43950 * at the end specified root element. 43951 * 43952 * @method prev 43953 * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from. 43954 * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found. 43955 */ 43956 prev: function(caretPosition) { 43957 return findCaretPosition(-1, caretPosition, rootNode); 43958 } 43959 }; 43960 }; 43961}); 43962 43963// Included from: js/tinymce/classes/caret/FakeCaret.js 43964 43965/** 43966 * FakeCaret.js 43967 * 43968 * Released under LGPL License. 43969 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 43970 * 43971 * License: http://www.tinymce.com/license 43972 * Contributing: http://www.tinymce.com/contributing 43973 */ 43974 43975/** 43976 * This module contains logic for rendering a fake visual caret. 43977 * 43978 * @private 43979 * @class tinymce.caret.FakeCaret 43980 */ 43981define("tinymce/caret/FakeCaret", [ 43982 "tinymce/caret/CaretContainer", 43983 "tinymce/caret/CaretPosition", 43984 "tinymce/dom/NodeType", 43985 "tinymce/dom/RangeUtils", 43986 "tinymce/dom/DomQuery", 43987 "tinymce/geom/ClientRect", 43988 "tinymce/util/Delay" 43989], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) { 43990 var isContentEditableFalse = NodeType.isContentEditableFalse; 43991 43992 return function(rootNode, isBlock) { 43993 var cursorInterval, $lastVisualCaret, caretContainerNode; 43994 43995 function getAbsoluteClientRect(node, before) { 43996 var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before), 43997 docElm, scrollX, scrollY, margin, rootRect; 43998 43999 if (rootNode.tagName == 'BODY') { 44000 docElm = rootNode.ownerDocument.documentElement; 44001 scrollX = rootNode.scrollLeft || docElm.scrollLeft; 44002 scrollY = rootNode.scrollTop || docElm.scrollTop; 44003 } else { 44004 rootRect = rootNode.getBoundingClientRect(); 44005 scrollX = rootNode.scrollLeft - rootRect.left; 44006 scrollY = rootNode.scrollTop - rootRect.top; 44007 } 44008 44009 clientRect.left += scrollX; 44010 clientRect.right += scrollX; 44011 clientRect.top += scrollY; 44012 clientRect.bottom += scrollY; 44013 clientRect.width = 1; 44014 44015 margin = node.offsetWidth - node.clientWidth; 44016 44017 if (margin > 0) { 44018 if (before) { 44019 margin *= -1; 44020 } 44021 44022 clientRect.left += margin; 44023 clientRect.right += margin; 44024 } 44025 44026 return clientRect; 44027 } 44028 44029 function trimInlineCaretContainers() { 44030 var contentEditableFalseNodes, node, sibling, i, data; 44031 44032 contentEditableFalseNodes = $('*[contentEditable=false]', rootNode); 44033 for (i = 0; i < contentEditableFalseNodes.length; i++) { 44034 node = contentEditableFalseNodes[i]; 44035 44036 sibling = node.previousSibling; 44037 if (CaretContainer.endsWithCaretContainer(sibling)) { 44038 data = sibling.data; 44039 44040 if (data.length == 1) { 44041 sibling.parentNode.removeChild(sibling); 44042 } else { 44043 sibling.deleteData(data.length - 1, 1); 44044 } 44045 } 44046 44047 sibling = node.nextSibling; 44048 if (CaretContainer.startsWithCaretContainer(sibling)) { 44049 data = sibling.data; 44050 44051 if (data.length == 1) { 44052 sibling.parentNode.removeChild(sibling); 44053 } else { 44054 sibling.deleteData(0, 1); 44055 } 44056 } 44057 } 44058 44059 return null; 44060 } 44061 44062 function show(before, node) { 44063 var clientRect, rng, container; 44064 44065 hide(); 44066 44067 if (isBlock(node)) { 44068 caretContainerNode = CaretContainer.insertBlock('p', node, before); 44069 clientRect = getAbsoluteClientRect(node, before); 44070 $(caretContainerNode).css('top', clientRect.top); 44071
44072 $lastVisualCaret = $('<div class="mce-visual-caret" data-mce-bogus="all"></div>').css(clientRect).appendTo(rootNode); 44073 44074 if (before) { 44075 $lastVisualCaret.addClass('mce-visual-caret-before'); 44076 } 44077 44078 startBlink(); 44079 44080 rng = node.ownerDocument.createRange(); 44081 container = caretContainerNode.firstChild; 44082 rng.setStart(container, 0); 44083 rng.setEnd(container, 1); 44084 } else { 44085 caretContainerNode = CaretContainer.insertInline(node, before); 44086 rng = node.ownerDocument.createRange(); 44087 44088 if (isContentEditableFalse(caretContainerNode.nextSibling)) { 44089 rng.setStart(caretContainerNode, 0); 44090 rng.setEnd(caretContainerNode, 0); 44091 } else { 44092 rng.setStart(caretContainerNode, 1); 44093 rng.setEnd(caretContainerNode, 1); 44094 } 44095 44096 return rng; 44097 } 44098 44099 return rng; 44100 } 44101 44102 function hide() { 44103 trimInlineCaretContainers(); 44104 44105 if (caretContainerNode) { 44106 CaretContainer.remove(caretContainerNode); 44107 caretContainerNode = null; 44108 } 44109 44110 if ($lastVisualCaret) { 44111 $lastVisualCaret.remove(); 44112 $lastVisualCaret = null; 44113 } 44114 44115 clearInterval(cursorInterval); 44116 } 44117 44118 function startBlink() { 44119 cursorInterval = Delay.setInterval(function() { 44120 $('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden'); 44121 }, 500); 44122 } 44123 44124 function destroy() { 44125 Delay.clearInterval(cursorInterval); 44126 } 44127 44128 function getCss() { 44129 return ( 44130 '.mce-visual-caret {' + 44131 'position: absolute;' + 44132 'background-color: black;' + 44133 'background-color: currentcolor;' + 44134 '}' + 44135 '.mce-visual-caret-hidden {' + 44136 'display: none;' + 44137 '}' + 44138 '*[data-mce-caret] {' + 44139 'position: absolute;' + 44140 'left: -1000px;' + 44141 'right: auto;' + 44142 'top: 0;' + 44143 'margin: 0;' + 44144 'padding: 0;' + 44145 '}' 44146 ); 44147 } 44148 44149 return { 44150 show: show, 44151 hide: hide, 44152 getCss: getCss, 44153 destroy: destroy 44154 }; 44155 }; 44156}); 44157 44158// Included from: js/tinymce/classes/dom/Dimensions.js 44159 44160/** 44161 * Dimensions.js 44162 * 44163 * Released under LGPL License. 44164 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 44165 * 44166 * License: http://www.tinymce.com/license 44167 * Contributing: http://www.tinymce.com/contributing 44168 */ 44169 44170/** 44171 * This module measures nodes and returns client rects. The client rects has an 44172 * extra node property. 44173 * 44174 * @private 44175 * @class tinymce.dom.Dimensions 44176 */ 44177define("tinymce/dom/Dimensions", [ 44178 "tinymce/util/Arr", 44179 "tinymce/dom/NodeType", 44180 "tinymce/geom/ClientRect" 44181], function(Arr, NodeType, ClientRect) { 44182 44183 function getClientRects(node) { 44184 function toArrayWithNode(clientRects) { 44185 return Arr.map(clientRects, function(clientRect) { 44186 clientRect = ClientRect.clone(clientRect); 44187 clientRect.node = node; 44188 44189 return clientRect; 44190 }); 44191 } 44192 44193 if (Arr.isArray(node)) { 44194 return Arr.reduce(node, function(result, node) { 44195 return result.concat(getClientRects(node)); 44196 }, []); 44197 } 44198 44199 if (NodeType.isElement(node)) { 44200 return toArrayWithNode(node.getClientRects()); 44201 } 44202 44203 if (NodeType.isText(node)) { 44204 var rng = node.ownerDocument.createRange(); 44205 44206 rng.setStart(node, 0); 44207 rng.setEnd(node, node.data.length); 44208 44209 return toArrayWithNode(rng.getClientRects()); 44210 } 44211 } 44212 44213 return { 44214 /** 44215 * Returns the client rects for a specific node. 44216 * 44217 * @method getClientRects 44218 * @param {Array/DOMNode} node Node or array of nodes to get client rects on. 44219 * @param {Array} Array of client rects with a extra node property. 44220 */ 44221 getClientRects: getClientRects 44222 }; 44223}); 44224 44225// Included from: js/tinymce/classes/caret/LineWalker.js 44226 44227/** 44228 * LineWalker.js 44229 * 44230 * Released under LGPL License. 44231 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 44232 * 44233 * License: http://www.tinymce.com/license 44234 * Contributing: http://www.tinymce.com/contributing 44235 */ 44236 44237/** 44238 * This module lets you walk the document line by line 44239 * returing nodes and client rects for each line. 44240 * 44241 * @private 44242 * @class tinymce.caret.LineWalker 44243 */ 44244define("tinymce/caret/LineWalker", [ 44245 "tinymce/util/Fun", 44246 "tinymce/util/Arr", 44247 "tinymce/dom/Dimensions", 44248 "tinymce/caret/CaretCandidate", 44249 "tinymce/caret/CaretUtils", 44250 "tinymce/caret/CaretWalker", 44251 "tinymce/caret/CaretPosition", 44252 "tinymce/geom/ClientRect" 44253], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) { 44254 var curry = Fun.curry; 44255 44256 function findUntil(direction, rootNode, predicateFn, node) { 44257 while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { 44258 if (predicateFn(node)) { 44259 return; 44260 } 44261 } 44262 } 44263 44264 function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) { 44265 var line = 0, node, result = [], targetClientRect; 44266 44267 function add(node) { 44268 var i, clientRect, clientRects; 44269 44270 clientRects = Dimensions.getClientRects(node); 44271 if (direction == -1) { 44272 clientRects = clientRects.reverse(); 44273 } 44274 44275 for (i = 0; i < clientRects.length; i++) { 44276 clientRect = clientRects[i]; 44277 if (isBeflowFn(clientRect, targetClientRect)) { 44278 continue; 44279 } 44280 44281 if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) { 44282 line++; 44283 } 44284 44285 clientRect.line = line; 44286 44287 if (predicateFn(clientRect)) { 44288 return true; 44289 } 44290 44291 result.push(clientRect); 44292 } 44293 } 44294 44295 targetClientRect = Arr.last(caretPosition.getClientRects()); 44296 if (!targetClientRect) { 44297 return result; 44298 } 44299 44300 node = caretPosition.getNode(); 44301 add(node); 44302 findUntil(direction, rootNode, add, node); 44303 44304 return result; 44305 } 44306
44307 function aboveLineNumber(lineNumber, clientRect) { 44308 return clientRect.line > lineNumber; 44309 } 44310 44311 function isLine(lineNumber, clientRect) { 44312 return clientRect.line === lineNumber; 44313 } 44314 44315 var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow); 44316 var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove); 44317 44318 function positionsUntil(direction, rootNode, predicateFn, node) { 44319 var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn, 44320 caretPosition, result = [], line = 0, clientRect, targetClientRect; 44321 44322 function getClientRect(caretPosition) { 44323 if (direction == 1) { 44324 return Arr.last(caretPosition.getClientRects()); 44325 } 44326 44327 return Arr.last(caretPosition.getClientRects()); 44328 } 44329 44330 if (direction == 1) { 44331 walkFn = caretWalker.next; 44332 isBelowFn = ClientRect.isBelow; 44333 isAboveFn = ClientRect.isAbove; 44334 caretPosition = CaretPosition.after(node); 44335 } else { 44336 walkFn = caretWalker.prev; 44337 isBelowFn = ClientRect.isAbove; 44338 isAboveFn = ClientRect.isBelow; 44339 caretPosition = CaretPosition.before(node); 44340 } 44341 44342 targetClientRect = getClientRect(caretPosition); 44343 44344 do { 44345 if (!caretPosition.isVisible()) { 44346 continue; 44347 } 44348 44349 clientRect = getClientRect(caretPosition); 44350 44351 if (isAboveFn(clientRect, targetClientRect)) { 44352 continue; 44353 } 44354 44355 if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) { 44356 line++; 44357 } 44358 44359 clientRect = ClientRect.clone(clientRect); 44360 clientRect.position = caretPosition; 44361 clientRect.line = line; 44362 44363 if (predicateFn(clientRect)) { 44364 return result; 44365 } 44366 44367 result.push(clientRect); 44368 } while ((caretPosition = walkFn(caretPosition))); 44369 44370 return result; 44371 } 44372 44373 return { 44374 upUntil: upUntil, 44375 downUntil: downUntil, 44376 44377 /** 44378 * Find client rects with line and caret position until the predicate returns true. 44379 * 44380 * @method positionsUntil 44381 * @param {Number} direction Direction forward/backward 1/-1. 44382 * @param {DOMNode} rootNode Root node to walk within. 44383 * @param {function} predicateFn Gets the client rect as it's input. 44384 * @param {DOMNode} node Node to start walking from. 44385 * @return {Array} Array of client rects with line and position properties. 44386 */ 44387 positionsUntil: positionsUntil, 44388 44389 isAboveLine: curry(aboveLineNumber), 44390 isLine: curry(isLine) 44391 }; 44392}); 44393 44394// Included from: js/tinymce/classes/caret/LineUtils.js 44395 44396/** 44397 * LineUtils.js 44398 * 44399 * Released under LGPL License. 44400 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 44401 * 44402 * License: http://www.tinymce.com/license 44403 * Contributing: http://www.tinymce.com/contributing 44404 */ 44405 44406/** 44407 * Utility functions for working with lines. 44408 * 44409 * @private 44410 * @class tinymce.caret.LineUtils 44411 */ 44412define("tinymce/caret/LineUtils", [ 44413 "tinymce/util/Fun", 44414 "tinymce/util/Arr", 44415 "tinymce/dom/NodeType", 44416 "tinymce/dom/Dimensions", 44417 "tinymce/geom/ClientRect", 44418 "tinymce/caret/CaretUtils", 44419 "tinymce/caret/CaretCandidate" 44420], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) { 44421 var isContentEditableFalse = NodeType.isContentEditableFalse, 44422 findNode = CaretUtils.findNode, 44423 curry = Fun.curry; 44424 44425 function distanceToRectLeft(clientRect, clientX) { 44426 return Math.abs(clientRect.left - clientX); 44427 } 44428 44429 function distanceToRectRight(clientRect, clientX) { 44430 return Math.abs(clientRect.right - clientX); 44431 } 44432 44433 function findClosestClientRect(clientRects, clientX) { 44434 function isInside(clientX, clientRect) { 44435 return clientX >= clientRect.left && clientX <= clientRect.right; 44436 } 44437 44438 return Arr.reduce(clientRects, function(oldClientRect, clientRect) { 44439 var oldDistance, newDistance; 44440 44441 oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX)); 44442 newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX)); 44443 44444 if (isInside(clientX, clientRect)) { 44445 return clientRect; 44446 } 44447 44448 if (isInside(clientX, oldClientRect)) { 44449 return oldClientRect; 44450 } 44451 44452 // cE=false has higher priority 44453 if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) { 44454 return clientRect; 44455 } 44456 44457 if (newDistance < oldDistance) { 44458 return clientRect; 44459 } 44460 44461 return oldClientRect; 44462 }); 44463 } 44464 44465 function walkUntil(direction, rootNode, predicateFn, node) { 44466 while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) { 44467 if (predicateFn(node)) { 44468 return; 44469 } 44470 } 44471 } 44472 44473 function findLineNodeRects(rootNode, targetNodeRect) { 44474 var clientRects = []; 44475 44476 function collect(checkPosFn, node) { 44477 var lineRects; 44478 44479 lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) { 44480 return !checkPosFn(clientRect, targetNodeRect); 44481 }); 44482 44483 clientRects = clientRects.concat(lineRects); 44484 44485 return lineRects.length === 0; 44486 } 44487 44488 clientRects.push(targetNodeRect); 44489 walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node); 44490 walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node); 44491 44492 return clientRects; 44493 } 44494 44495 function getContentEditableFalseChildren(rootNode) { 44496 return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse); 44497 } 44498 44499 function caretInfo(clientRect, clientX) { 44500 return { 44501 node: clientRect.node, 44502 before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX) 44503 }; 44504 } 44505 44506 function closestCaret(rootNode, clientX, clientY) { 44507 var contentEditableFalseNodeRects, closestNodeRect; 44508 44509 contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode)); 44510 contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) { 44511 return clientY >= clientRect.top && clientY <= clientRect.bottom; 44512 }); 44513 44514 closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX); 44515 if (closestNodeRect) { 44516 closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX); 44517 if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) { 44518 return caretInfo(closestNodeRect, clientX); 44519 } 44520 } 44521 44522 return null; 44523 } 44524 44525 return { 44526 findClosestClientRect: findClosestClientRect, 44527 findLineNodeRects: findLineNodeRects, 44528 closestCaret: closestCaret 44529 }; 44530}); 44531 44532// Included from: js/tinymce/classes/DragDropOverrides.js 44533 44534/** 44535 * DragDropOverrides.js 44536 * 44537 * Released under LGPL License. 44538 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 44539 * 44540 * License: http://www.tinymce.com/license 44541 * Contributing: http://www.tinymce.com/contributing 44542 */ 44543 44544/** 44545 * This module contains logic overriding the drag/drop logic of the editor. 44546 * 44547 * @private 44548 * @class tinymce.DragDropOverrides 44549 */ 44550define("tinymce/DragDropOverrides", [ 44551 "tinymce/dom/NodeType" 44552], function( 44553 NodeType 44554) { 44555 var isContentEditableFalse = NodeType.isContentEditableFalse; 44556 44557 function init(editor) { 44558 var $ = editor.$, rootDocument = document, 44559 editableDoc = editor.getDoc(), 44560 dom = editor.dom, state = {}; 44561 44562 function isDraggable(elm) { 44563 return isContentEditableFalse(elm); 44564 } 44565 44566 function setBodyCursor(cursor) { 44567 $(editor.getBody()).css('cursor', cursor); 44568 } 44569 44570 function isValidDropTarget(elm) { 44571 if (elm == state.element || editor.dom.isChildOf(elm, state.element)) { 44572 return false;
44573 } 44574 44575 if (isContentEditableFalse(elm)) { 44576 return false; 44577 } 44578 44579 return true; 44580 } 44581 44582 function move(e) { 44583 var deltaX, deltaY, pos, viewPort, 44584 overflowX = 0, overflowY = 0, movement, 44585 clientX, clientY, rootClientRect; 44586 44587 if (e.button !== 0) { 44588 return; 44589 } 44590 44591 deltaX = e.screenX - state.screenX; 44592 deltaY = e.screenY - state.screenY; 44593 movement = Math.max(Math.abs(deltaX), Math.abs(deltaY)); 44594 44595 if (!state.dragging && movement > 10) { 44596 state.dragging = true; 44597 setBodyCursor('default'); 44598 44599 state.clone = state.element.cloneNode(true); 44600 44601 pos = dom.getPos(state.element); 44602 state.relX = state.clientX - pos.x; 44603 state.relY = state.clientY - pos.y; 44604 state.width = state.element.offsetWidth; 44605 state.height = state.element.offsetHeight; 44606 44607 $(state.clone).css({ 44608 width: state.width, 44609 height: state.height 44610 }).removeAttr('data-mce-selected'); 44611 44612 state.ghost = $('<div>').css({ 44613 position: 'absolute', 44614 opacity: 0.5, 44615 overflow: 'hidden', 44616 width: state.width, 44617 height: state.height 44618 }).attr({ 44619 'data-mce-bogus': 'all', 44620 unselectable: 'on', 44621 contenteditable: 'false' 44622 }).addClass('mce-drag-container mce-reset'). 44623 append(state.clone). 44624 appendTo(editor.getBody())[0]; 44625 44626 viewPort = editor.dom.getViewPort(editor.getWin()); 44627 state.maxX = viewPort.w; 44628 state.maxY = viewPort.h; 44629 } 44630 44631 if (state.dragging) { 44632 editor.selection.placeCaretAt(e.clientX, e.clientY); 44633 44634 clientX = state.clientX + deltaX - state.relX; 44635 clientY = state.clientY + deltaY + 5; 44636 44637 if (clientX + state.width > state.maxX) { 44638 overflowX = (clientX + state.width) - state.maxX; 44639 } 44640 44641 if (clientY + state.height > state.maxY) { 44642 overflowY = (clientY + state.height) - state.maxY; 44643 } 44644 44645 if (editor.getBody().nodeName != 'BODY') { 44646 rootClientRect = editor.getBody().getBoundingClientRect(); 44647 } else { 44648 rootClientRect = {left: 0, top: 0}; 44649 } 44650 44651 $(state.ghost).css({ 44652 left: clientX - rootClientRect.left, 44653 top: clientY - rootClientRect.top, 44654 width: state.width - overflowX, 44655 height: state.height - overflowY 44656 }); 44657 } 44658 } 44659 44660 function drop() { 44661 if (state.dragging) { 44662 // Hack for IE since it doesn't sync W3C Range with IE Specific range 44663 editor.selection.setRng(editor.selection.getSel().getRangeAt(0)); 44664 44665 if (isValidDropTarget(editor.selection.getNode())) { 44666 editor.undoManager.transact(function() { 44667 editor.insertContent(dom.getOuterHTML(state.element)); 44668 $(state.element).remove(); 44669 }); 44670 } 44671 } 44672 44673 stop(); 44674 } 44675 44676 function start(e) { 44677 stop(); 44678 44679 if (isDraggable(e.target)) { 44680 if (editor.fire('dragstart', {target: e.target}).isDefaultPrevented()) { 44681 return; 44682 } 44683 44684 editor.on('mousemove', move); 44685 editor.on('mouseup', drop); 44686 44687 if (rootDocument != editableDoc) { 44688 dom.bind(rootDocument, 'mousemove', move); 44689 dom.bind(rootDocument, 'mouseup', drop); 44690 } 44691 44692 state = { 44693 screenX: e.screenX, 44694 screenY: e.screenY, 44695 clientX: e.clientX, 44696 clientY: e.clientY, 44697 element: e.target 44698 }; 44699 } 44700 } 44701 44702 function stop() { 44703 $(state.ghost).remove(); 44704 setBodyCursor(null); 44705 44706 editor.off('mousemove', move); 44707 editor.off('mouseup', stop); 44708 44709 if (rootDocument != editableDoc) { 44710 dom.unbind(rootDocument, 'mousemove', move); 44711 dom.unbind(rootDocument, 'mouseup', stop); 44712 } 44713 44714 state = {}; 44715 } 44716 44717 editor.on('mousedown', start); 44718 44719 // Blocks drop inside cE=false on IE 44720 editor.on('drop', function(e) { 44721 var realTarget = editor.getDoc().elementFromPoint(e.clientX, e.clientY); 44722 44723 if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) { 44724 e.preventDefault(); 44725 } 44726 }); 44727 } 44728 44729 return { 44730 init: init 44731 }; 44732}); 44733 44734// Included from: js/tinymce/classes/SelectionOverrides.js 44735 44736/** 44737 * SelectionOverrides.js 44738 * 44739 * Released under LGPL License. 44740 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 44741 * 44742 * License: http://www.tinymce.com/license 44743 * Contributing: http://www.tinymce.com/contributing 44744 */ 44745 44746/** 44747 * This module contains logic overriding the selection with keyboard/mouse 44748 * around contentEditable=false regions. 44749 * 44750 * @example 44751 * // Disable the default cE=false selection 44752 * tinymce.activeEditor.on('ShowCaret ObjectSelected', function(e) { 44753 * e.preventDefault(); 44754 * }); 44755 * 44756 * @private 44757 * @class tinymce.SelectionOverrides 44758 */ 44759define("tinymce/SelectionOverrides", [ 44760 "tinymce/Env", 44761 "tinymce/caret/CaretWalker", 44762 "tinymce/caret/CaretPosition", 44763 "tinymce/caret/CaretContainer", 44764 "tinymce/caret/CaretUtils", 44765 "tinymce/caret/FakeCaret", 44766 "tinymce/caret/LineWalker", 44767 "tinymce/caret/LineUtils", 44768 "tinymce/dom/NodeType", 44769 "tinymce/dom/RangeUtils", 44770 "tinymce/util/VK",
44771 "tinymce/util/Fun", 44772 "tinymce/util/Arr", 44773 "tinymce/util/Delay", 44774 "tinymce/DragDropOverrides" 44775], function( 44776 Env, CaretWalker, CaretPosition, CaretContainer, CaretUtils, FakeCaret, LineWalker, 44777 LineUtils, NodeType, RangeUtils, VK, Fun, Arr, Delay, DragDropOverrides 44778) { 44779 var curry = Fun.curry, 44780 isContentEditableTrue = NodeType.isContentEditableTrue, 44781 isContentEditableFalse = NodeType.isContentEditableFalse, 44782 isElement = NodeType.isElement, 44783 isAfterContentEditableFalse = CaretUtils.isAfterContentEditableFalse, 44784 isBeforeContentEditableFalse = CaretUtils.isBeforeContentEditableFalse, 44785 getSelectedNode = RangeUtils.getSelectedNode; 44786 44787 function getVisualCaretPosition(walkFn, caretPosition) { 44788 while ((caretPosition = walkFn(caretPosition))) { 44789 if (caretPosition.isVisible()) { 44790 return caretPosition; 44791 } 44792 } 44793 44794 return caretPosition; 44795 } 44796 44797 function SelectionOverrides(editor) { 44798 var rootNode = editor.getBody(), caretWalker = new CaretWalker(rootNode); 44799 var getNextVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.next); 44800 var getPrevVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.prev), 44801 fakeCaret = new FakeCaret(editor.getBody(), isBlock), 44802 realSelectionId = 'sel-' + editor.dom.uniqueId(), 44803 selectedContentEditableNode, $ = editor.$; 44804 44805 function isBlock(node) { 44806 return editor.dom.isBlock(node); 44807 } 44808 44809 function setRange(range) { 44810 //console.log('setRange', range); 44811 if (range) { 44812 editor.selection.setRng(range); 44813 } 44814 } 44815 44816 function getRange() { 44817 return editor.selection.getRng(); 44818 } 44819 44820 function scrollIntoView(node, alignToTop) { 44821 editor.selection.scrollIntoView(node, alignToTop); 44822 } 44823 44824 function showCaret(direction, node, before) { 44825 var e; 44826 44827 e = editor.fire('ShowCaret', { 44828 target: node, 44829 direction: direction, 44830 before: before 44831 }); 44832 44833 if (e.isDefaultPrevented()) { 44834 return null; 44835 } 44836 44837 scrollIntoView(node, direction === -1); 44838 44839 return fakeCaret.show(before, node); 44840 } 44841 44842 function selectNode(node) { 44843 var e; 44844 44845 e = editor.fire('ObjectSelected', {target: node}); 44846 if (e.isDefaultPrevented()) { 44847 return null; 44848 } 44849 44850 fakeCaret.hide(); 44851 return getNodeRange(node); 44852 } 44853 44854 function getNodeRange(node) { 44855 var rng = node.ownerDocument.createRange(); 44856 44857 rng.selectNode(node); 44858 44859 return rng; 44860 } 44861 44862 function isMoveInsideSameBlock(fromCaretPosition, toCaretPosition) { 44863 var inSameBlock = CaretUtils.isInSameBlock(fromCaretPosition, toCaretPosition); 44864 44865 // Handle bogus BR <p>abc|<br></p> 44866 if (!inSameBlock && NodeType.isBr(fromCaretPosition.getNode())) { 44867 return true; 44868 } 44869 44870 return inSameBlock; 44871 } 44872 44873 function getNormalizedRangeEndPoint(direction, range) { 44874 range = CaretUtils.normalizeRange(direction, rootNode, range); 44875 44876 if (direction == -1) { 44877 return CaretPosition.fromRangeStart(range); 44878 } 44879 44880 return CaretPosition.fromRangeEnd(range); 44881 } 44882 44883 function isRangeInCaretContainerBlock(range) { 44884 return CaretContainer.isCaretContainerBlock(range.startContainer); 44885 } 44886 44887 function moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) { 44888 var node, caretPosition, peekCaretPosition, rangeIsInContainerBlock; 44889 44890 if (!range.collapsed) { 44891 node = getSelectedNode(range); 44892 if (isContentEditableFalse(node)) { 44893 return showCaret(direction, node, direction == -1); 44894 } 44895 } 44896 44897 rangeIsInContainerBlock = isRangeInCaretContainerBlock(range); 44898 caretPosition = getNormalizedRangeEndPoint(direction, range); 44899 44900 if (isBeforeContentEditableFalseFn(caretPosition)) { 44901 return selectNode(caretPosition.getNode(direction == -1)); 44902 } 44903 44904 caretPosition = getNextPosFn(caretPosition); 44905 if (!caretPosition) { 44906 if (rangeIsInContainerBlock) { 44907 return range; 44908 } 44909 44910 return null; 44911 } 44912 44913 if (isBeforeContentEditableFalseFn(caretPosition)) { 44914 return showCaret(direction, caretPosition.getNode(direction == -1), direction == 1); 44915 } 44916 44917 // Peek ahead for handling of ab|c<span cE=false> -> abc|<span cE=false> 44918 peekCaretPosition = getNextPosFn(caretPosition); 44919 if (isBeforeContentEditableFalseFn(peekCaretPosition)) { 44920 if (isMoveInsideSameBlock(caretPosition, peekCaretPosition)) { 44921 return showCaret(direction, peekCaretPosition.getNode(direction == -1), direction == 1); 44922 } 44923 } 44924 44925 if (rangeIsInContainerBlock) { 44926 return renderRangeCaret(caretPosition.toRange()); 44927 } 44928 44929 return null; 44930 } 44931 44932 function moveToCeFalseVertically(direction, walkerFn, range) { 44933 var caretPosition, linePositions, nextLinePositions, 44934 closestNextLineRect, caretClientRect, clientX, 44935 dist1, dist2, contentEditableFalseNode; 44936 44937 contentEditableFalseNode = getSelectedNode(range); 44938 caretPosition = getNormalizedRangeEndPoint(direction, range); 44939 linePositions = walkerFn(rootNode, LineWalker.isAboveLine(1), caretPosition); 44940 nextLinePositions = Arr.filter(linePositions, LineWalker.isLine(1)); 44941 caretClientRect = Arr.last(caretPosition.getClientRects()); 44942 44943 if (isBeforeContentEditableFalse(caretPosition)) { 44944 contentEditableFalseNode = caretPosition.getNode(); 44945 } 44946 44947 if (isAfterContentEditableFalse(caretPosition)) { 44948 contentEditableFalseNode = caretPosition.getNode(true); 44949 } 44950 44951 if (!caretClientRect) { 44952 return null; 44953 } 44954 44955 clientX = caretClientRect.left; 44956 44957 closestNextLineRect = LineUtils.findClosestClientRect(nextLinePositions, clientX); 44958 if (closestNextLineRect) { 44959 if (isContentEditableFalse(closestNextLineRect.node)) { 44960 dist1 = Math.abs(clientX - closestNextLineRect.left); 44961 dist2 = Math.abs(clientX - closestNextLineRect.right); 44962 44963 return showCaret(direction, closestNextLineRect.node, dist1 < dist2); 44964 } 44965 } 44966 44967 if (contentEditableFalseNode) { 44968 var caretPositions = LineWalker.positionsUntil(direction, rootNode, LineWalker.isAboveLine(1), contentEditableFalseNode); 44969 44970 closestNextLineRect = LineUtils.findClosestClientRect(Arr.filter(caretPositions, LineWalker.isLine(1)), clientX); 44971 if (closestNextLineRect) { 44972 return renderRangeCaret(closestNextLineRect.position.toRange()); 44973 } 44974 44975 closestNextLineRect = Arr.last(Arr.filter(caretPositions, LineWalker.isLine(0))); 44976 if (closestNextLineRect) { 44977 return renderRangeCaret(closestNextLineRect.position.toRange()); 44978 } 44979 } 44980 } 44981 44982 function exitPreBlock(direction, range) { 44983 var pre, caretPos, newBlock; 44984 44985 function createTextBlock() { 44986 var textBlock = editor.dom.create(editor.settings.forced_root_block); 44987 44988 if (!Env.ie || Env.ie >= 11) { 44989 textBlock.innerHTML = '<br data-mce-bogus="1">'; 44990 } 44991 44992 return textBlock; 44993 } 44994 44995 if (range.collapsed && editor.settings.forced_root_block) { 44996 pre = editor.dom.getParent(range.startContainer, 'PRE'); 44997 if (!pre) { 44998 return; 44999 } 45000 45001 if (direction == 1) { 45002 caretPos = getNextVisualCaretPosition(CaretPosition.fromRangeStart(range)); 45003 } else { 45004 caretPos = getPrevVisualCaretPosition(CaretPosition.fromRangeStart(range)); 45005 } 45006 45007 if (!caretPos) { 45008 newBlock = createTextBlock(); 45009 45010 if (direction == 1) { 45011 editor.$(pre).after(newBlock); 45012 } else { 45013 editor.$(pre).before(newBlock); 45014 } 45015 45016 editor.selection.select(newBlock, true); 45017 editor.selection.collapse(); 45018 } 45019 } 45020 } 45021 45022 function moveH(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) { 45023 var newRange; 45024 45025 newRange = moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range); 45026 if (newRange) { 45027 return newRange; 45028 } 45029 45030 newRange = exitPreBlock(direction, range); 45031 if (newRange) { 45032 return newRange; 45033 } 45034 45035 return null; 45036 } 45037 45038 function moveV(direction, walkerFn, range) { 45039 var newRange; 45040 45041 newRange = moveToCeFalseVertically(direction, walkerFn, range); 45042 if (newRange) { 45043 return newRange; 45044 } 45045 45046 newRange = exitPreBlock(direction, range); 45047 if (newRange) { 45048 return newRange; 45049 } 45050 45051 return null; 45052 } 45053 45054 function getBlockCaretContainer() { 45055 return $('*[data-mce-caret]')[0]; 45056 } 45057 45058 function showBlockCaretContainer(blockCaretContainer) { 45059 blockCaretContainer = $(blockCaretContainer); 45060 45061 if (blockCaretContainer.attr('data-mce-caret')) { 45062 fakeCaret.hide(); 45063 blockCaretContainer.removeAttr('data-mce-caret'); 45064 blockCaretContainer.removeAttr('data-mce-bogus'); 45065 blockCaretContainer.removeAttr('style'); 45066 45067 // Removes control rect on IE 45068 setRange(getRange()); 45069 scrollIntoView(blockCaretContainer[0]); 45070 } 45071 } 45072 45073 function renderCaretAtRange(range) { 45074 var caretPosition; 45075 45076 range = CaretUtils.normalizeRange(1, rootNode, range); 45077 caretPosition = CaretPosition.fromRangeStart(range); 45078 45079 if (isContentEditableFalse(caretPosition.getNode())) { 45080 return showCaret(1, caretPosition.getNode(), !caretPosition.isAt
45080End()); 45081 } 45082 45083 if (isContentEditableFalse(caretPosition.getNode(true))) { 45084 return showCaret(1, caretPosition.getNode(true), false); 45085 } 45086 45087 fakeCaret.hide(); 45088 45089 return null; 45090 } 45091 45092 function renderRangeCaret(range) { 45093 var caretRange; 45094 45095 if (!range || !range.collapsed) { 45096 return range; 45097 } 45098 45099 caretRange = renderCaretAtRange(range); 45100 if (caretRange) { 45101 return caretRange; 45102 } 45103 45104 return range; 45105 } 45106 45107 function deleteContentEditableNode(node) { 45108 var nextCaretPosition, prevCaretPosition, prevCeFalseElm, nextElement; 45109 45110 if (!isContentEditableFalse(node)) { 45111 return null; 45112 } 45113 45114 if (isContentEditableFalse(node.previousSibling)) { 45115 prevCeFalseElm = node.previousSibling; 45116 } 45117 45118 prevCaretPosition = getPrevVisualCaretPosition(CaretPosition.before(node)); 45119 if (!prevCaretPosition) { 45120 nextCaretPosition = getNextVisualCaretPosition(CaretPosition.after(node)); 45121 } 45122 45123 if (nextCaretPosition && isElement(nextCaretPosition.getNode())) { 45124 nextElement = nextCaretPosition.getNode(); 45125 } 45126 45127 CaretContainer.remove(node.previousSibling); 45128 CaretContainer.remove(node.nextSibling); 45129 editor.dom.remove(node); 45130 clearContentEditableSelection(); 45131 45132 if (editor.dom.isEmpty(editor.getBody())) { 45133 editor.setContent(''); 45134 editor.focus(); 45135 return; 45136 } 45137 45138 if (prevCeFalseElm) { 45139 return CaretPosition.after(prevCeFalseElm).toRange(); 45140 } 45141 45142 if (nextElement) { 45143 return CaretPosition.before(nextElement).toRange(); 45144 } 45145 45146 if (prevCaretPosition) { 45147 return prevCaretPosition.toRange(); 45148 } 45149 45150 if (nextCaretPosition) { 45151 return nextCaretPosition.toRange(); 45152 } 45153 45154 return null; 45155 } 45156 45157 function backspaceDelete(direction, beforeFn, range) { 45158 var node, caretPosition; 45159 45160 if (!range.collapsed) { 45161 node = getSelectedNode(range); 45162 if (isContentEditableFalse(node)) { 45163 return renderRangeCaret(deleteContentEditableNode(node)); 45164 } 45165 } 45166 45167 caretPosition = getNormalizedRangeEndPoint(direction, range); 45168 45169 if (beforeFn(caretPosition)) { 45170 return renderRangeCaret(deleteContentEditableNode(caretPosition.getNode(direction == -1))); 45171 } 45172 } 45173 45174 function registerEvents() { 45175 var right = curry(moveH, 1, getNextVisualCaretPosition, isBeforeContentEditableFalse); 45176 var left = curry(moveH, -1, getPrevVisualCaretPosition, isAfterContentEditableFalse); 45177 var deleteForward = curry(backspaceDelete, 1, isBeforeContentEditableFalse); 45178 var backspace = curry(backspaceDelete, -1, isAfterContentEditableFalse); 45179 var up = curry(moveV, -1, LineWalker.upUntil); 45180 var down = curry(moveV, 1, LineWalker.downUntil); 45181 45182 function override(moveFn) { 45183 var range = moveFn(getRange()); 45184 45185 if (range) { 45186 setRange(range); 45187 return true; 45188 } 45189 45190 return false; 45191 } 45192 45193 function getContentEditableRoot(node) { 45194 var root = editor.getBody(); 45195 45196 while (node && node != root) { 45197 if (isContentEditableTrue(node) || isContentEditableFalse(node)) { 45198 return node; 45199 } 45200 45201 node = node.parentNode; 45202 } 45203 45204 return null; 45205 } 45206 45207 // Some browsers (Chrome) lets you place the caret after a cE=false 45208 // Make sure we render the caret container in this case 45209 editor.on('mouseup', function() { 45210 var range = getRange(); 45211 45212 if (range.collapsed) { 45213 setRange(renderCaretAtRange(range)); 45214 } 45215 }); 45216 45217 editor.on('mousedown', function(e) { 45218 var contentEditableRoot; 45219 45220 contentEditableRoot = getContentEditableRoot(e.target); 45221 if (contentEditableRoot) { 45222 if (isContentEditableFalse(contentEditableRoot)) { 45223 e.preventDefault(); 45224 setContentEditableSelection(selectNode(contentEditableRoot), false); 45225 } else { 45226 editor.selection.placeCaretAt(e.clientX, e.clientY); 45227 } 45228 } else { 45229 clearContentEditableSelection(); 45230 fakeCaret.hide(); 45231 45232 var caretInfo = LineUtils.closestCaret(rootNode, e.clientX, e.clientY); 45233 if (caretInfo) { 45234 e.preventDefault(); 45235 editor.getBody().focus(); 45236 setRange(showCaret(1, caretInfo.node, caretInfo.before)); 45237 } 45238 } 45239 }); 45240 45241 editor.on('keydown', function(e) { 45242 var prevent; 45243 45244 if (VK.modifierPressed(e)) { 45245 return; 45246 } 45247 45248 switch (e.keyCode) { 45249 case VK.RIGHT: 45250 prevent = override(right); 45251 break; 45252
45253 case VK.DOWN: 45254 prevent = override(down); 45255 break; 45256 45257 case VK.LEFT: 45258 prevent = override(left); 45259 break; 45260 45261 case VK.UP: 45262 prevent = override(up); 45263 break; 45264 45265 case VK.DELETE: 45266 prevent = override(deleteForward); 45267 break; 45268 45269 case VK.BACKSPACE: 45270 prevent = override(backspace); 45271 break; 45272 45273 default: 45274 prevent = isContentEditableFalse(editor.selection.getNode()); 45275 break; 45276 } 45277 45278 if (prevent) { 45279 e.preventDefault(); 45280 } 45281 }); 45282 45283 function paddEmptyContentEditableArea() { 45284 var br, ceRoot = getContentEditableRoot(editor.selection.getNode()); 45285 45286 if (isContentEditableTrue(ceRoot) && isBlock(ceRoot) && editor.dom.isEmpty(ceRoot)) { 45287 br = editor.dom.create('br', {"data-mce-bogus": "1"}); 45288 editor.$(ceRoot).empty().append(br); 45289 editor.selection.setRng(CaretPosition.before(br).toRange()); 45290 } 45291 } 45292 45293 function handleBlockContainer(e) { 45294 var blockCaretContainer = getBlockCaretContainer(); 45295 45296 if (!blockCaretContainer) { 45297 return; 45298 } 45299 45300 if (e.type == 'compositionstart') { 45301 e.preventDefault(); 45302 e.stopPropagation(); 45303 showBlockCaretContainer(blockCaretContainer); 45304 return; 45305 } 45306 45307 if (blockCaretContainer.innerHTML != ' ') { 45308 showBlockCaretContainer(blockCaretContainer); 45309 } 45310 } 45311 45312 function handleEmptyBackspaceDelete(e) { 45313 var prevent; 45314 45315 switch (e.keyCode) { 45316 case VK.DELETE: 45317 prevent = paddEmptyContentEditableArea(); 45318 break; 45319 45320 case VK.BACKSPACE: 45321 prevent = paddEmptyContentEditableArea(); 45322 break; 45323 } 45324 45325 if (prevent) { 45326 e.preventDefault(); 45327 } 45328 } 45329 45330 // Must be added to "top" since undoManager needs to be executed after 45331 editor.on('keyup compositionstart', function(e) { 45332 handleBlockContainer(e); 45333 handleEmptyBackspaceDelete(e); 45334 }, true); 45335 45336 editor.on('cut', function() { 45337 var node = editor.selection.getNode(); 45338 45339 if (isContentEditableFalse(node)) { 45340 Delay.setEditorTimeout(editor, function() { 45341 setRange(renderRangeCaret(deleteContentEditableNode(node))); 45342 }); 45343 } 45344 }); 45345 45346 editor.on('getSelectionRange', function(e) { 45347 var rng = e.range; 45348 45349 if (selectedContentEditableNode) { 45350 if (!selectedContentEditableNode.parentNode) { 45351 selectedContentEditableNode = null; 45352 return; 45353 } 45354 45355 rng = rng.cloneRange(); 45356 rng.selectNode(selectedContentEditableNode); 45357 e.range = rng; 45358 } 45359 }); 45360 45361 editor.on('setSelectionRange', function(e) { 45362 var rng; 45363 45364 rng = setContentEditableSelection(e.range); 45365 if (rng) { 45366 e.range = rng; 45367 } 45368 }); 45369 45370 editor.on('focus', function() { 45371 // Make sure we have a proper fake caret on focus 45372 Delay.setEditorTimeout(editor, function() { 45373 editor.selection.setRng(renderRangeCaret(editor.selection.getRng())); 45374 }); 45375 }); 45376 45377 DragDropOverrides.init(editor); 45378 } 45379 45380 function addCss() { 45381 var styles = editor.contentStyles, rootClass = '.mce-content-body'; 45382 45383 styles.push(fakeCaret.getCss()); 45384 styles.push( 45385 rootClass + ' .mce-offscreen-selection {' + 45386 'position: absolute;' + 45387 'left: -9999999999px;' + 45388 'width: 100px' + 45389 'height: 100px' + 45390 '}' + 45391 rootClass + ' *[contentEditable=false] {' + 45392 'cursor: default;' + 45393 '}' + 45394 rootClass + ' *[contentEditable=true] {' + 45395 'cursor: text;' + 45396 '}' 45397 ); 45398 } 45399 45400 function isRangeInCaretContainer(rng) { 45401 return CaretContainer.isCaretContainer(rng.startContainer) || CaretContainer.isCaretContainer(rng.endContainer); 45402 } 45403 45404 function setContentEditableSelection(range, fireEvent) { 45405 var node, $ = editor.$, dom = editor.dom, $realSelectionContainer, sel, 45406 startContainer, startOffset, endOffset, e, caretPosition; 45407 45408 if (!range) { 45409 clearContentEditableSelection(); 45410 return null; 45411 } 45412 45413 if (range.collapsed) { 45414 clearContentEditableSelection(); 45415 45416 if (!isRangeInCaretContainer(range)) { 45417 caretPosition = getNormalizedRangeEndPoint(1, range); 45418 45419 if (isContentEditableFalse(caretPosition.getNode())) { 45420 return showCaret(1, caretPosition.getNode(), !caretPosition.isAtEnd()); 45421 } 45422 45423 if (isContentEditableFalse(caretPosition.getNode(true))) { 45424 return showCaret(1, caretPosition.getNode(true), false); 45425 } 45426 } 45427 45428 return null; 45429 } 45430 45431 startContainer = range.startContainer; 45432 startOffset = range.startOffset; 45433 endOffset = range.endOffset; 45434 45435 // Normalizes <span cE=false>[</span>] to [<span cE=false></span>] 45436 if (startContainer.nodeType == 3 && startOffset == 0 && isContentEditableFalse(startContainer.parentNode)) { 45437 startContainer = startContainer.parentNode; 45438 startOffset = dom.nodeIndex(startContainer); 45439 startContainer = startContainer.parentNode; 45440 } 45441 45442 if (startContainer.nodeType != 1) { 45443 clearContentEditableSelection(); 45444 return null; 45445 } 45446 45447 if (endOffset == startOffset + 1) { 45448 node = startContainer.childNodes[startOffset]; 45449 } 45450 45451 if (isContentEditableFalse(node)) { 45452 if (fireEvent !== false) { 45453 e = editor.fire('ObjectSelected', {target: node}); 45454 if (e.isDefaultPrevented()) { 45455 clearContentEditableSelection(); 45456 return null; 45457 } 45458 } 45459 45460 $realSelectionContainer = $('#' + realSelectionId); 45461 if ($realSelectionContainer.length === 0) { 45462 $realSelectionContainer = $( 45463 '<div data-mce-bogus="all" class="mce-offscreen-selection"></div>' 45464 ).attr('id', realSelectionId); 45465 45466 $realSelectionContainer.appendTo(editor.getBody()); 45467 } 45468 45469 $realSelectionContainer.empty().append('\u00a0').append(node.cloneNode(true)).append('\u00a0').css({ 45470 top: dom.getPos(node, editor.getBody()).y 45471 }); 45472 45473 range = editor.dom.createRng(); 45474 range.setStart($realSelectionContainer[0].firstChild, 1); 45475 range.setEnd($realSelectionContainer[0].lastChild, 0); 45476 45477 editor.getBody().focus(); 45478 $realSelectionContainer[0].focus(); 45479 sel = editor.selection.getSel(); 45480 sel.removeAllRanges(); 45481 sel.addRange(range); 45482 45483 editor.$('*[data-mce-selected]').removeAttr('data-mce-selected'); 45484 node.setAttribute('data-mce-selected', 1); 45485 selectedContentEditableNode = node; 45486 45487 return range; 45488 } 45489 45490 clearContentEditableSelection(); 45491 return null; 45492 } 45493 45494 function clearContentEditableSelection() { 45495 if (selectedContentEditableNode) { 45496 selectedContentEditableNode.removeAttribute('data-mce-selected'); 45497 editor.$('#' + realSelectionId).remove(); 45498 selectedContentEditableNode = null; 45499 } 45500 } 45501 45502 function destroy() { 45503 fakeCaret.destroy(); 45504 selectedContentEditableNode = null; 45505 } 45506 45507 if (Env.ceFalse) { 45508 registerEvents(); 45509 addCss(); 45510 } 45511 45512 return { 45513 showBlockCaretContainer: showBlockCaretContainer, 45514 destroy: destroy 45515 }; 45516 } 45517 45518 return SelectionOverrides; 45519}); 45520 45521// Included from: js/tinymce/classes/Editor.js 45522 45523/** 45524 * Editor.js 45525 * 45526 * Released under LGPL License. 45527 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 45528 * 45529 * License: http://www.tinymce.com/license 45530 * Contributing: http://www.tinymce.com/contributing 45531 */ 45532 45533/*jshint scripturl:true */ 45534 45535/** 45536 * Include the base event class documentation. 45537 * 45538 * @include ../../../tools/docs/tinymce.Event.js 45539 */ 45540 45541/** 45542 * This class contains the core logic for a TinyMCE editor. 45543 * 45544 * @class tinymce.Editor 45545 * @mixes tinymce.util.Observable 45546 * @example 45547 * // Add a class to all paragraphs in the editor. 45548 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 45549 * 45550 * // Gets the current editors selection as text 45551 * tinymce.activeEditor.selection.getContent({format: 'text'}); 45552 * 45553 * // Creates a new editor instance 45554 * var ed = new tinymce.Editor('textareaid', { 45555 * some_setting: 1 45556 * }, tinymce.EditorManager); 45557 * 45558 * // Select each item the user clicks on 45559 * ed.on('click', function(e) { 45560 * ed.selection.select(e.target); 45561 * }); 45562 * 45563 * ed.render(); 45564 */ 45565define("tinymce/Editor", [ 45566 "tinymce/dom/DOMUtils", 45567 "tinymce/dom/DomQuery", 45568 "tinymce/AddOnManager", 45569 "tinymce/NodeChange", 45570 "tinymce/html/Node", 45571 "tinymce/dom/Serializer", 45572 "tinymce/html/Serializer", 45573 "tinymce/dom/Selection", 45574 "tinymce/Formatter", 45575 "tinymce/UndoManager", 45576 "tinymce/EnterKey", 45577 "tinymce/ForceBlocks",
45578 "tinymce/EditorCommands", 45579 "tinymce/util/URI", 45580 "tinymce/dom/ScriptLoader", 45581 "tinymce/dom/EventUtils", 45582 "tinymce/WindowManager", 45583 "tinymce/NotificationManager", 45584 "tinymce/html/Schema", 45585 "tinymce/html/DomParser", 45586 "tinymce/util/Quirks", 45587 "tinymce/Env", 45588 "tinymce/util/Tools", 45589 "tinymce/util/Delay", 45590 "tinymce/EditorObservable", 45591 "tinymce/Mode", 45592 "tinymce/Shortcuts", 45593 "tinymce/EditorUpload", 45594 "tinymce/SelectionOverrides" 45595], function( 45596 DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer, 45597 Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands, 45598 URI, ScriptLoader, EventUtils, WindowManager, NotificationManager, 45599 Schema, DomParser, Quirks, Env, Tools, Delay, EditorObservable, Mode, Shortcuts, EditorUpload, 45600 SelectionOverrides 45601) { 45602 // Shorten these names 45603 var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager; 45604 var extend = Tools.extend, each = Tools.each, explode = Tools.explode; 45605 var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve; 45606 var Event = EventUtils.Event; 45607 var isGecko = Env.gecko, ie = Env.ie; 45608 45609 /** 45610 * Include documentation for all the events. 45611 * 45612 * @include ../../../tools/docs/tinymce.Editor.js 45613 */ 45614 45615 /** 45616 * Constructs a editor instance by id. 45617 * 45618 * @constructor 45619 * @method Editor 45620 * @param {String} id Unique id for the editor. 45621 * @param {Object} settings Settings for the editor. 45622 * @param {tinymce.EditorManager} editorManager EditorManager instance. 45623 */ 45624 function Editor(id, settings, editorManager) { 45625 var self = this, documentBaseUrl, baseUri; 45626 45627 documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL; 45628 baseUri = editorManager.baseURI; 45629 45630 /** 45631 * Name/value collection with editor settings. 45632 * 45633 * @property settings 45634 * @type Object 45635 * @example 45636 * // Get the value of the theme setting 45637 * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme"); 45638 */ 45639 self.settings = settings = extend({ 45640 id: id, 45641 theme: 'modern', 45642 delta_width: 0, 45643 delta_height: 0, 45644 popup_css: '', 45645 plugins: '', 45646 document_base_url: documentBaseUrl, 45647 add_form_submit_trigger: true, 45648 submit_patch: true, 45649 add_unload_trigger: true, 45650 convert_urls: true, 45651 relative_urls: true, 45652 remove_script_host: true, 45653 object_resizing: true, 45654 doctype: '<!DOCTYPE html>', 45655 visual: true, 45656 font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large', 45657 45658 // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size 45659 font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%', 45660 forced_root_block: 'p', 45661 hidden_input: true, 45662 padd_empty_editor: true, 45663 render_ui: true, 45664 indentation: '30px', 45665 inline_styles: true, 45666 convert_fonts_to_spans: true, 45667 indent: 'simple', 45668 indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 45669 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist', 45670 indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 45671 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist', 45672 validate: true, 45673 entity_encoding: 'named', 45674 url_converter: self.convertURL, 45675 url_converter_scope: self, 45676 ie7_compat: true 45677 }, settings); 45678 45679 AddOnManager.language = settings.language || 'en'; 45680 AddOnManager.languageLoad = settings.language_load; 45681 45682 AddOnManager.baseURL = editorManager.baseURL; 45683 45684 /** 45685 * Editor instance id, normally the same as the div/textarea that was replaced. 45686 * 45687 * @property id
45688 * @type String 45689 */ 45690 self.id = settings.id = id; 45691 45692 /** 45693 * State to force the editor to return false on a isDirty call. 45694 * 45695 * @property isNotDirty 45696 * @type Boolean 45697 * @deprecated Use editor.setDirty instead. 45698 */ 45699 self.setDirty(false); 45700 45701 /** 45702 * Name/Value object containing plugin instances. 45703 * 45704 * @property plugins 45705 * @type Object 45706 * @example 45707 * // Execute a method inside a plugin directly 45708 * tinymce.activeEditor.plugins.someplugin.someMethod(); 45709 */ 45710 self.plugins = {}; 45711 45712 /** 45713 * URI object to document configured for the TinyMCE instance. 45714 * 45715 * @property documentBaseURI 45716 * @type tinymce.util.URI 45717 * @example 45718 * // Get relative URL from the location of document_base_url 45719 * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm'); 45720 * 45721 * // Get absolute URL from the location of document_base_url 45722 * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm'); 45723 */ 45724 self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, { 45725 base_uri: baseUri 45726 }); 45727 45728 /** 45729 * URI object to current document that holds the TinyMCE editor instance. 45730 * 45731 * @property baseURI 45732 * @type tinymce.util.URI 45733 * @example 45734 * // Get relative URL from the location of the API 45735 * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm'); 45736 * 45737 * // Get absolute URL from the location of the API 45738 * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm'); 45739 */ 45740 self.baseURI = baseUri; 45741 45742 /** 45743 * Array with CSS files to load into the iframe. 45744 * 45745 * @property contentCSS 45746 * @type Array 45747 */ 45748 self.contentCSS = []; 45749 45750 /** 45751 * Array of CSS styles to add to head of document when the editor loads. 45752 * 45753 * @property contentStyles 45754 * @type Array 45755 */ 45756 self.contentStyles = []; 45757 45758 // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic 45759 self.shortcuts = new Shortcuts(self); 45760 self.loadedCSS = {}; 45761 self.editorCommands = new EditorCommands(self); 45762 45763 if (settings.target) { 45764 self.targetElm = settings.target; 45765 } 45766 45767 self.suffix = editorManager.suffix; 45768 self.editorManager = editorManager; 45769 self.inline = settings.inline; 45770 45771 if (settings.cache_suffix) { 45772 Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, ''); 45773 } 45774 45775 if (settings.override_viewport === false) { 45776 Env.overrideViewPort = false;
45777 } 45778 45779 // Call setup 45780 editorManager.fire('SetupEditor', self); 45781 self.execCallback('setup', self); 45782 45783 /** 45784 * Dom query instance with default scope to the editor document and default element is the body of the editor. 45785 * 45786 * @property $ 45787 * @type tinymce.dom.DomQuery 45788 * @example 45789 * tinymce.activeEditor.$('p').css('color', 'red'); 45790 * tinymce.activeEditor.$().append('<p>new</p>'); 45791 */ 45792 self.$ = DomQuery.overrideDefaults(function() { 45793 return { 45794 context: self.inline ? self.getBody() : self.getDoc(), 45795 element: self.getBody() 45796 }; 45797 }); 45798 } 45799 45800 Editor.prototype = { 45801 /** 45802 * Renders the editor/adds it to the page. 45803 * 45804 * @method render 45805 */ 45806 render: function() { 45807 var self = this, settings = self.settings, id = self.id, suffix = self.suffix; 45808 45809 function readyHandler() { 45810 DOM.unbind(window, 'ready', readyHandler); 45811 self.render(); 45812 } 45813 45814 // Page is not loaded yet, wait for it 45815 if (!Event.domLoaded) { 45816 DOM.bind(window, 'ready', readyHandler); 45817 return; 45818 } 45819 45820 // Element not found, then skip initialization 45821 if (!self.getElement()) { 45822 return; 45823 } 45824 45825 // No editable support old iOS versions etc 45826 if (!Env.contentEditable) { 45827 return; 45828 } 45829 45830 // Hide target element early to prevent content flashing 45831 if (!settings.inline) { 45832 self.orgVisibility = self.getElement().style.visibility; 45833 self.getElement().style.visibility = 'hidden'; 45834 } else { 45835 self.inline = true; 45836 } 45837 45838 var form = self.getElement().form || DOM.getParent(id, 'form'); 45839 if (form) { 45840 self.formElement = form; 45841 45842 // Add hidden input for non input elements inside form elements 45843 if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) { 45844 DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id); 45845 self.hasHiddenInput = true; 45846 } 45847 45848 // Pass submit/reset from form to editor instance 45849 self.formEventDelegate = function(e) { 45850 self.fire(e.type, e); 45851 }; 45852 45853 DOM.bind(form, 'submit reset', self.formEventDelegate); 45854 45855 // Reset contents in editor when the form is reset 45856 self.on('reset', function() { 45857 self.setContent(self.startContent, {format: 'raw'}); 45858 }); 45859 45860 // Check page uses id="submit" or name="submit" for it's submit button 45861 if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) { 45862 form._mceOldSubmit = form.submit; 45863 form.submit = function() { 45864 self.editorManager.triggerSave(); 45865 self.setDirty(false); 45866 45867 return form._mceOldSubmit(form); 45868 }; 45869 } 45870 } 45871 45872 /** 45873 * Window manager reference, use this to open new windows and dialogs. 45874 * 45875 * @property windowManager 45876 * @type tinymce.WindowManager 45877 * @example 45878 * // Shows an alert message 45879 * tinymce.activeEditor.windowManager.alert('Hello world!'); 45880 * 45881 * // Opens a new dialog with the file.htm file and the size 320x240 45882 * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog. 45883 * tinymce.activeEditor.windowManager.open({ 45884 * url: 'file.htm', 45885 * width: 320, 45886 * height: 240 45887 * }, { 45888 * custom_param: 1 45889 * }); 45890 */ 45891 self.windowManager = new WindowManager(self); 45892 45893 /** 45894 * Notification manager reference, use this to open new windows and dialogs. 45895 * 45896 * @property notificationManager 45897 * @type tinymce.NotificationManager 45898 * @example 45899 * // Shows a notification info message. 45900 * tinymce.activeEditor.notificationManager.open({text: 'Hello world!', type: 'info'}); 45901 */ 45902 self.notificationManager = new NotificationManager(self); 45903 45904 if (settings.encoding == 'xml') { 45905 self.on('GetContent', function(e) { 45906 if (e.save) { 45907 e.content = DOM.encode(e.content); 45908 } 45909 }); 45910 } 45911 45912 if (settings.add_form_submit_trigger) { 45913 self.on('submit', function() { 45914 if (self.initialized) { 45915 self.save(); 45916 } 45917 }); 45918 } 45919 45920 if (settings.add_unload_trigger) { 45921 self._beforeUnload = function() { 45922 if (self.initialized && !self.destroyed && !self.isHidden()) { 45923 self.save({format: 'raw', no_events: true, set_dirty: false}); 45924 } 45925 }; 45926 45927 self.editorManager.on('BeforeUnload', self._beforeUnload); 45928 } 45929 45930 // Load scripts 45931 function loadScripts() { 45932 var scriptLoader = ScriptLoader.ScriptLoader; 45933
45934 if (settings.language && settings.language != 'en' && !settings.language_url) { 45935 settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js'; 45936 } 45937 45938 if (settings.language_url) { 45939 scriptLoader.add(settings.language_url); 45940 } 45941 45942 if (settings.theme && typeof settings.theme != "function" && 45943 settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) { 45944 var themeUrl = settings.theme_url; 45945 45946 if (themeUrl) { 45947 themeUrl = self.documentBaseURI.toAbsolute(themeUrl); 45948 } else { 45949 themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js'; 45950 } 45951 45952 ThemeManager.load(settings.theme, themeUrl); 45953 } 45954 45955 if (Tools.isArray(settings.plugins)) { 45956 settings.plugins = settings.plugins.join(' '); 45957 } 45958 45959 each(settings.external_plugins, function(url, name) { 45960 PluginManager.load(name, url); 45961 settings.plugins += ' ' + name; 45962 }); 45963 45964 each(settings.plugins.split(/[ ,]/), function(plugin) { 45965 plugin = trim(plugin); 45966 45967 if (plugin && !PluginManager.urls[plugin]) { 45968 if (plugin.charAt(0) == '-') { 45969 plugin = plugin.substr(1, plugin.length); 45970 45971 var dependencies = PluginManager.dependencies(plugin); 45972 45973 each(dependencies, function(dep) { 45974 var defaultSettings = { 45975 prefix: 'plugins/', 45976 resource: dep, 45977 suffix: '/plugin' + suffix + '.js' 45978 }; 45979 45980 dep = PluginManager.createUrl(defaultSettings, dep); 45981 PluginManager.load(dep.resource, dep); 45982 }); 45983 } else { 45984 PluginManager.load(plugin, { 45985 prefix: 'plugins/', 45986 resource: plugin, 45987 suffix: '/plugin' + suffix + '.js' 45988 }); 45989 } 45990 } 45991 }); 45992 45993 scriptLoader.loadQueue(function() { 45994 if (!self.removed) { 45995 self.init(); 45996 } 45997 }); 45998 } 45999 46000 loadScripts(); 46001 }, 46002 46003 /** 46004 * Initializes the editor this will be called automatically when 46005 * all plugins/themes and language packs are loaded by the rendered method. 46006 * This method will setup the iframe and create the theme and plugin instances. 46007 * 46008 * @method init 46009 */ 46010 init: function() { 46011 var self = this, settings = self.settings, elm = self.getElement(); 46012 var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = []; 46013 46014 this.editorManager.i18n.setCode(settings.language); 46015 self.rtl = settings.rtl_ui || this.editorManager.i18n.rtl; 46016 self.editorManager.add(self); 46017 46018 settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area')); 46019 46020 /** 46021 * Reference to the theme instance that was used to generate the UI. 46022 * 46023 * @property theme 46024 * @type tinymce.Theme 46025 * @example 46026 * // Executes a method on the theme directly 46027 * tinymce.activeEditor.theme.someMethod(); 46028 */ 46029 if (settings.theme) { 46030 if (typeof settings.theme != "function") { 46031 settings.theme = settings.theme.replace(/-/, ''); 46032 Theme = ThemeManager.get(settings.theme); 46033 self.theme = new Theme(self, ThemeManager.urls[settings.theme]); 46034 46035 if (self.theme.init) { 46036 self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$); 46037 } 46038 } else { 46039 self.theme = settings.theme; 46040 } 46041 } 46042 46043 function initPlugin(plugin) { 46044 var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance; 46045 46046 pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, ''); 46047 plugin = trim(plugin); 46048 if (Plugin && inArray(initializedPlugins, plugin) === -1) { 46049 each(PluginManager.dependencies(plugin), function(dep) { 46050 initPlugin(dep); 46051 }); 46052 46053 if (self.plugins[plugin]) { 46054 return; 46055 } 46056 46057 pluginInstance = new Plugin(self, pluginUrl, self.$); 46058 46059 self.plugins[plugin] = pluginInstance; 46060 46061 if (pluginInstance.init) { 46062 pluginInstance.init(self, pluginUrl); 46063 initializedPlugins.push(plugin); 46064 } 46065 } 46066 } 46067 46068 // Create all plugins 46069 each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin); 46070 46071 // Measure box 46072 if (settings.render_ui && self.theme) { 46073 self.orgDisplay = elm.style.display; 46074 46075 if (typeof settings.theme != "function") { 46076 w = settings.width || elm.style.width || elm.offsetWidth; 46077 h = settings.height || elm.style.height || elm.offsetHeight; 46078 minHeight = settings.min_height || 100; 46079 re = /^[0-9\.]+(|px)$/i; 46080 46081 if (re.test('' + w)) { 46082 w = Math.max(parseInt(w, 10), 100); 46083 } 46084 46085 if (re.test('' + h)) { 46086 h = Math.max(parseInt(h, 10), minHeight); 46087 } 46088 46089 // Render UI 46090 o = self.theme.renderUI({ 46091 targetNode: elm, 46092 width: w, 46093 height: h, 46094 deltaWidth: settings.delta_width, 46095 deltaHeight: settings.delta_height 46096 }); 46097 46098 // Resize editor 46099 if (!settings.content_editable) { 46100 h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0
46100) : ''); 46101 if (h < minHeight) { 46102 h = minHeight; 46103 } 46104 } 46105 } else { 46106 o = settings.theme(self, elm); 46107 46108 // Convert element type to id:s 46109 if (o.editorContainer.nodeType) { 46110 o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent"; 46111 } 46112 46113 // Convert element type to id:s 46114 if (o.iframeContainer.nodeType) { 46115 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer"; 46116 } 46117 46118 // Use specified iframe height or the targets offsetHeight 46119 h = o.iframeHeight || elm.offsetHeight; 46120 } 46121 46122 self.editorContainer = o.editorContainer; 46123 } 46124 46125 // Load specified content CSS last 46126 if (settings.content_css) { 46127 each(explode(settings.content_css), function(u) { 46128 self.contentCSS.push(self.documentBaseURI.toAbsolute(u)); 46129 }); 46130 } 46131 46132 // Load specified content CSS last 46133 if (settings.content_style) { 46134 self.contentStyles.push(settings.content_style); 46135 } 46136 46137 // Content editable mode ends here 46138 if (settings.content_editable) { 46139 elm = n = o = null; // Fix IE leak 46140 return self.initContentBody(); 46141 } 46142 46143 self.iframeHTML = settings.doctype + '<html><head>'; 46144 46145 // We only need to override paths if we have to 46146 // IE has a bug where it remove site absolute urls to relative ones if this is specified 46147 if (settings.document_base_url != self.documentBaseUrl) { 46148 self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />'; 46149 } 46150 46151 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. 46152 if (!Env.caretAfter && settings.ie7_compat) { 46153 self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />'; 46154 } 46155 46156 self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />'; 46157 46158 // Load the CSS by injecting them into the HTML this will reduce "flicker" 46159 for (i = 0; i < self.contentCSS.length; i++) { 46160 var cssUrl = self.contentCSS[i]; 46161 self.iframeHTML += ( 46162 '<link type="text/css" ' + 46163 'rel="stylesheet" ' + 46164 'href="' + Tools._addCacheSuffix(cssUrl) + '" />' 46165 ); 46166 self.loadedCSS[cssUrl] = true; 46167 } 46168 46169 bodyId = settings.body_id || 'tinymce'; 46170 if (bodyId.indexOf('=') != -1) { 46171 bodyId = self.getParam('body_id', '', 'hash'); 46172 bodyId = bodyId[self.id] || bodyId; 46173 } 46174 46175 bodyClass = settings.body_class || ''; 46176 if (bodyClass.indexOf('=') != -1) { 46177 bodyClass = self.getParam('body_class', '', 'hash'); 46178 bodyClass = bodyClass[self.id] || ''; 46179 } 46180 46181 if (settings.content_security_policy) { 46182 self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />'; 46183 } 46184 46185 self.iframeHTML += '</head><body id="' + bodyId + 46186 '" class="mce-content-body ' + bodyClass + 46187 '" data-id="' + self.id + '"><br></body></html>'; 46188 46189 /*eslint no-script-url:0 */ 46190 var domainRelaxUrl = 'javascript:(function(){' + 46191 'document.open();document.domain="' + document.domain + '";' + 46192 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' + 46193 'document.close();ed.initContentBody(true);})()'; 46194 46195 // Domain relaxing is required since the user has messed around with document.domain 46196 if (document.domain != location.hostname) { 46197 // Edge seems to be able to handle domain relaxing 46198 if (Env.ie && Env.ie < 12) { 46199 url = domainRelaxUrl; 46200 } 46201 } 46202 46203 // Create iframe 46204 // TODO: ACC add the appropriate description on this. 46205 var ifr = DOM.create('iframe', { 46206 id: self.id + "_ifr", 46207 //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7 46208 frameBorder: '0', 46209 allowTransparency: "true", 46210 title: self.editorManager.translate( 46211 "Rich Text Area. Press ALT-F9 for menu. " + 46212 "Press ALT-F10 for toolbar. Press ALT-0 for help" 46213 ), 46214 style: { 46215 width: '100%', 46216 height: h, 46217 display: 'block' // Important for Gecko to render the iframe correctly 46218 } 46219 }); 46220 46221 ifr.onload = function() { 46222 ifr.onload = null; 46223 self.fire("load"); 46224 }; 46225 46226 DOM.setAttrib(ifr, "src", url || 'javascript:""'); 46227
46228 self.contentAreaContainer = o.iframeContainer; 46229 self.iframeElement = ifr; 46230 46231 n = DOM.add(o.iframeContainer, ifr); 46232 46233 // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname 46234 // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!! 46235 if (ie) { 46236 try { 46237 self.getDoc(); 46238 } catch (e) { 46239 n.src = url = domainRelaxUrl; 46240 } 46241 } 46242 46243 if (o.editorContainer) { 46244 DOM.get(o.editorContainer).style.display = self.orgDisplay; 46245 self.hidden = DOM.isHidden(o.editorContainer); 46246 } 46247 46248 self.getElement().style.display = 'none'; 46249 DOM.setAttrib(self.id, 'aria-hidden', true); 46250 46251 if (!url) { 46252 self.initContentBody(); 46253 } 46254 46255 elm = n = o = null; // Cleanup 46256 }, 46257 46258 /** 46259 * This method get called by the init method once the iframe is loaded. 46260 * It will fill the iframe with contents, sets up DOM and selection objects for the iframe. 46261 * 46262 * @method initContentBody 46263 * @private 46264 */ 46265 initContentBody: function(skipWrite) { 46266 var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText; 46267 46268 // Restore visibility on target element 46269 if (!settings.inline) { 46270 self.getElement().style.visibility = self.orgVisibility; 46271 } 46272 46273 // Setup iframe body 46274 if (!skipWrite && !settings.content_editable) { 46275 doc.open(); 46276 doc.write(self.iframeHTML); 46277 doc.close(); 46278 } 46279 46280 if (settings.content_editable) { 46281 self.on('remove', function() { 46282 var bodyEl = this.getBody(); 46283 46284 DOM.removeClass(bodyEl, 'mce-content-body'); 46285 DOM.removeClass(bodyEl, 'mce-edit-focus'); 46286 DOM.setAttrib(bodyEl, 'contentEditable', null); 46287 }); 46288 46289 DOM.addClass(targetElm, 'mce-content-body'); 46290 self.contentDocument = doc = settings.content_document || document; 46291 self.contentWindow = settings.content_window || window; 46292 self.bodyElement = targetElm; 46293 46294 // Prevent leak in IE 46295 settings.content_document = settings.content_window = null; 46296 46297 // TODO: Fix this 46298 settings.root_name = targetElm.nodeName.toLowerCase(); 46299 } 46300 46301 // It will not steal focus while setting contentEditable 46302 body = self.getBody(); 46303 body.disabled = true; 46304 self.readonly = settings.readonly; 46305 46306 if (!self.readonly) { 46307 if (self.inline && DOM.getStyle(body, 'position', true) == 'static') { 46308 body.style.position = 'relative'; 46309 } 46310 46311 body.contentEditable = self.getParam('content_editable_state', true); 46312 } 46313 46314 body.disabled = false; 46315 46316 self.editorUpload = new EditorUpload(self); 46317 46318 /** 46319 * Schema instance, enables you to validate elements and its children. 46320 * 46321 * @property schema 46322 * @type tinymce.html.Schema 46323 */ 46324 self.schema = new Schema(settings); 46325 46326 /** 46327 * DOM instance for the editor. 46328 * 46329 * @property dom 46330 * @type tinymce.dom.DOMUtils 46331 * @example 46332 * // Adds a class to all paragraphs within the editor 46333 * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass'); 46334 */ 46335 self.dom = new DOMUtils(doc, { 46336 keep_values: true, 46337 url_converter: self.convertURL, 46338 url_converter_scope: self, 46339 hex_colors: settings.force_hex_style_colors, 46340 class_filter: settings.class_filter, 46341 update_styles: true, 46342 root_element: self.inline ? self.getBody() : null, 46343 collect: settings.content_editable, 46344 schema: self.schema, 46345 onSetAttrib: function(e) { 46346 self.fire('SetAttrib', e); 46347 } 46348 }); 46349 46350 /** 46351 * HTML parser will be used when contents is inserted into the editor. 46352 * 46353 * @property parser 46354 * @type tinymce.html.DomParser 46355 */ 46356 self.parser = new DomParser(settings, self.schema); 46357 46358 // Convert src and href into data-mce-src, data-mce-href and data-mce-style 46359 self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) { 46360 var i = nodes.length, node, dom = self.dom, value, internalName; 46361 46362 while (i--) { 46363 node = nodes[i]; 46364 value = node.attr(name); 46365 internalName = 'data-mce-' + name; 46366 46367 // Add internal attribute if we need to we don't on a refresh of the document 46368 if (!node.attributes.map[internalName]) { 46369 // Don't duplicate these since they won't get modified by any browser 46370 if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) { 46371 continue; 46372 } 46373 46374 if (name === "style") { 46375 value = dom.serializeStyle(dom.parseStyle(value), node.name); 46376 46377 if (!value.length) { 46378 value = null; 46379 } 46380 46381 node.attr(internalName, value); 46382 node.attr(name, value); 46383 } else if (name === "tabindex") { 46384 node.attr(internalName, value); 46385 node.attr(name, null); 46386 } else { 46387 node.attr(internalName, self.convertURL(value, name, node.name)); 46388 } 46389 } 46390 } 46391 }); 46392 46393 // Keep scripts from executing 46394 self.parser.addNodeFilter('script', function(nodes) { 46395 var i = nodes.length, node, type; 46396 46397 while (i--) { 46398 node = nodes[i]; 46399 type = node.attr('type') || 'no/type'; 46400 if (type.indexOf('mce-') !== 0) { 46401 node.attr('type', 'mce-' + type); 46402 } 46403 } 46404 }); 46405 46406 self.parser.addNodeFilter('#cdata', function(nodes) { 46407 var i = nodes.length, node; 46408 46409 while (i--) { 46410 node = nodes[i]; 46411 node.type = 8; 46412 node.name = '#comment'; 46413 node.value = '[CDATA[' + node.value + ']]'; 46414 } 46415 }); 46416 46417 self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) { 46418 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements(); 46419 46420 while (i--) { 46421 node = nodes[i]; 46422 46423 if (node.isEmpty(nonEmptyElements)) { 46424 node.append(new Node('br', 1)).shortEnded = true; 46425 } 46426 } 46427 }); 46428 46429 /** 46430 * DOM serializer for the editor. Will be used when contents is extracted from the editor. 46431 * 46432 * @property serializer 46433 * @type tinymce.dom.Serializer 46434 * @example 46435 * // Serializes the first paragraph in the editor into a string 46436 * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]); 46437 */ 46438 self.serializer = new DomSerializer(settings, self); 46439 46440 /** 46441 * Selection instance for the editor. 46442 * 46443 * @property selection 46444 * @type tinymce.dom.Selection 46445 * @example 46446 * // Sets some contents to the current selection in the editor
46447 * tinymce.activeEditor.selection.setContent('Some contents'); 46448 * 46449 * // Gets the current selection 46450 * alert(tinymce.activeEditor.selection.getContent()); 46451 * 46452 * // Selects the first paragraph found 46453 * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]); 46454 */ 46455 self.selection = new Selection(self.dom, self.getWin(), self.serializer, self); 46456 46457 /** 46458 * Formatter instance. 46459 * 46460 * @property formatter 46461 * @type tinymce.Formatter 46462 */ 46463 self.formatter = new Formatter(self); 46464 46465 /** 46466 * Undo manager instance, responsible for handling undo levels. 46467 * 46468 * @property undoManager 46469 * @type tinymce.UndoManager 46470 * @example 46471 * // Undoes the last modification to the editor 46472 * tinymce.activeEditor.undoManager.undo(); 46473 */ 46474 self.undoManager = new UndoManager(self); 46475 46476 self.forceBlocks = new ForceBlocks(self); 46477 self.enterKey = new EnterKey(self); 46478 self._nodeChangeDispatcher = new NodeChange(self); 46479 self._selectionOverrides = new SelectionOverrides(self); 46480 46481 self.fire('PreInit'); 46482 46483 if (!settings.browser_spellcheck && !settings.gecko_spellcheck) { 46484 doc.body.spellcheck = false; // Gecko 46485 DOM.setAttrib(body, "spellcheck", "false"); 46486 } 46487 46488 self.fire('PostRender'); 46489 46490 self.quirks = new Quirks(self); 46491 46492 if (settings.directionality) { 46493 body.dir = settings.directionality; 46494 } 46495 46496 if (settings.nowrap) { 46497 body.style.whiteSpace = "nowrap"; 46498 } 46499 46500 if (settings.protect) { 46501 self.on('BeforeSetContent', function(e) { 46502 each(settings.protect, function(pattern) { 46503 e.content = e.content.replace(pattern, function(str) { 46504 return '<!--mce:protected ' + escape(str) + '-->'; 46505 }); 46506 }); 46507 }); 46508 } 46509 46510 self.on('SetContent', function() { 46511 self.addVisual(self.getBody()); 46512 }); 46513 46514 // Remove empty contents 46515 if (settings.padd_empty_editor) { 46516 self.on('PostProcess', function(e) { 46517 e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, ''); 46518 }); 46519 } 46520 46521 self.load({initial: true, format: 'html'}); 46522 self.startContent = self.getContent({format: 'raw'}); 46523 46524 /** 46525 * Is set to true after the editor instance has been initialized 46526 * 46527 * @property initialized 46528 * @type Boolean 46529 * @example 46530 * function isEditorInitialized(editor) { 46531 * return editor && editor.initialized; 46532 * } 46533 */ 46534 self.initialized = true; 46535 self.bindPendingEventDelegates(); 46536 46537 self.fire('init'); 46538 self.focus(true); 46539 self.nodeChanged({initial: true}); 46540 self.execCallback('init_instance_callback', self); 46541 46542 // Add editor specific CSS styles 46543 if (self.contentStyles.length > 0) { 46544 contentCssText = ''; 46545 46546 each(self.contentStyles, function(style) { 46547 contentCssText += style + "\r\n"; 46548 }); 46549 46550 self.dom.addStyle(contentCssText); 46551 } 46552 46553 // Load specified content CSS last 46554 each(self.contentCSS, function(cssUrl) { 46555 if (!self.loadedCSS[cssUrl]) { 46556 self.dom.loadCSS(cssUrl); 46557 self.loadedCSS[cssUrl] = true; 46558 } 46559 }); 46560 46561 // Handle auto focus 46562 if (settings.auto_focus) { 46563 Delay.setEditorTimeout(self, function() { 46564 var editor; 46565 46566 if (settings.auto_focus === true) { 46567 editor = self; 46568 } else { 46569 editor = self.editorManager.get(settings.auto_focus); 46570 } 46571 46572 if (!editor.destroyed) { 46573 editor.focus(); 46574 } 46575 }, 100); 46576 } 46577 46578 // Clean up references for IE 46579 targetElm = doc = body = null; 46580 }, 46581 46582 /** 46583 * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection 46584 * it will also place DOM focus inside the editor. 46585 * 46586 * @method focus 46587 * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor. 46588 */ 46589 focus: function(skipFocus) { 46590 var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng; 46591 var controlElm, doc = self.getDoc(), body = self.getBody(), contentEditableHost; 46592 46593 function getContentEditableHost(node) { 46594 return self.dom.getParent(node, function(node) { 46595 return self.dom.getContentEditable(node) === "true"; 46596 }); 46597 } 46598 46599 if (!skipFocus) { 46600 // Get selected control element 46601 rng = selection.getRng(); 46602 if (rng.item) { 46603 controlElm = rng.item(0); 46604 } 46605 46606 self._refreshContentEditable(); 46607 46608 // Move focus to contentEditable=true child if needed 46609 contentEditableHost = getContentEditableHost(selection.getNode()); 46610 if (self.$.contains(body, contentEditableHost)) { 46611 contentEditableHost.focus(); 46612 selection.normalize(); 46613 self.editorManager.setActive(self); 46614 return; 46615 } 46616 46617 // Focus the window iframe 46618 if (!contentEditable) { 46619 // WebKit needs this call to fire focusin event properly see #5948 46620 // But Opera pre Blink engine will produce an empty selection so skip Opera 46621 if (!Env.opera) { 46622 self.getBody().focus(); 46623 } 46624 46625 self.getWin().focus(); 46626 } 46627 46628 // Focus the body as well since it's contentEditable 46629 if (isGecko || contentEditable) { 46630 // Check for setActive since it doesn't scroll to the element 46631 if (body.setActive) { 46632 // IE 11 sometimes throws "Invalid function" then fallback to focus 46633 try { 46634 body.setActive(); 46635 } catch (ex) { 46636 body.focus(); 46637 } 46638 } else { 46639 body.focus(); 46640 } 46641 46642 if (contentEditable) { 46643 selection.normalize(); 46644 } 46645 } 46646 46647 // Restore selected control element 46648 // This is needed when for example an image is selected within a 46649 // layer a call to focus will then remove the control selection 46650 if (controlElm && controlElm.ownerDocument == doc) { 46651 rng = doc.body.createControlRange(); 46652 rng.addElement(controlElm); 46653 rng.select(); 46654 } 46655 } 46656 46657 self.editorManager.setActive(self); 46658 }, 46659 46660 /** 46661 * Executes a legacy callback. This method is useful to call old 2.x option callbacks. 46662 * There new event model is a better way to add callback so this method might be removed in the future. 46663 * 46664 * @method execCallback 46665 * @param {String} name Name of the callback to execute. 46666 * @return {Object} Return value passed from callback function. 46667 */ 46668 execCallback: function(name) { 46669 var self = this, callback = self.settings[name], scope; 46670 46671 if (!callback) { 46672 return; 46673 } 46674 46675 // Look through lookup 46676 if (self.callbackLookup && (scope = self.callbackLookup[name])) { 46677 callback = scope.func; 46678 scope = scope.scope; 46679 } 46680 46681 if (typeof callback === 'string') { 46682 scope = callback.replace(/\.\w+$/, ''); 46683 scope = scope ? resolve(scope) : 0; 46684 callback = resolve(callback); 46685 self.callbackLookup = self.callbackLookup || {}; 46686 self.callbackLookup[name] = {func: callback, scope: scope}; 46687 } 46688 46689 return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1)); 46690 }, 46691 46692 /** 46693 * Translates the specified string by replacing variables with language pack items it will also check if there is 46694 * a key mathcin the input. 46695 * 46696 * @method translate 46697 * @param {String} text String to translate by the language pack data. 46698 * @return {String} Translated string. 46699 */ 46700 translate: function(text) { 46701 var lang = this.settings.language || 'en', i18n = this.editorManager.i18n; 46702 46703 if (!text) { 46704 return ''; 46705 } 46706 46707 return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) { 46708 return i18n.data[lang + '.' + b] || '{#' + b + '}'; 46709 }); 46710 }, 46711 46712 /** 46713 * Returns a language pack item by name/key. 46714 * 46715 * @method getLang 46716 * @param {String} name Name/key to get from the language pack. 46717 * @param {String} defaultVal Optional default value to retrive. 46718 */ 46719 getLang: function(name, defaultVal) { 46720 return ( 46721 this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] || 46722 (defaultVal !== undefined ? defaultVal : '{#' + name + '}') 46723 ); 46724 }, 46725 46726 /** 46727 * Returns a configuration parameter by name. 46728 * 46729 * @method getParam 46730 * @param {String} name Configruation parameter to retrive. 46731 * @param {String} defaultVal Optional default value to return. 46732 * @param {String} type Optional type parameter. 46733 * @return {String} Configuration parameter value or default value. 46734 * @example 46735 * // Returns a specific config value from the currently active editor 46736 * var someval = tinymce.activeEditor.getParam('myvalue'); 46737 * 46738 * // Returns a specific config value from a specific editor instance by id 46739 * var someval2 = tinymce.get('my_editor').getParam('myvalue'); 46740 */ 46741 getParam: function(name, defaultVal, type) { 46742 var value = name in this.settings ? this.settings[name] : defaultVal, output; 46743 46744 if (type === 'hash') { 46745 output = {}; 46746 46747 if (typeof value === 'string') { 46748 each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) { 46749 value = value.split('='); 46750 46751 if (value.length > 1) { 46752 output[trim(value[0])] = trim(value[1]); 46753 } else { 46754 output[trim(value[0])] = trim(value); 46755 } 46756 }); 46757 } else { 46758 output = value; 46759 } 46760 46761 return output; 46762 } 46763 46764 return value; 46765 }, 46766 46767 /** 46768 * Dispatches out a onNodeChange event to all observers. This method should be called when you 46769 * need to update the UI states or element path etc. 46770 * 46771 * @method nodeChanged 46772 * @param {Object} args Optional args to pass to NodeChange event handlers. 46773 */ 46774 nodeChanged: function(args) { 46775 this._nodeChangeDispatcher.nodeChanged(args); 46776 }, 46777 46778 /** 46779 * Adds a button that later gets created by the theme in the editors toolbars. 46780 * 46781 * @method addButton 46782 * @param {String} name Button name to add. 46783 * @param {Object} settings Settings object with title, cmd etc. 46784 * @example 46785 * // Adds a custom button to the editor that inserts contents when clicked 46786 * tinymce.init({ 46787 * ... 46788 * 46789 * toolbar: 'example' 46790 * 46791 * setup: function(ed) { 46792 * ed.addButton('example', { 46793 * title: 'My title', 46794 * image: '../js/tinymce/plugins/example/img/example.gif', 46795 * onclick: function() { 46796 * ed.insertContent('Hello world!!'); 46797 * } 46798 * }); 46799 * } 46800 * }); 46801 */ 46802 addButton: function(name, settings) { 46803 var self = this; 46804 46805 if (settings.cmd) { 46806 settings.onclick = function() { 46807 self.execCommand(settings.cmd); 46808 }; 46809 } 46810
46811 if (!settings.text && !settings.icon) { 46812 settings.icon = name; 46813 } 46814 46815 self.buttons = self.buttons || {}; 46816 settings.tooltip = settings.tooltip || settings.title; 46817 self.buttons[name] = settings; 46818 }, 46819 46820 /** 46821 * Adds a menu item to be used in the menus of the theme. There might be multiple instances 46822 * of this menu item for example it might be used in the main menus of the theme but also in 46823 * the context menu so make sure that it's self contained and supports multiple instances. 46824 * 46825 * @method addMenuItem 46826 * @param {String} name Menu item name to add. 46827 * @param {Object} settings Settings object with title, cmd etc. 46828 * @example 46829 * // Adds a custom menu item to the editor that inserts contents when clicked 46830 * // The context option allows you to add the menu item to an existing default menu 46831 * tinymce.init({ 46832 * ... 46833 * 46834 * setup: function(ed) { 46835 * ed.addMenuItem('example', { 46836 * text: 'My menu item', 46837 * context: 'tools', 46838 * onclick: function() { 46839 * ed.insertContent('Hello world!!'); 46840 * } 46841 * }); 46842 * } 46843 * }); 46844 */ 46845 addMenuItem: function(name, settings) { 46846 var self = this; 46847 46848 if (settings.cmd) { 46849 settings.onclick = function() { 46850 self.execCommand(settings.cmd); 46851 }; 46852 } 46853 46854 self.menuItems = self.menuItems || {}; 46855 self.menuItems[name] = settings; 46856 }, 46857 46858 /** 46859 * Adds a contextual toolbar to be rendered when the selector matches. 46860 * 46861 * @method addContextToolbar 46862 * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates. 46863 * @param {String/Array} items String or array with items to add to the context toolbar. 46864 */ 46865 addContextToolbar: function(predicate, items) { 46866 var self = this, selector; 46867 46868 self.contextToolbars = self.contextToolbars || []; 46869 46870 // Convert selector to predicate 46871 if (typeof predicate == "string") { 46872 selector = predicate; 46873 predicate = function(elm) { 46874 return self.dom.is(elm, selector); 46875 }; 46876 } 46877 46878 self.contextToolbars.push({ 46879 predicate: predicate, 46880 items: items 46881 }); 46882 }, 46883 46884 /** 46885 * Adds a custom command to the editor, you can also override existing commands with this method. 46886 * The command that you add can be executed with execCommand. 46887 * 46888 * @method addCommand 46889 * @param {String} name Command name to add/override. 46890 * @param {addCommandCallback} callback Function to execute when the command occurs. 46891 * @param {Object} scope Optional scope to execute the function in. 46892 * @example 46893 * // Adds a custom command that later can be executed using execCommand 46894 * tinymce.init({ 46895 * ... 46896 * 46897 * setup: function(ed) { 46898 * // Register example command 46899 * ed.addCommand('mycommand', function(ui, v) { 46900 * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'})); 46901 * }); 46902 * } 46903 * }); 46904 */ 46905 addCommand: function(name, callback, scope) { 46906 /** 46907 * Callback function that gets called when a command is executed. 46908 * 46909 * @callback addCommandCallback 46910 * @param {Boolean} ui Display UI state true/false. 46911 * @param {Object} value Optional value for command. 46912 * @return {Boolean} True/false state if the command was handled or not. 46913 */ 46914 this.editorCommands.addCommand(name, callback, scope); 46915 }, 46916 46917 /** 46918 * Adds a custom query state command to the editor, you can also override existing commands with this method. 46919 * The command that you add can be executed with queryCommandState function. 46920 * 46921 * @method addQueryStateHandler 46922 * @param {String} name Command name to add/override. 46923 * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs. 46924 * @param {Object} scope Optional scope to execute the function in. 46925 */ 46926 addQueryStateHandler: function(name, callback, scope) { 46927 /** 46928 * Callback function that gets called when a queryCommandState is executed. 46929 * 46930 * @callback addQueryStateHandlerCallback 46931 * @return {Boolean} True/false state if the command is enabled or not like is it bold. 46932 */ 46933 this.editorCommands.addQueryStateHandler(name, callback, scope); 46934 }, 46935 46936 /** 46937 * Adds a custom query value command to the editor, you can also override existing commands with this method. 46938 * The command that you add can be executed with queryCommandValue function. 46939 * 46940 * @method addQueryValueHandler 46941 * @param {String} name Command name to add/override. 46942 * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs. 46943 * @param {Object} scope Optional scope to execute the function in. 46944 */ 46945 addQueryValueHandler: function(name, callback, scope) { 46946 /** 46947 * Callback function that gets called when a queryCommandValue is executed. 46948 * 46949 * @callback addQueryValueHandlerCallback 46950 * @return {Object} Value of the command or undefined. 46951 */ 46952 this.editorCommands.addQueryValueHandler(name, callback, scope); 46953 }, 46954 46955 /** 46956 * Adds a keyboard shortcut for some command or function. 46957 * 46958 * @method addShortcut 46959 * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o. 46960 * @param {String} desc Text description for the command. 46961 * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed. 46962 * @param {Object} sc Optional scope to execute the function in. 46963 * @return {Boolean} true/false state if the shortcut was added or not. 46964 */ 46965 addShortcut: function(pattern, desc, cmdFunc, scope) { 46966 this.shortcuts.add(pattern, desc, cmdFunc, scope); 46967 }, 46968 46969 /**
46970 * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or 46971 * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org. 46972 * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these 46973 * return true it will handle the command as a internal browser command. 46974 * 46975 * @method execCommand 46976 * @param {String} cmd Command name to execute, for example mceLink or Bold. 46977 * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not. 46978 * @param {mixed} value Optional command value, this can be anything. 46979 * @param {Object} args Optional arguments object. 46980 */ 46981 execCommand: function(cmd, ui, value, args) { 46982 return this.editorCommands.execCommand(cmd, ui, value, args); 46983 }, 46984 46985 /** 46986 * Returns a command specific state, for example if bold is enabled or not. 46987 * 46988 * @method queryCommandState 46989 * @param {string} cmd Command to query state from. 46990 * @return {Boolean} Command specific state, for example if bold is enabled or not. 46991 */ 46992 queryCommandState: function(cmd) { 46993 return this.editorCommands.queryCommandState(cmd); 46994 }, 46995 46996 /** 46997 * Returns a command specific value, for example the current font size. 46998 * 46999 * @method queryCommandValue 47000 * @param {string} cmd Command to query value from. 47001 * @return {Object} Command specific value, for example the current font size. 47002 */ 47003 queryCommandValue: function(cmd) { 47004 return this.editorCommands.queryCommandValue(cmd); 47005 }, 47006 47007 /** 47008 * Returns true/false if the command is supported or not. 47009 * 47010 * @method queryCommandSupported 47011 * @param {String} cmd Command that we check support for. 47012 * @return {Boolean} true/false if the command is supported or not. 47013 */ 47014 queryCommandSupported: function(cmd) { 47015 return this.editorCommands.queryCommandSupported(cmd); 47016 }, 47017 47018 /** 47019 * Shows the editor and hides any textarea/div that the editor is supposed to replace. 47020 * 47021 * @method show 47022 */ 47023 show: function() { 47024 var self = this; 47025 47026 if (self.hidden) { 47027 self.hidden = false; 47028 47029 if (self.inline) { 47030 self.getBody().contentEditable = true; 47031 } else { 47032 DOM.show(self.getContainer()); 47033 DOM.hide(self.id); 47034 } 47035 47036 self.load(); 47037 self.fire('show'); 47038 } 47039 }, 47040 47041 /** 47042 * Hides the editor and shows any textarea/div that the editor is supposed to replace. 47043 * 47044 * @method hide 47045 */ 47046 hide: function() { 47047 var self = this, doc = self.getDoc(); 47048 47049 if (!self.hidden) { 47050 // Fixed bug where IE has a blinking cursor left from the editor 47051 if (ie && doc && !self.inline) { 47052 doc.execCommand('SelectAll'); 47053 } 47054 47055 // We must save before we hide so Safari doesn't crash 47056 self.save(); 47057 47058 if (self.inline) { 47059 self.getBody().contentEditable = false; 47060 47061 // Make sure the editor gets blurred 47062 if (self == self.editorManager.focusedEditor) { 47063 self.editorManager.focusedEditor = null; 47064 } 47065 } else { 47066 DOM.hide(self.getContainer()); 47067 DOM.setStyle(self.id, 'display', self.orgDisplay); 47068 } 47069 47070 self.hidden = true; 47071 self.fire('hide'); 47072 } 47073 }, 47074 47075 /** 47076 * Returns true/false if the editor is hidden or not. 47077 * 47078 * @method isHidden 47079 * @return {Boolean} True/false if the editor is hidden or not. 47080 */ 47081 isHidden: function() { 47082 return !!this.hidden; 47083 }, 47084 47085 /** 47086 * Sets the progress state, this will display a throbber/progess for the editor. 47087 * This is ideal for asynchronous operations like an AJAX save call. 47088 * 47089 * @method setProgressState 47090 * @param {Boolean} state Boolean state if the progress should be shown or hidden. 47091 * @param {Number} time Optional time to wait before the progress gets shown. 47092 * @return {Boolean} Same as the input state. 47093 * @example 47094 * // Show progress for the active editor 47095 * tinymce.activeEditor.setProgressState(true); 47096 * 47097 * // Hide progress for the active editor 47098 * tinymce.activeEditor.setProgressState(false); 47099 *
47100 * // Show progress after 3 seconds 47101 * tinymce.activeEditor.setProgressState(true, 3000); 47102 */ 47103 setProgressState: function(state, time) { 47104 this.fire('ProgressState', {state: state, time: time}); 47105 }, 47106 47107 /** 47108 * Loads contents from the textarea or div element that got converted into an editor instance. 47109 * This method will move the contents from that textarea or div into the editor by using setContent 47110 * so all events etc that method has will get dispatched as well. 47111 * 47112 * @method load 47113 * @param {Object} args Optional content object, this gets passed around through the whole load process. 47114 * @return {String} HTML string that got set into the editor. 47115 */ 47116 load: function(args) { 47117 var self = this, elm = self.getElement(), html; 47118 47119 if (elm) { 47120 args = args || {}; 47121 args.load = true; 47122 47123 html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args); 47124 args.element = elm; 47125 47126 if (!args.no_events) { 47127 self.fire('LoadContent', args); 47128 } 47129 47130 args.element = elm = null; 47131 47132 return html; 47133 } 47134 }, 47135 47136 /** 47137 * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance. 47138 * This method will move the HTML contents from the editor into that textarea or div by getContent 47139 * so all events etc that method has will get dispatched as well. 47140 * 47141 * @method save 47142 * @param {Object} args Optional content object, this gets passed around through the whole save process. 47143 * @return {String} HTML string that got set into the textarea/div. 47144 */ 47145 save: function(args) { 47146 var self = this, elm = self.getElement(), html, form; 47147 47148 if (!elm || !self.initialized) { 47149 return; 47150 } 47151 47152 args = args || {}; 47153 args.save = true; 47154 47155 args.element = elm; 47156 html = args.content = self.getContent(args); 47157 47158 if (!args.no_events) { 47159 self.fire('SaveContent', args); 47160 } 47161 47162 // Always run this internal event 47163 if (args.format == 'raw') { 47164 self.fire('RawSaveContent', args); 47165 } 47166 47167 html = args.content; 47168 47169 if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) { 47170 // Update DIV element when not in inline mode 47171 if (!self.inline) { 47172 elm.innerHTML = html; 47173 } 47174 47175 // Update hidden form element 47176 if ((form = DOM.getParent(self.id, 'form'))) { 47177 each(form.elements, function(elm) { 47178 if (elm.name == self.id) { 47179 elm.value = html; 47180 return false; 47181 } 47182 }); 47183 } 47184 } else { 47185 elm.value = html; 47186 } 47187 47188 args.element = elm = null; 47189 47190 if (args.set_dirty !== false) { 47191 self.setDirty(false); 47192 } 47193 47194 return html; 47195 }, 47196 47197 /** 47198 * Sets the specified content to the editor instance, this will cleanup the content before it gets set using 47199 * the different cleanup rules options. 47200 * 47201 * @method setContent 47202 * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well. 47203 * @param {Object} args Optional content object, this gets passed around through the whole set process. 47204 * @return {String} HTML string that got set into the editor. 47205 * @example 47206 * // Sets the HTML contents of the activeEditor editor 47207 * tinymce.activeEditor.setContent('<span>some</span> html'); 47208 *
47209 * // Sets the raw contents of the activeEditor editor 47210 * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'}); 47211 * 47212 * // Sets the content of a specific editor (my_editor in this example) 47213 * tinymce.get('my_editor').setContent(data); 47214 * 47215 * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added 47216 * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'}); 47217 */ 47218 setContent: function(content, args) { 47219 var self = this, body = self.getBody(), forcedRootBlockName, padd; 47220 47221 // Setup args object 47222 args = args || {}; 47223 args.format = args.format || 'html'; 47224 args.set = true; 47225 args.content = content; 47226 47227 // Do preprocessing 47228 if (!args.no_events) { 47229 self.fire('BeforeSetContent', args); 47230 } 47231 47232 content = args.content; 47233 47234 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content 47235 // It will also be impossible to place the caret in the editor unless there is a BR element present 47236 if (content.length === 0 || /^\s+$/.test(content)) { 47237 padd = ie && ie < 11 ? '' : '<br data-mce-bogus="1">'; 47238 47239 // Todo: There is a lot more root elements that need special padding 47240 // so separate this and add all of them at some point. 47241 if (body.nodeName == 'TABLE') { 47242 content = '<tr><td>' + padd + '</td></tr>'; 47243 } else if (/^(UL|OL)$/.test(body.nodeName)) { 47244 content = '<li>' + padd + '</li>'; 47245 } 47246 47247 forcedRootBlockName = self.settings.forced_root_block; 47248 47249 // Check if forcedRootBlock is configured and that the block is a valid child of the body 47250 if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) { 47251 // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly 47252 content = padd; 47253 content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content); 47254 } else if (!ie && !content) { 47255 // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret 47256 content = '<br data-mce-bogus="1">'; 47257 } 47258 47259 self.dom.setHTML(body, content); 47260 47261 self.fire('SetContent', args); 47262 } else { 47263 // Parse and serialize the html 47264 if (args.format !== 'raw') { 47265 content = new Serializer({ 47266 validate: self.validate 47267 }, self.schema).serialize( 47268 self.parser.parse(content, {isRootContent: true}) 47269 ); 47270 } 47271 47272 // Set the new cleaned contents to the editor 47273 args.content = trim(content); 47274 self.dom.setHTML(body, args.content); 47275 47276 // Do post processing 47277 if (!args.no_events) { 47278 self.fire('SetContent', args); 47279 } 47280 47281 // Don't normalize selection if the focused element isn't the body in 47282 // content editable mode since it will steal focus otherwise 47283 /*if (!self.settings.content_editable || document.activeElement === self.getBody()) { 47284 self.selection.normalize(); 47285 }*/ 47286 } 47287 47288 return args.content; 47289 }, 47290 47291 /** 47292 * Gets the content from the editor instance, this will cleanup the content before it gets returned using 47293 * the different cleanup rules options. 47294 * 47295 * @method getContent 47296 * @param {Object} args Optional content object, this gets passed around through the whole get process. 47297 * @return {String} Cleaned content string, normally HTML contents. 47298 * @example 47299 * // Get the HTML contents of the currently active editor 47300 * console.debug(tinymce.activeEditor.getContent()); 47301 * 47302 * // Get the raw contents of the currently active editor 47303 * tinymce.activeEditor.getContent({format: 'raw'}); 47304 * 47305 * // Get content of a specific editor: 47306 * tinymce.get('content id').getContent() 47307 */ 47308 getContent: function(args) { 47309 var self = this, content, body = self.getBody(); 47310 47311 // Setup args object 47312 args = args || {}; 47313 args.format = args.format || 'html'; 47314 args.get = true; 47315 args.getInner = true; 47316 47317 // Do preprocessing 47318 if (!args.no_events) { 47319 self.fire('BeforeGetContent', args); 47320 } 47321 47322 // Get raw contents or by default the cleaned contents 47323 if (args.format == 'raw') {
47324 content = self.serializer.getTrimmedContent(); 47325 } else if (args.format == 'text') { 47326 content = body.innerText || body.textContent; 47327 } else { 47328 content = self.serializer.serialize(body, args); 47329 } 47330 47331 // Trim whitespace in beginning/end of HTML 47332 if (args.format != 'text') { 47333 args.content = trim(content); 47334 } else { 47335 args.content = content; 47336 } 47337 47338 // Do post processing 47339 if (!args.no_events) { 47340 self.fire('GetContent', args); 47341 } 47342 47343 return args.content; 47344 }, 47345 47346 /** 47347 * Inserts content at caret position. 47348 * 47349 * @method insertContent 47350 * @param {String} content Content to insert. 47351 * @param {Object} args Optional args to pass to insert call. 47352 */ 47353 insertContent: function(content, args) { 47354 if (args) { 47355 content = extend({content: content}, args); 47356 } 47357 47358 this.execCommand('mceInsertContent', false, content); 47359 }, 47360 47361 /** 47362 * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 47363 * 47364 * The dirty state is automatically set to true if you do modifications to the content in other 47365 * words when new undo levels is created or if you undo/redo to update the contents of the editor. It will also be set 47366 * to false if you call editor.save(). 47367 * 47368 * @method isDirty 47369 * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents. 47370 * @example 47371 * if (tinymce.activeEditor.isDirty()) 47372 * alert("You must save your contents."); 47373 */ 47374 isDirty: function() { 47375 return !this.isNotDirty; 47376 }, 47377 47378 /** 47379 * Explicitly sets the dirty state. This will fire the dirty event if the editor dirty state is changed from false to true 47380 * by invoking this method. 47381 * 47382 * @method setDirty 47383 * @param {Boolean} state True/false if the editor is considered dirty. 47384 * @example 47385 * function ajaxSave() { 47386 * var editor = tinymce.get('elm1'); 47387 * 47388 * // Save contents using some XHR call 47389 * alert(editor.getContent()); 47390 * 47391 * editor.setDirty(false); // Force not dirty state 47392 * } 47393 */ 47394 setDirty: function(state) { 47395 var oldState = !this.isNotDirty; 47396 47397 this.isNotDirty = !state; 47398 47399 if (state && state != oldState) { 47400 this.fire('dirty'); 47401 } 47402 }, 47403 47404 /** 47405 * Sets the editor mode. Mode can be for example "design", "code" or "readonly". 47406 * 47407 * @method setMode 47408 * @param {String} mode Mode to set the editor in. 47409 */ 47410 setMode: function(mode) { 47411 Mode.setMode(this, mode); 47412 }, 47413 47414 /** 47415 * Returns the editors container element. The container element wrappes in 47416 * all the elements added to the page for the editor. Such as UI, iframe etc. 47417 * 47418 * @method getContainer 47419 * @return {Element} HTML DOM element for the editor container. 47420 */ 47421 getContainer: function() { 47422 var self = this; 47423 47424 if (!self.container) { 47425 self.container = DOM.get(self.editorContainer || self.id + '_parent'); 47426 } 47427 47428 return self.container; 47429 }, 47430 47431 /** 47432 * Returns the editors content area container element. The this element is the one who 47433 * holds the iframe or the editable element. 47434 * 47435 * @method getContentAreaContainer 47436 * @return {Element} HTML DOM element for the editor area container. 47437 */ 47438 getContentAreaContainer: function() { 47439 return this.contentAreaContainer; 47440 }, 47441 47442 /** 47443 * Returns the target element/textarea that got replaced with a TinyMCE editor instance. 47444 * 47445 * @method getElement 47446 * @return {Element} HTML DOM element for the replaced element. 47447 */ 47448 getElement: function() { 47449 if (!this.targetElm) { 47450 this.targetElm = DOM.get(this.id); 47451 } 47452 47453 return this.targetElm; 47454 }, 47455 47456 /** 47457 * Returns the iframes window object. 47458 * 47459 * @method getWin 47460 * @return {Window} Iframe DOM window object. 47461 */ 47462 getWin: function() { 47463 var self = this, elm; 47464 47465 if (!self.contentWindow) { 47466 elm = self.iframeElement; 47467 47468 if (elm) { 47469 self.contentWindow = elm.contentWindow; 47470 } 47471 } 47472 47473 return self.contentWindow; 47474 }, 47475 47476 /** 47477 * Returns the iframes document object. 47478 * 47479 * @method getDoc 47480 * @return {Document} Iframe DOM document object. 47481 */ 47482 getDoc: function() { 47483 var self = this, win; 47484 47485 if (!self.contentDocument) { 47486 win = self.getWin(); 47487 47488 if (win) { 47489 self.contentDocument = win.document; 47490 } 47491 } 47492 47493 return self.contentDocument; 47494 }, 47495 47496 /** 47497 * Returns the root element of the editable area. 47498 * For a non-inline iframe-based editor, returns the iframe's body element. 47499 * 47500 * @method getBody 47501 * @return {Element} The root element of the editable area. 47502 */ 47503 getBody: function() { 47504 return this.bodyElement || this.getDoc().body; 47505 }, 47506 47507 /** 47508 * URL converter function this gets executed each time a user adds an img, a or 47509 * any other element that has a URL in it. This will be called both by the DOM and HTML 47510 * manipulation functions. 47511 * 47512 * @method convertURL 47513 * @param {string} url URL to convert. 47514 * @param {string} name Attribute name src, href etc. 47515 * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert. 47516 * @return {string} Converted URL string. 47517 */ 47518 convertURL: function(url, name, elm) { 47519 var self = this, settings = self.settings; 47520 47521 // Use callback instead 47522 if (settings.urlconverter_callback) { 47523 return self.execCallback('urlconverter_callback', url, elm, true, name); 47524 } 47525 47526 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs 47527 if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) { 47528 return url; 47529 } 47530 47531 // Convert to relative 47532 if (settings.relative_urls) { 47533 return self.documentBaseURI.toRelative(url); 47534 } 47535 47536 // Convert to absolute 47537 url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host); 47538 47539 return url; 47540 }, 47541 47542 /** 47543 * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor. 47544 * 47545 * @method addVisual 47546 * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid. 47547 */ 47548 addVisual: function(elm) { 47549 var self = this, settings = self.settings, dom = self.dom, cls; 47550 47551 elm = elm || self.getBody(); 47552 47553 if (self.hasVisual === undefined) { 47554 self.hasVisual = settings.visual; 47555 } 47556 47557 each(dom.select('table,a', elm), function(elm) { 47558 var value; 47559 47560 switch (elm.nodeName) { 47561 case 'TABLE': 47562 cls = settings.visual_table_class || 'mce-item-table'; 47563 value = dom.getAttrib(elm, 'border'); 47564 47565 if ((!value || value == '0') && self.hasVisual) { 47566 dom.addClass(elm, cls); 47567 } else { 47568 dom.removeClass(elm, cls); 47569 } 47570 47571 return; 47572 47573 case 'A': 47574 if (!dom.getAttrib(elm, 'href', false)) { 47575 value = dom.getAttrib(elm, 'name') || elm.id; 47576 cls = settings.visual_anchor_class || 'mce-item-anchor'; 47577 47578 if (value && self.hasVisual) { 47579 dom.addClass(elm, cls); 47580 } else { 47581 dom.removeClass(elm, cls); 47582 } 47583 } 47584 47585 return; 47586 } 47587 }); 47588 47589 self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual}); 47590 }, 47591 47592 /** 47593 * Removes the editor from the dom and tinymce collection. 47594 * 47595 * @method remove 47596 */ 47597 remove: function() { 47598 var self = this; 47599 47600 if (!self.removed) { 47601 self.save(); 47602 self.removed = 1; 47603 self.unbindAllNativeEvents(); 47604 47605 // Remove any hidden input 47606 if (self.hasHiddenInput) { 47607 DOM.remove(self.getElement().nextSibling); 47608 } 47609 47610 if (!self.inline) { 47611 // IE 9 has a bug where the selection stops working if you place the 47612 // caret inside the editor then remove the iframe 47613 if (ie && ie < 10) { 47614 self.getDoc().execCommand('SelectAll', false, null); 47615 } 47616 47617 DOM.setStyle(self.id, 'display', self.orgDisplay); 47618 self.getBody().onload = null; // Prevent #6816 47619 } 47620 47621 self.fire('remove'); 47622 47623 self.editorManager.remove(self); 47624 DOM.remove(self.getContainer()); 47625 self._selectionOverrides.destroy(); 47626 self.editorUpload.destroy(); 47627 self.destroy(); 47628 } 47629 }, 47630 47631 /** 47632 * Destroys the editor instance by removing all events, element references or other resources 47633 * that could leak memory. This method will be called automatically when the page is unloaded
47634 * but you can also call it directly if you know what you are doing. 47635 * 47636 * @method destroy 47637 * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one. 47638 */ 47639 destroy: function(automatic) { 47640 var self = this, form; 47641 47642 // One time is enough 47643 if (self.destroyed) { 47644 return; 47645 } 47646 47647 // If user manually calls destroy and not remove 47648 // Users seems to have logic that calls destroy instead of remove 47649 if (!automatic && !self.removed) { 47650 self.remove(); 47651 return; 47652 } 47653 47654 if (!automatic) { 47655 self.editorManager.off('beforeunload', self._beforeUnload); 47656 47657 // Manual destroy 47658 if (self.theme && self.theme.destroy) { 47659 self.theme.destroy(); 47660 } 47661 47662 // Destroy controls, selection and dom 47663 self.selection.destroy(); 47664 self.dom.destroy(); 47665 } 47666 47667 form = self.formElement; 47668 if (form) { 47669 if (form._mceOldSubmit) { 47670 form.submit = form._mceOldSubmit; 47671 form._mceOldSubmit = null; 47672 } 47673 47674 DOM.unbind(form, 'submit reset', self.formEventDelegate); 47675 } 47676 47677 self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null; 47678 self.bodyElement = self.contentDocument = self.contentWindow = null; 47679 self.iframeElement = self.targetElm = null; 47680 47681 if (self.selection) { 47682 self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null; 47683 } 47684 47685 self.destroyed = 1; 47686 }, 47687 47688 /** 47689 * Uploads all data uri/blob uri images in the editor contents to server. 47690 * 47691 * @method uploadImages 47692 * @param {function} callback Optional callback with images and status for each image. 47693 * @return {tinymce.util.Promise} Promise instance. 47694 */ 47695 uploadImages: function(callback) { 47696 return this.editorUpload.uploadImages(callback); 47697 }, 47698 47699 // Internal functions 47700 47701 _scanForImages: function() { 47702 return this.editorUpload.scanForImages(); 47703 }, 47704 47705 _refreshContentEditable: function() { 47706 var self = this, body, parent; 47707 47708 // Check if the editor was hidden and the re-initialize contentEditable mode by removing and adding the body again 47709 if (self._isHidden()) { 47710 body = self.getBody(); 47711 parent = body.parentNode; 47712 47713 parent.removeChild(body); 47714 parent.appendChild(body); 47715 47716 body.focus(); 47717 } 47718 }, 47719 47720 _isHidden: function() { 47721 var sel; 47722 47723 if (!isGecko) { 47724 return 0; 47725 } 47726 47727 // Weird, wheres that cursor selection? 47728 sel = this.selection.getSel(); 47729 return (!sel || !sel.rangeCount || sel.rangeCount === 0); 47730 } 47731 }; 47732 47733 extend(Editor.prototype, EditorObservable); 47734 47735 return Editor; 47736}); 47737 47738// Included from: js/tinymce/classes/util/I18n.js 47739 47740/** 47741 * I18n.js 47742 * 47743 * Released under LGPL License. 47744 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 47745 * 47746 * License: http://www.tinymce.com/license 47747 * Contributing: http://www.tinymce.com/contributing 47748 */ 47749 47750/** 47751 * I18n class that handles translation of TinyMCE UI. 47752 * Uses po style with csharp style parameters. 47753 * 47754 * @class tinymce.util.I18n 47755 */ 47756define("tinymce/util/I18n", [], function() { 47757 "use strict"; 47758 47759 var data = {}, code = "en"; 47760 47761 return { 47762 /** 47763 * Sets the current language code. 47764 * 47765 * @method setCode 47766 * @param {String} newCode Current language code. 47767 */ 47768 setCode: function(newCode) { 47769 if (newCode) { 47770 code = newCode; 47771 this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
47772 } 47773 }, 47774 47775 /** 47776 * Returns the current language code. 47777 * 47778 * @method getCode 47779 * @return {String} Current language code. 47780 */ 47781 getCode: function() { 47782 return code; 47783 }, 47784 47785 /** 47786 * Property gets set to true if a RTL language pack was loaded. 47787 * 47788 * @property rtl 47789 * @type Boolean 47790 */ 47791 rtl: false, 47792 47793 /** 47794 * Adds translations for a specific language code. 47795 * 47796 * @method add 47797 * @param {String} code Language code like sv_SE. 47798 * @param {Array} items Name/value array with English en_US to sv_SE. 47799 */ 47800 add: function(code, items) { 47801 var langData = data[code]; 47802 47803 if (!langData) { 47804 data[code] = langData = {}; 47805 } 47806 47807 for (var name in items) { 47808 langData[name] = items[name]; 47809 } 47810 47811 this.setCode(code); 47812 }, 47813 47814 /** 47815 * Translates the specified text. 47816 * 47817 * It has a few formats: 47818 * I18n.translate("Text"); 47819 * I18n.translate(["Text {0}/{1}", 0, 1]); 47820 * I18n.translate({raw: "Raw string"}); 47821 * 47822 * @method translate 47823 * @param {String/Object/Array} text Text to translate. 47824 * @return {String} String that got translated. 47825 */ 47826 translate: function(text) { 47827 var langData; 47828 47829 langData = data[code]; 47830 if (!langData) { 47831 langData = {}; 47832 } 47833 47834 if (typeof text == "undefined") { 47835 return text; 47836 } 47837 47838 if (typeof text != "string" && text.raw) { 47839 return text.raw; 47840 } 47841 47842 if (text.push) { 47843 var values = text.slice(1); 47844 47845 text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) { 47846 return values[match2]; 47847 }); 47848 } 47849 47850 return (langData[text] || text).replace(/{context:\w+}$/, ''); 47851 }, 47852 47853 data: data 47854 }; 47855}); 47856 47857// Included from: js/tinymce/classes/FocusManager.js 47858 47859/** 47860 * FocusManager.js 47861 * 47862 * Released under LGPL License. 47863 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 47864 * 47865 * License: http://www.tinymce.com/license 47866 * Contributing: http://www.tinymce.com/contributing 47867 */ 47868 47869/** 47870 * This class manages the focus/blur state of the editor. This class is needed since some 47871 * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar. 47872 * 47873 * This class will fire two events focus and blur on the editor instances that got affected. 47874 * It will also handle the restore of selection when the focus is lost and returned. 47875 * 47876 * @class tinymce.FocusManager 47877 */ 47878define("tinymce/FocusManager", [ 47879 "tinymce/dom/DOMUtils", 47880 "tinymce/util/Delay", 47881 "tinymce/Env" 47882], function(DOMUtils, Delay, Env) { 47883 var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM; 47884 47885 /** 47886 * Constructs a new focus manager instance. 47887 * 47888 * @constructor FocusManager 47889 * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for. 47890 */ 47891 function FocusManager(editorManager) { 47892 function getActiveElement() { 47893 try { 47894 return document.activeElement; 47895 } catch (ex) { 47896 // IE sometimes fails to get the activeElement when resizing table 47897 // TODO: Investigate this 47898 return document.body; 47899 } 47900 } 47901 47902 // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object 47903 // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well. 47904 function createBookmark(dom, rng) { 47905 if (rng && rng.startContainer) { 47906 // Verify that the range is within the root of the editor 47907 if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) { 47908 return; 47909 } 47910 47911 return { 47912 startContainer: rng.startContainer, 47913 startOffset: rng.startOffset, 47914 endContainer: rng.endContainer, 47915 endOffset: rng.endOffset 47916 }; 47917 } 47918 47919 return rng; 47920 } 47921 47922 function bookmarkToRng(editor, bookmark) { 47923 var rng; 47924 47925 if (bookmark.startContainer) { 47926 rng = editor.getDoc().createRange(); 47927 rng.setStart(bookmark.startContainer, bookmark.startOffset); 47928 rng.setEnd(bookmark.endContainer, bookmark.endOffset); 47929 } else { 47930 rng = bookmark; 47931 } 47932 47933 return rng; 47934 } 47935 47936 function isUIElement(elm) { 47937 return !!DOM.getParent(elm, FocusManager.isEditorUIElement); 47938 } 47939 47940 function registerEvents(e) { 47941 var editor = e.editor; 47942 47943 editor.on('init', function() { 47944 // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab 47945 if (editor.inline || Env.ie) { 47946 // Use the onbeforedeactivate event when available since it works better see #7023 47947 if ("onbeforedeactivate" in document && Env.ie < 9) { 47948 editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) { 47949 if (e.target != editor.getBody()) { 47950 return; 47951 } 47952 47953 try { 47954 editor.lastRng = editor.selection.getRng(); 47955 } catch (ex) { 47956 // IE throws "Unexcpected call to method or property access" some times so lets ignore it 47957 } 47958 }); 47959 } else { 47960 // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes 47961 editor.on('nodechange mouseup keyup', function(e) { 47962 var node = getActiveElement(); 47963 47964 // Only act on manual nodechanges 47965 if (e.type == 'nodechange' && e.selectionChange) { 47966 return; 47967 } 47968 47969 // IE 11 reports active element as iframe not body of iframe 47970 if (node && node.id == editor.id + '_ifr') { 47971 node = editor.getBody(); 47972 } 47973 47974 if (editor.dom.isChildOf(node, editor.getBody())) { 47975 editor.lastRng = editor.selection.getRng(); 47976 } 47977 }); 47978 } 47979 47980 // Handles the issue with WebKit not retaining selection within inline document 47981 // If the user releases the mouse out side the body since a mouse up event wont occur on the body 47982 if (Env.webkit && !selectionChangeHandler) { 47983 selectionChangeHandler = function() { 47984 var activeEditor = editorManager.activeEditor; 47985 47986 if (activeEditor && activeEditor.selection) { 47987 var rng = activeEditor.selection.getRng(); 47988 47989 // Store when it's non collapsed 47990 if (rng && !rng.collapsed) { 47991 editor.lastRng = rng; 47992 } 47993 } 47994 }; 47995 47996 DOM.bind(document, 'selectionchange', selectionChangeHandler); 47997 } 47998 } 47999 }); 48000 48001 editor.on('setcontent', function() { 48002 editor.lastRng = null; 48003 }); 48004 48005 // Remove last selection bookmark on mousedown see #6305 48006 editor.on('mousedown', function() { 48007 editor.selection.lastFocusBookmark = null; 48008 }); 48009 48010 editor.on('focusin', function() { 48011 var focusedEditor = editorManager.focusedEditor, lastRng; 48012 48013 if (editor.selection.lastFocusBookmark) { 48014 lastRng = bookmarkToRng(editor, editor.selection.lastFocusBookmark); 48015 editor.selection.lastFocusBookmark = null; 48016 editor.selection.setRng(lastRng); 48017 } 48018 48019 if (focusedEditor != editor) { 48020 if (focusedEditor) { 48021 focusedEditor.fire('blur', {focusedEditor: editor}); 48022 } 48023 48024 editorManager.setActive(editor); 48025 editorManager.focusedEditor = editor; 48026 editor.fire('focus', {blurredEditor: focusedEditor}); 48027 editor.focus(true); 48028 } 48029 48030 editor.lastRng = null; 48031 }); 48032 48033 editor.on('focusout', function() { 48034 Delay.setEditorTimeout(editor, function() { 48035 var focusedEditor = editorManager.focusedEditor; 48036 48037 // Still the same editor the blur was outside any editor UI 48038 if (!isUIElement(getActiveElement()) && focusedEditor == editor) { 48039 editor.fire('blur', {focusedEditor: null}); 48040 editorManager.focusedEditor = null; 48041 48042 // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs 48043 if (editor.selection) { 48044 editor.selection.lastFocusBookmark = null; 48045 } 48046 } 48047 }); 48048 }); 48049 48050 // Check if focus is moved to an element outside the active editor by checking if the target node 48051 // isn't within the body of the activeEditor nor a UI element such as a dialog child control 48052 if (!documentFocusInHandler) { 48053 documentFocusInHandler = function(e) { 48054 var activeEditor = editorManager.activeEditor; 48055 48056 if (activeEditor && e.target.ownerDocument == document) { 48057 // Check to make sure we have a valid selection don't update the bookmark if it's 48058 // a focusin to the body of the editor see #7025 48059 if (activeEditor.selection && e.target != activeEditor.getBody()) { 48060 activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng); 48061 } 48062 48063 // Fire a blur event if the element isn't a UI element 48064 if (e.target != document.body && !isUIElement(e.target) && editorManager.focusedEditor == activeEditor) { 48065 activeEditor.fire('blur', {focusedEditor: null}); 48066 editorManager.focusedEditor = null; 48067 } 48068 } 48069 }; 48070 48071 DOM.bind(document, 'focusin', documentFocusInHandler); 48072 } 48073 48074 // Handle edge case when user starts the selection inside the editor and releases 48075 // the mouse outside the editor producing a new selection. This weird workaround is needed since 48076 // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843 48077 if (editor.inline && !documentMouseUpHandler) { 48078 documentMouseUpHandler = function(e) { 48079 var activeEditor = editorManager.activeEditor; 48080 48081 if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) { 48082 var rng = activeEditor.selection.getRng(); 48083 48084 if (!rng.collapsed) { 48085 activeEditor.lastRng = rng; 48086 } 48087 } 48088 }; 48089 48090 DOM.bind(document, 'mouseup', documentMouseUpHandler); 48091 } 48092 } 48093 48094 function unregisterDocumentEvents(e) { 48095 if (editorManager.focusedEditor == e.editor) { 48096 editorManager.focusedEditor = null; 48097 } 48098 48099 if (!editorManager.activeEditor) { 48100 DOM.unbind(document, 'selectionchange', selectionChangeHandler); 48101 DOM.unbind(document, 'focusin', documentFocusInHandler); 48102 DOM.unbind(document, 'mouseup', documentMouseUpHandler); 48103 selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null; 48104 } 48105 } 48106 48107 editorManager.on('AddEditor', registerEvents); 48108 editorManager.on('RemoveEditor', unregisterDocumentEvents); 48109 } 48110 48111 /** 48112 * Returns true if the specified element is part of the UI for example an button or text input. 48113 * 48114 * @method isEditorUIElement 48115 * @param {Element} elm Element to check if it's part of the UI or not. 48116 * @return {Boolean} True/false state if the element is part of the UI or not. 48117 */ 48118 FocusManager.isEditorUIElement = function(elm) { 48119 // Needs to be converted to string since svg can have focus: #6776 48120 return elm.className.toString().indexOf('mce-') !== -1; 48121 }; 48122 48123 return FocusManager; 48124}); 48125 48126// Included from: js/tinymce/classes/EditorManager.js 48127 48128/** 48129 * EditorManager.js 48130 * 48131 * Released under LGPL License. 48132 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 48133 * 48134 * License: http://www.tinymce.com/license 48135 * Contributing: http://www.tinymce.com/contributing 48136 */ 48137 48138/** 48139 * This class used as a factory for manager for tinymce.Editor instances. 48140 * 48141 * @example 48142 * tinymce.EditorManager.init({}); 48143 * 48144 * @class tinymce.EditorManager 48145 * @mixes tinymce.util.Observable
48146 * @static 48147 */ 48148define("tinymce/EditorManager", [ 48149 "tinymce/Editor", 48150 "tinymce/dom/DomQuery", 48151 "tinymce/dom/DOMUtils", 48152 "tinymce/util/URI", 48153 "tinymce/Env", 48154 "tinymce/util/Tools", 48155 "tinymce/util/Observable", 48156 "tinymce/util/I18n", 48157 "tinymce/FocusManager" 48158], function(Editor, $, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) { 48159 var DOM = DOMUtils.DOM; 48160 var explode = Tools.explode, each = Tools.each, extend = Tools.extend; 48161 var instanceCounter = 0, beforeUnloadDelegate, EditorManager, boundGlobalEvents = false; 48162 48163 function globalEventDelegate(e) { 48164 each(EditorManager.editors, function(editor) { 48165 editor.fire('ResizeWindow', e); 48166 }); 48167 } 48168 48169 function toggleGlobalEvents(editors, state) { 48170 if (state !== boundGlobalEvents) { 48171 if (state) { 48172 $(window).on('resize', globalEventDelegate); 48173 } else { 48174 $(window).off('resize', globalEventDelegate); 48175 } 48176 48177 boundGlobalEvents = state; 48178 } 48179 } 48180 48181 function removeEditorFromList(editor) { 48182 var editors = EditorManager.editors, removedFromList; 48183 48184 delete editors[editor.id]; 48185 48186 for (var i = 0; i < editors.length; i++) { 48187 if (editors[i] == editor) { 48188 editors.splice(i, 1); 48189 removedFromList = true; 48190 break; 48191 } 48192 } 48193 48194 // Select another editor since the active one was removed 48195 if (EditorManager.activeEditor == editor) { 48196 EditorManager.activeEditor = editors[0]; 48197 } 48198 48199 // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor 48200 if (EditorManager.focusedEditor == editor) { 48201 EditorManager.focusedEditor = null; 48202 } 48203 48204 return removedFromList; 48205 } 48206 48207 function purgeDestroyedEditor(editor) { 48208 // User has manually destroyed the editor lets clean up the mess 48209 if (editor && !(editor.getContainer() || editor.getBody()).parentNode) { 48210 removeEditorFromList(editor); 48211 editor.unbindAllNativeEvents(); 48212 editor.destroy(true); 48213 editor = null; 48214 } 48215 48216 return editor; 48217 } 48218 48219 EditorManager = { 48220 /** 48221 * Dom query instance. 48222 * 48223 * @property $ 48224 * @type tinymce.dom.DomQuery 48225 */ 48226 $: $, 48227 48228 /** 48229 * Major version of TinyMCE build. 48230 * 48231 * @property majorVersion
48232 * @type String 48233 */ 48234 majorVersion: '4', 48235 48236 /** 48237 * Minor version of TinyMCE build. 48238 * 48239 * @property minorVersion 48240 * @type String 48241 */ 48242 minorVersion: '3.3', 48243 48244 /** 48245 * Release date of TinyMCE build. 48246 * 48247 * @property releaseDate 48248 * @type String 48249 */ 48250 releaseDate: '2016-01-14', 48251 48252 /** 48253 * Collection of editor instances. 48254 * 48255 * @property editors 48256 * @type Object 48257 * @example 48258 * for (edId in tinymce.editors) 48259 * tinymce.editors[edId].save(); 48260 */ 48261 editors: [], 48262 48263 /** 48264 * Collection of language pack data. 48265 * 48266 * @property i18n 48267 * @type Object 48268 */ 48269 i18n: I18n, 48270 48271 /** 48272 * Currently active editor instance. 48273 * 48274 * @property activeEditor 48275 * @type tinymce.Editor 48276 * @example 48277 * tinyMCE.activeEditor.selection.getContent(); 48278 * tinymce.EditorManager.activeEditor.selection.getContent(); 48279 */ 48280 activeEditor: null, 48281 48282 setup: function() { 48283 var self = this, baseURL, documentBaseURL, suffix = "", preInit, src; 48284 48285 // Get base URL for the current document 48286 documentBaseURL = document.location.href; 48287 48288 // Check if the URL is a document based format like: http://site/dir/file and file:/// 48289 // leave other formats like applewebdata://... intact 48290 if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) { 48291 documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); 48292 48293 if (!/[\/\\]$/.test(documentBaseURL)) { 48294 documentBaseURL += '/'; 48295 } 48296 } 48297 48298 // If tinymce is defined and has a base use that or use the old tinyMCEPreInit 48299 preInit = window.tinymce || window.tinyMCEPreInit; 48300 if (preInit) { 48301 baseURL = preInit.base || preInit.baseURL; 48302 suffix = preInit.suffix; 48303 } else { 48304 // Get base where the tinymce script is located 48305 var scripts = document.getElementsByTagName('script'); 48306 for (var i = 0; i < scripts.length; i++) { 48307 src = scripts[i].src; 48308 48309 // Script types supported: 48310 // tinymce.js tinymce.min.js tinymce.dev.js 48311 // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js 48312 // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js 48313 var srcScript = src.substring(src.lastIndexOf('/')); 48314 if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) { 48315 if (srcScript.indexOf('.min') != -1) { 48316 suffix = '.min'; 48317 } 48318 48319 baseURL = src.substring(0, src.lastIndexOf('/')); 48320 break; 48321 } 48322 } 48323 48324 // We didn't find any baseURL by looking at the script elements 48325 // Try to use the document.currentScript as a fallback 48326 if (!baseURL && document.currentScript) { 48327 src = document.currentScript.src; 48328 48329 if (src.indexOf('.min') != -1) { 48330 suffix = '.min'; 48331 } 48332 48333 baseURL = src.substring(0, src.lastIndexOf('/')); 48334 } 48335 } 48336 48337 /** 48338 * Base URL where the root directory if TinyMCE is located. 48339 * 48340 * @property baseURL
48341 * @type String 48342 */ 48343 self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL); 48344 48345 /** 48346 * Document base URL where the current document is located. 48347 * 48348 * @property documentBaseURL 48349 * @type String 48350 */ 48351 self.documentBaseURL = documentBaseURL; 48352 48353 /** 48354 * Absolute baseURI for the installation path of TinyMCE. 48355 * 48356 * @property baseURI 48357 * @type tinymce.util.URI 48358 */ 48359 self.baseURI = new URI(self.baseURL); 48360 48361 /** 48362 * Current suffix to add to each plugin/theme that gets loaded for example ".min". 48363 * 48364 * @property suffix 48365 * @type String 48366 */ 48367 self.suffix = suffix; 48368 48369 self.focusManager = new FocusManager(self); 48370 }, 48371 48372 /** 48373 * Initializes a set of editors. This method will create editors based on various settings. 48374 * 48375 * @method init 48376 * @param {Object} settings Settings object to be passed to each editor instance. 48377 * @example 48378 * // Initializes a editor using the longer method 48379 * tinymce.EditorManager.init({ 48380 * some_settings : 'some value' 48381 * }); 48382 * 48383 * // Initializes a editor instance using the shorter version 48384 * tinyMCE.init({ 48385 * some_settings : 'some value' 48386 * }); 48387 */ 48388 init: function(settings) { 48389 var self = this, editors = []; 48390 48391 function createId(elm) { 48392 var id = elm.id; 48393 48394 // Use element id, or unique name or generate a unique id 48395 if (!id) { 48396 id = elm.name; 48397 48398 if (id && !DOM.get(id)) { 48399 id = elm.name; 48400 } else { 48401 // Generate unique name 48402 id = DOM.uniqueId(); 48403 } 48404 48405 elm.setAttribute('id', id); 48406 } 48407 48408 return id; 48409 } 48410 48411 function createEditor(id, settings, targetElm) { 48412 if (!purgeDestroyedEditor(self.get(id))) { 48413 var editor = new Editor(id, settings, self); 48414 48415 editor.targetElm = editor.targetElm || targetElm; 48416 editors.push(editor); 48417 editor.render(); 48418 } 48419 } 48420 48421 function execCallback(name) { 48422 var callback = settings[name]; 48423 48424 if (!callback) { 48425 return; 48426 } 48427 48428 return callback.apply(self, Array.prototype.slice.call(arguments, 2)); 48429 } 48430 48431 function hasClass(elm, className) { 48432 return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className); 48433 } 48434 48435 function readyHandler() { 48436 var l, co; 48437 48438 DOM.unbind(window, 'ready', readyHandler); 48439 48440 execCallback('onpageload'); 48441 48442 if (settings.types) { 48443 // Process type specific selector 48444 each(settings.types, function(type) { 48445 each(DOM.select(type.selector), function(elm) { 48446 createEditor(createId(elm), extend({}, settings, type), elm); 48447 }); 48448 }); 48449 48450 return; 48451 } else if (settings.selector) { 48452 // Process global selector 48453 each(DOM.select(settings.selector), function(elm) { 48454 createEditor(createId(elm), settings, elm); 48455 }); 48456 48457 return; 48458 } else if (settings.target) { 48459 createEditor(createId(settings.target), settings); 48460 } 48461 48462 // Fallback to old setting 48463 switch (settings.mode) { 48464 case "exact": 48465 l = settings.elements || ''; 48466 48467 if (l.length > 0) { 48468 each(explode(l), function(id) { 48469 var elm; 48470 48471 if ((elm = DOM.get(id))) { 48472 createEditor(id, settings, elm); 48473 } else { 48474 each(document.forms, function(f) { 48475 each(f.elements, function(e) { 48476 if (e.name === id) { 48477 id = 'mce_editor_' + instanceCounter++; 48478 DOM.setAttrib(e, 'id', id); 48479 createEditor(id, settings, e); 48480 } 48481 }); 48482 }); 48483 } 48484 }); 48485 } 48486 break; 48487 48488 case "textareas": 48489 case "specific_textareas": 48490 each(DOM.select('textarea'), function(elm) { 48491 if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) { 48492 return; 48493 } 48494 48495 if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) { 48496 createEditor(createId(elm), settings, elm); 48497 } 48498 }); 48499 break; 48500 } 48501 48502 // Call onInit when all editors are initialized 48503 if (settings.oninit) { 48504 l = co = 0; 48505 48506 each(editors, function(ed) { 48507 co++; 48508 48509 if (!ed.initialized) { 48510 // Wait for it 48511 ed.on('init', function() { 48512 l++; 48513 48514 // All done 48515 if (l == co) { 48516 execCallback('oninit'); 48517 } 48518 }); 48519 } else { 48520 l++; 48521 } 48522 48523 // All done 48524 if (l == co) { 48525 execCallback('oninit'); 48526 } 48527 }); 48528 } 48529 } 48530 48531 self.settings = settings; 48532 48533 DOM.bind(window, 'ready', readyHandler); 48534 }, 48535 48536 /** 48537 * Returns a editor instance by id. 48538 * 48539 * @method get 48540 * @param {String/Number} id Editor instance id or index to return. 48541 * @return {tinymce.Editor} Editor instance to return. 48542 * @example 48543 * // Adds an onclick event to an editor by id (shorter version) 48544 * tinymce.get('mytextbox').on('click', function(e) { 48545 * ed.windowManager.alert('Hello world!'); 48546 * }); 48547 * 48548 * // Adds an onclick event to an editor by id (longer version) 48549 * tinymce.EditorManager.get('mytextbox').on('click', function(e) { 48550 * ed.windowManager.alert('Hello world!'); 48551 * }); 48552 */ 48553 get: function(id) { 48554 if (!arguments.length) { 48555 return this.editors; 48556 } 48557 48558 return id in this.editors ? this.editors[id] : null; 48559 }, 48560 48561 /** 48562 * Adds an editor instance to the editor collection. This will also set it as the active editor. 48563 * 48564 * @method add 48565 * @param {tinymce.Editor} editor Editor instance to add to the collection. 48566 * @return {tinymce.Editor} The same instance that got passed in. 48567 */ 48568 add: function(editor) { 48569 var self = this, editors = self.editors; 48570 48571 // Add named and index editor instance 48572 editors[editor.id] = editor; 48573 editors.push(editor); 48574 48575 toggleGlobalEvents(editors, true); 48576 48577 // Doesn't call setActive method since we don't want 48578 // to fire a bunch of activate/deactivate calls while initializing 48579 self.activeEditor = editor; 48580 48581 /** 48582 * Fires when an editor is added to the EditorManager collection. 48583 * 48584 * @event AddEditor 48585 * @param {Object} e Event arguments. 48586 */ 48587 self.fire('AddEditor', {editor: editor}); 48588 48589 if (!beforeUnloadDelegate) { 48590 beforeUnloadDelegate = function() { 48591 self.fire('BeforeUnload'); 48592 }; 48593 48594 DOM.bind(window, 'beforeunload', beforeUnloadDelegate); 48595 } 48596 48597 return editor; 48598 }, 48599 48600 /** 48601 * Creates an editor instance and adds it to the EditorManager collection. 48602 * 48603 * @method createEditor 48604 * @param {String} id Instance id to use for editor. 48605 * @param {Object} settings Editor instance settings. 48606 * @return {tinymce.Editor} Editor instance that got created. 48607 */ 48608 createEditor: function(id, settings) { 48609 return this.add(new Editor(id, settings, this)); 48610 }, 48611 48612 /** 48613 * Removes a editor or editors form page. 48614 * 48615 * @example 48616 * // Remove all editors bound to divs 48617 * tinymce.remove('div'); 48618 * 48619 * // Remove all editors bound to textareas 48620 * tinymce.remove('textarea'); 48621 * 48622 * // Remove all editors 48623 * tinymce.remove(); 48624 * 48625 * // Remove specific instance by id 48626 * tinymce.remove('#id'); 48627 * 48628 * @method remove 48629 * @param {tinymce.Editor/String/Object}
48629 [selector] CSS selector or editor instance to remove. 48630 * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null. 48631 */ 48632 remove: function(selector) { 48633 var self = this, i, editors = self.editors, editor; 48634 48635 // Remove all editors 48636 if (!selector) { 48637 for (i = editors.length - 1; i >= 0; i--) { 48638 self.remove(editors[i]); 48639 } 48640 48641 return; 48642 } 48643 48644 // Remove editors by selector 48645 if (typeof selector == "string") { 48646 selector = selector.selector || selector; 48647 48648 each(DOM.select(selector), function(elm) { 48649 editor = editors[elm.id]; 48650 48651 if (editor) { 48652 self.remove(editor); 48653 } 48654 }); 48655 48656 return; 48657 } 48658 48659 // Remove specific editor 48660 editor = selector; 48661 48662 // Not in the collection 48663 if (!editors[editor.id]) { 48664 return null; 48665 } 48666 48667 /** 48668 * Fires when an editor is removed from EditorManager collection. 48669 * 48670 * @event RemoveEditor 48671 * @param {Object} e Event arguments. 48672 */ 48673 if (removeEditorFromList(editor)) { 48674 self.fire('RemoveEditor', {editor: editor}); 48675 } 48676 48677 if (!editors.length) { 48678 DOM.unbind(window, 'beforeunload', beforeUnloadDelegate); 48679 } 48680 48681 editor.remove(); 48682 48683 toggleGlobalEvents(editors, editors.length > 0); 48684 48685 return editor; 48686 }, 48687 48688 /** 48689 * Executes a specific command on the currently active editor. 48690 * 48691 * @method execCommand 48692 * @param {String} cmd Command to perform for example Bold. 48693 * @param {Boolean} ui Optional boolean state if a UI should be presented for the command or not. 48694 * @param {String} value Optional value parameter like for example an URL to a link. 48695 * @return {Boolean} true/false if the command was executed or not. 48696 */ 48697 execCommand: function(cmd, ui, value) { 48698 var self = this, editor = self.get(value); 48699 48700 // Manager commands 48701 switch (cmd) { 48702 case "mceAddEditor": 48703 if (!self.get(value)) { 48704 new Editor(value, self.settings, self).render(); 48705 } 48706 48707 return true; 48708 48709 case "mceRemoveEditor": 48710 if (editor) { 48711 editor.remove(); 48712 } 48713 48714 return true; 48715 48716 case 'mceToggleEditor': 48717 if (!editor) { 48718 self.execCommand('mceAddEditor', 0, value); 48719 return true; 48720 } 48721 48722 if (editor.isHidden()) { 48723 editor.show(); 48724 } else { 48725 editor.hide(); 48726 } 48727 48728 return true; 48729 } 48730 48731 // Run command on active editor 48732 if (self.activeEditor) { 48733 return self.activeEditor.execCommand(cmd, ui, value); 48734 } 48735 48736 return false; 48737 }, 48738 48739 /** 48740 * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted. 48741 * 48742 * @method triggerSave 48743 * @example 48744 * // Saves all contents 48745 * tinyMCE.triggerSave(); 48746 */ 48747 triggerSave: function() { 48748 each(this.editors, function(editor) { 48749 editor.save(); 48750 }); 48751 }, 48752 48753 /** 48754 * Adds a language pack, this gets called by the loaded language files like en.js. 48755 * 48756 * @method addI18n 48757 * @param {String} code Optional language code. 48758 * @param {Object} items Name/value object with translations. 48759 */ 48760 addI18n: function(code, items) { 48761 I18n.add(code, items); 48762 }, 48763 48764 /** 48765 * Translates the specified string using the language pack items. 48766 * 48767 * @method translate 48768 * @param {String/Array/Object} text String to translate 48769 * @return {String} Translated string. 48770 */ 48771 translate: function(text) { 48772 return I18n.translate(text); 48773 }, 48774 48775 /** 48776 * Sets the active editor instance and fires the deactivate/activate events. 48777 * 48778 * @method setActive 48779 * @param {tinymce.Editor} editor Editor instance to set as the active instance. 48780 */ 48781 setActive: function(editor) { 48782 var activeEditor = this.activeEditor; 48783 48784 if (this.activeEditor != editor) { 48785 if (activeEditor) { 48786 activeEditor.fire('deactivate', {relatedTarget: editor}); 48787 } 48788 48789 editor.fire('activate', {relatedTarget: activeEditor}); 48790 } 48791 48792 this.activeEditor = editor; 48793 } 48794 }; 48795 48796 extend(EditorManager, Observable); 48797 48798 EditorManager.setup(); 48799 48800 // Export EditorManager as tinymce/tinymce in global namespace 48801 window.tinymce = window.tinyMCE = EditorManager; 48802 48803 return EditorManager; 48804}); 48805 48806// Included from: js/tinymce/classes/LegacyInput.js 48807 48808/** 48809 * LegacyInput.js 48810 * 48811 * Released under LGPL License. 48812 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 48813 * 48814 * License: http://www.tinymce.com/license 48815 * Contributing: http://www.tinymce.com/contributing 48816 */ 48817 48818/** 48819 * Converts legacy input to modern HTML. 48820 * 48821 * @class tinymce.LegacyInput 48822 * @private 48823 */ 48824define("tinymce/LegacyInput", [ 48825 "tinymce/EditorManager", 48826 "tinymce/util/Tools" 48827], function(EditorManager, Tools) { 48828 var each = Tools.each, explode = Tools.explode; 48829 48830 EditorManager.on('AddEditor', function(e) { 48831 var editor = e.editor; 48832 48833 editor.on('preInit', function() { 48834 var filters, fontSizes, dom, settings = editor.settings; 48835 48836 function replaceWithSpan(node, styles) { 48837 each(styles, function(value, name) { 48838 if (value) { 48839 dom.setStyle(node, name, value); 48840 } 48841 }); 48842 48843 dom.rename(node, 'span'); 48844 } 48845 48846 function convert(e) { 48847 dom = editor.dom; 48848 48849 if (settings.convert_fonts_to_spans) { 48850 each(dom.select('font,u,strike', e.node), function(node) { 48851 filters[node.nodeName.toLowerCase()](dom, node); 48852 }); 48853 } 48854 } 48855 48856 if (settings.inline_styles) { 48857 fontSizes = explode(settings.font_size_legacy_values); 48858 48859 filters = { 48860 font: function(dom, node) { 48861 replaceWithSpan(node, { 48862 backgroundColor: node.style.backgroundColor, 48863 color: node.color, 48864 fontFamily: node.face, 48865 fontSize: fontSizes[parseInt(node.size, 10) - 1] 48866 }); 48867 }, 48868 48869 u: function(dom, node) { 48870 // HTML5 allows U element 48871 if (editor.settings.schema === "html4") { 48872 replaceWithSpan(node, { 48873 textDecoration: 'underline' 48874 }); 48875 } 48876 }, 48877 48878 strike: function(dom, node) { 48879 replaceWithSpan(node, { 48880 textDecoration: 'line-through' 48881 }); 48882 } 48883 }; 48884 48885 editor.on('PreProcess SetContent', convert); 48886 } 48887 }); 48888 }); 48889}); 48890 48891// Included from: js/tinymce/classes/util/XHR.js 48892 48893/** 48894 * XHR.js 48895 * 48896 * Released under LGPL License. 48897 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 48898 * 48899 * License: http://www.tinymce.com/license 48900 * Contributing: http://www.tinymce.com/contributing 48901 */ 48902 48903/** 48904 * This class enables you to send XMLHTTPRequests cross browser. 48905 * @class tinymce.util.XHR 48906 * @mixes tinymce.util.Observable
48907 * @static 48908 * @example 48909 * // Sends a low level Ajax request 48910 * tinymce.util.XHR.send({ 48911 * url: 'someurl', 48912 * success: function(text) { 48913 * console.debug(text); 48914 * } 48915 * }); 48916 * 48917 * // Add custom header to XHR request 48918 * tinymce.util.XHR.on('beforeSend', function(e) { 48919 * e.xhr.setRequestHeader('X-Requested-With', 'Something'); 48920 * }); 48921 */ 48922define("tinymce/util/XHR", [ 48923 "tinymce/util/Observable", 48924 "tinymce/util/Tools" 48925], function(Observable, Tools) { 48926 var XHR = { 48927 /** 48928 * Sends a XMLHTTPRequest. 48929 * Consult the Wiki for details on what settings this method takes. 48930 * 48931 * @method send 48932 * @param {Object} settings Object will target URL, callbacks and other info needed to make the request. 48933 */ 48934 send: function(settings) { 48935 var xhr, count = 0; 48936 48937 function ready() { 48938 if (!settings.async || xhr.readyState == 4 || count++ > 10000) { 48939 if (settings.success && count < 10000 && xhr.status == 200) { 48940 settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings); 48941 } else if (settings.error) { 48942 settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings); 48943 } 48944 48945 xhr = null; 48946 } else { 48947 setTimeout(ready, 10); 48948 } 48949 } 48950 48951 // Default settings 48952 settings.scope = settings.scope || this; 48953 settings.success_scope = settings.success_scope || settings.scope; 48954 settings.error_scope = settings.error_scope || settings.scope; 48955 settings.async = settings.async === false ? false : true; 48956 settings.data = settings.data || ''; 48957 48958 XHR.fire('beforeInitialize', {settings: settings}); 48959 48960 xhr = new XMLHttpRequest(); 48961 48962 if (xhr) { 48963 if (xhr.overrideMimeType) { 48964 xhr.overrideMimeType(settings.content_type); 48965 } 48966 48967 xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async); 48968 48969 if (settings.crossDomain) { 48970 xhr.withCredentials = true; 48971 } 48972 48973 if (settings.content_type) { 48974 xhr.setRequestHeader('Content-Type', settings.content_type); 48975 } 48976 48977 if (settings.requestheaders) { 48978 Tools.each(settings.requestheaders, function(header) { 48979 xhr.setRequestHeader(header.key, header.value); 48980 }); 48981 } 48982 48983 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 48984 48985 xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr; 48986 xhr.send(settings.data); 48987 48988 // Syncronous request 48989 if (!settings.async) { 48990 return ready(); 48991 } 48992 48993 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE 48994 setTimeout(ready, 10); 48995 } 48996 } 48997 }; 48998 48999 Tools.extend(XHR, Observable); 49000 49001 return XHR; 49002}); 49003 49004// Included from: js/tinymce/classes/util/JSON.js 49005 49006/** 49007 * JSON.js 49008 * 49009 * Released under LGPL License. 49010 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49011 * 49012 * License: http://www.tinymce.com/license 49013 * Contributing: http://www.tinymce.com/contributing 49014 */ 49015 49016/** 49017 * JSON parser and serializer class. 49018 * 49019 * @class tinymce.util.JSON 49020 * @static 49021 * @example 49022 * // JSON parse a string into an object 49023 * var obj = tinymce.util.JSON.parse(somestring); 49024 * 49025 * // JSON serialize a object into an string 49026 * var str = tinymce.util.JSON.serialize(obj); 49027 */ 49028define("tinymce/util/JSON", [], function() { 49029 function serialize(o, quote) { 49030 var i, v, t, name; 49031 49032 quote = quote || '"'; 49033 49034 if (o === null) { 49035 return 'null'; 49036 } 49037 49038 t = typeof o; 49039 49040 if (t == 'string') { 49041 v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; 49042 49043 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { 49044 // Make sure single quotes never get encoded inside double quotes for JSON compatibility 49045 if (quote === '"' && a === "'") { 49046 return a; 49047 } 49048 49049 i = v.indexOf(b); 49050 49051 if (i + 1) { 49052 return '\\' + v.charAt(i + 1); 49053 } 49054 49055 a = b.charCodeAt().toString(16); 49056 49057 return '\\u' + '0000'.substring(a.length) + a; 49058 }) + quote; 49059 } 49060 49061 if (t == 'object') { 49062 if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') { 49063 for (i = 0, v = '['; i < o.length; i++) { 49064 v += (i > 0 ? ',' : '') + serialize(o[i], quote); 49065 } 49066 49067 return v + ']'; 49068 } 49069 49070 v = '{'; 49071 49072 for (name in o) { 49073 if (o.hasOwnProperty(name)) { 49074 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + 49075 quote + ':' + serialize(o[name], quote) : ''; 49076 } 49077 } 49078 49079 return v + '}'; 49080 } 49081 49082 return '' + o; 49083 } 49084 49085 return { 49086 /** 49087 * Serializes the specified object as a JSON string. 49088 * 49089 * @method serialize 49090 * @param {Object} obj Object to serialize as a JSON string. 49091 * @param {String}
49091 quote Optional quote string defaults to ". 49092 * @return {string} JSON string serialized from input. 49093 */ 49094 serialize: serialize, 49095 49096 /** 49097 * Unserializes/parses the specified JSON string into a object. 49098 * 49099 * @method parse 49100 * @param {string} s JSON String to parse into a JavaScript object. 49101 * @return {Object} Object from input JSON string or undefined if it failed. 49102 */ 49103 parse: function(text) { 49104 try { 49105 // Trick uglify JS 49106 return window[String.fromCharCode(101) + 'val']('(' + text + ')'); 49107 } catch (ex) { 49108 // Ignore 49109 } 49110 } 49111 49112 /**#@-*/ 49113 }; 49114}); 49115 49116// Included from: js/tinymce/classes/util/JSONRequest.js 49117 49118/** 49119 * JSONRequest.js 49120 * 49121 * Released under LGPL License. 49122 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49123 * 49124 * License: http://www.tinymce.com/license 49125 * Contributing: http://www.tinymce.com/contributing 49126 */ 49127 49128/** 49129 * This class enables you to use JSON-RPC to call backend methods. 49130 * 49131 * @class tinymce.util.JSONRequest 49132 * @example 49133 * var json = new tinymce.util.JSONRequest({ 49134 * url: 'somebackend.php' 49135 * }); 49136 * 49137 * // Send RPC call 1 49138 * json.send({ 49139 * method: 'someMethod1', 49140 * params: ['a', 'b'], 49141 * success: function(result) { 49142 * console.dir(result); 49143 * } 49144 * }); 49145 * 49146 * // Send RPC call 2 49147 * json.send({ 49148 * method: 'someMethod2', 49149 * params: ['a', 'b'], 49150 * success: function(result) { 49151 * console.dir(result); 49152 * } 49153 * }); 49154 */ 49155define("tinymce/util/JSONRequest", [ 49156 "tinymce/util/JSON", 49157 "tinymce/util/XHR", 49158 "tinymce/util/Tools" 49159], function(JSON, XHR, Tools) { 49160 var extend = Tools.extend; 49161 49162 function JSONRequest(settings) { 49163 this.settings = extend({}, settings); 49164 this.count = 0; 49165 } 49166 49167 /** 49168 * Simple helper function to send a JSON-RPC request without the need to initialize an object. 49169 * Consult the Wiki API documentation for more details on what you can pass to this function. 49170 * 49171 * @method sendRPC 49172 * @static 49173 * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc. 49174 */ 49175 JSONRequest.sendRPC = function(o) { 49176 return new JSONRequest().send(o); 49177 }; 49178 49179 JSONRequest.prototype = { 49180 /** 49181 * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function. 49182 * 49183 * @method send 49184 * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc. 49185 */ 49186 send: function(args) { 49187 var ecb = args.error, scb = args.success; 49188 49189 args = extend(this.settings, args); 49190 49191 args.success = function(c, x) { 49192 c = JSON.parse(c); 49193 49194 if (typeof c == 'undefined') { 49195 c = { 49196 error: 'JSON Parse error.' 49197 }; 49198 } 49199 49200 if (c.error) { 49201 ecb.call(args.error_scope || args.scope, c.error, x); 49202 } else { 49203 scb.call(args.success_scope || args.scope, c.result); 49204 } 49205 }; 49206 49207 args.error = function(ty, x) { 49208 if (ecb) { 49209 ecb.call(args.error_scope || args.scope, ty, x); 49210 } 49211 }; 49212 49213 args.data = JSON.serialize({ 49214 id: args.id || 'c' + (this.count++), 49215 method: args.method, 49216 params: args.params 49217 }); 49218 49219 // JSON content type for Ruby on rails. Bug: #1883287 49220 args.content_type = 'application/json'; 49221 49222 XHR.send(args); 49223 } 49224 }; 49225 49226 return JSONRequest; 49227}); 49228 49229// Included from: js/tinymce/classes/util/JSONP.js 49230 49231/** 49232 * JSONP.js 49233 * 49234 * Released under LGPL License. 49235 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49236 * 49237 * License: http://www.tinymce.com/license 49238 * Contributing: http://www.tinymce.com/contributing 49239 */ 49240 49241define("tinymce/util/JSONP", [ 49242 "tinymce/dom/DOMUtils" 49243], function(DOMUtils) { 49244 return { 49245 callbacks: {}, 49246 count: 0, 49247 49248 send: function(settings) { 49249 var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count; 49250 var id = 'tinymce_jsonp_' + count; 49251 49252 self.callbacks[count] = function(json) { 49253 dom.remove(id); 49254 delete self.callbacks[count]; 49255 49256 settings.callback(json); 49257 }; 49258 49259 dom.add(dom.doc.body, 'script', { 49260 id: id, 49261 src: settings.url, 49262 type: 'text/javascript' 49263 }); 49264 49265 self.count++; 49266 } 49267 }; 49268}); 49269 49270// Included from: js/tinymce/classes/util/LocalStorage.js 49271 49272/** 49273 * LocalStorage.js 49274 * 49275 * Released under LGPL License. 49276 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49277 * 49278 * License: http://www.tinymce.com/license 49279 * Contributing: http://www.tinymce.com/contributing 49280 */ 49281 49282/** 49283 * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers. 49284 * Storage is done using userData on IE 7 and a special serialization format. The format is designed 49285 * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This 49286 * makes it possible to store for example HTML data. 49287 * 49288 * Storage format for userData: 49289 * <base 32 key length>,<key string>,<base 32 value length>,<value>,... 49290 * 49291 * For example this data key1=value1,key2=value2 would be: 49292 * 4,key1,6,value1,4,key2,6,value2 49293 * 49294 * @class tinymce.util.LocalStorage
49295 * @static 49296 * @version 4.0 49297 * @example 49298 * tinymce.util.LocalStorage.setItem('key', 'value'); 49299 * var value = tinymce.util.LocalStorage.getItem('key'); 49300 */ 49301define("tinymce/util/LocalStorage", [], function() { 49302 var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport; 49303 49304 // Check for native support 49305 try { 49306 if (window.localStorage) { 49307 return localStorage; 49308 } 49309 } catch (ex) { 49310 // Ignore 49311 } 49312 49313 userDataKey = "tinymce"; 49314 storageElm = document.documentElement; 49315 hasOldIEDataSupport = !!storageElm.addBehavior; 49316 49317 if (hasOldIEDataSupport) { 49318 storageElm.addBehavior('#default#userData'); 49319 } 49320 49321 /** 49322 * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters. 49323 */ 49324 function updateKeys() { 49325 keys = []; 49326 49327 for (var key in items) { 49328 keys.push(key); 49329 } 49330 49331 LocalStorage.length = keys.length; 49332 } 49333 49334 /** 49335 * Loads the userData string and parses it into the items structure. 49336 */ 49337 function load() { 49338 var key, data, value, pos = 0; 49339 49340 items = {}; 49341 49342 // localStorage can be disabled on WebKit/Gecko s
49342o make a dummy storage 49343 if (!hasOldIEDataSupport) { 49344 return; 49345 } 49346 49347 function next(end) { 49348 var value, nextPos; 49349 49350 nextPos = end !== undefined ? pos + end : data.indexOf(',', pos); 49351 if (nextPos === -1 || nextPos > data.length) { 49352 return null; 49353 } 49354 49355 value = data.substring(pos, nextPos); 49356 pos = nextPos + 1; 49357 49358 return value; 49359 } 49360 49361 storageElm.load(userDataKey); 49362 data = storageElm.getAttribute(userDataKey) || ''; 49363 49364 do { 49365 var offset = next(); 49366 if (offset === null) { 49367 break; 49368 } 49369 49370 key = next(parseInt(offset, 32) || 0); 49371 if (key !== null) { 49372 offset = next(); 49373 if (offset === null) { 49374 break; 49375 } 49376 49377 value = next(parseInt(offset, 32) || 0); 49378 49379 if (key) { 49380 items[key] = value; 49381 } 49382 } 49383 } while (key !== null); 49384 49385 updateKeys(); 49386 } 49387 49388 /** 49389 * Saves the items structure into a the userData format. 49390 */ 49391 function save() { 49392 var value, data = ''; 49393 49394 // localStorage can be disabled on WebKit/Gecko so make a dummy storage 49395 if (!hasOldIEDataSupport) { 49396 return; 49397 } 49398 49399 for (var key in items) { 49400 value = items[key]; 49401 data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value; 49402 } 49403 49404 storageElm.setAttribute(userDataKey, data); 49405 49406 try { 49407 storageElm.save(userDataKey); 49408 } catch (ex) { 49409 // Ignore disk full 49410 } 49411 49412 updateKeys(); 49413 } 49414 49415 LocalStorage = { 49416 /** 49417 * Length of the number of items in storage. 49418 * 49419 * @property length 49420 * @type Number 49421 * @return {Number} Number of items in storage. 49422 */ 49423 //length:0, 49424 49425 /** 49426 * Returns the key name by index. 49427 * 49428 * @method key 49429 * @param {Number} index Index of key to return. 49430 * @return {String} Key value or null if it wasn't found. 49431 */ 49432 key: function(index) { 49433 return keys[index]; 49434 }, 49435 49436 /** 49437 * Returns the value if the specified key or null if it wasn't found. 49438 * 49439 * @method getItem 49440 * @param {String} key Key of item to retrive. 49441 * @return {String} Value of the specified item or null if it wasn't found. 49442 */ 49443 getItem: function(key) { 49444 return key in items ? items[key] : null; 49445 }, 49446 49447 /** 49448 * Sets the value of the specified item by it's key. 49449 * 49450 * @method setItem 49451 * @param {String} key Key of the item to set. 49452 * @param {String} value Value of the item to set. 49453 */ 49454 setItem: function(key, value) { 49455 items[key] = "" + value; 49456 save(); 49457 }, 49458 49459 /** 49460 * Removes the specified item by key. 49461 * 49462 * @method removeItem 49463 * @param {String} key Key of item to remove. 49464 */ 49465 removeItem: function(key) { 49466 delete items[key]; 49467 save(); 49468 }, 49469 49470 /** 49471 * Removes all items. 49472 * 49473 * @method clear 49474 */ 49475 clear: function() { 49476 items = {}; 49477 save(); 49478 } 49479 }; 49480 49481 load(); 49482 49483 return LocalStorage; 49484}); 49485 49486// Included from: js/tinymce/classes/Compat.js 49487 49488/** 49489 * Compat.js 49490 * 49491 * Released under LGPL License. 49492 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49493 * 49494 * License: http://www.tinymce.com/license 49495 * Contributing: http://www.tinymce.com/contributing 49496 */ 49497 49498/** 49499 * TinyMCE core class. 49500 * 49501 * @static 49502 * @class tinymce 49503 * @borrow-members tinymce.EditorManager 49504 * @borrow-members tinymce.util.Tools 49505 */ 49506define("tinymce/Compat", [ 49507 "tinymce/dom/DOMUtils", 49508 "tinymce/dom/EventUtils", 49509 "tinymce/dom/ScriptLoader", 49510 "tinymce/AddOnManager", 49511 "tinymce/util/Tools", 49512 "tinymce/Env" 49513], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) { 49514 var tinymce = window.tinymce; 49515 49516 /** 49517 * @property {tinymce.dom.DOMUtils} DOM Global DOM instance. 49518 * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance. 49519 * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance. 49520 * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance. 49521 */ 49522 tinymce.DOM = DOMUtils.DOM; 49523 tinymce.ScriptLoader = ScriptLoader.ScriptLoader; 49524 tinymce.PluginManager = AddOnManager.PluginManager; 49525 tinymce.ThemeManager = AddOnManager.ThemeManager; 49526 49527 tinymce.dom = tinymce.dom || {}; 49528 tinymce.dom.Event = EventUtils.Event; 49529 49530 Tools.each(Tools, function(func, key) { 49531 tinymce[key] = func; 49532 }); 49533 49534 Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) { 49535 tinymce[name] = Env[name.substr(2).toLowerCase()]; 49536 }); 49537 49538 return {}; 49539}); 49540
49541// Describe the different namespaces 49542 49543/** 49544 * Root level namespace this contains classes directly related to the TinyMCE editor. 49545 * 49546 * @namespace tinymce 49547 */ 49548 49549/** 49550 * Contains classes for handling the browsers DOM. 49551 * 49552 * @namespace tinymce.dom 49553 */ 49554 49555/** 49556 * Contains html parser and serializer logic. 49557 * 49558 * @namespace tinymce.html 49559 */ 49560 49561/** 49562 * Contains the different UI types such as buttons, listboxes etc. 49563 * 49564 * @namespace tinymce.ui 49565 */ 49566 49567/** 49568 * Contains various utility classes such as json parser, cookies etc. 49569 * 49570 * @namespace tinymce.util 49571 */ 49572 49573/** 49574 * Contains modules to handle data binding. 49575 * 49576 * @namespace tinymce.data 49577 */ 49578 49579// Included from: js/tinymce/classes/ui/Layout.js 49580 49581/** 49582 * Layout.js 49583 * 49584 * Released under LGPL License. 49585 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49586 * 49587 * License: http://www.tinymce.com/license 49588 * Contributing: http://www.tinymce.com/contributing 49589 */ 49590 49591/** 49592 * Base layout manager class. 49593 * 49594 * @class tinymce.ui.Layout 49595 */ 49596define("tinymce/ui/Layout", [ 49597 "tinymce/util/Class", 49598 "tinymce/util/Tools" 49599], function(Class, Tools) { 49600 "use strict"; 49601 49602 return Class.extend({ 49603 Defaults: { 49604 firstControlClass: 'first', 49605 lastControlClass: 'last' 49606 }, 49607 49608 /** 49609 * Constructs a layout instance with the specified settings. 49610 * 49611 * @constructor 49612 * @param {Object} settings Name/value object with settings. 49613 */ 49614 init: function(settings) { 49615 this.settings = Tools.extend({}, this.Defaults, settings); 49616 }, 49617 49618 /** 49619 * This method gets invoked before the layout renders the controls. 49620 * 49621 * @method preRender 49622 * @param {tinymce.ui.Container} container Container instance to preRender. 49623 */ 49624 preRender: function(container) { 49625 container.bodyClasses.add(this.settings.containerClass); 49626 }, 49627 49628 /** 49629 * Applies layout classes to the container. 49630 * 49631 * @private 49632 */ 49633 applyClasses: function(items) { 49634 var self = this, settings = self.settings, firstClass, lastClass, firstItem, lastItem; 49635 49636 firstClass = settings.firstControlClass; 49637 lastClass = settings.lastControlClass; 49638 49639 items.each(function(item) { 49640 item.classes.remove(firstClass).remove(lastClass).add(settings.controlClass); 49641 49642 if (item.visible()) { 49643 if (!firstItem) { 49644 firstItem = item; 49645 } 49646 49647 lastItem = item; 49648 } 49649 }); 49650 49651 if (firstItem) { 49652 firstItem.classes.add(firstClass); 49653 } 49654 49655 if (lastItem) { 49656 lastItem.classes.add(lastClass); 49657 } 49658 }, 49659 49660 /** 49661 * Renders the specified container and any layout specific HTML. 49662 * 49663 * @method renderHtml 49664 * @param {tinymce.ui.Container} container Container to render HTML for. 49665 */ 49666 renderHtml: function(container) { 49667 var self = this, html = ''; 49668 49669 self.applyClasses(container.items()); 49670 49671 container.items().each(function(item) { 49672 html += item.renderHtml(); 49673 }); 49674 49675 return html; 49676 }, 49677 49678 /** 49679 * Recalculates the positions of the controls in the specified container. 49680 * 49681 * @method recalc 49682 * @param {tinymce.ui.Container} container Container instance to recalc. 49683 */ 49684 recalc: function() { 49685 }, 49686 49687 /** 49688 * This method gets invoked after the layout renders the controls. 49689 * 49690 * @method postRender 49691 * @param {tinymce.ui.Container} container Container instance to postRender. 49692 */ 49693 postRender: function() { 49694 }, 49695 49696 isNative: function() { 49697 return false; 49698 } 49699 }); 49700}); 49701 49702// Included from: js/tinymce/classes/ui/AbsoluteLayout.js 49703 49704/** 49705 * AbsoluteLayout.js 49706 * 49707 * Released under LGPL License. 49708 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49709 * 49710 * License: http://www.tinymce.com/license 49711 * Contributing: http://www.tinymce.com/contributing 49712 */ 49713 49714/** 49715 * LayoutManager for absolute positioning. This layout manager is more of 49716 * a base class for other layouts but can be created and used directly. 49717 * 49718 * @-x-less AbsoluteLayout.less 49719 * @class tinymce.ui.AbsoluteLayout 49720 * @extends tinymce.ui.Layout 49721 */ 49722define("tinymce/ui/AbsoluteLayout", [ 49723 "tinymce/ui/Layout" 49724], function(Layout) { 49725 "use strict"; 49726 49727 return Layout.extend({ 49728 Defaults: { 49729 containerClass: 'abs-layout', 49730 controlClass: 'abs-layout-item' 49731 }, 49732 49733 /** 49734 * Recalculates the positions of the controls in the specified container. 49735 * 49736 * @method recalc 49737 * @param {tinymce.ui.Container} container Container instance to recalc. 49738 */ 49739 recalc: function(container) { 49740 container.items().filter(':visible').each(function(ctrl) { 49741 var settings = ctrl.settings; 49742 49743 ctrl.layoutRect({ 49744 x: settings.x, 49745 y: settings.y, 49746 w: settings.w, 49747 h: settings.h 49748 }); 49749 49750 if (ctrl.recalc) { 49751 ctrl.recalc(); 49752 } 49753 }); 49754 }, 49755 49756 /** 49757 * Renders the specified container and any layout specific HTML. 49758 * 49759 * @method renderHtml 49760 * @param {tinymce.ui.Container} container Container to render HTML for. 49761 */ 49762 renderHtml: function(container) { 49763 return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container); 49764 } 49765 }); 49766}); 49767 49768// Included from: js/tinymce/classes/ui/Button.js 49769 49770/** 49771 * Button.js 49772 * 49773 * Released under LGPL License. 49774 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49775 * 49776 * License: http://www.tinymce.com/license 49777 * Contributing: http://www.tinymce.com/contributing 49778 */ 49779 49780/** 49781 * This class is used to create buttons. You can create them directly or through the Factory. 49782 * 49783 * @example 49784 * // Create and render a button to the body element 49785 * tinymce.ui.Factory.create({ 49786 * type: 'button', 49787 * text: 'My button' 49788 * }).renderTo(document.body); 49789 * 49790 * @-x-less Button.less 49791 * @class tinymce.ui.Button 49792 * @extends tinymce.ui.Widget 49793 */
49794define("tinymce/ui/Button", [ 49795 "tinymce/ui/Widget" 49796], function(Widget) { 49797 "use strict"; 49798 49799 return Widget.extend({ 49800 Defaults: { 49801 classes: "widget btn", 49802 role: "button" 49803 }, 49804 49805 /** 49806 * Constructs a new button instance with the specified settings. 49807 * 49808 * @constructor 49809 * @param {Object} settings Name/value object with settings. 49810 * @setting {String} size Size of the button small|medium|large. 49811 * @setting {String} image Image to use for icon. 49812 * @setting {String} icon Icon to use for button. 49813 */ 49814 init: function(settings) { 49815 var self = this, size; 49816 49817 self._super(settings); 49818 settings = self.settings; 49819 49820 size = self.settings.size; 49821 49822 self.on('click mousedown', function(e) { 49823 e.preventDefault(); 49824 }); 49825 49826 self.on('touchstart', function(e) { 49827 self.fire('click', e); 49828 e.preventDefault(); 49829 }); 49830 49831 if (settings.subtype) { 49832 self.classes.add(settings.subtype); 49833 } 49834 49835 if (size) { 49836 self.classes.add('btn-' + size); 49837 } 49838 49839 if (settings.icon) { 49840 self.icon(settings.icon); 49841 } 49842 }, 49843 49844 /** 49845 * Sets/gets the current button icon. 49846 * 49847 * @method icon 49848 * @param {String} [icon] New icon identifier. 49849 * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance. 49850 */ 49851 icon: function(icon) { 49852 if (!arguments.length) { 49853 return this.state.get('icon'); 49854 } 49855 49856 this.state.set('icon', icon); 49857 49858 return this; 49859 }, 49860 49861 /** 49862 * Repaints the button for example after it's been resizes by a layout engine. 49863 * 49864 * @method repaint 49865 */ 49866 repaint: function() { 49867 var btnElm = this.getEl().firstChild, 49868 btnStyle; 49869 49870 if (btnElm) { 49871 btnStyle = btnElm.style; 49872 btnStyle.width = btnStyle.height = "100%"; 49873 } 49874 49875 this._super(); 49876 }, 49877 49878 /** 49879 * Renders the control as a HTML string. 49880 * 49881 * @method renderHtml 49882 * @return {String} HTML representing the control. 49883 */ 49884 renderHtml: function() { 49885 var self = this, id = self._id, prefix = self.classPrefix; 49886 var icon = self.state.get('icon'), image, text = self.state.get('text'), textHtml = ''; 49887 49888 image = self.settings.image; 49889 if (image) { 49890 icon = 'none'; 49891 49892 // Support for [high dpi, low dpi] image sources 49893 if (typeof image != "string") { 49894 image = window.getSelection ? image[0] : image[1]; 49895 } 49896 49897 image = ' style="background-image: url(\'' + image + '\')"'; 49898 } else { 49899 image = ''; 49900 } 49901 49902 if (text) { 49903 self.classes.add('btn-has-text'); 49904 textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; 49905 } 49906 49907 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 49908 49909 return ( 49910 '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' + 49911 '<button role="presentation" type="button" tabindex="-1">' + 49912 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 49913 textHtml + 49914 '</button>' + 49915 '</div>' 49916 ); 49917 }, 49918 49919 bindStates: function() { 49920 var self = this, $ = self.$, textCls = self.classPrefix + 'txt'; 49921 49922 function setButtonText(text) { 49923 var $span = $('span.' + textCls, self.getEl()); 49924 49925 if (text) { 49926 if (!$span[0]) { 49927 $('button:first', self.getEl()).append('<span class="' + textCls + '"></span>'); 49928 $span = $('span.' + textCls, self.getEl()); 49929 } 49930 49931 $span.html(self.encode(text)); 49932 } else { 49933 $span.remove(); 49934 } 49935 49936 self.classes.toggle('btn-has-text', !!text); 49937 } 49938 49939 self.state.on('change:text', function(e) { 49940 setButtonText(e.value); 49941 }); 49942 49943 self.state.on('change:icon', function(e) { 49944 var icon = e.value, prefix = self.classPrefix; 49945 49946 self.settings.icon = icon; 49947 icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 49948 49949 var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; 49950 49951 if (icon) { 49952 if (!iconElm || iconElm != btnElm.firstChild) { 49953 iconElm = document.createElement('i'); 49954 btnElm.insertBefore(iconElm, btnElm.firstChild); 49955 } 49956 49957 iconElm.className = icon; 49958 } else if (iconElm) { 49959 btnElm.removeChild(iconElm); 49960 } 49961 49962 setButtonText(self.state.get('text')); 49963 }); 49964 49965 return self._super(); 49966 } 49967 }); 49968}); 49969 49970// Included from: js/tinymce/classes/ui/ButtonGroup.js 49971 49972/** 49973 * ButtonGroup.js 49974 * 49975 * Released under LGPL License. 49976 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 49977 * 49978 * License: http://www.tinymce.com/license 49979 * Contributing: http://www.tinymce.com/contributing 49980 */ 49981 49982/** 49983 * This control enables you to put multiple buttons into a group. This is 49984 * useful when you want to combine similar toolbar buttons into a group. 49985 * 49986 * @example 49987 * // Create and render a buttongroup with two buttons to the body element 49988 * tinymce.ui.Factory.create({ 49989 * type: 'buttongroup', 49990 * items: [ 49991 * {text: 'Button A'}, 49992 * {text: 'Button B'} 49993 * ] 49994 * }).renderTo(document.body); 49995 * 49996 * @-x-less ButtonGroup.less 49997 * @class tinymce.ui.ButtonGroup 49998 * @extends tinymce.ui.Container 49999 */
50000define("tinymce/ui/ButtonGroup", [ 50001 "tinymce/ui/Container" 50002], function(Container) { 50003 "use strict"; 50004 50005 return Container.extend({ 50006 Defaults: { 50007 defaultType: 'button', 50008 role: 'group' 50009 }, 50010 50011 /** 50012 * Renders the control as a HTML string. 50013 * 50014 * @method renderHtml 50015 * @return {String} HTML representing the control. 50016 */ 50017 renderHtml: function() { 50018 var self = this, layout = self._layout; 50019 50020 self.classes.add('btn-group'); 50021 self.preRender(); 50022 layout.preRender(self); 50023 50024 return ( 50025 '<div id="' + self._id + '" class="' + self.classes + '">' + 50026 '<div id="' + self._id + '-body">' + 50027 (self.settings.html || '') + layout.renderHtml(self) + 50028 '</div>' + 50029 '</div>' 50030 ); 50031 } 50032 }); 50033}); 50034 50035// Included from: js/tinymce/classes/ui/Checkbox.js 50036 50037/** 50038 * Checkbox.js 50039 * 50040 * Released under LGPL License. 50041 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50042 * 50043 * License: http://www.tinymce.com/license 50044 * Contributing: http://www.tinymce.com/contributing 50045 */ 50046 50047/** 50048 * This control creates a custom checkbox. 50049 * 50050 * @example 50051 * // Create and render a checkbox to the body element 50052 * tinymce.ui.Factory.create({ 50053 * type: 'checkbox', 50054 * checked: true, 50055 * text: 'My checkbox' 50056 * }).renderTo(document.body); 50057 * 50058 * @-x-less Checkbox.less 50059 * @class tinymce.ui.Checkbox 50060 * @extends tinymce.ui.Widget 50061 */ 50062define("tinymce/ui/Checkbox", [ 50063 "tinymce/ui/Widget" 50064], function(Widget) { 50065 "use strict"; 50066 50067 return Widget.extend({ 50068 Defaults: { 50069 classes: "checkbox", 50070 role: "checkbox", 50071 checked: false 50072 }, 50073 50074 /** 50075 * Constructs a new Checkbox instance with the specified settings. 50076 * 50077 * @constructor 50078 * @param {Object} settings Name/value object with settings. 50079 * @setting {Boolean} checked True if the checkbox should be checked by default. 50080 */ 50081 init: function(settings) { 50082 var self = this; 50083 50084 self._super(settings); 50085 50086 self.on('click mousedown', function(e) { 50087 e.preventDefault(); 50088 }); 50089 50090 self.on('click', function(e) { 50091 e.preventDefault(); 50092 50093 if (!self.disabled()) { 50094 self.checked(!self.checked()); 50095 } 50096 }); 50097 50098 self.checked(self.settings.checked); 50099 }, 50100 50101 /** 50102 * Getter/setter function for the checked state. 50103 * 50104 * @method checked 50105 * @param {Boolean} [state] State to be set. 50106 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 50107 */ 50108 checked: function(state) { 50109 if (!arguments.length) { 50110 return this.state.get('checked'); 50111 } 50112 50113 this.state.set('checked', state); 50114 50115 return this; 50116 }, 50117 50118 /** 50119 * Getter/setter function for the value state. 50120 * 50121 * @method value 50122 * @param {Boolean} [state] State to be set. 50123 * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation. 50124 */ 50125 value: function(state) { 50126 if (!arguments.length) { 50127 return this.checked(); 50128 } 50129 50130 return this.checked(state); 50131 }, 50132 50133 /** 50134 * Renders the control as a HTML string. 50135 * 50136 * @method renderHtml 50137 * @return {String} HTML representing the control. 50138 */ 50139 renderHtml: function() { 50140 var self = this, id = self._id, prefix = self.classPrefix; 50141 50142 return ( 50143 '<div id="' + id + '" class="' + self.classes + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' + 50144 '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' + 50145 '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self.state.get('text')) + '</span>' + 50146 '</div>' 50147 ); 50148 }, 50149 50150 bindStates: function() { 50151 var self = this; 50152 50153 function checked(state) { 50154 self.classes.toggle("checked", state); 50155 self.aria('checked', state); 50156 } 50157 50158 self.state.on('change:text', function(e) { 50159 self.getEl('al').firstChild.data = self.translate(e.value); 50160 }); 50161 50162 self.state.on('change:checked change:value', function(e) { 50163 self.fire('change'); 50164 checked(e.value); 50165 }); 50166 50167 self.state.on('change:icon', function(e) { 50168 var icon = e.value, prefix = self.classPrefix; 50169 50170 if (typeof icon == 'undefined') { 50171 return self.settings.icon; 50172 } 50173 50174 self.settings.icon = icon; 50175 icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 50176 50177 var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0]; 50178 50179 if (icon) { 50180 if (!iconElm || iconElm != btnElm.firstChild) { 50181 iconElm = document.createElement('i'); 50182 btnElm.insertBefore(iconElm, btnElm.firstChild); 50183 } 50184 50185 iconElm.className = icon; 50186 } else if (iconElm) { 50187 btnElm.removeChild(iconElm); 50188 } 50189 }); 50190 50191 if (self.state.get('checked')) { 50192 checked(true); 50193 } 50194 50195 return self._super(); 50196 } 50197 }); 50198}); 50199 50200// Included from: js/tinymce/classes/ui/ComboBox.js 50201 50202/** 50203 * ComboBox.js 50204 * 50205 * Released under LGPL License. 50206 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50207 * 50208 * License: http://www.tinymce.com/license 50209 * Contributing: http://www.tinymce.com/contributing 50210 */ 50211 50212/** 50213 * This class creates a combobox control. Select box that you select a value from or 50214 * type a value into. 50215 * 50216 * @-x-less ComboBox.less 50217 * @class tinymce.ui.ComboBox 50218 * @extends tinymce.ui.Widget 50219 */
50220define("tinymce/ui/ComboBox", [ 50221 "tinymce/ui/Widget", 50222 "tinymce/ui/Factory", 50223 "tinymce/ui/DomUtils", 50224 "tinymce/dom/DomQuery" 50225], function(Widget, Factory, DomUtils, $) { 50226 "use strict"; 50227 50228 return Widget.extend({ 50229 /** 50230 * Constructs a new control instance with the specified settings. 50231 * 50232 * @constructor 50233 * @param {Object} settings Name/value object with settings. 50234 * @setting {String} placeholder Placeholder text to display. 50235 */ 50236 init: function(settings) { 50237 var self = this; 50238 50239 self._super(settings); 50240 settings = self.settings; 50241 50242 self.classes.add('combobox'); 50243 self.subinput = true; 50244 self.ariaTarget = 'inp'; // TODO: Figure out a better way 50245 50246 settings.menu = settings.menu || settings.values; 50247 50248 if (settings.menu) { 50249 settings.icon = 'caret'; 50250 } 50251 50252 self.on('click', function(e) { 50253 var elm = e.target, root = self.getEl(); 50254 50255 if (!$.contains(root, elm) && elm != root) { 50256 return; 50257 } 50258 50259 while (elm && elm != root) { 50260 if (elm.id && elm.id.indexOf('-open') != -1) { 50261 self.fire('action'); 50262 50263 if (settings.menu) { 50264 self.showMenu(); 50265 50266 if (e.aria) { 50267 self.menu.items()[0].focus(); 50268 } 50269 } 50270 } 50271 50272 elm = elm.parentNode; 50273 } 50274 }); 50275 50276 // TODO: Rework this 50277 self.on('keydown', function(e) { 50278 if (e.target.nodeName == "INPUT" && e.keyCode == 13) { 50279 self.parents().reverse().each(function(ctrl) { 50280 var stateValue = self.state.get('value'), inputValue = self.getEl('inp').value; 50281 50282 e.preventDefault(); 50283 50284 self.state.set('value', inputValue); 50285 50286 if (stateValue != inputValue) { 50287 self.fire('change'); 50288 } 50289 50290 if (ctrl.hasEventListeners('submit') && ctrl.toJSON) { 50291 ctrl.fire('submit', {data: ctrl.toJSON()}); 50292 return false; 50293 } 50294 }); 50295 } 50296 }); 50297 50298 self.on('keyup', function(e) { 50299 if (e.target.nodeName == "INPUT") { 50300 self.state.set('value', e.target.value); 50301 } 50302 }); 50303 }, 50304 50305 showMenu: function() { 50306 var self = this, settings = self.settings, menu; 50307 50308 if (!self.menu) { 50309 menu = settings.menu || []; 50310 50311 // Is menu array then auto constuct menu control 50312 if (menu.length) { 50313 menu = { 50314 type: 'menu', 50315 items: menu 50316 }; 50317 } else { 50318 menu.type = menu.type || 'menu'; 50319 } 50320 50321 self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm()); 50322 self.fire('createmenu'); 50323 self.menu.reflow(); 50324 self.menu.on('cancel', function(e) { 50325 if (e.control === self.menu) { 50326 self.focus(); 50327 } 50328 }); 50329 50330 self.menu.on('show hide', function(e) { 50331 e.control.items().each(function(ctrl) { 50332 ctrl.active(ctrl.value() == self.value()); 50333 }); 50334 }).fire('show'); 50335 50336 self.menu.on('select', function(e) { 50337 self.value(e.control.value()); 50338 }); 50339 50340 self.on('focusin', function(e) { 50341 if (e.target.tagName.toUpperCase() == 'INPUT') { 50342 self.menu.hide(); 50343 } 50344 }); 50345 50346 self.aria('expanded', true); 50347 } 50348 50349 self.menu.show(); 50350 self.menu.layoutRect({w: self.layoutRect().w}); 50351 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); 50352 }, 50353 50354 /** 50355 * Focuses the input area of the control. 50356 * 50357 * @method focus 50358 */ 50359 focus: function() { 50360 this.getEl('inp').focus(); 50361 }, 50362 50363 /** 50364 * Repaints the control after a layout operation. 50365 * 50366 * @method repaint 50367 */ 50368 repaint: function() { 50369 var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect(); 50370 var width, lineHeight; 50371 50372 if (openElm) { 50373 width = rect.w - DomUtils.getSize(openElm).width - 10; 50374 } else { 50375 width = rect.w - 10; 50376 } 50377 50378 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 50379 var doc = document; 50380 if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 50381 lineHeight = (self.layoutRect().h - 2) + 'px'; 50382 } 50383 50384 $(elm.firstChild).css({ 50385 width: width, 50386 lineHeight: lineHeight 50387 }); 50388 50389 self._super(); 50390 50391 return self; 50392 }, 50393 50394 /** 50395 * Post render method. Called after the control has been rendered to the target. 50396 * 50397 * @method postRender 50398 * @return {tinymce.ui.ComboBox} Current combobox instance. 50399 */ 50400 postRender: function() { 50401 var self = this; 50402 50403 $(this.getEl('inp')).on('change', function(e) { 50404 self.state.set('value', e.target.value); 50405 self.fire('change', e); 50406 }); 50407 50408 return self._super(); 50409 }, 50410 50411 /** 50412 * Renders the control as a HTML string. 50413 * 50414 * @method renderHtml 50415 * @return {String} HTML representing the control. 50416 */ 50417 renderHtml: function() { 50418 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix; 50419 var value = self.state.get('value') || ''; 50420 var icon, text, openBtnHtml = '', extraAttrs = ''; 50421 50422 if ("spellcheck" in settings) { 50423 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; 50424 } 50425 50426 if (settings.maxLength) { 50427 extraAttrs += ' maxlength="' + settings.maxLength + '"'; 50428 } 50429 50430 if (settings.size) { 50431 extraAttrs += ' size="' + settings.size + '"'; 50432 } 50433 50434 if (settings.subtype) { 50435 extraAttrs += ' type="' + settings.subtype + '"'; 50436 } 50437 50438 if (self.disabled()) { 50439 extraAttrs += ' disabled="disabled"'; 50440 } 50441 50442 icon = settings.icon; 50443 if (icon && icon != 'caret') { 50444 icon = prefix + 'ico ' + prefix + 'i-' + settings.icon; 50445 } 50446 50447 text = self.state.get('text'); 50448 50449 if (icon || text) { 50450 openBtnHtml = ( 50451 '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' + 50452 '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' + 50453 (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') + 50454 (text ? (icon ? ' ' : '') + text : '') + 50455 '</button>' + 50456 '</div>' 50457 ); 50458 50459 self.classes.add('has-open'); 50460 } 50461 50462 return ( 50463 '<div id="' + id + '" class="' + self.classes + '">' + 50464 '<input id="' + id + '-inp" class="' + prefix + 'textbox" value="' + 50465 self.encode(value, false) + '" hidefocus="1"' + extraAttrs + ' placeholder="' + 50466 self.encode(settings.placeholder) + '" />' + 50467 openBtnHtml + 50468 '</div>' 50469 ); 50470 }, 50471 50472 value: function(value) { 50473 if (arguments.length) { 50474 this.state.set('value', value); 50475 return this; 50476 } 50477 50478 // Make sure the real state is in sync 50479 if (this.state.get('rendered')) { 50480 this.state.set('value', this.getEl('inp').value); 50481 } 50482 50483 return this.state.get('value'); 50484 }, 50485 50486 bindStates: function() { 50487 var self = this; 50488 50489 self.state.on('change:value', function(e) { 50490 if (self.getEl('inp').value != e.value) { 50491 self.getEl('inp').value = e.value; 50492 } 50493 }); 50494 50495 self.state.on('change:disabled', function(e) { 50496 self.getEl('inp').disabled = e.value; 50497 }); 50498 50499 return self._super(); 50500 }, 50501 50502 remove: function() { 50503 $(this.getEl('inp')).off(); 50504 this._super(); 50505 } 50506 }); 50507}); 50508 50509// Included from: js/tinymce/classes/ui/ColorBox.js 50510 50511/** 50512 * ColorBox.js 50513 * 50514 * Released under LGPL License. 50515 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50516 * 50517 * License: http://www.tinymce.com/license 50518 * Contributing: http://www.tinymce.com/contributing 50519 */ 50520 50521/** 50522 * This widget lets you enter colors and browse for colors by pressing the color button. It also dis
50522plays 50523 * a preview of the current color. 50524 * 50525 * @-x-less ColorBox.less 50526 * @class tinymce.ui.ColorBox 50527 * @extends tinymce.ui.ComboBox 50528 */ 50529define("tinymce/ui/ColorBox", [ 50530 "tinymce/ui/ComboBox" 50531], function(ComboBox) { 50532 "use strict"; 50533 50534 return ComboBox.extend({ 50535 /** 50536 * Constructs a new control instance with the specified settings. 50537 * 50538 * @constructor 50539 * @param {Object} settings Name/value object with settings. 50540 */ 50541 init: function(settings) { 50542 var self = this; 50543 50544 settings.spellcheck = false; 50545 50546 if (settings.onaction) { 50547 settings.icon = 'none'; 50548 } 50549 50550 self._super(settings); 50551 50552 self.classes.add('colorbox'); 50553 self.on('change keyup postrender', function() { 50554 self.repaintColor(self.value()); 50555 }); 50556 }, 50557 50558 repaintColor: function(value) { 50559 var elm = this.getEl().getElementsByTagName('i')[0]; 50560 50561 if (elm) { 50562 try { 50563 elm.style.background = value; 50564 } catch (ex) { 50565 // Ignore 50566 } 50567 } 50568 }, 50569 50570 bindStates: function() { 50571 var self = this; 50572 50573 self.state.on('change:value', function(e) { 50574 if (self._rendered) { 50575 self.repaintColor(e.value); 50576 } 50577 }); 50578 50579 return self._super(); 50580 } 50581 }); 50582}); 50583 50584// Included from: js/tinymce/classes/ui/PanelButton.js 50585 50586/** 50587 * PanelButton.js 50588 * 50589 * Released under LGPL License. 50590 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50591 * 50592 * License: http://www.tinymce.com/license 50593 * Contributing: http://www.tinymce.com/contributing 50594 */ 50595 50596/** 50597 * Creates a new panel button. 50598 * 50599 * @class tinymce.ui.PanelButton 50600 * @extends tinymce.ui.Button 50601 */ 50602define("tinymce/ui/PanelButton", [ 50603 "tinymce/ui/Button", 50604 "tinymce/ui/FloatPanel" 50605], function(Button, FloatPanel) { 50606 "use strict"; 50607 50608 return Button.extend({ 50609 /** 50610 * Shows the panel for the button. 50611 * 50612 * @method showPanel 50613 */ 50614 showPanel: function() { 50615 var self = this, settings = self.settings; 50616 50617 self.active(true); 50618 50619 if (!self.panel) { 50620 var panelSettings = settings.panel; 50621 50622 // Wrap panel in grid layout if type if specified 50623 // This makes it possible to add forms or other containers directly in the panel option 50624 if (panelSettings.type) { 50625 panelSettings = { 50626 layout: 'grid', 50627 items: panelSettings 50628 }; 50629 } 50630 50631 panelSettings.role = panelSettings.role || 'dialog'; 50632 panelSettings.popover = true; 50633 panelSettings.autohide = true; 50634 panelSettings.ariaRoot = true; 50635 50636 self.panel = new FloatPanel(panelSettings).on('hide', function() { 50637 self.active(false); 50638 }).on('cancel', function(e) { 50639 e.stopPropagation(); 50640 self.focus(); 50641 self.hidePanel(); 50642 }).parent(self).renderTo(self.getContainerElm()); 50643 50644 self.panel.fire('show'); 50645 self.panel.reflow(); 50646 } else { 50647 self.panel.show(); 50648 } 50649 50650 self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc'])); 50651 }, 50652 50653 /** 50654 * Hides the panel for the button. 50655 * 50656 * @method hidePanel 50657 */ 50658 hidePanel: function() { 50659 var self = this; 50660 50661 if (self.panel) { 50662 self.panel.hide(); 50663 } 50664 }, 50665 50666 /** 50667 * Called after the control has been rendered. 50668 * 50669 * @method postRender 50670 */ 50671 postRender: function() { 50672 var self = this; 50673 50674 self.aria('haspopup', true); 50675 50676 self.on('click', function(e) { 50677 if (e.control === self) { 50678 if (self.panel && self.panel.visible()) { 50679 self.hidePanel(); 50680 } else { 50681 self.showPanel(); 50682 self.panel.focus(!!e.aria); 50683 } 50684 } 50685 }); 50686 50687 return self._super(); 50688 }, 50689 50690 remove: function() { 50691 if (this.panel) { 50692 this.panel.remove(); 50693 this.panel = null; 50694 } 50695 50696 return this._super(); 50697 } 50698 }); 50699}); 50700 50701// Included from: js/tinymce/classes/ui/ColorButton.js 50702 50703/** 50704 * ColorButton.js 50705 * 50706 * Released under LGPL License. 50707 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50708 * 50709 * License: http://www.tinymce.com/license 50710 * Contributing: http://www.tinymce.com/contributing 50711 */ 50712 50713/** 50714 * This class creates a color button control. This is a split button in which the main 50715 * button has a visual representation of the currently selected color. When clicked 50716 * the caret button displays a color picker, allowing the user to select a new color. 50717 * 50718 * @-x-less ColorButton.less 50719 * @class tinymce.ui.ColorButton 50720 * @extends tinymce.ui.PanelButton 50721 */
50722define("tinymce/ui/ColorButton", [ 50723 "tinymce/ui/PanelButton", 50724 "tinymce/dom/DOMUtils" 50725], function(PanelButton, DomUtils) { 50726 "use strict"; 50727 50728 var DOM = DomUtils.DOM; 50729 50730 return PanelButton.extend({ 50731 /** 50732 * Constructs a new ColorButton instance with the specified settings. 50733 * 50734 * @constructor 50735 * @param {Object} settings Name/value object with settings. 50736 */ 50737 init: function(settings) { 50738 this._super(settings); 50739 this.classes.add('colorbutton'); 50740 }, 50741 50742 /** 50743 * Getter/setter for the current color. 50744 * 50745 * @method color 50746 * @param {String} [color] Color to set. 50747 * @return {String|tinymce.ui.ColorButton} Current color or current instance. 50748 */ 50749 color: function(color) { 50750 if (color) { 50751 this._color = color; 50752 this.getEl('preview').style.backgroundColor = color; 50753 return this; 50754 } 50755 50756 return this._color; 50757 }, 50758 50759 /** 50760 * Resets the current color. 50761 * 50762 * @method resetColor 50763 * @return {tinymce.ui.ColorButton} Current instance. 50764 */ 50765 resetColor: function() { 50766 this._color = null; 50767 this.getEl('preview').style.backgroundColor = null; 50768 return this; 50769 }, 50770 50771 /** 50772 * Renders the control as a HTML string. 50773 * 50774 * @method renderHtml 50775 * @return {String} HTML representing the control. 50776 */ 50777 renderHtml: function() { 50778 var self = this, id = self._id, prefix = self.classPrefix, text = self.state.get('text'); 50779 var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : ''; 50780 var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '', 50781 textHtml = ''; 50782 50783 if (text) { 50784 self.classes.add('btn-has-text'); 50785 textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; 50786 } 50787 50788 return ( 50789 '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1" aria-haspopup="true">' + 50790 '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' + 50791 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 50792 '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' + 50793 textHtml + 50794 '</button>' + 50795 '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + 50796 ' <i class="' + prefix + 'caret"></i>' + 50797 '</button>' + 50798 '</div>' 50799 ); 50800 }, 50801 50802 /** 50803 * Called after the control has been rendered. 50804 * 50805 * @method postRender 50806 */ 50807 postRender: function() { 50808 var self = this, onClickHandler = self.settings.onclick; 50809 50810 self.on('click', function(e) { 50811 if (e.aria && e.aria.key == 'down') { 50812 return; 50813 } 50814 50815 if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) { 50816 e.stopImmediatePropagation(); 50817 onClickHandler.call(self, e); 50818 } 50819 }); 50820 50821 delete self.settings.onclick; 50822 50823 return self._super(); 50824 } 50825 }); 50826}); 50827 50828// Included from: js/tinymce/classes/util/Color.js 50829 50830/** 50831 * Color.js 50832 * 50833 * Released under LGPL License. 50834 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 50835 * 50836 * License: http://www.tinymce.com/license 50837 * Contributing: http://www.tinymce.com/contributing 50838 */ 50839 50840/** 50841 * This class lets you parse/serialize colors and convert rgb/hsb. 50842 * 50843 * @class tinymce.util.Color 50844 * @example 50845 * var white = new tinymce.util.Color({r: 255, g: 255, b: 255}); 50846 * var red = new tinymce.util.Color('#FF0000'); 50847 * 50848 * console.log(white.toHex(), red.toHsv()); 50849 */ 50850define("tinymce/util/Color", [], function() { 50851 var min = Math.min, max = Math.max, round = Math.round; 50852 50853 /** 50854 * Constructs a new color instance. 50855 * 50856 * @constructor 50857 * @method Color 50858 * @param {String} value Optional initial value to parse. 50859 */ 50860 function Color(value) { 50861 var self = this, r = 0, g = 0, b = 0; 50862 50863 function rgb2hsv(r, g, b) { 50864 var h, s, v, d, minRGB, maxRGB; 50865 50866 h = 0; 50867 s = 0; 50868 v = 0; 50869 r = r / 255; 50870 g = g / 255; 50871 b = b / 255; 50872 50873 minRGB = min(r, min(g, b)); 50874 maxRGB = max(r, max(g, b)); 50875 50876 if (minRGB == maxRGB) { 50877 v = minRGB; 50878 50879 return { 50880 h: 0, 50881 s: 0, 50882 v: v * 100 50883 }; 50884 } 50885 50886 /*eslint no-nested-ternary:0 */ 50887 d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r); 50888 h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5); 50889 h = 60 * (h - d / (maxRGB - minRGB)); 50890 s = (maxRGB - minRGB) / maxRGB; 50891 v = maxRGB; 50892 50893 return { 50894 h: round(h), 50895 s: round(s * 100), 50896 v: round(v * 100) 50897 }; 50898 } 50899 50900 function hsvToRgb(hue, saturation, brightness) { 50901 var side, chroma, x, match; 50902 50903 hue = (parseInt(hue, 10) || 0) % 360; 50904 saturation = parseInt(saturation, 10) / 100; 50905 brightness = parseInt(brightness, 10) / 100; 50906 saturation = max(0, min(saturation, 1)); 50907 brightness = max(0, min(brightness, 1)); 50908 50909 if (saturation === 0) { 50910 r = g = b = round(255 * brightness); 50911 return; 50912 } 50913 50914 side = hue / 60; 50915 chroma = brightness * saturation; 50916 x = chroma * (1 - Math.abs(side % 2 - 1)); 50917 match = brightness - chroma; 50918 50919 switch (Math.floor(side)) { 50920 case 0: 50921 r = chroma; 50922 g = x; 50923 b = 0; 50924 break; 50925
50926 case 1: 50927 r = x; 50928 g = chroma; 50929 b = 0; 50930 break; 50931 50932 case 2: 50933 r = 0; 50934 g = chroma; 50935 b = x; 50936 break; 50937 50938 case 3: 50939 r = 0; 50940 g = x; 50941 b = chroma; 50942 break; 50943 50944 case 4: 50945 r = x; 50946 g = 0; 50947 b = chroma; 50948 break; 50949 50950 case 5: 50951 r = chroma; 50952 g = 0; 50953 b = x; 50954 break; 50955 50956 default: 50957 r = g = b = 0; 50958 } 50959 50960 r = round(255 * (r + match)); 50961 g = round(255 * (g + match)); 50962 b = round(255 * (b + match)); 50963 } 50964 50965 /** 50966 * Returns the hex string of the current color. For example: #ff00ff 50967 * 50968 * @method toHex 50969 * @return {String} Hex string of current color. 50970 */ 50971 function toHex() { 50972 function hex(val) { 50973 val = parseInt(val, 10).toString(16); 50974 50975 return val.length > 1 ? val : '0' + val; 50976 } 50977 50978 return '#' + hex(r) + hex(g) + hex(b); 50979 } 50980 50981 /** 50982 * Returns the r, g, b values of the color. Each channel has a range from 0-255. 50983 * 50984 * @method toRgb 50985 * @return {Object} Object with r, g, b fields. 50986 */ 50987 function toRgb() { 50988 return { 50989 r: r, 50990 g: g, 50991 b: b 50992 }; 50993 } 50994 50995 /** 50996 * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100. 50997 * 50998 * @method toHsv 50999 * @return {Object} Object with h, s, v fields. 51000 */ 51001 function toHsv() { 51002 return rgb2hsv(r, g, b); 51003 } 51004 51005 /** 51006 * Parses the specified value and populates the color instance. 51007 * 51008 * Supported format examples: 51009 * * rbg(255,0,0) 51010 * * #ff0000 51011 * * #fff 51012 * * {r: 255, g: 0, b: 0} 51013 * * {h: 360, s: 100, v: 100} 51014 * 51015 * @method parse 51016 * @param {Object/String} value Color value to parse. 51017 * @return {tinymce.util.Color} Current color instance. 51018 */ 51019 function parse(value) { 51020 var matches; 51021 51022 if (typeof value == 'object') { 51023 if ("r" in value) { 51024 r = value.r; 51025 g = value.g; 51026 b = value.b; 51027 } else if ("v" in value) { 51028 hsvToRgb(value.h, value.s, value.v); 51029 } 51030 } else { 51031 if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) { 51032 r = parseInt(matches[1], 10); 51033 g = parseInt(matches[2], 10); 51034 b = parseInt(matches[3], 10); 51035 } else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) { 51036 r = parseInt(matches[1], 16); 51037 g = parseInt(matches[2], 16); 51038 b = parseInt(matches[3], 16); 51039 } else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) { 51040 r = parseInt(matches[1] + matches[1], 16); 51041 g = parseInt(matches[2] + matches[2], 16); 51042 b = parseInt(matches[3] + matches[3], 16); 51043 } 51044 } 51045 51046 r = r < 0 ? 0 : (r > 255 ? 255 : r); 51047 g = g < 0 ? 0 : (g > 255 ? 255 : g); 51048 b = b < 0 ? 0 : (b > 255 ? 255 : b); 51049 51050 return self; 51051 } 51052 51053 if (value) { 51054 parse(value); 51055 } 51056 51057 self.toRgb = toRgb; 51058 self.toHsv = toHsv; 51059 self.toHex = toHex; 51060 self.parse = parse; 51061 } 51062 51063 return Color; 51064}); 51065 51066// Included from: js/tinymce/classes/ui/ColorPicker.js 51067 51068/** 51069 * ColorPicker.js 51070 * 51071 * Released under LGPL License. 51072 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51073 * 51074 * License: http://www.tinymce.com/license 51075 * Contributing: http://www.tinymce.com/contributing 51076 */ 51077 51078/** 51079 * Color picker widget lets you select colors. 51080 * 51081 * @-x-less ColorPicker.less 51082 * @class tinymce.ui.ColorPicker 51083 * @extends tinymce.ui.Widget 51084 */ 51085define("tinymce/ui/ColorPicker", [ 51086 "tinymce/ui/Widget", 51087 "tinymce/ui/DragHelper", 51088 "tinymce/ui/DomUtils", 51089 "tinymce/util/Color" 51090], function(Widget, DragHelper, DomUtils, Color) { 51091 "use strict"; 51092 51093 return Widget.extend({ 51094 Defaults: { 51095 classes: "widget colorpicker" 51096 }, 51097 51098 /** 51099 * Constructs a new colorpicker instance with the specified settings. 51100 * 51101 * @constructor 51102 * @param {Object} settings Name/value object with settings. 51103 * @setting {String} color Initial color value. 51104 */ 51105 init: function(settings) { 51106 this._super(settings); 51107 }, 51108 51109 postRender: function() { 51110 var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm; 51111 51112 hueRootElm = self.getEl('h'); 51113 huePointElm = self.getEl('hp'); 51114 svRootElm = self.getEl('sv'); 51115 svPointElm = self.getEl('svp'); 51116 51117 function getPos(elm, event) { 51118 var pos = DomUtils.getPos(elm), x, y; 51119 51120 x = event.pageX - pos.x; 51121 y = event.pageY - pos.y; 51122 51123 x = Math.max(0, Math.min(x / elm.clientWidth, 1)); 51124 y = Math.max(0, Math.min(y / elm.clientHeight, 1)); 51125 51126 return { 51127 x: x, 51128 y: y 51129 }; 51130 } 51131 51132 function updateColor(hsv, hueUpdate) { 51133 var hue = (360 - hsv.h) / 360; 51134 51135 DomUtils.css(huePointElm, { 51136 top: (hue * 100) + '%' 51137 }); 51138 51139 if (!hueUpdate) { 51140 DomUtils.css(svPointElm, { 51141 left: hsv.s + '%', 51142 top: (100 - hsv.v) + '%' 51143 }); 51144 } 51145 51146 svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex(); 51147 self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h}); 51148 } 51149 51150 function updateSaturationAndValue(e) { 51151 var pos; 51152 51153 pos = getPos(svRootElm, e); 51154 hsv.s = pos.x * 100; 51155 hsv.v = (1 - pos.y) * 100; 51156 51157 updateColor(hsv); 51158 self.fire('change'); 51159 } 51160 51161 function updateHue(e) { 51162 var pos; 51163 51164 pos = getPos(hueRootElm, e); 51165 hsv = color.toHsv(); 51166 hsv.h = (1 - pos.y) * 360; 51167 updateColor(hsv, true); 51168 self.fire('change'); 51169 } 51170 51171 self._repaint = function() { 51172 hsv = color.toHsv(); 51173 updateColor(hsv); 51174 }; 51175 51176 self._super(); 51177 51178 self._svdraghelper = new DragHelper(self._id + '-sv', { 51179 start: updateSaturationAndValue, 51180 drag: updateSaturationAndValue 51181 }); 51182 51183 self._hdraghelper = new DragHelper(self._id + '-h', { 51184 start: updateHue, 51185 drag: updateHue 51186 }); 51187 51188 self._repaint(); 51189 }, 51190 51191 rgb: function() { 51192 return this.color().toRgb(); 51193 }, 51194 51195 value: function(value) { 51196 var self = this; 51197
51198 if (arguments.length) { 51199 self.color().parse(value); 51200 51201 if (self._rendered) { 51202 self._repaint(); 51203 } 51204 } else { 51205 return self.color().toHex(); 51206 } 51207 }, 51208 51209 color: function() { 51210 if (!this._color) { 51211 this._color = new Color(); 51212 } 51213 51214 return this._color; 51215 }, 51216 51217 /** 51218 * Renders the control as a HTML string. 51219 * 51220 * @method renderHtml 51221 * @return {String} HTML representing the control. 51222 */ 51223 renderHtml: function() { 51224 var self = this, id = self._id, prefix = self.classPrefix, hueHtml; 51225 var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000'; 51226 51227 function getOldIeFallbackHtml() { 51228 var i, l, html = '', gradientPrefix, stopsList; 51229 51230 gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='; 51231 stopsList = stops.split(','); 51232 for (i = 0, l = stopsList.length - 1; i < l; i++) { 51233 html += ( 51234 '<div class="' + prefix + 'colorpicker-h-chunk" style="' + 51235 'height:' + (100 / l) + '%;' + 51236 gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' + 51237 '-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' + 51238 '"></div>' 51239 ); 51240 } 51241 51242 return html; 51243 } 51244 51245 var gradientCssText = ( 51246 'background: -ms-linear-gradient(top,' + stops + ');' + 51247 'background: linear-gradient(to bottom,' + stops + ');' 51248 ); 51249 51250 hueHtml = ( 51251 '<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' + 51252 getOldIeFallbackHtml() + 51253 '<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' + 51254 '</div>' 51255 ); 51256 51257 return ( 51258 '<div id="' + id + '" class="' + self.classes + '">' + 51259 '<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' + 51260 '<div class="' + prefix + 'colorpicker-overlay1">' + 51261 '<div class="' + prefix + 'colorpicker-overlay2">' + 51262 '<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' + 51263 '<div class="' + prefix + 'colorpicker-selector2"></div>' + 51264 '</div>' + 51265 '</div>' + 51266 '</div>' + 51267 '</div>' + 51268 hueHtml + 51269 '</div>' 51270 ); 51271 } 51272 }); 51273}); 51274 51275// Included from: js/tinymce/classes/ui/Path.js 51276 51277/** 51278 * Path.js 51279 * 51280 * Released under LGPL License. 51281 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51282 * 51283 * License: http://www.tinymce.com/license 51284 * Contributing: http://www.tinymce.com/contributing 51285 */ 51286 51287/** 51288 * Creates a new path control. 51289 * 51290 * @-x-less Path.less 51291 * @class tinymce.ui.Path 51292 * @extends tinymce.ui.Widget 51293 */ 51294define("tinymce/ui/Path", [ 51295 "tinymce/ui/Widget" 51296], function(Widget) { 51297 "use strict"; 51298 51299 return Widget.extend({ 51300 /** 51301 * Constructs a instance with the specified settings. 51302 * 51303 * @constructor 51304 * @param {Object} settings Name/value object with settings. 51305 * @setting {String} delimiter Delimiter to display between row in path. 51306 */ 51307 init: function(settings) { 51308 var self = this; 51309 51310 if (!settings.delimiter) { 51311 settings.delimiter = '\u00BB'; 51312 } 51313 51314 self._super(settings); 51315 self.classes.add('path'); 51316 self.canFocus = true; 51317 51318 self.on('click', function(e) { 51319 var index, target = e.target; 51320 51321 if ((index = target.getAttribute('data-index'))) { 51322 self.fire('select', {value: self.row()[index], index: index}); 51323 } 51324 }); 51325 51326 self.row(self.settings.row); 51327 }, 51328 51329 /** 51330 * Focuses the current control. 51331 * 51332 * @method focus 51333 * @return {tinymce.ui.Control} Current control instance. 51334 */ 51335 focus: function() { 51336 var self = this; 51337 51338 self.getEl().firstChild.focus(); 51339 51340 return self; 51341 }, 51342 51343 /** 51344 * Sets/gets the data to be used for the path. 51345 * 51346 * @method row 51347 * @param {Array} row Array with row name is rendered to path. 51348 */ 51349 row: function(row) { 51350 if (!arguments.length) { 51351 return this.state.get('row'); 51352 } 51353 51354 this.state.set('row', row); 51355 51356 return this; 51357 }, 51358 51359 /** 51360 * Renders the control as a HTML string. 51361 * 51362 * @method renderHtml 51363 * @return {String} HTML representing the control. 51364 */ 51365 renderHtml: function() { 51366 var self = this; 51367 51368 return ( 51369 '<div id="' + self._id + '" class="' + self.classes + '">' + 51370 self._getDataPathHtml(self.state.get('row')) + 51371 '</div>' 51372 ); 51373 }, 51374 51375 bindStates: function() { 51376 var self = this; 51377 51378 self.state.on('change:row', function(e) { 51379 self.innerHtml(self._getDataPathHtml(e.value)); 51380 }); 51381 51382 return self._super(); 51383 }, 51384 51385 _getDataPathHtml: function(data) { 51386 var self = this, parts = data || [], i, l, html = '', prefix = self.classPrefix; 51387 51388 for (i = 0, l = parts.length; i < l; i++) { 51389 html += ( 51390 (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') + 51391 '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' + 51392 i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>' 51393 ); 51394 } 51395 51396 if (!html) { 51397 html = '<div class="' + prefix + 'path-item">\u00a0</div>'; 51398 } 51399 51400 return html; 51401 } 51402 }); 51403}); 51404 51405// Included from: js/tinymce/classes/ui/ElementPath.js 51406 51407/** 51408 * ElementPath.js 51409 * 51410 * Released under LGPL License. 51411 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51412 * 51413 * License: http://www.tinymce.com/license 51414 * Contributing: http://www.tinymce.com/contributing 51415 */ 51416 51417/** 51418 * This control creates an path for the current selections parent elements in TinyMCE. 51419 * 51420 * @class tinymce.ui.ElementPath 51421 * @extends tinymce.ui.Path 51422 */
51423define("tinymce/ui/ElementPath", [ 51424 "tinymce/ui/Path", 51425 "tinymce/EditorManager" 51426], function(Path, EditorManager) { 51427 return Path.extend({ 51428 /** 51429 * Post render method. Called after the control has been rendered to the target. 51430 * 51431 * @method postRender 51432 * @return {tinymce.ui.ElementPath} Current combobox instance. 51433 */ 51434 postRender: function() { 51435 var self = this, editor = EditorManager.activeEditor; 51436 51437 function isHidden(elm) { 51438 if (elm.nodeType === 1) { 51439 if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) { 51440 return true; 51441 } 51442 51443 if (elm.getAttribute('data-mce-type') === 'bookmark') { 51444 return true; 51445 } 51446 } 51447 51448 return false; 51449 } 51450 51451 if (editor.settings.elementpath !== false) { 51452 self.on('select', function(e) { 51453 editor.focus(); 51454 editor.selection.select(this.row()[e.index].element); 51455 editor.nodeChanged(); 51456 }); 51457 51458 editor.on('nodeChange', function(e) { 51459 var outParents = [], parents = e.parents, i = parents.length; 51460 51461 while (i--) { 51462 if (parents[i].nodeType == 1 && !isHidden(parents[i])) { 51463 var args = editor.fire('ResolveName', { 51464 name: parents[i].nodeName.toLowerCase(), 51465 target: parents[i] 51466 }); 51467 51468 if (!args.isDefaultPrevented()) { 51469 outParents.push({name: args.name, element: parents[i]}); 51470 } 51471 51472 if (args.isPropagationStopped()) { 51473 break; 51474 } 51475 } 51476 } 51477 51478 self.row(outParents); 51479 }); 51480 } 51481 51482 return self._super(); 51483 } 51484 }); 51485}); 51486 51487// Included from: js/tinymce/classes/ui/FormItem.js 51488 51489/** 51490 * FormItem.js 51491 * 51492 * Released under LGPL License. 51493 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51494 * 51495 * License: http://www.tinymce.com/license 51496 * Contributing: http://www.tinymce.com/contributing 51497 */ 51498 51499/** 51500 * This class is a container created by the form element with 51501 * a label and control item. 51502 * 51503 * @class tinymce.ui.FormItem 51504 * @extends tinymce.ui.Container 51505 * @setting {String} label Label to display for the form item. 51506 */ 51507define("tinymce/ui/FormItem", [ 51508 "tinymce/ui/Container" 51509], function(Container) { 51510 "use strict"; 51511 51512 return Container.extend({ 51513 Defaults: { 51514 layout: 'flex', 51515 align: 'center', 51516 defaults: { 51517 flex: 1 51518 } 51519 }, 51520 51521 /** 51522 * Renders the control as a HTML string. 51523 * 51524 * @method renderHtml 51525 * @return {String} HTML representing the control. 51526 */ 51527 renderHtml: function() { 51528 var self = this, layout = self._layout, prefix = self.classPrefix; 51529 51530 self.classes.add('formitem'); 51531 layout.preRender(self); 51532 51533 return ( 51534 '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + 51535 (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' + 51536 self.settings.title + '</div>') : '') + 51537 '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + 51538 (self.settings.html || '') + layout.renderHtml(self) + 51539 '</div>' + 51540 '</div>' 51541 ); 51542 } 51543 }); 51544}); 51545 51546// Included from: js/tinymce/classes/ui/Form.js 51547 51548/** 51549 * Form.js 51550 * 51551 * Released under LGPL License. 51552 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51553 * 51554 * License: http://www.tinymce.com/license 51555 * Contributing: http://www.tinymce.com/contributing 51556 */ 51557 51558/** 51559 * This class creates a form container. A form container has the ability 51560 * to automatically wrap items in tinymce.ui.FormItem instances. 51561 * 51562 * Each FormItem instance is a container for the label and the item. 51563 * 51564 * @example 51565 * tinymce.ui.Factory.create({ 51566 * type: 'form', 51567 * items: [ 51568 * {type: 'textbox', label: 'My text box'} 51569 * ] 51570 * }).renderTo(document.body); 51571 * 51572 * @class tinymce.ui.Form 51573 * @extends tinymce.ui.Container 51574 */ 51575define("tinymce/ui/Form", [ 51576 "tinymce/ui/Container", 51577 "tinymce/ui/FormItem", 51578 "tinymce/util/Tools" 51579], function(Container, FormItem, Tools) { 51580 "use strict"; 51581 51582 return Container.extend({ 51583 Defaults: { 51584 containerCls: 'form', 51585 layout: 'flex', 51586 direction: 'column', 51587 align: 'stretch', 51588 flex: 1, 51589 padding: 20, 51590 labelGap: 30, 51591 spacing: 10, 51592 callbacks: { 51593 submit: function() { 51594 this.submit(); 51595 } 51596 } 51597 }, 51598 51599 /** 51600 * This method gets invoked before the control is rendered. 51601 * 51602 * @method preRender 51603 */ 51604 preRender: function() { 51605 var self = this, items = self.items(); 51606 51607 if (!self.settings.formItemDefaults) { 51608 self.settings.formItemDefaults = { 51609 layout: 'flex', 51610 autoResize: "overflow", 51611 defaults: {flex: 1} 51612 }; 51613 } 51614 51615 // Wrap any labeled items in FormItems 51616 items.each(function(ctrl) { 51617 var formItem, label = ctrl.settings.label; 51618 51619 if (label) { 51620 formItem = new FormItem(Tools.extend({ 51621 items: { 51622 type: 'label', 51623 id: ctrl._id + '-l', 51624 text: label, 51625 flex: 0, 51626 forId: ctrl._id, 51627 disabled: ctrl.disabled() 51628 } 51629 }, self.settings.formItemDefaults)); 51630 51631 formItem.type = 'formitem'; 51632 ctrl.aria('labelledby', ctrl._id + '-l'); 51633 51634 if (typeof ctrl.settings.flex == "undefined") { 51635 ctrl.settings.flex = 1; 51636 } 51637 51638 self.replace(ctrl, formItem); 51639 formItem.add(ctrl); 51640 } 51641 }); 51642 }, 51643 51644 /** 51645 * Fires a submit event with the serialized form. 51646 * 51647 * @method submit 51648 * @return {Object} Event arguments object. 51649 */ 51650 submit: function() { 51651 return this.fire('submit', {data: this.toJSON()}); 51652 }, 51653 51654 /** 51655 * Post render method. Called after the control has been rendered to the target. 51656 * 51657 * @method postRender 51658 * @return {tinymce.ui.ComboBox} Current combobox instance. 51659 */ 51660 postRender: function() { 51661 var self = this; 51662 51663 self._super(); 51664 self.fromJSON(self.settings.data); 51665 }, 51666 51667 bindStates: function() { 51668 var self = this; 51669 51670 self._super(); 51671 51672 function recalcLabels() { 51673 var maxLabelWidth = 0, labels = [], i, labelGap, items; 51674 51675 if (self.settings.labelGapCalc === false) { 51676 return; 51677 } 51678 51679 if (self.settings.labelGapCalc == "children") { 51680 items = self.find('formitem'); 51681 } else { 51682 items = self.items(); 51683 } 51684 51685 items.filter('formitem').each(function(item) { 51686 var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth; 51687 51688 maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth; 51689 labels.push(labelCtrl); 51690 }); 51691 51692 labelGap = self.settings.labelGap || 0; 51693 51694 i = labels.length; 51695 while (i--) { 51696 labels[i].settings.minWidth = maxLabelWidth + labelGap; 51697 } 51698 } 51699 51700 self.on('show', recalcLabels); 51701 recalcLabels(); 51702 } 51703 }); 51704}); 51705 51706// Included from: js/tinymce/classes/ui/FieldSet.js 51707 51708/** 51709 * FieldSet.js 51710 * 51711 * Released under LGPL License. 51712 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51713 * 51714 * License: http://www.tinymce.com/license 51715 * Contributing: http://www.tinymce.com/contributing 51716 */ 51717 51718/** 51719 * This class creates fieldset containers. 51720 * 51721 * @-x-less FieldSet.less 51722 * @class tinymce.ui.FieldSet 51723 * @extends tinymce.ui.Form 51724 */
51725define("tinymce/ui/FieldSet", [ 51726 "tinymce/ui/Form" 51727], function(Form) { 51728 "use strict"; 51729 51730 return Form.extend({ 51731 Defaults: { 51732 containerCls: 'fieldset', 51733 layout: 'flex', 51734 direction: 'column', 51735 align: 'stretch', 51736 flex: 1, 51737 padding: "25 15 5 15", 51738 labelGap: 30, 51739 spacing: 10, 51740 border: 1 51741 }, 51742 51743 /** 51744 * Renders the control as a HTML string. 51745 * 51746 * @method renderHtml 51747 * @return {String} HTML representing the control. 51748 */ 51749 renderHtml: function() { 51750 var self = this, layout = self._layout, prefix = self.classPrefix; 51751 51752 self.preRender(); 51753 layout.preRender(self); 51754 51755 return ( 51756 '<fieldset id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + 51757 (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' + 51758 self.settings.title + '</legend>') : '') + 51759 '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + 51760 (self.settings.html || '') + layout.renderHtml(self) + 51761 '</div>' + 51762 '</fieldset>' 51763 ); 51764 } 51765 }); 51766}); 51767 51768// Included from: js/tinymce/classes/ui/FilePicker.js 51769 51770/** 51771 * FilePicker.js 51772 * 51773 * Released under LGPL License. 51774 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51775 * 51776 * License: http://www.tinymce.com/license 51777 * Contributing: http://www.tinymce.com/contributing 51778 */ 51779 51780/*global tinymce:true */ 51781 51782/** 51783 * This class creates a file picker control. 51784 * 51785 * @class tinymce.ui.FilePicker 51786 * @extends tinymce.ui.ComboBox 51787 */ 51788define("tinymce/ui/FilePicker", [ 51789 "tinymce/ui/ComboBox", 51790 "tinymce/util/Tools" 51791], function(ComboBox, Tools) { 51792 "use strict"; 51793 51794 return ComboBox.extend({ 51795 /** 51796 * Constructs a new control instance with the specified settings. 51797 * 51798 * @constructor 51799 * @param {Object} settings Name/value object with settings. 51800 */ 51801 init: function(settings) { 51802 var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings; 51803 var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes; 51804 51805 settings.spellcheck = false; 51806 51807 fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types; 51808 if (fileBrowserCallbackTypes) { 51809 fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/); 51810 } 51811 51812 if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) { 51813 fileBrowserCallback = editorSettings.file_picker_callback; 51814 if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) { 51815 actionCallback = function() { 51816 var meta = self.fire('beforecall').meta; 51817 51818 meta = Tools.extend({filetype: settings.filetype}, meta); 51819 51820 // file_picker_callback(callback, currentValue, metaData) 51821 fileBrowserCallback.call( 51822 editor, 51823 function(value, meta) { 51824 self.value(value).fire('change', {meta: meta}); 51825 }, 51826 self.value(), 51827 meta 51828 ); 51829 }; 51830 } else { 51831 // Legacy callback: file_picker_callback(id, currentValue, filetype, window) 51832 fileBrowserCallback = editorSettings.file_browser_callback; 51833 if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) { 51834 actionCallback = function() { 51835 fileBrowserCallback( 51836 self.getEl('inp').id, 51837 self.value(), 51838 settings.filetype, 51839 window 51840 ); 51841 }; 51842 } 51843 } 51844 } 51845 51846 if (actionCallback) { 51847 settings.icon = 'browse'; 51848 settings.onaction = actionCallback; 51849 } 51850 51851 self._super(settings); 51852 } 51853 }); 51854}); 51855 51856// Included from: js/tinymce/classes/ui/FitLayout.js 51857 51858/** 51859 * FitLayout.js 51860 * 51861 * Released under LGPL License. 51862 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51863 * 51864 * License: http://www.tinymce.com/license 51865 * Contributing: http://www.tinymce.com/contributing 51866 */ 51867 51868/** 51869 * This layout manager will resize the control to be the size of it's parent container. 51870 * In other words width: 100% and height: 100%. 51871 * 51872 * @-x-less FitLayout.less 51873 * @class tinymce.ui.FitLayout 51874 * @extends tinymce.ui.AbsoluteLayout 51875 */ 51876define("tinymce/ui/FitLayout", [ 51877 "tinymce/ui/AbsoluteLayout" 51878], function(AbsoluteLayout) { 51879 "use strict"; 51880 51881 return AbsoluteLayout.extend({ 51882 /** 51883 * Recalculates the positions of the controls in the specified container. 51884 * 51885 * @method recalc 51886 * @param {tinymce.ui.Container} container Container instance to recalc. 51887 */ 51888 recalc: function(container) { 51889 var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox; 51890 51891 container.items().filter(':visible').each(function(ctrl) { 51892 ctrl.layoutRect({ 51893 x: paddingBox.left, 51894 y: paddingBox.top, 51895 w: contLayoutRect.innerW - paddingBox.right - paddingBox.left, 51896 h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom 51897 }); 51898 51899 if (ctrl.recalc) { 51900 ctrl.recalc(); 51901 } 51902 }); 51903 } 51904 }); 51905}); 51906 51907// Included from: js/tinymce/classes/ui/FlexLayout.js 51908 51909/** 51910 * FlexLayout.js 51911 * 51912 * Released under LGPL License. 51913 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 51914 * 51915 * License: http://www.tinymce.com/license 51916 * Contributing: http://www.tinymce.com/contributing 51917 */ 51918 51919/** 51920 * This layout manager works similar to the CSS flex box. 51921 *
51922 * @setting {String} direction row|row-reverse|column|column-reverse 51923 * @setting {Number} flex A positive-number to flex by. 51924 * @setting {String} align start|end|center|stretch 51925 * @setting {String} pack start|end|justify 51926 * 51927 * @class tinymce.ui.FlexLayout 51928 * @extends tinymce.ui.AbsoluteLayout 51929 */ 51930define("tinymce/ui/FlexLayout", [ 51931 "tinymce/ui/AbsoluteLayout" 51932], function(AbsoluteLayout) { 51933 "use strict"; 51934 51935 return AbsoluteLayout.extend({ 51936 /** 51937 * Recalculates the positions of the controls in the specified container. 51938 * 51939 * @method recalc 51940 * @param {tinymce.ui.Container} container Container instance to recalc. 51941 */ 51942 recalc: function(container) { 51943 // A ton of variables, needs to be in the same scope for performance 51944 var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction; 51945 var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos; 51946 var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName; 51947 var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName; 51948 var alignDeltaSizeName, alignContentSizeName; 51949 var max = Math.max, min = Math.min; 51950 51951 // Get container items, properties and settings 51952 items = container.items().filter(':visible'); 51953 contLayoutRect = container.layoutRect(); 51954 contPaddingBox = container.paddingBox; 51955 contSettings = container.settings; 51956 direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction; 51957 align = contSettings.align; 51958 pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack; 51959 spacing = contSettings.spacing || 0; 51960 51961 if (direction == "row-reversed" || direction == "column-reverse") { 51962 items = items.set(items.toArray().reverse()); 51963 direction = direction.split('-')[0]; 51964 } 51965 51966 // Setup axis variable name for row/column direction since the calculations is the same 51967 if (direction == "column") { 51968 posName = "y"; 51969 sizeName = "h"; 51970 minSizeName = "minH"; 51971 maxSizeName = "maxH"; 51972 innerSizeName = "innerH"; 51973 beforeName = 'top'; 51974 deltaSizeName = "deltaH"; 51975 contentSizeName = "contentH"; 51976 51977 alignBeforeName = "left"; 51978 alignSizeName = "w"; 51979 alignAxisName = "x"; 51980 alignInnerSizeName = "innerW"; 51981 alignMinSizeName = "minW"; 51982 alignAfterName = "right"; 51983 alignDeltaSizeName = "deltaW"; 51984 alignContentSizeName = "contentW"; 51985 } else { 51986 posName = "x"; 51987 sizeName = "w"; 51988 minSizeName = "minW"; 51989 maxSizeName = "maxW"; 51990 innerSizeName = "innerW"; 51991 beforeName = 'left'; 51992 deltaSizeName = "deltaW"; 51993 contentSizeName = "contentW"; 51994 51995 alignBeforeName = "top"; 51996 alignSizeName = "h"; 51997 alignAxisName = "y"; 51998 alignInnerSizeName = "innerH"; 51999 alignMinSizeName = "minH"; 52000 alignAfterName = "bottom"; 52001 alignDeltaSizeName = "deltaH"; 52002 alignContentSizeName = "contentH"; 52003 } 52004 52005 // Figure out total flex, availableSpace and collect any max size elements 52006 availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName]; 52007 maxAlignEndPos = totalFlex = 0; 52008 for (i = 0, l = items.length; i < l; i++) { 52009 ctrl = items[i]; 52010 ctrlLayoutRect = ctrl.layoutRect(); 52011 ctrlSettings = ctrl.settings; 52012 flex = ctrlSettings.flex; 52013 availableSpace -= (i < l - 1 ? spacing : 0); 52014 52015 if (flex > 0) { 52016 totalFlex += flex; 52017 52018 // Flexed item has a max size then we need to check if we will hit that size 52019 if (ctrlLayoutRect[maxSizeName]) { 52020 maxSizeItems.push(ctrl); 52021 } 52022 52023 ctrlLayoutRect.flex = flex; 52024 } 52025 52026 availableSpace -= ctrlLayoutRect[minSizeName]; 52027 52028 // Calculate the align end position to be used to check for overflow/underflow 52029 size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName]; 52030 if (size > maxAlignEndPos) { 52031 maxAlignEndPos = size; 52032 } 52033 } 52034 52035 // Calculate minW/minH 52036 rect = {}; 52037 if (availableSpace < 0) { 52038 rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 52039 } else { 52040 rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName]; 52041 } 52042 52043 rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName]; 52044 52045 rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace; 52046 rect[alignContentSizeName] = maxAlignEndPos; 52047 rect.minW = min(rect.minW, contLayoutRect.maxW); 52048 rect.minH = min(rect.minH, contLayoutRect.maxH); 52049 rect.minW = max(rect.minW, contLayoutRect.startMinWidth); 52050 rect.minH = max(rect.minH, contLayoutRect.startMinHeight); 52051 52052 // Resize container container if minSize was changed 52053 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 52054 rect.w = rect.minW; 52055 rect.h = rect.minH; 52056 52057 container.layoutRect(rect); 52058 this.recalc(container); 52059 52060 // Forced recalc for example if items are hidden/shown 52061 if (container._lastRect === null) { 52062 var parentCtrl = container.parent(); 52063 if (parentCtrl) { 52064 parentCtrl._lastRect = null; 52065 parentCtrl.recalc(); 52066 } 52067 } 52068 52069 return; 52070 } 52071 52072 // Handle max size elements, check if they will become to wide with current options 52073 ratio = availableSpace / totalFlex; 52074 for (i = 0, l = maxSizeItems.length; i < l; i++) { 52075 ctrl = maxSizeItems[i]; 52076 ctrlLayoutRect = ctrl.layoutRect(); 52077 maxSize = ctrlLayoutRect[maxSizeName]; 52078 size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio; 52079 52080 if (size > maxSize) { 52081 availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]); 52082 totalFlex -= ctrlLayoutRect.flex; 52083 ctrlLayoutRect.flex = 0; 52084 ctrlLayoutRect.maxFlexSize = maxSize; 52085 } else { 52086 ctrlLayoutRect.maxFlexSize = 0; 52087 } 52088 } 52089 52090 // Setup new ratio, target layout rect, start position 52091 ratio = availableSpace / totalFlex; 52092 pos = contPaddingBox[beforeName]; 52093 rect = {}; 52094 52095 // Handle pack setting moves the start position to end, center 52096 if (totalFlex === 0) { 52097 if (pack == "end") { 52098 pos = availableSpace + contPaddingBox[beforeName]; 52099 } else if (pack == "center") { 52100 pos = Math.round( 52101 (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2) 52102 ) + contPaddingBox[beforeName]; 52103 52104 if (pos < 0) { 52105 pos = contPaddingBox[beforeName]; 52106 } 52107 } else if (pack == "justify") { 52108 pos = contPaddingBox[beforeName]; 52109 spacing = Math.floor(availableSpace / (items.length - 1)); 52110 } 52111 } 52112 52113 // Default aligning (start) the other ones needs to be calculated while doing the layout 52114 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 52115 52116 // Start laying out controls 52117 for (i = 0, l = items.length; i < l; i++) { 52118 ctrl = items[i]; 52119 ctrlLayoutRect = ctrl.layoutRect(); 52120 size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName]; 52121 52122 // Align the control on the other axis 52123 if (align === "center") { 52124 rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2)); 52125 } else if (align === "stretch") { 52126 rect[alignSizeName] = max( 52127 ctrlLayoutRect[alignMinSizeName] || 0, 52128 contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName] 52129 ); 52130 rect[alignAxisName] = contPaddingBox[alignBeforeName]; 52131 } else if (align === "end") { 52132 rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top; 52133 } 52134 52135 // Calculate new size based on flex 52136 if (ctrlLayoutRect.flex > 0) { 52137 size += ctrlLayoutRect.flex * ratio; 52138 } 52139 52140 rect[sizeName] = size; 52141 rect[posName] = pos; 52142 ctrl.layoutRect(rect); 52143 52144 // Recalculate containers 52145 if (ctrl.recalc) { 52146 ctrl.recalc(); 52147 } 52148 52149 // Move x/y position 52150 pos += size + spacing; 52151 } 52152 } 52153 }); 52154}); 52155 52156// Included from: js/tinymce/classes/ui/FlowLayout.js 52157 52158/** 52159 * FlowLayout.js 52160 * 52161 * Released under LGPL License. 52162 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 52163 * 52164 * License: http://www.tinymce.com/license 52165 * Contributing: http://www.tinymce.com/contributing 52166 */ 52167 52168/** 52169 * This layout manager will place the controls by using the browsers native layout. 52170 * 52171 * @-x-less FlowLayout.less 52172 * @class tinymce.ui.FlowLayout 52173 * @extends tinymce.ui.Layout 52174 */
52175define("tinymce/ui/FlowLayout", [ 52176 "tinymce/ui/Layout" 52177], function(Layout) { 52178 return Layout.extend({ 52179 Defaults: { 52180 containerClass: 'flow-layout', 52181 controlClass: 'flow-layout-item', 52182 endClass: 'break' 52183 }, 52184 52185 /** 52186 * Recalculates the positions of the controls in the specified container. 52187 * 52188 * @method recalc 52189 * @param {tinymce.ui.Container} container Container instance to recalc. 52190 */ 52191 recalc: function(container) { 52192 container.items().filter(':visible').each(function(ctrl) { 52193 if (ctrl.recalc) { 52194 ctrl.recalc(); 52195 } 52196 }); 52197 }, 52198 52199 isNative: function() { 52200 return true; 52201 } 52202 }); 52203}); 52204 52205// Included from: js/tinymce/classes/ui/FormatControls.js 52206 52207/** 52208 * FormatControls.js 52209 * 52210 * Released under LGPL License. 52211 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 52212 * 52213 * License: http://www.tinymce.com/license 52214 * Contributing: http://www.tinymce.com/contributing 52215 */ 52216 52217/** 52218 * Internal class containing all TinyMCE specific control types such as 52219 * format listboxes, fontlist boxes, toolbar buttons etc. 52220 * 52221 * @class tinymce.ui.FormatControls 52222 */ 52223define("tinymce/ui/FormatControls", [ 52224 "tinymce/ui/Control", 52225 "tinymce/ui/Widget", 52226 "tinymce/ui/FloatPanel", 52227 "tinymce/util/Tools", 52228 "tinymce/EditorManager", 52229 "tinymce/Env" 52230], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) { 52231 var each = Tools.each; 52232 52233 EditorManager.on('AddEditor', function(e) { 52234 if (e.editor.rtl) { 52235 Control.rtl = true; 52236 } 52237 52238 registerControls(e.editor); 52239 }); 52240 52241 Control.translate = function(text) { 52242 return EditorManager.translate(text); 52243 }; 52244 52245 Widget.tooltips = !Env.iOS; 52246 52247 function registerControls(editor) { 52248 var formatMenu; 52249 52250 function createListBoxChangeHandler(items, formatName) { 52251 return function() { 52252 var self = this; 52253 52254 editor.on('nodeChange', function(e) { 52255 var formatter = editor.formatter; 52256 var value = null; 52257 52258 each(e.parents, function(node) { 52259 each(items, function(item) { 52260 if (formatName) { 52261 if (formatter.matchNode(node, formatName, {value: item.value})) { 52262 value = item.value; 52263 } 52264 } else { 52265 if (formatter.matchNode(node, item.value)) { 52266 value = item.value; 52267 } 52268 } 52269 52270 if (value) { 52271 return false; 52272 } 52273 }); 52274 52275 if (value) { 52276 return false; 52277 } 52278 }); 52279 52280 self.value(value); 52281 }); 52282 }; 52283 } 52284 52285 function createFormats(formats) { 52286 formats = formats.replace(/;$/, '').split(';'); 52287 52288 var i = formats.length; 52289 while (i--) { 52290 formats[i] = formats[i].split('='); 52291 } 52292 52293 return formats; 52294 } 52295 52296 function createFormatMenu() { 52297 var count = 0, newFormats = []; 52298 52299 var defaultStyleFormats = [ 52300 {title: 'Headings', items: [ 52301 {title: 'Heading 1', format: 'h1'}, 52302 {title: 'Heading 2', format: 'h2'}, 52303 {title: 'Heading 3', format: 'h3'}, 52304 {title: 'Heading 4', format: 'h4'}, 52305 {title: 'Heading 5', format: 'h5'}, 52306 {title: 'Heading 6', format: 'h6'} 52307 ]}, 52308 52309 {title: 'Inline', items: [ 52310 {title: 'Bold', icon: 'bold', format: 'bold'}, 52311 {title: 'Italic', icon: 'italic', format: 'italic'}, 52312 {title: 'Underline', icon: 'underline', format: 'underline'}, 52313 {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'}, 52314 {title: 'Superscript', icon: 'superscript', format: 'superscript'}, 52315 {title: 'Subscript', icon: 'subscript', format: 'subscript'}, 52316 {title: 'Code', icon: 'code', format: 'code'} 52317 ]}, 52318 52319 {title: 'Blocks', items: [ 52320 {title: 'Paragraph', format: 'p'}, 52321 {title: 'Blockquote', format: 'blockquote'}, 52322 {title: 'Div', format: 'div'}, 52323 {title: 'Pre', format: 'pre'} 52324 ]}, 52325 52326 {title: 'Alignment', items: [ 52327 {title: 'Left', icon: 'alignleft', format: 'alignleft'}, 52328 {title: 'Center', icon: 'aligncenter', format: 'aligncenter'}, 52329 {title: 'Right', icon: 'alignright', format: 'alignright'}, 52330 {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'} 52331 ]} 52332 ]; 52333 52334 function createMenu(formats) { 52335 var menu = []; 52336 52337 if (!formats) { 52338 return; 52339 } 52340 52341 each(formats, function(format) { 52342 var menuItem = { 52343 text: format.title, 52344 icon: format.icon 52345 }; 52346 52347 if (format.items) { 52348 menuItem.menu = createMenu(format.items); 52349 } else { 52350 var formatName = format.format || "custom" + count++; 52351 52352 if (!format.format) { 52353 format.name = formatName; 52354 newFormats.push(format); 52355 } 52356 52357 menuItem.format = formatName; 52358 menuItem.cmd = format.cmd; 52359 } 52360 52361 menu.push(menuItem); 52362 }); 52363 52364 return menu; 52365 } 52366 52367 function createStylesMenu() { 52368 var menu; 52369 52370 if (editor.settings.style_formats_merge) { 52371 if (editor.settings.style_formats) { 52372 menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats)); 52373 } else { 52374 menu = createMenu(defaultStyleFormats); 52375 } 52376 } else { 52377 menu = createMenu(editor.settings.style_formats || defaultStyleFormats); 52378 } 52379 52380 return menu; 52381 } 52382 52383 editor.on('init', function() { 52384 each(newFormats, function(format) { 52385 editor.formatter.register(format.name, format); 52386 }); 52387 }); 52388 52389 return { 52390 type: 'menu', 52391 items: createStylesMenu(), 52392 onPostRender: function(e) { 52393 editor.fire('renderFormatsMenu', {control: e.control}); 52394 }, 52395 itemDefaults: { 52396 preview: true, 52397 52398 textStyle: function() { 52399 if (this.settings.format) { 52400 return editor.formatter.getCssText(this.settings.format); 52401 } 52402 }, 52403 52404 onPostRender: function() { 52405 var self = this; 52406 52407 self.parent().on('show', function() { 52408 var formatName, command; 52409 52410 formatName = self.settings.format; 52411 if (formatName) { 52412 self.disabled(!editor.formatter.canApply(formatName)); 52413 self.active(editor.formatter.match(formatName)); 52414 } 52415 52416 command = self.settings.cmd; 52417 if (command) { 52418 self.active(editor.queryCommandState(command)); 52419 } 52420 }); 52421 }, 52422 52423 onclick: function() { 52424 if (this.settings.format) { 52425 toggleFormat(this.settings.format); 52426 } 52427 52428 if (this.settings.cmd) { 52429 editor.execCommand(this.settings.cmd); 52430 } 52431 } 52432 } 52433 }; 52434 } 52435 52436 formatMenu = createFormatMenu(); 52437 52438 function initOnPostRender(name) { 52439 return function() { 52440 var self = this; 52441 52442 // TODO: Fix this 52443 if (editor.formatter) { 52444 editor.formatter.formatChanged(name, function(state) { 52445 self.active(state); 52446 }); 52447 } else { 52448 editor.on('init', function() { 52449 editor.formatter.formatChanged(name, function(state) { 52450 self.active(state); 52451 }); 52452 }); 52453 } 52454 }; 52455 } 52456 52457 // Simple format controls <control/format>:<UI text> 52458 each({ 52459 bold: 'Bold', 52460 italic: 'Italic', 52461 underline: 'Underline', 52462 strikethrough: 'Strikethrough', 52463 subscript: 'Subscript', 52464 superscript: 'Superscript' 52465 }, function(text, name) { 52466 editor.addButton(name, { 52467 tooltip: text, 52468 onPostRender: initOnPostRender(name), 52469 onclick: function() { 52470 toggleFormat(name); 52471 } 52472 }); 52473 }); 52474
52475 // Simple command controls <control>:[<UI text>,<Command>] 52476 each({ 52477 outdent: ['Decrease indent', 'Outdent'], 52478 indent: ['Increase indent', 'Indent'], 52479 cut: ['Cut', 'Cut'], 52480 copy: ['Copy', 'Copy'], 52481 paste: ['Paste', 'Paste'], 52482 help: ['Help', 'mceHelp'], 52483 selectall: ['Select all', 'SelectAll'], 52484 removeformat: ['Clear formatting', 'RemoveFormat'], 52485 visualaid: ['Visual aids', 'mceToggleVisualAid'], 52486 newdocument: ['New document', 'mceNewDocument'] 52487 }, function(item, name) { 52488 editor.addButton(name, { 52489 tooltip: item[0], 52490 cmd: item[1] 52491 }); 52492 }); 52493 52494 // Simple command controls with format state 52495 each({ 52496 blockquote: ['Blockquote', 'mceBlockQuote'], 52497 numlist: ['Numbered list', 'InsertOrderedList'], 52498 bullist: ['Bullet list', 'InsertUnorderedList'], 52499 subscript: ['Subscript', 'Subscript'], 52500 superscript: ['Superscript', 'Superscript'], 52501 alignleft: ['Align left', 'JustifyLeft'], 52502 aligncenter: ['Align center', 'JustifyCenter'], 52503 alignright: ['Align right', 'JustifyRight'], 52504 alignjustify: ['Justify', 'JustifyFull'], 52505 alignnone: ['No alignment', 'JustifyNone'] 52506 }, function(item, name) { 52507 editor.addButton(name, { 52508 tooltip: item[0], 52509 cmd: item[1], 52510 onPostRender: initOnPostRender(name) 52511 }); 52512 }); 52513 52514 function toggleUndoRedoState(type) { 52515 return function() { 52516 var self = this; 52517 52518 type = type == 'redo' ? 'hasRedo' : 'hasUndo'; 52519 52520 function checkState() { 52521 return editor.undoManager ? editor.undoManager[type]() : false; 52522 } 52523 52524 self.disabled(!checkState()); 52525 editor.on('Undo Redo AddUndo TypingUndo ClearUndos SwitchMode', function() { 52526 self.disabled(editor.readonly || !checkState()); 52527 }); 52528 }; 52529 } 52530 52531 function toggleVisualAidState() { 52532 var self = this; 52533 52534 editor.on('VisualAid', function(e) { 52535 self.active(e.hasVisual); 52536 }); 52537 52538 self.active(editor.hasVisual); 52539 } 52540 52541 editor.addButton('undo', { 52542 tooltip: 'Undo', 52543 onPostRender: toggleUndoRedoState('undo'), 52544 cmd: 'undo' 52545 }); 52546 52547 editor.addButton('redo', { 52548 tooltip: 'Redo', 52549 onPostRender: toggleUndoRedoState('redo'), 52550 cmd: 'redo' 52551 }); 52552 52553 editor.addMenuItem('newdocument', { 52554 text: 'New document', 52555 icon: 'newdocument', 52556 cmd: 'mceNewDocument' 52557 }); 52558 52559 editor.addMenuItem('undo', { 52560 text: 'Undo', 52561 icon: 'undo', 52562 shortcut: 'Meta+Z', 52563 onPostRender: toggleUndoRedoState('undo'), 52564 cmd: 'undo' 52565 }); 52566 52567 editor.addMenuItem('redo', { 52568 text: 'Redo', 52569 icon: 'redo', 52570 shortcut: 'Meta+Y', 52571 onPostRender: toggleUndoRedoState('redo'), 52572 cmd: 'redo' 52573 }); 52574 52575 editor.addMenuItem('visualaid', { 52576 text: 'Visual aids', 52577 selectable: true, 52578 onPostRender: toggleVisualAidState, 52579 cmd: 'mceToggleVisualAid' 52580 }); 52581 52582 editor.addButton('remove', { 52583 tooltip: 'Remove', 52584 icon: 'remove', 52585 cmd: 'Delete' 52586 }); 52587 52588 each({ 52589 cut: ['Cut', 'Cut', 'Meta+X'], 52590 copy: ['Copy', 'Copy', 'Meta+C'], 52591 paste: ['Paste', 'Paste', 'Meta+V'], 52592 selectall: ['Select all', 'SelectAll', 'Meta+A'], 52593 bold: ['Bold', 'Bold', 'Meta+B'], 52594 italic: ['Italic', 'Italic', 'Meta+I'], 52595 underline: ['Underline', 'Underline'], 52596 strikethrough: ['Strikethrough', 'Strikethrough'], 52597 subscript: ['Subscript', 'Subscript'], 52598 superscript: ['Superscript', 'Superscript'], 52599 removeformat: ['Clear formatting', 'RemoveFormat'] 52600 }, function(item, name) { 52601 editor.addMenuItem(name, { 52602 text: item[0], 52603 icon: name, 52604 shortcut: item[2], 52605 cmd: item[1] 52606 }); 52607 }); 52608 52609 editor.on('mousedown', function() { 52610 FloatPanel.hideAll(); 52611 }); 52612 52613 function toggleFormat(fmt) { 52614 if (fmt.control) { 52615 fmt = fmt.control.value(); 52616 } 52617 52618 if (fmt) { 52619 editor.execCommand('mceToggleFormat', false, fmt); 52620 } 52621 } 52622 52623 editor.addButton('styleselect', { 52624 type: 'menubutton', 52625 text: 'Formats', 52626 menu: formatMenu 52627 }); 52628 52629 editor.addButton('formatselect', function() { 52630 var items = [], blocks = createFormats(editor.settings.block_formats || 52631 'Paragraph=p;' + 52632 'Heading 1=h1;' + 52633 'Heading 2=h2;' + 52634 'Heading 3=h3;' + 52635 'Heading 4=h4;' + 52636 'Heading 5=h5;' + 52637 'Heading 6=h6;' + 52638 'Preformatted=pre' 52639 ); 52640 52641 each(blocks, function(block) { 52642 items.push({ 52643 text: block[0], 52644 value: block[1], 52645 textStyle: function() { 52646 return editor.formatter.getCssText(block[1]); 52647 } 52648 }); 52649 }); 52650 52651 return { 52652 type: 'listbox', 52653 text: blocks[0][0], 52654 values: items, 52655 fixedWidth: true, 52656 onselect: toggleFormat, 52657 onPostRender: createListBoxChangeHandler(items) 52658 }; 52659 }); 52660 52661 editor.addButton('fontselect', function() { 52662 var defaultFontsFormats =
52663 'Andale Mono=andale mono,monospace;' + 52664 'Arial=arial,helvetica,sans-serif;' + 52665 'Arial Black=arial black,sans-serif;' + 52666 'Book Antiqua=book antiqua,palatino,serif;' + 52667 'Comic Sans MS=comic sans ms,sans-serif;' + 52668 'Courier New=courier new,courier,monospace;' + 52669 'Georgia=georgia,palatino,serif;' + 52670 'Helvetica=helvetica,arial,sans-serif;' + 52671 'Impact=impact,sans-serif;' + 52672 'Symbol=symbol;' + 52673 'Tahoma=tahoma,arial,helvetica,sans-serif;' + 52674 'Terminal=terminal,monaco,monospace;' + 52675 'Times New Roman=times new roman,times,serif;' + 52676 'Trebuchet MS=trebuchet ms,geneva,sans-serif;' + 52677 'Verdana=verdana,geneva,sans-serif;' + 52678 'Webdings=webdings;' + 52679 'Wingdings=wingdings,zapf dingbats'; 52680 52681 var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats); 52682 52683 each(fonts, function(font) { 52684 items.push({ 52685 text: {raw: font[0]}, 52686 value: font[1], 52687 textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : '' 52688 }); 52689 }); 52690 52691 return { 52692 type: 'listbox', 52693 text: 'Font Family', 52694 tooltip: 'Font Family', 52695 values: items, 52696 fixedWidth: true, 52697 onPostRender: createListBoxChangeHandler(items, 'fontname'), 52698 onselect: function(e) { 52699 if (e.control.settings.value) { 52700 editor.execCommand('FontName', false, e.control.settings.value); 52701 } 52702 } 52703 }; 52704 }); 52705 52706 editor.addButton('fontsizeselect', function() { 52707 var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt'; 52708 var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats; 52709 52710 each(fontsize_formats.split(' '), function(item) { 52711 var text = item, value = item; 52712 // Allow text=value font sizes. 52713 var values = item.split('='); 52714 if (values.length > 1) { 52715 text = values[0]; 52716 value = values[1]; 52717 } 52718 items.push({text: text, value: value}); 52719 }); 52720 52721 return { 52722 type: 'listbox', 52723 text: 'Font Sizes', 52724 tooltip: 'Font Sizes', 52725 values: items, 52726 fixedWidth: true, 52727 onPostRender: createListBoxChangeHandler(items, 'fontsize'), 52728 onclick: function(e) { 52729 if (e.control.settings.value) { 52730 editor.execCommand('FontSize', false, e.control.settings.value); 52731 } 52732 } 52733 }; 52734 }); 52735 52736 editor.addMenuItem('formats', { 52737 text: 'Formats', 52738 menu: formatMenu 52739 }); 52740 } 52741}); 52742 52743// Included from: js/tinymce/classes/ui/GridLayout.js 52744 52745/** 52746 * GridLayout.js 52747 * 52748 * Released under LGPL License. 52749 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 52750 * 52751 * License: http://www.tinymce.com/license 52752 * Contributing: http://www.tinymce.com/contributing 52753 */ 52754 52755/** 52756 * This layout manager places controls in a grid. 52757 * 52758 * @setting {Number} spacing Spacing between controls. 52759 * @setting {Number} spacingH Horizontal spacing between controls. 52760 * @setting {Number} spacingV Vertical spacing between controls. 52761 * @setting {Number} columns Number of columns to use. 52762 * @setting {String/Array} alignH start|end|center|stretch or array of values for each column. 52763 * @setting {String/Array} alignV start|end|center|stretch or array of values for each column. 52764 * @setting {String} pack start|end 52765 * 52766 * @class tinymce.ui.GridLayout 52767 * @extends tinymce.ui.AbsoluteLayout 52768 */ 52769define("tinymce/ui/GridLayout", [ 52770 "tinymce/ui/AbsoluteLayout" 52771], function(AbsoluteLayout) { 52772 "use strict"; 52773 52774 return AbsoluteLayout.extend({ 52775 /** 52776 * Recalculates the positions of the controls in the specified container. 52777 * 52778 * @method recalc 52779 * @param {tinymce.ui.Container} container Container instance to recalc. 52780 */ 52781 recalc: function(container) { 52782 var settings, rows, cols, items, contLayoutRect, width, height, rect, 52783 ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY, 52784 colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx; 52785 52786 // Get layout settings 52787 settings = container.settings; 52788 items = container.items().filter(':visible'); 52789 contLayoutRect = container.layoutRect(); 52790 cols = settings.columns || Math.ceil(Math.sqrt(items.length));
52791 rows = Math.ceil(items.length / cols); 52792 spacingH = settings.spacingH || settings.spacing || 0; 52793 spacingV = settings.spacingV || settings.spacing || 0; 52794 alignH = settings.alignH || settings.align; 52795 alignV = settings.alignV || settings.align; 52796 contPaddingBox = container.paddingBox; 52797 reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl(); 52798 52799 if (alignH && typeof alignH == "string") { 52800 alignH = [alignH]; 52801 } 52802 52803 if (alignV && typeof alignV == "string") { 52804 alignV = [alignV]; 52805 } 52806 52807 // Zero padd columnWidths 52808 for (x = 0; x < cols; x++) { 52809 colWidths.push(0); 52810 } 52811 52812 // Zero padd rowHeights 52813 for (y = 0; y < rows; y++) { 52814 rowHeights.push(0); 52815 } 52816 52817 // Calculate columnWidths and rowHeights 52818 for (y = 0; y < rows; y++) { 52819 for (x = 0; x < cols; x++) { 52820 ctrl = items[y * cols + x]; 52821 52822 // Out of bounds 52823 if (!ctrl) { 52824 break; 52825 } 52826 52827 ctrlLayoutRect = ctrl.layoutRect(); 52828 ctrlMinWidth = ctrlLayoutRect.minW; 52829 ctrlMinHeight = ctrlLayoutRect.minH; 52830 52831 colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x]; 52832 rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y]; 52833 } 52834 } 52835 52836 // Calculate maxX 52837 availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right; 52838 for (maxX = 0, x = 0; x < cols; x++) { 52839 maxX += colWidths[x] + (x > 0 ? spacingH : 0); 52840 availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x]; 52841 } 52842 52843 // Calculate maxY 52844 availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom; 52845 for (maxY = 0, y = 0; y < rows; y++) { 52846 maxY += rowHeights[y] + (y > 0 ? spacingV : 0); 52847 availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y]; 52848 } 52849 52850 maxX += contPaddingBox.left + contPaddingBox.right; 52851 maxY += contPaddingBox.top + contPaddingBox.bottom; 52852 52853 // Calculate minW/minH 52854 rect = {}; 52855 rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW); 52856 rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH); 52857 52858 rect.contentW = rect.minW - contLayoutRect.deltaW; 52859 rect.contentH = rect.minH - contLayoutRect.deltaH; 52860 rect.minW = Math.min(rect.minW, contLayoutRect.maxW); 52861 rect.minH = Math.min(rect.minH, contLayoutRect.maxH); 52862 rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth); 52863 rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight); 52864 52865 // Resize container container if minSize was changed 52866 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) { 52867 rect.w = rect.minW; 52868 rect.h = rect.minH; 52869 52870 container.layoutRect(rect); 52871 this.recalc(container); 52872 52873 // Forced recalc for example if items are hidden/shown 52874 if (container._lastRect === null) { 52875 var parentCtrl = container.parent(); 52876 if (parentCtrl) { 52877 parentCtrl._lastRect = null; 52878 parentCtrl.recalc(); 52879 } 52880 } 52881 52882 return; 52883 } 52884 52885 // Update contentW/contentH so absEnd moves correctly 52886 if (contLayoutRect.autoResize) { 52887 rect = container.layoutRect(rect); 52888 rect.contentW = rect.minW - contLayoutRect.deltaW; 52889 rect.contentH = rect.minH - contLayoutRect.deltaH; 52890 } 52891 52892 var flexV; 52893 52894 if (settings.packV == 'start') { 52895 flexV = 0; 52896 } else { 52897 flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0; 52898 } 52899 52900 // Calculate totalFlex 52901 var totalFlex = 0; 52902 var flexWidths = settings.flexWidths; 52903 if (flexWidths) { 52904 for (x = 0; x < flexWidths.length; x++) { 52905 totalFlex += flexWidths[x]; 52906 } 52907 } else { 52908 totalFlex = cols; 52909 } 52910 52911 // Calculate new column widths based on flex values 52912 var ratio = availableWidth / totalFlex; 52913 for (x = 0; x < cols; x++) { 52914 colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio; 52915 } 52916 52917 // Move/resize controls 52918 posY = contPaddingBox.top; 52919 for (y = 0; y < rows; y++) { 52920 posX = contPaddingBox.left; 52921 height = rowHeights[y] + flexV; 52922 52923 for (x = 0; x < cols; x++) { 52924 if (reverseRows) { 52925 idx = y * cols + cols - 1 - x; 52926 } else { 52927 idx = y * cols + x; 52928 } 52929 52930 ctrl = items[idx]; 52931 52932 // No more controls to render then break 52933 if (!ctrl) { 52934 break; 52935 } 52936 52937 // Get control settings and calculate x, y 52938 ctrlSettings = ctrl.settings; 52939 ctrlLayoutRect = ctrl.layoutRect(); 52940 width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth); 52941 ctrlLayoutRect.x = posX; 52942 ctrlLayoutRect.y = posY; 52943 52944 // Align control horizontal
52945 align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null); 52946 if (align == "center") { 52947 ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2); 52948 } else if (align == "right") { 52949 ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w; 52950 } else if (align == "stretch") { 52951 ctrlLayoutRect.w = width; 52952 } 52953 52954 // Align control vertical 52955 align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null); 52956 if (align == "center") { 52957 ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2); 52958 } else if (align == "bottom") { 52959 ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h; 52960 } else if (align == "stretch") { 52961 ctrlLayoutRect.h = height; 52962 } 52963 52964 ctrl.layoutRect(ctrlLayoutRect); 52965 52966 posX += width + spacingH; 52967 52968 if (ctrl.recalc) { 52969 ctrl.recalc(); 52970 } 52971 } 52972 52973 posY += height + spacingV; 52974 } 52975 } 52976 }); 52977}); 52978 52979// Included from: js/tinymce/classes/ui/Iframe.js 52980 52981/** 52982 * Iframe.js 52983 * 52984 * Released under LGPL License. 52985 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 52986 * 52987 * License: http://www.tinymce.com/license 52988 * Contributing: http://www.tinymce.com/contributing 52989 */ 52990 52991/*jshint scripturl:true */ 52992 52993/** 52994 * This class creates an iframe. 52995 * 52996 * @setting {String} url Url to open in the iframe. 52997 * 52998 * @-x-less Iframe.less 52999 * @class tinymce.ui.Iframe 53000 * @extends tinymce.ui.Widget 53001 */ 53002define("tinymce/ui/Iframe", [ 53003 "tinymce/ui/Widget", 53004 "tinymce/util/Delay" 53005], function(Widget, Delay) { 53006 "use strict"; 53007 53008 return Widget.extend({ 53009 /** 53010 * Renders the control as a HTML string. 53011 * 53012 * @method renderHtml 53013 * @return {String} HTML representing the control. 53014 */ 53015 renderHtml: function() { 53016 var self = this; 53017 53018 self.classes.add('iframe'); 53019 self.canFocus = false; 53020 53021 /*eslint no-script-url:0 */ 53022 return ( 53023 '<iframe id="' + self._id + '" class="' + self.classes + '" tabindex="-1" src="' + 53024 (self.settings.url || "javascript:''") + '" frameborder="0"></iframe>' 53025 ); 53026 }, 53027 53028 /** 53029 * Setter for the iframe source. 53030 * 53031 * @method src 53032 * @param {String} src Source URL for iframe. 53033 */ 53034 src: function(src) { 53035 this.getEl().src = src; 53036 }, 53037 53038 /** 53039 * Inner HTML for the iframe. 53040 * 53041 * @method html 53042 * @param {String} html HTML string to set as HTML inside the iframe. 53043 * @param {function} callback Optional callback to execute when the iframe body is filled with contents. 53044 * @return {tinymce.ui.Iframe} Current iframe control. 53045 */ 53046 html: function(html, callback) { 53047 var self = this, body = this.getEl().contentWindow.document.body; 53048 53049 // Wait for iframe to initialize IE 10 takes time 53050 if (!body) { 53051 Delay.setTimeout(function() { 53052 self.html(html); 53053 }); 53054 } else { 53055 body.innerHTML = html; 53056 53057 if (callback) { 53058 callback(); 53059 } 53060 } 53061 53062 return this; 53063 } 53064 }); 53065}); 53066 53067// Included from: js/tinymce/classes/ui/Label.js 53068 53069/** 53070 * Label.js 53071 * 53072 * Released under LGPL License. 53073 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53074 * 53075 * License: http://www.tinymce.com/license 53076 * Contributing: http://www.tinymce.com/contributing 53077 */ 53078 53079/** 53080 * This class creates a label element. A label is a simple text control 53081 * that can be bound to other controls. 53082 * 53083 * @-x-less Label.less 53084 * @class tinymce.ui.Label 53085 * @extends tinymce.ui.Widget 53086 */ 53087define("tinymce/ui/Label", [ 53088 "tinymce/ui/Widget", 53089 "tinymce/ui/DomUtils" 53090], function(Widget, DomUtils) { 53091 "use strict"; 53092 53093 return Widget.extend({ 53094 /** 53095 * Constructs a instance with the specified settings. 53096 * 53097 * @constructor 53098 * @param {Object} settings Name/value object with settings. 53099 * @setting {Boolean} multiline Multiline label. 53100 */ 53101 init: function(settings) { 53102 var self = this; 53103 53104 self._super(settings); 53105 self.classes.add('widget').add('label'); 53106 self.canFocus = false; 53107 53108 if (settings.multiline) { 53109 self.classes.add('autoscroll'); 53110 } 53111 53112 if (settings.strong) { 53113 self.classes.add('strong'); 53114 } 53115 }, 53116 53117 /** 53118 * Initializes the current controls layout rect. 53119 * This will be executed by the layout managers to determine the 53120 * default minWidth/minHeight etc. 53121 * 53122 * @method initLayoutRect 53123 * @return {Object} Layout rect instance. 53124 */ 53125 initLayoutRect: function() { 53126 var self = this, layoutRect = self._super(); 53127 53128 if (self.settings.multiline) { 53129 var size = DomUtils.getSize(self.getEl()); 53130 53131 // Check if the text fits within maxW if not then try word wrapping it 53132 if (size.width > layoutRect.maxW) { 53133 layoutRect.minW = layoutRect.maxW; 53134 self.classes.add('multiline'); 53135 } 53136 53137 self.getEl().style.width = layoutRect.minW + 'px'; 53138 layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height); 53139 } 53140 53141 return layoutRect; 53142 }, 53143 53144 /** 53145 * Repaints the control after a layout operation. 53146 * 53147 * @method repaint 53148 */ 53149 repaint: function() { 53150 var self = this; 53151 53152 if (!self.settings.multiline) { 53153 self.getEl().style.lineHeight = self.layoutRect().h + 'px'; 53154 } 53155 53156 return self._super(); 53157 }, 53158 53159 /** 53160 * Renders the control as a HTML string. 53161 * 53162 * @method renderHtml 53163 * @return {String} HTML representing the control. 53164 */ 53165 renderHtml: function() { 53166 var self = this, forId = self.settings.forId; 53167 53168 return ( 53169 '<label id="' + self._id + '" class="' + self.classes + '"' + (forId ? ' for="' + forId + '"' : '') + '>
53169' + 53170 self.encode(self.state.get('text')) + 53171 '</label>' 53172 ); 53173 }, 53174 53175 bindStates: function() { 53176 var self = this; 53177 53178 self.state.on('change:text', function(e) { 53179 self.innerHtml(self.encode(e.value)); 53180 }); 53181 53182 return self._super(); 53183 } 53184 }); 53185}); 53186 53187// Included from: js/tinymce/classes/ui/Toolbar.js 53188 53189/** 53190 * Toolbar.js 53191 * 53192 * Released under LGPL License. 53193 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53194 * 53195 * License: http://www.tinymce.com/license 53196 * Contributing: http://www.tinymce.com/contributing 53197 */ 53198 53199/** 53200 * Creates a new toolbar. 53201 * 53202 * @class tinymce.ui.Toolbar 53203 * @extends tinymce.ui.Container 53204 */ 53205define("tinymce/ui/Toolbar", [ 53206 "tinymce/ui/Container" 53207], function(Container) { 53208 "use strict"; 53209 53210 return Container.extend({ 53211 Defaults: { 53212 role: 'toolbar', 53213 layout: 'flow' 53214 }, 53215 53216 /** 53217 * Constructs a instance with the specified settings. 53218 * 53219 * @constructor 53220 * @param {Object} settings Name/value object with settings. 53221 */ 53222 init: function(settings) { 53223 var self = this; 53224 53225 self._super(settings); 53226 self.classes.add('toolbar'); 53227 }, 53228 53229 /** 53230 * Called after the control has been rendered. 53231 * 53232 * @method postRender 53233 */ 53234 postRender: function() { 53235 var self = this; 53236 53237 self.items().each(function(ctrl) { 53238 ctrl.classes.add('toolbar-item'); 53239 }); 53240 53241 return self._super(); 53242 } 53243 }); 53244}); 53245 53246// Included from: js/tinymce/classes/ui/MenuBar.js 53247 53248/** 53249 * MenuBar.js 53250 * 53251 * Released under LGPL License. 53252 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53253 * 53254 * License: http://www.tinymce.com/license 53255 * Contributing: http://www.tinymce.com/contributing 53256 */ 53257 53258/** 53259 * Creates a new menubar. 53260 * 53261 * @-x-less MenuBar.less 53262 * @class tinymce.ui.MenuBar 53263 * @extends tinymce.ui.Container 53264 */ 53265define("tinymce/ui/MenuBar", [ 53266 "tinymce/ui/Toolbar" 53267], function(Toolbar) { 53268 "use strict"; 53269 53270 return Toolbar.extend({ 53271 Defaults: { 53272 role: 'menubar', 53273 containerCls: 'menubar', 53274 ariaRoot: true, 53275 defaults: { 53276 type: 'menubutton' 53277 } 53278 } 53279 }); 53280}); 53281 53282// Included from: js/tinymce/classes/ui/MenuButton.js 53283 53284/** 53285 * MenuButton.js 53286 * 53287 * Released under LGPL License. 53288 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53289 * 53290 * License: http://www.tinymce.com/license 53291 * Contributing: http://www.tinymce.com/contributing 53292 */ 53293 53294/** 53295 * Creates a new menu button. 53296 * 53297 * @-x-less MenuButton.less 53298 * @class tinymce.ui.MenuButton 53299 * @extends tinymce.ui.Button 53300 */ 53301define("tinymce/ui/MenuButton", [ 53302 "tinymce/ui/Button", 53303 "tinymce/ui/Factory", 53304 "tinymce/ui/MenuBar" 53305], function(Button, Factory, MenuBar) { 53306 "use strict"; 53307 53308 // TODO: Maybe add as some global function 53309 function isChildOf(node, parent) { 53310 while (node) { 53311 if (parent === node) { 53312 return true; 53313 } 53314 53315 node = node.parentNode; 53316 } 53317 53318 return false; 53319 } 53320 53321 var MenuButton = Button.extend({ 53322 /** 53323 * Constructs a instance with the specified settings. 53324 * 53325 * @constructor 53326 * @param {Object} settings Name/value object with settings. 53327 */ 53328 init: function(settings) { 53329 var self = this; 53330 53331 self._renderOpen = true; 53332 53333 self._super(settings); 53334 settings = self.settings; 53335 53336 self.classes.add('menubtn'); 53337 53338 if (settings.fixedWidth) { 53339 self.classes.add('fixed-width'); 53340 } 53341 53342 self.aria('haspopup', true); 53343 53344 self.state.set('menu', settings.menu || self.render()); 53345 }, 53346 53347 /** 53348 * Shows the menu for the button. 53349 * 53350 * @method showMenu 53351 */ 53352 showMenu: function() { 53353 var self = this, menu; 53354 53355 if (self.menu && self.menu.visible()) { 53356 return self.hideMenu(); 53357 } 53358 53359 if (!self.menu) { 53360 menu = self.state.get('menu') || []; 53361 53362 // Is menu array then auto constuct menu control 53363 if (menu.length) { 53364 menu = { 53365 type: 'menu', 53366 items: menu 53367 }; 53368 } else { 53369 menu.type = menu.type || 'menu'; 53370 } 53371 53372 if (!menu.renderTo) { 53373 self.menu = Factory.create(menu).parent(self).renderTo(); 53374 } else { 53375 self.menu = menu.parent(self).show().renderTo(); 53376 } 53377 53378 self.fire('createmenu'); 53379 self.menu.reflow(); 53380 self.menu.on('cancel', function(e) { 53381 if (e.control.parent() === self.menu) { 53382 e.stopPropagation(); 53383 self.focus(); 53384 self.hideMenu(); 53385 } 53386 }); 53387 53388 // Move focus to button when a menu item is selected/clicked 53389 self.menu.on('select', function() { 53390 self.focus(); 53391 }); 53392 53393 self.menu.on('show hide', function(e) { 53394 if (e.control == self.menu) { 53395 self.activeMenu(e.type == 'show'); 53396 } 53397 53398 self.aria('expanded', e.type == 'show'); 53399 }).fire('show'); 53400 } 53401 53402 self.menu.show(); 53403 self.menu.layoutRect({w: self.layoutRect().w}); 53404 self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']); 53405 }, 53406 53407 /** 53408 * Hides the menu for the button. 53409 * 53410 * @method hideMenu 53411 */ 53412 hideMenu: function() { 53413 var self = this; 53414 53415 if (self.menu) { 53416 self.menu.items().each(function(item) { 53417 if (item.hideMenu) { 53418 item.hideMenu(); 53419 } 53420 }); 53421 53422 self.menu.hide(); 53423 } 53424 }, 53425 53426 /** 53427 * Sets the active menu state. 53428 * 53429 * @private 53430 */ 53431 activeMenu: function(state) { 53432 this.classes.toggle('active', state); 53433 }, 53434 53435 /** 53436 * Renders the control as a HTML string. 53437 * 53438 * @method renderHtml 53439 * @return {String} HTML representing the control. 53440 */ 53441 renderHtml: function() { 53442 var self = this, id = self._id, prefix = self.classPrefix; 53443 var icon = self.settings.icon, image, text = self.state.get('text'), 53444 textHtml = ''; 53445 53446 image = self.settings.image; 53447 if (image) { 53448 icon = 'none'; 53449
53450 // Support for [high dpi, low dpi] image sources 53451 if (typeof image != "string") { 53452 image = window.getSelection ? image[0] : image[1]; 53453 } 53454 53455 image = ' style="background-image: url(\'' + image + '\')"'; 53456 } else { 53457 image = ''; 53458 } 53459 53460 if (text) { 53461 self.classes.add('btn-has-text'); 53462 textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; 53463 } 53464 53465 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 53466 53467 self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button'); 53468 53469 return ( 53470 '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' + 53471 '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' + 53472 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 53473 textHtml + 53474 ' <i class="' + prefix + 'caret"></i>' + 53475 '</button>' + 53476 '</div>' 53477 ); 53478 }, 53479 53480 /** 53481 * Gets invoked after the control has been rendered. 53482 * 53483 * @method postRender 53484 */ 53485 postRender: function() { 53486 var self = this; 53487 53488 self.on('click', function(e) { 53489 if (e.control === self && isChildOf(e.target, self.getEl())) { 53490 self.showMenu(); 53491 53492 if (e.aria) { 53493 self.menu.items()[0].focus(); 53494 } 53495 } 53496 }); 53497 53498 self.on('mouseenter', function(e) { 53499 var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu; 53500 53501 if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) { 53502 parent.items().filter('MenuButton').each(function(ctrl) { 53503 if (ctrl.hideMenu && ctrl != overCtrl) { 53504 if (ctrl.menu && ctrl.menu.visible()) { 53505 hasVisibleSiblingMenu = true; 53506 } 53507 53508 ctrl.hideMenu(); 53509 } 53510 }); 53511 53512 if (hasVisibleSiblingMenu) { 53513 overCtrl.focus(); // Fix for: #5887 53514 overCtrl.showMenu(); 53515 } 53516 } 53517 }); 53518 53519 return self._super(); 53520 }, 53521 53522 bindStates: function() { 53523 var self = this; 53524 53525 self.state.on('change:menu', function() { 53526 if (self.menu) { 53527 self.menu.remove(); 53528 } 53529 53530 self.menu = null; 53531 }); 53532 53533 return self._super(); 53534 }, 53535 53536 /** 53537 * Removes the control and it's menus. 53538 * 53539 * @method remove 53540 */ 53541 remove: function() { 53542 this._super(); 53543 53544 if (this.menu) { 53545 this.menu.remove(); 53546 } 53547 } 53548 }); 53549 53550 return MenuButton; 53551}); 53552 53553// Included from: js/tinymce/classes/ui/MenuItem.js 53554 53555/** 53556 * MenuItem.js 53557 * 53558 * Released under LGPL License. 53559 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53560 * 53561 * License: http://www.tinymce.com/license 53562 * Contributing: http://www.tinymce.com/contributing 53563 */ 53564 53565/** 53566 * Creates a new menu item. 53567 * 53568 * @-x-less MenuItem.less 53569 * @class tinymce.ui.MenuItem 53570 * @extends tinymce.ui.Control 53571 */ 53572define("tinymce/ui/MenuItem", [ 53573 "tinymce/ui/Widget", 53574 "tinymce/ui/Factory", 53575 "tinymce/Env" 53576], function(Widget, Factory, Env) { 53577 "use strict"; 53578 53579 return Widget.extend({ 53580 Defaults: { 53581 border: 0, 53582 role: 'menuitem' 53583 }, 53584 53585 /** 53586 * Constructs a instance with the specified settings. 53587 * 53588 * @constructor 53589 * @param {Object} settings Name/value object with settings. 53590 * @setting {Boolean} selectable Selectable menu. 53591 * @setting {Array} menu Submenu array with items. 53592 * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X 53593 */ 53594 init: function(settings) { 53595 var self = this, text; 53596 53597 self._super(settings); 53598 53599 settings = self.settings; 53600 53601 self.classes.add('menu-item'); 53602 53603 if (settings.menu) { 53604 self.classes.add('menu-item-expand'); 53605 } 53606 53607 if (settings.preview) { 53608 self.classes.add('menu-item-preview'); 53609 } 53610 53611 text = self.state.get('text'); 53612 if (text === '-' || text === '|') { 53613 self.classes.add('menu-item-sep'); 53614 self.aria('role', 'separator'); 53615 self.state.set('text', '-'); 53616 } 53617 53618 if (settings.selectable) { 53619 self.aria('role', 'menuitemcheckbox'); 53620 self.classes.add('menu-item-checkbox'); 53621 settings.icon = 'selected'; 53622 } 53623 53624 if (!settings.preview && !settings.selectable) { 53625 self.classes.add('menu-item-normal'); 53626 } 53627 53628 self.on('mousedown', function(e) { 53629 e.preventDefault(); 53630 }); 53631 53632 if (settings.menu && !settings.ariaHideMenu) { 53633 self.aria('haspopup', true); 53634 } 53635 }, 53636 53637 /** 53638 * Returns true/false if the menuitem has sub menu. 53639 * 53640 * @method hasMenus 53641 * @return {Boolean} True/false state if it has submenu. 53642 */ 53643 hasMenus: function() { 53644 return !!this.settings.menu; 53645 }, 53646 53647 /** 53648 * Shows the menu for the menu item. 53649 * 53650 * @method showMenu 53651 */ 53652 showMenu: function() { 53653 var self = this, settings = self.settings, menu, parent = self.parent(); 53654 53655 parent.items().each(function(ctrl) { 53656 if (ctrl !== self) { 53657 ctrl.hideMenu(); 53658 } 53659 }); 53660 53661 if (settings.menu) { 53662 menu = self.menu; 53663 53664 if (!menu) { 53665 menu = settings.menu; 53666 53667 // Is menu array then auto constuct menu control 53668 if (menu.length) { 53669 menu = { 53670 type: 'menu', 53671 items: menu 53672 }; 53673 } else { 53674 menu.type = menu.type || 'menu'; 53675 } 53676 53677 if (parent.settings.itemDefaults) { 53678 menu.itemDefaults = parent.settings.itemDefaults; 53679 } 53680 53681 menu = self.menu = Factory.create(menu).parent(self).renderTo(); 53682 menu.reflow(); 53683 menu.on('cancel', function(e) { 53684 e.stopPropagation(); 53685 self.focus(); 53686 menu.hide(); 53687 }); 53688 menu.on('show hide', function(e) { 53689 e.control.items().each(function(ctrl) { 53690 ctrl.active(ctrl.settings.selected); 53691 }); 53692 }).fire('show'); 53693 53694 menu.on('hide', function(e) { 53695 if (e.control === menu) { 53696 self.classes.remove('selected'); 53697 } 53698 }); 53699 53700 menu.submenu = true; 53701 } else { 53702 menu.show(); 53703 } 53704 53705 menu._parentMenu = parent; 53706 53707 menu.classes.add('menu-sub'); 53708 53709 var rel = menu.testMoveRel( 53710 self.getEl(), 53711 self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br'] 53712 ); 53713 53714 menu.moveRel(self.getEl(), rel); 53715 menu.rel = rel; 53716 53717 rel = 'menu-sub-' + rel; 53718 menu.classes.remove(menu._lastRel).add(rel); 53719 menu._lastRel = rel; 53720 53721 self.classes.add('selected'); 53722 self.aria('expanded', true); 53723 } 53724 }, 53725 53726 /** 53727 * Hides the menu for the menu item. 53728 * 53729 * @method hideMenu 53730 */ 53731 hideMenu: function() { 53732 var self = this; 53733 53734 if (self.menu) { 53735 self.menu.items().each(function(item) { 53736 if (item.hideMenu) { 53737 item.hideMenu(); 53738 } 53739 }); 53740 53741 self.menu.hide(); 53742 self.aria('expanded', false); 53743 } 53744 53745 return self; 53746 }, 53747 53748 /** 53749 * Renders the control as a HTML string. 53750 * 53751 * @method renderHtml 53752 * @return {String} HTML representing the control. 53753 */ 53754 renderHtml: function() { 53755 var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self.state.get('text')); 53756 var icon = self.settings.icon, image = '', shortcut = settings.shortcut; 53757 53758 // Converts shortcut format to Mac/PC variants 53759 function convertShortcut(shortcut) { 53760 var i, value, replace = {}; 53761 53762 if (Env.mac) { 53763 replace = { 53764 alt: '⌥', 53765 ctrl: '⌘', 53766 shift: '⇧', 53767 meta: '⌘' 53768 }; 53769 } else { 53770 replace = { 53771 meta: 'Ctrl' 53772 }; 53773 } 53774 53775 shortcut = shortcut.split('+'); 53776 53777 for (i = 0; i < shortcut.length; i++) { 53778 value = replace[shortcut[i].toLowerCase()]; 53779 53780 if (value) { 53781 shortcut[i] = value; 53782 } 53783 } 53784 53785 return shortcut.join('+'); 53786 } 53787 53788 if (icon) { 53789 self.parent().classes.add('menu-has-icons'); 53790 } 53791 53792 if (settings.image) { 53793 image = ' style="background-image: url(\'' + settings.image + '\')"'; 53794 } 53795 53796 if (shortcut) { 53797 shortcut = convertShortcut(shortcut); 53798 } 53799 53800 icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none'); 53801 53802 return ( 53803 '<div id="' + id + '" class="' + self.classes + '" tabindex="-1">' +
53804 (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') + 53805 (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') + 53806 (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') + 53807 (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') + 53808 '</div>' 53809 ); 53810 }, 53811 53812 /** 53813 * Gets invoked after the control has been rendered. 53814 * 53815 * @method postRender 53816 */ 53817 postRender: function() { 53818 var self = this, settings = self.settings; 53819 53820 var textStyle = settings.textStyle; 53821 if (typeof textStyle == "function") { 53822 textStyle = textStyle.call(this); 53823 } 53824 53825 if (textStyle) { 53826 var textElm = self.getEl('text'); 53827 if (textElm) { 53828 textElm.setAttribute('style', textStyle); 53829 } 53830 } 53831 53832 self.on('mouseenter click', function(e) { 53833 if (e.control === self) { 53834 if (!settings.menu && e.type === 'click') { 53835 self.fire('select'); 53836 self.parent().hideAll(); 53837 } else { 53838 self.showMenu(); 53839 53840 if (e.aria) { 53841 self.menu.focus(true); 53842 } 53843 } 53844 } 53845 }); 53846 53847 self._super(); 53848 53849 return self; 53850 }, 53851 53852 active: function(state) { 53853 if (typeof state != "undefined") { 53854 this.aria('checked', state); 53855 } 53856 53857 return this._super(state); 53858 }, 53859 53860 /** 53861 * Removes the control and it's menus. 53862 * 53863 * @method remove 53864 */ 53865 remove: function() { 53866 this._super(); 53867 53868 if (this.menu) { 53869 this.menu.remove(); 53870 } 53871 } 53872 }); 53873}); 53874 53875// Included from: js/tinymce/classes/ui/Menu.js 53876 53877/** 53878 * Menu.js 53879 * 53880 * Released under LGPL License. 53881 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 53882 * 53883 * License: http://www.tinymce.com/license 53884 * Contributing: http://www.tinymce.com/contributing 53885 */ 53886 53887/** 53888 * Creates a new menu. 53889 * 53890 * @-x-less Menu.less 53891 * @class tinymce.ui.Menu 53892 * @extends tinymce.ui.FloatPanel 53893 */ 53894define("tinymce/ui/Menu", [ 53895 "tinymce/ui/FloatPanel", 53896 "tinymce/ui/MenuItem", 53897 "tinymce/util/Tools" 53898], function(FloatPanel, MenuItem, Tools) { 53899 "use strict"; 53900 53901 return FloatPanel.extend({ 53902 Defaults: { 53903 defaultType: 'menuitem', 53904 border: 1, 53905 layout: 'stack', 53906 role: 'application', 53907 bodyRole: 'menu', 53908 ariaRoot: true 53909 }, 53910 53911 /** 53912 * Constructs a instance with the specified settings. 53913 * 53914 * @constructor 53915 * @param {Object} settings Name/value object with settings. 53916 */ 53917 init: function(settings) { 53918 var self = this; 53919 53920 settings.autohide = true; 53921 settings.constrainToViewport = true; 53922 53923 if (settings.itemDefaults) { 53924 var items = settings.items, i = items.length; 53925 53926 while (i--) { 53927 items[i] = Tools.extend({}, settings.itemDefaults, items[i]); 53928 } 53929 } 53930 53931 self._super(settings); 53932 self.classes.add('menu'); 53933 }, 53934 53935 /** 53936 * Repaints the control after a layout operation. 53937 * 53938 * @method repaint 53939 */ 53940 repaint: function() { 53941 this.classes.toggle('menu-align', true); 53942 53943 this._super(); 53944 53945 this.getEl().style.height = ''; 53946 this.getEl('body').style.height = ''; 53947 53948 return this; 53949 }, 53950 53951 /** 53952 * Hides/closes the menu. 53953 * 53954 * @method cancel 53955 */ 53956 cancel: function() { 53957 var self = this; 53958 53959 self.hideAll(); 53960 self.fire('select'); 53961 }, 53962 53963 /** 53964 * Hide menu and all sub menus. 53965 * 53966 * @method hideAll 53967 */ 53968 hideAll: function() { 53969 var self = this; 53970 53971 this.find('menuitem').exec('hideMenu'); 53972 53973 return self._super(); 53974 }, 53975 53976 /** 53977 * Invoked before the menu is rendered. 53978 * 53979 * @method preRender 53980 */ 53981 preRender: function() { 53982 var self = this; 53983 53984 self.items().each(function(ctrl) { 53985 var settings = ctrl.settings; 53986 53987 if (settings.icon || settings.image || settings.selectable) { 53988 self._hasIcons = true; 53989 return false; 53990 } 53991 }); 53992 53993 return self._super(); 53994 } 53995 }); 53996}); 53997 53998// Included from: js/tinymce/classes/ui/ListBox.js 53999 54000/** 54001 * ListBox.js 54002 * 54003 * Released under LGPL License. 54004 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54005 * 54006 * License: http://www.tinymce.com/license 54007 * Contributing: http://www.tinymce.com/contributing 54008 */ 54009 54010/** 54011 * Creates a new list box control. 54012 * 54013 * @-x-less ListBox.less 54014 * @class tinymce.ui.ListBox 54015 * @extends tinymce.ui.MenuButton 54016 */
54017define("tinymce/ui/ListBox", [ 54018 "tinymce/ui/MenuButton", 54019 "tinymce/ui/Menu" 54020], function(MenuButton, Menu) { 54021 "use strict"; 54022 54023 return MenuButton.extend({ 54024 /** 54025 * Constructs a instance with the specified settings. 54026 * 54027 * @constructor 54028 * @param {Object} settings Name/value object with settings. 54029 * @setting {Array} values Array with values to add to list box. 54030 */ 54031 init: function(settings) { 54032 var self = this, values, selected, selectedText, lastItemCtrl; 54033 54034 function setSelected(menuValues) { 54035 // Try to find a selected value 54036 for (var i = 0; i < menuValues.length; i++) { 54037 selected = menuValues[i].selected || settings.value === menuValues[i].value; 54038 54039 if (selected) { 54040 selectedText = selectedText || menuValues[i].text; 54041 self.state.set('value', menuValues[i].value); 54042 return true; 54043 } 54044 54045 // If the value has a submenu, try to find the selected values in that menu 54046 if (menuValues[i].menu) { 54047 if (setSelected(menuValues[i].menu)) { 54048 return true; 54049 } 54050 } 54051 } 54052 } 54053 54054 self._super(settings); 54055 settings = self.settings; 54056 54057 self._values = values = settings.values; 54058 if (values) { 54059 if (typeof settings.value != "undefined") { 54060 setSelected(values); 54061 } 54062 54063 // Default with first item 54064 if (!selected && values.length > 0) { 54065 selectedText = values[0].text; 54066 self.state.set('value', values[0].value); 54067 } 54068 54069 self.state.set('menu', values); 54070 } 54071 54072 self.state.set('text', settings.text || selectedText); 54073 54074 self.classes.add('listbox'); 54075 54076 self.on('select', function(e) { 54077 var ctrl = e.control; 54078 54079 if (lastItemCtrl) { 54080 e.lastControl = lastItemCtrl; 54081 } 54082 54083 if (settings.multiple) { 54084 ctrl.active(!ctrl.active()); 54085 } else { 54086 self.value(e.control.value()); 54087 } 54088 54089 lastItemCtrl = ctrl; 54090 }); 54091 }, 54092 54093 /** 54094 * Getter/setter function for the control value. 54095 * 54096 * @method value 54097 * @param {String} [value] Value to be set. 54098 * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation. 54099 */ 54100 bindStates: function() { 54101 var self = this; 54102 54103 function activateMenuItemsByValue(menu, value) { 54104 if (menu instanceof Menu) { 54105 menu.items().each(function(ctrl) { 54106 if (!ctrl.hasMenus()) { 54107 ctrl.active(ctrl.value() === value); 54108 } 54109 }); 54110 } 54111 } 54112 54113 function getSelectedItem(menuValues, value) { 54114 var selectedItem; 54115 54116 if (!menuValues) { 54117 return; 54118 } 54119 54120 for (var i = 0; i < menuValues.length; i++) { 54121 if (menuValues[i].value === value) { 54122 return menuValues[i]; 54123 } 54124 54125 if (menuValues[i].menu) { 54126 selectedItem = getSelectedItem(menuValues[i].menu, value); 54127 if (selectedItem) { 54128 return selectedItem; 54129 } 54130 } 54131 } 54132 } 54133 54134 self.on('show', function(e) { 54135 activateMenuItemsByValue(e.control, self.value()); 54136 }); 54137 54138 self.state.on('change:value', function(e) { 54139 var selectedItem = getSelectedItem(self.state.get('menu'), e.value); 54140 54141 if (selectedItem) { 54142 self.text(selectedItem.text); 54143 } else { 54144 self.text(self.settings.text); 54145 } 54146 }); 54147 54148 return self._super(); 54149 } 54150 }); 54151}); 54152 54153// Included from: js/tinymce/classes/ui/Radio.js 54154 54155/** 54156 * Radio.js 54157 * 54158 * Released under LGPL License. 54159 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54160 * 54161 * License: http://www.tinymce.com/license 54162 * Contributing: http://www.tinymce.com/contributing 54163 */ 54164 54165/** 54166 * Creates a new radio button. 54167 * 54168 * @-x-less Radio.less 54169 * @class tinymce.ui.Radio 54170 * @extends tinymce.ui.Checkbox 54171 */ 54172define("tinymce/ui/Radio", [ 54173 "tinymce/ui/Checkbox" 54174], function(Checkbox) { 54175 "use strict"; 54176 54177 return Checkbox.extend({ 54178 Defaults: { 54179 classes: "radio", 54180 role: "radio" 54181 } 54182 }); 54183}); 54184 54185// Included from: js/tinymce/classes/ui/ResizeHandle.js 54186 54187/** 54188 * ResizeHandle.js 54189 * 54190 * Released under LGPL License. 54191 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54192 * 54193 * License: http://www.tinymce.com/license 54194 * Contributing: http://www.tinymce.com/contributing 54195 */ 54196 54197/** 54198 * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events. 54199 * 54200 * @-x-less ResizeHandle.less 54201 * @class tinymce.ui.ResizeHandle 54202 * @extends tinymce.ui.Widget 54203 */
54204define("tinymce/ui/ResizeHandle", [ 54205 "tinymce/ui/Widget", 54206 "tinymce/ui/DragHelper" 54207], function(Widget, DragHelper) { 54208 "use strict"; 54209 54210 return Widget.extend({ 54211 /** 54212 * Renders the control as a HTML string. 54213 * 54214 * @method renderHtml 54215 * @return {String} HTML representing the control. 54216 */ 54217 renderHtml: function() { 54218 var self = this, prefix = self.classPrefix; 54219 54220 self.classes.add('resizehandle'); 54221 54222 if (self.settings.direction == "both") { 54223 self.classes.add('resizehandle-both'); 54224 } 54225 54226 self.canFocus = false; 54227 54228 return ( 54229 '<div id="' + self._id + '" class="' + self.classes + '">' + 54230 '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' + 54231 '</div>' 54232 ); 54233 }, 54234 54235 /** 54236 * Called after the control has been rendered. 54237 * 54238 * @method postRender 54239 */ 54240 postRender: function() { 54241 var self = this; 54242 54243 self._super(); 54244 54245 self.resizeDragHelper = new DragHelper(this._id, { 54246 start: function() { 54247 self.fire('ResizeStart'); 54248 }, 54249 54250 drag: function(e) { 54251 if (self.settings.direction != "both") { 54252 e.deltaX = 0; 54253 } 54254 54255 self.fire('Resize', e); 54256 }, 54257 54258 stop: function() { 54259 self.fire('ResizeEnd'); 54260 } 54261 }); 54262 }, 54263 54264 remove: function() { 54265 if (this.resizeDragHelper) { 54266 this.resizeDragHelper.destroy(); 54267 } 54268 54269 return this._super(); 54270 } 54271 }); 54272}); 54273 54274// Included from: js/tinymce/classes/ui/SelectBox.js 54275 54276/** 54277 * SelectBox.js 54278 * 54279 * Released under LGPL License. 54280 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54281 * 54282 * License: http://www.tinymce.com/license 54283 * Contributing: http://www.tinymce.com/contributing 54284 */ 54285 54286/** 54287 * Creates a new select box control. 54288 * 54289 * @-x-less SelectBox.less 54290 * @class tinymce.ui.SelectBox 54291 * @extends tinymce.ui.Widget 54292 */ 54293define("tinymce/ui/SelectBox", [ 54294 "tinymce/ui/Widget" 54295], function(Widget) { 54296 "use strict"; 54297 54298 function createOptions(options) { 54299 var strOptions = ''; 54300 if (options) { 54301 for (var i = 0; i < options.length; i++) { 54302 strOptions += '<option value="' + options[i] + '">' + options[i] + '</option>'; 54303 } 54304 } 54305 return strOptions; 54306 } 54307 54308 return Widget.extend({ 54309 Defaults: { 54310 classes: "selectbox", 54311 role: "selectbox", 54312 options: [] 54313 }, 54314 /** 54315 * Constructs a instance with the specified settings. 54316 * 54317 * @constructor 54318 * @param {Object} settings Name/value object with settings. 54319 * @setting {Array} values Array with values to add to list box. 54320 */ 54321 init: function(settings) { 54322 var self = this; 54323 54324 self._super(settings); 54325 54326 if (self.settings.size) { 54327 54328 self.size = self.settings.size; 54329 54330 } 54331 54332 if (self.settings.options) { 54333 self._options = self.settings.options; 54334 } 54335 54336 }, 54337 54338 /** 54339 * Getter/setter function for the options state. 54340 * 54341 * @method options 54342 * @param {Array} [state] State to be set. 54343 * @return {Array|tinymce.ui.SelectBox} Array of string options. 54344 */ 54345 options: function(state) { 54346 if (!arguments.length) { 54347 return this.state.get('options'); 54348 } 54349 54350 this.state.set('options', state); 54351 54352 return this; 54353 }, 54354 54355 renderHtml: function() { 54356 var self = this, options, size = ''; 54357 54358 options = createOptions(self._options); 54359 54360 if (self.size) { 54361 size = ' size = "' + self.size + '"'; 54362 } 54363 54364 return ( 54365 '<select id="' + self._id + '" class="' + self.classes + '"' + size + '>' + 54366 options + 54367 '</select>' 54368 ); 54369 }, 54370 54371 bindStates: function() { 54372 var self = this; 54373 54374 self.state.on('change:options', function(e) { 54375 self.getEl().innerHTML = createOptions(e.value); 54376 }); 54377 54378 return self._super(); 54379 } 54380 }); 54381}); 54382 54383// Included from: js/tinymce/classes/ui/Slider.js 54384 54385/** 54386 * Slider.js 54387 * 54388 * Released under LGPL License. 54389 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54390 * 54391 * License: http://www.tinymce.com/license 54392 * Contributing: http://www.tinymce.com/contributing 54393 */ 54394 54395/** 54396 * Slider control. 54397 * 54398 * @-x-less Slider.less 54399 * @class tinymce.ui.Slider 54400 * @extends tinymce.ui.Widget 54401 */ 54402define("tinymce/ui/Slider", [ 54403 "tinymce/ui/Widget", 54404 "tinymce/ui/DragHelper", 54405 "tinymce/ui/DomUtils" 54406], function(Widget, DragHelper, DomUtils) { 54407 "use strict"; 54408 54409 function constrain(value, minVal, maxVal) { 54410 if (value < minVal) { 54411 value = minVal; 54412 } 54413 54414 if (value > maxVal) { 54415 value = maxVal; 54416 } 54417 54418 return value; 54419 } 54420 54421 function updateSliderHandle(ctrl, value) { 54422 var maxHandlePos, shortSizeName, sizeName, stylePosName, styleValue; 54423 54424 if (ctrl.settings.orientation == "v") { 54425 stylePosName = "top"; 54426 sizeName = "height"; 54427 shortSizeName = "h"; 54428 } else { 54429 stylePosName = "left"; 54430 sizeName = "width"; 54431 shortSizeName = "w"; 54432 } 54433 54434 maxHandlePos = (ctrl.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(ctrl.getEl('handle'))[sizeName]; 54435 54436 styleValue = (maxHandlePos * ((value - ctrl._minValue) / (ctrl._maxValue - ctrl._minValue))) + 'px'; 54437 ctrl.getEl('handle').style[stylePosName] = styleValue; 54438 ctrl.getEl('handle').style.height = ctrl.layoutRect().h + 'px'; 54439 } 54440 54441 return Widget.extend({ 54442 init: function(settings) { 54443 var self = this; 54444 54445 if (!settings.previewFilter) { 54446 settings.previewFilter = function(value) { 54447 return Math.round(value * 100) / 100.0; 54448 }; 54449 } 54450 54451 self._super(settings); 54452 self.classes.add('slider'); 54453 54454 if (settings.orientation == "v") { 54455 self.classes.add('vertical'); 54456 } 54457 54458 self._minValue = settings.minValue || 0; 54459 self._maxValue = settings.maxValue || 100; 54460 self._initValue = self.state.get('value'); 54461 }, 54462 54463 renderHtml: function() { 54464 var self = this, id = self._id, prefix = self.classPrefix; 54465 54466 return ( 54467 '<div id="' + id + '" class="' + self.classes + '">' + 54468 '<div id="' + id + '-handle" class="' + prefix + 'slider-handle"></div>' + 54469 '</div>' 54470 ); 54471 }, 54472 54473 reset: function() { 54474 this.value(this._initValue).repaint(); 54475 }, 54476 54477 postRender: function() { 54478 var self = this, startPos, startHandlePos, handlePos = 0, value, minValue, maxValue, maxHandlePos; 54479 var screenCordName, stylePosName, sizeName, shortSizeName; 54480 54481 minValue = self._minValue; 54482 maxValue = self._maxValue; 54483 value = self.value(); 54484 54485 if (self.settings.orientation == "v") { 54486 screenCordName = "screenY"; 54487 stylePosName = "top"; 54488 sizeName = "height"; 54489 shortSizeName = "h"; 54490 } else { 54491 screenCordName = "screenX"; 54492 stylePosName = "left"; 54493 sizeName = "width"; 54494 shortSizeName = "w"; 54495 } 54496 54497 self._super(); 54498 54499 self._dragHelper = new DragHelper(self._id, { 54500 handle: self._id + "-handle", 54501 54502 start: function(e) { 54503 startPos = e[screenCordName]; 54504 startHandlePos = parseInt(self.getEl('handle').style[stylePosName], 10); 54505 maxHandlePos = (self.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(self.getEl('handle'))[sizeName]; 54506 self.fire('dragstart', {value: value}); 54507 }, 54508 54509 drag: function(e) { 54510 var delta = e[screenCordName] - startPos, handleEl = self.getEl('handle'); 54511 54512 handlePos = constrain(startHandlePos + delta, 0, maxHandlePos); 54513 handleEl.style[stylePosName] = handlePos + 'px'; 54514 54515 value = minValue + (handlePos / maxHandlePos) * (maxValue - minValue); 54516 self.value(value); 54517 54518 self.tooltip().text('' + self.settings.previewFilter(value)).show().moveRel(handleEl, 'bc tc'); 54519 54520 self.fire('drag', {value: value}); 54521 }, 54522 54523 stop: function() { 54524 self.tooltip().hide(); 54525 self.fire('dragend', {value: value}); 54526 } 54527 }); 54528 }, 54529 54530 repaint: function() { 54531 this._super(); 54532 updateSliderHandle(this, this.value()); 54533 }, 54534 54535 bindStates: function() { 54536 var self = this; 54537 54538 self.state.on('change:value', function(e) { 54539 updateSliderHandle(self, e.value); 54540 }); 54541 54542 return self._super(); 54543 } 54544 }); 54545}); 54546 54547// Included from: js/tinymce/classes/ui/Spacer.js 54548 54549/** 54550 * Spacer.js 54551 * 54552 * Released under LGPL License. 54553 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54554 * 54555 * License: http://www.tinymce.com/license 54556 * Contributing: http://www.tinymce.com/contributing 54557 */ 54558 54559/** 54560 * Creates a spacer. This control is used in flex layouts for example. 54561 * 54562 * @-x-less Spacer.less 54563 * @class tinymce.ui.Spacer 54564 * @extends tinymce.ui.Widget 54565 */
54566define("tinymce/ui/Spacer", [ 54567 "tinymce/ui/Widget" 54568], function(Widget) { 54569 "use strict"; 54570 54571 return Widget.extend({ 54572 /** 54573 * Renders the control as a HTML string. 54574 * 54575 * @method renderHtml 54576 * @return {String} HTML representing the control. 54577 */ 54578 renderHtml: function() { 54579 var self = this; 54580 54581 self.classes.add('spacer'); 54582 self.canFocus = false; 54583 54584 return '<div id="' + self._id + '" class="' + self.classes + '"></div>'; 54585 } 54586 }); 54587}); 54588 54589// Included from: js/tinymce/classes/ui/SplitButton.js 54590 54591/** 54592 * SplitButton.js 54593 * 54594 * Released under LGPL License. 54595 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54596 * 54597 * License: http://www.tinymce.com/license 54598 * Contributing: http://www.tinymce.com/contributing 54599 */ 54600 54601/** 54602 * Creates a split button. 54603 * 54604 * @-x-less SplitButton.less 54605 * @class tinymce.ui.SplitButton 54606 * @extends tinymce.ui.Button 54607 */ 54608define("tinymce/ui/SplitButton", [ 54609 "tinymce/ui/MenuButton", 54610 "tinymce/ui/DomUtils", 54611 "tinymce/dom/DomQuery" 54612], function(MenuButton, DomUtils, $) { 54613 return MenuButton.extend({ 54614 Defaults: { 54615 classes: "widget btn splitbtn", 54616 role: "button" 54617 }, 54618 54619 /** 54620 * Repaints the control after a layout operation. 54621 * 54622 * @method repaint 54623 */ 54624 repaint: function() { 54625 var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm; 54626 54627 self._super(); 54628 54629 mainButtonElm = elm.firstChild; 54630 menuButtonElm = elm.lastChild; 54631 54632 $(mainButtonElm).css({ 54633 width: rect.w - DomUtils.getSize(menuButtonElm).width, 54634 height: rect.h - 2 54635 }); 54636 54637 $(menuButtonElm).css({ 54638 height: rect.h - 2 54639 }); 54640 54641 return self; 54642 }, 54643 54644 /** 54645 * Sets the active menu state. 54646 * 54647 * @private 54648 */ 54649 activeMenu: function(state) { 54650 var self = this; 54651 54652 $(self.getEl().lastChild).toggleClass(self.classPrefix + 'active', state); 54653 }, 54654 54655 /** 54656 * Renders the control as a HTML string. 54657 * 54658 * @method renderHtml 54659 * @return {String} HTML representing the control. 54660 */ 54661 renderHtml: function() { 54662 var self = this, id = self._id, prefix = self.classPrefix, image; 54663 var icon = self.state.get('icon'), text = self.state.get('text'), 54664 textHtml = ''; 54665 54666 image = self.settings.image; 54667 if (image) { 54668 icon = 'none'; 54669 54670 // Support for [high dpi, low dpi] image sources 54671 if (typeof image != "string") { 54672 image = window.getSelection ? image[0] : image[1]; 54673 } 54674 54675 image = ' style="background-image: url(\'' + image + '\')"'; 54676 } else { 54677 image = ''; 54678 } 54679 54680 icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : ''; 54681 54682 if (text) { 54683 self.classes.add('btn-has-text'); 54684 textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>'; 54685 } 54686 54687 return ( 54688 '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1">' + 54689 '<button type="button" hidefocus="1" tabindex="-1">' + 54690 (icon ? '<i class="' + icon + '"' + image + '></i>' : '') + 54691 textHtml + 54692 '</button>' + 54693 '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' + 54694 //(icon ? '<i class="' + icon + '"></i>' : '') + 54695 (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') + 54696 ' <i class="' + prefix + 'caret"></i>' + 54697 '</button>' + 54698 '</div>' 54699 ); 54700 }, 54701 54702 /** 54703 * Called after the control has been rendered. 54704 * 54705 * @method postRender 54706 */ 54707 postRender: function() { 54708 var self = this, onClickHandler = self.settings.onclick; 54709 54710 self.on('click', function(e) { 54711 var node = e.target; 54712 54713 if (e.control == this) { 54714 // Find clicks that is on the main button 54715 while (node) { 54716 if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) { 54717 e.stopImmediatePropagation(); 54718 54719 if (onClickHandler) { 54720 onClickHandler.call(this, e); 54721 } 54722 54723 return; 54724 } 54725 54726 node = node.parentNode; 54727 } 54728 } 54729 }); 54730 54731 delete self.settings.onclick; 54732 54733 return self._super(); 54734 } 54735 }); 54736}); 54737 54738// Included from: js/tinymce/classes/ui/StackLayout.js 54739 54740/** 54741 * StackLayout.js 54742 * 54743 * Released under LGPL License. 54744 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54745 * 54746 * License: http://www.tinymce.com/license 54747 * Contributing: http://www.tinymce.com/contributing 54748 */ 54749 54750/** 54751 * This layout uses the browsers layout when the items are blocks. 54752 * 54753 * @-x-less StackLayout.less 54754 * @class tinymce.ui.StackLayout 54755 * @extends tinymce.ui.FlowLayout 54756 */
54757define("tinymce/ui/StackLayout", [ 54758 "tinymce/ui/FlowLayout" 54759], function(FlowLayout) { 54760 "use strict"; 54761 54762 return FlowLayout.extend({ 54763 Defaults: { 54764 containerClass: 'stack-layout', 54765 controlClass: 'stack-layout-item', 54766 endClass: 'break' 54767 }, 54768 54769 isNative: function() { 54770 return true; 54771 } 54772 }); 54773}); 54774 54775// Included from: js/tinymce/classes/ui/TabPanel.js 54776 54777/** 54778 * TabPanel.js 54779 * 54780 * Released under LGPL License. 54781 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54782 * 54783 * License: http://www.tinymce.com/license 54784 * Contributing: http://www.tinymce.com/contributing 54785 */ 54786 54787/** 54788 * Creates a tab panel control. 54789 * 54790 * @-x-less TabPanel.less 54791 * @class tinymce.ui.TabPanel 54792 * @extends tinymce.ui.Panel 54793 * 54794 * @setting {Number} activeTab Active tab index. 54795 */ 54796define("tinymce/ui/TabPanel", [ 54797 "tinymce/ui/Panel", 54798 "tinymce/dom/DomQuery", 54799 "tinymce/ui/DomUtils" 54800], function(Panel, $, DomUtils) { 54801 "use strict"; 54802 54803 return Panel.extend({ 54804 Defaults: { 54805 layout: 'absolute', 54806 defaults: { 54807 type: 'panel' 54808 } 54809 }, 54810 54811 /** 54812 * Activates the specified tab by index. 54813 * 54814 * @method activateTab 54815 * @param {Number} idx Index of the tab to activate. 54816 */ 54817 activateTab: function(idx) { 54818 var activeTabElm; 54819 54820 if (this.activeTabId) { 54821 activeTabElm = this.getEl(this.activeTabId); 54822 $(activeTabElm).removeClass(this.classPrefix + 'active'); 54823 activeTabElm.setAttribute('aria-selected', "false"); 54824 } 54825 54826 this.activeTabId = 't' + idx; 54827 54828 activeTabElm = this.getEl('t' + idx); 54829 activeTabElm.setAttribute('aria-selected', "true"); 54830 $(activeTabElm).addClass(this.classPrefix + 'active'); 54831 54832 this.items()[idx].show().fire('showtab'); 54833 this.reflow(); 54834 54835 this.items().each(function(item, i) { 54836 if (idx != i) { 54837 item.hide(); 54838 } 54839 }); 54840 }, 54841 54842 /** 54843 * Renders the control as a HTML string. 54844 * 54845 * @method renderHtml 54846 * @return {String} HTML representing the control. 54847 */ 54848 renderHtml: function() { 54849 var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix; 54850 54851 self.preRender(); 54852 layout.preRender(self); 54853 54854 self.items().each(function(ctrl, i) { 54855 var id = self._id + '-t' + i; 54856 54857 ctrl.aria('role', 'tabpanel'); 54858 ctrl.aria('labelledby', id); 54859 54860 tabsHtml += ( 54861 '<div id="' + id + '" class="' + prefix + 'tab" ' + 54862 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' + 54863 self.encode(ctrl.settings.title) + 54864 '</div>' 54865 ); 54866 }); 54867 54868 return ( 54869 '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' + 54870 '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' + 54871 tabsHtml + 54872 '</div>' + 54873 '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' + 54874 layout.renderHtml(self) + 54875 '</div>' + 54876 '</div>' 54877 ); 54878 }, 54879 54880 /** 54881 * Called after the control has been rendered. 54882 * 54883 * @method postRender 54884 */ 54885 postRender: function() { 54886 var self = this; 54887 54888 self._super(); 54889 54890 self.settings.activeTab = self.settings.activeTab || 0; 54891 self.activateTab(self.settings.activeTab); 54892 54893 this.on('click', function(e) { 54894 var targetParent = e.target.parentNode; 54895 54896 if (e.target.parentNode.id == self._id + '-head') { 54897 var i = targetParent.childNodes.length; 54898 54899 while (i--) { 54900 if (targetParent.childNodes[i] == e.target) { 54901 self.activateTab(i); 54902 } 54903 } 54904 } 54905 }); 54906 }, 54907 54908 /** 54909 * Initializes the current controls layout rect. 54910 * This will be executed by the layout managers to determine the 54911 * default minWidth/minHeight etc. 54912 * 54913 * @method initLayoutRect 54914 * @return {Object} Layout rect instance. 54915 */ 54916 initLayoutRect: function() { 54917 var self = this, rect, minW, minH; 54918 54919 minW = DomUtils.getSize(self.getEl('head')).width; 54920 minW = minW < 0 ? 0 : minW; 54921 minH = 0; 54922 54923 self.items().each(function(item) { 54924 minW = Math.max(minW, item.layoutRect().minW); 54925 minH = Math.max(minH, item.layoutRect().minH); 54926 }); 54927 54928 self.items().each(function(ctrl) { 54929 ctrl.settings.x = 0; 54930 ctrl.settings.y = 0; 54931 ctrl.settings.w = minW; 54932 ctrl.settings.h = minH; 54933 54934 ctrl.layoutRect({ 54935 x: 0, 54936 y: 0, 54937 w: minW, 54938 h: minH 54939 }); 54940 }); 54941 54942 var headH = DomUtils.getSize(self.getEl('head')).height; 54943 54944 self.settings.minWidth = minW; 54945 self.settings.minHeight = minH + headH; 54946 54947 rect = self._super(); 54948 rect.deltaH += headH; 54949 rect.innerH = rect.h - rect.deltaH; 54950 54951 return rect; 54952 } 54953 }); 54954}); 54955 54956// Included from: js/tinymce/classes/ui/TextBox.js 54957 54958/** 54959 * TextBox.js 54960 * 54961 * Released under LGPL License. 54962 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 54963 * 54964 * License: http://www.tinymce.com/license 54965 * Contributing: http://www.tinymce.com/contributing 54966 */ 54967 54968/** 54969 * Creates a new textbox. 54970 * 54971 * @-x-less TextBox.less 54972 * @class tinymce.ui.TextBox 54973 * @extends tinymce.ui.Widget 54974 */
54975define("tinymce/ui/TextBox", [ 54976 "tinymce/ui/Widget" 54977], function(Widget) { 54978 "use strict"; 54979 54980 return Widget.extend({ 54981 /** 54982 * Constructs a instance with the specified settings. 54983 * 54984 * @constructor 54985 * @param {Object} settings Name/value object with settings. 54986 * @setting {Boolean} multiline True if the textbox is a multiline control. 54987 * @setting {Number} maxLength Max length for the textbox. 54988 * @setting {Number} size Size of the textbox in characters. 54989 */ 54990 init: function(settings) { 54991 var self = this; 54992 54993 self._super(settings); 54994 54995 self.classes.add('textbox'); 54996 54997 if (settings.multiline) { 54998 self.classes.add('multiline'); 54999 } else { 55000 self.on('keydown', function(e) { 55001 var rootControl; 55002 55003 if (e.keyCode == 13) { 55004 e.preventDefault(); 55005 55006 // Find root control that we can do toJSON on 55007 self.parents().reverse().each(function(ctrl) { 55008 if (ctrl.toJSON) { 55009 rootControl = ctrl; 55010 return false; 55011 } 55012 }); 55013 55014 // Fire event on current text box with the serialized data of the whole form 55015 self.fire('submit', {data: rootControl.toJSON()}); 55016 } 55017 }); 55018 55019 self.on('keyup', function(e) { 55020 self.state.set('value', e.target.value); 55021 }); 55022 } 55023 }, 55024 55025 /** 55026 * Repaints the control after a layout operation. 55027 * 55028 * @method repaint 55029 */ 55030 repaint: function() { 55031 var self = this, style, rect, borderBox, borderW, borderH = 0, lastRepaintRect; 55032 55033 style = self.getEl().style; 55034 rect = self._layoutRect; 55035 lastRepaintRect = self._lastRepaintRect || {}; 55036 55037 // Detect old IE 7+8 add lineHeight to align caret vertically in the middle 55038 var doc = document; 55039 if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) { 55040 style.lineHeight = (rect.h - borderH) + 'px'; 55041 } 55042 55043 borderBox = self.borderBox; 55044 borderW = borderBox.left + borderBox.right + 8; 55045 borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0); 55046 55047 if (rect.x !== lastRepaintRect.x) { 55048 style.left = rect.x + 'px'; 55049 lastRepaintRect.x = rect.x; 55050 } 55051 55052 if (rect.y !== lastRepaintRect.y) { 55053 style.top = rect.y + 'px'; 55054 lastRepaintRect.y = rect.y; 55055 } 55056 55057 if (rect.w !== lastRepaintRect.w) { 55058 style.width = (rect.w - borderW) + 'px'; 55059 lastRepaintRect.w = rect.w; 55060 } 55061 55062 if (rect.h !== lastRepaintRect.h) { 55063 style.height = (rect.h - borderH) + 'px'; 55064 lastRepaintRect.h = rect.h; 55065 } 55066 55067 self._lastRepaintRect = lastRepaintRect; 55068 self.fire('repaint', {}, false); 55069 55070 return self; 55071 }, 55072 55073 /** 55074 * Renders the control as a HTML string. 55075 * 55076 * @method renderHtml 55077 * @return {String} HTML representing the control. 55078 */ 55079 renderHtml: function() { 55080 var self = this, id = self._id, settings = self.settings, value = self.encode(self.state.get('value'), false), extraAttrs = ''; 55081 55082 if ("spellcheck" in settings) { 55083 extraAttrs += ' spellcheck="' + settings.spellcheck + '"'; 55084 } 55085 55086 if (settings.maxLength) { 55087 extraAttrs += ' maxlength="' + settings.maxLength + '"'; 55088 } 55089 55090 if (settings.size) { 55091 extraAttrs += ' size="' + settings.size + '"'; 55092 } 55093 55094 if (settings.subtype) { 55095 extraAttrs += ' type="' + settings.subtype + '"'; 55096 } 55097 55098 if (self.disabled()) { 55099 extraAttrs += ' disabled="disabled"'; 55100 } 55101 55102 if (settings.multiline) { 55103 return ( 55104 '<textarea id="' + id + '" class="' + self.classes + '" ' + 55105 (settings.rows ? ' rows="' + settings.rows + '"' : '') + 55106 ' hidefocus="1"' + extraAttrs + '>' + value + 55107 '</textarea>' 55108 ); 55109 } 55110 55111 return '<input id="' + id + '" class="' + self.classes + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />'; 55112 }, 55113 55114 value: function(value) { 55115 if (arguments.length) { 55116 this.state.set('value', value); 55117 return this; 55118 } 55119 55120 // Make sure the real state is in sync 55121 if (this.state.get('rendered')) { 55122 this.state.set('value', this.getEl().value); 55123 } 55124 55125 return this.state.get('value'); 55126 }, 55127 55128 /** 55129 * Called after the control has been rendered. 55130 * 55131 * @method postRender 55132 */ 55133 postRender: function() { 55134 var self = this; 55135 55136 self._super(); 55137 55138 self.$el.on('change', function(e) { 55139 self.state.set('value', e.target.value); 55140 self.fire('change', e); 55141 }); 55142 }, 55143 55144 bindStates: function() { 55145 var self = this; 55146 55147 self.state.on('change:value', function(e) { 55148 if (self.getEl().value != e.value) { 55149 self.getEl().value = e.value; 55150 } 55151 }); 55152 55153 self.state.on('change:disabled', function(e) { 55154 self.getEl().disabled = e.value; 55155 }); 55156 55157 return self._super(); 55158 }, 55159 55160 remove: function() { 55161 this.$el.off(); 55162 this._super(); 55163 } 55164 }); 55165}); 55166 55167// Included from: js/tinymce/classes/ui/Throbber.js 55168 55169/** 55170 * Throbber.js 55171 * 55172 * Released under LGPL License. 55173 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved 55174 * 55175 * License: http://www.tinymce.com/license 55176 * Contributing: http://www.tinymce.com/contributing 55177 */ 55178 55179/** 55180 * This class enables you to display a Throbber for any element. 55181 * 55182 * @-x-less Throbber.less 55183 * @class tinymce.ui.Throbber 55184 */
55185define("tinymce/ui/Throbber", [ 55186 "tinymce/dom/DomQuery", 55187 "tinymce/ui/Control", 55188 "tinymce/util/Delay" 55189], function($, Control, Delay) { 55190 "use strict"; 55191 55192 /** 55193 * Constructs a new throbber. 55194 * 55195 * @constructor 55196 * @param {Element} elm DOM Html element to display throbber in. 55197 * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll. 55198 */ 55199 return function(elm, inline) { 55200 var self = this, state, classPrefix = Control.classPrefix; 55201 55202 /** 55203 * Shows the throbber. 55204 * 55205 * @method show 55206 * @param {Number} [time] Time to wait before showing. 55207 * @param {function} [callback] Optional callback to execute when the throbber is shown. 55208 * @return {tinymce.ui.Throbber} Current throbber instance. 55209 */ 55210 self.show = function(time, callback) { 55211 self.hide(); 55212 55213 state = true; 55214 55215 Delay.setTimeout(function() { 55216 if (state) { 55217 $(elm).append( 55218 '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>' 55219 ); 55220 55221 if (callback) { 55222 callback(); 55223 } 55224 } 55225 }, time); 55226 55227 return self; 55228 }; 55229 55230 /** 55231 * Hides the throbber. 55232 * 55233 * @method hide 55234 * @return {tinymce.ui.Throbber} Current throbber instance. 55235 */ 55236 self.hide = function() { 55237 var child = elm.lastChild; 55238 55239 if (child && child.className.indexOf('throbber') != -1) { 55240 child.parentNode.removeChild(child); 55241 } 55242 55243 state = false; 55244 55245 return self; 55246 }; 55247 }; 55248}); 55249 55250expose(["tinymce/geom/Rect","tinymce/util/Promise","tinymce/util/Delay","tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/Env","tinymce/util/Tools","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/dom/RangeUtils","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/util/Observable","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/ReflowQueue","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Progress","tinymce/ui/Notification","tinymce/NotificationManager","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","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/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui/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/MenuItem","tinymce/ui/Menu","tinymce/ui/ListBox","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/SelectBox","tinymce/ui/Slider","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]); 55251})(this); 55252!function(e){function t(){function t(e){"remove"===e&&this.each(function(e,t){var n=i(t);n&&n.remove()}),this.find("span.mceEditor,div.mceEditor").each(function(e,t){var n=tinymce.get(t.id.replace(/_parent$/,""));n&&n.remove()})}function r(e){var n,r=this;if(null!=e)t.call(r),r.each(function(t,n){var r;(r=tinymce.get(n.id))&&r.setContent(e)});else if(r.length>0&&(n=tinymce.get(r[0].id)))return n.getContent()}function i(e){var t=null;return e&&e.id&&a.tinymce&&(t=tinymce.get(e.id)),t}function o(e){return!!(e&&e.length&&a.tinymce&&e.is(":tinymce"))}var s={};e.each(["text","html","val"],function(t,a){var l=s[a]=e.fn[a],c="text"===a;e.fn[a]=function(t){var a=this;
55252if(!o(a))return l.apply(a,arguments);if(t!==n)return r.call(a.filter(":tinymce"),t),l.apply(a.not(":tinymce"),arguments),a;var s="",u=arguments;return(c?a:a.eq(0)).each(function(t,n){var r=i(n);s+=r?c?r.getContent().replace(/<(?:"[^"]*"|'[^']*'|[^'">])*>/g,""):r.getContent({save:!0}):l.apply(e(n),u)}),s}}),e.each(["append","prepend"],function(t,r){var a=s[r]=e.fn[r],l="prepend"===r;e.fn[r]=function(e){var t=this;return o(t)?e!==n?("string"==typeof e&&t.filter(":tinymce").each(function(t,n){var r=i(n);r&&r.setContent(l?e+r.getContent():r.getContent()+e)}),a.apply(t.not(":tinymce"),arguments),t):void 0:a.apply(t,arguments)}}),e.each(["remove","replaceWith","replaceAll","empty"],function(n,r){var i=s[r]=e.fn[r];e.fn[r]=function(){return t.call(this,r),i.apply(this,arguments)}}),s.attr=e.fn.attr,e.fn.attr=function(t,a){var l=this,c=arguments;if(!t||"value"!==t||!o(l))return a!==n?s.attr.apply(l,c):s.attr.apply(l,c);if(a!==n)return r.call(l.filter(":tinymce"),a),s.attr.apply(l.not(":tinymce"),c),l;var u=l[0],d=i(u);return d?d.getContent({save:!0}):s.attr.apply(e(u),c)}}var n,r,i,o=[],a=window;e.fn.tinymce=function(n){function s(){var r=[],o=0;i||(t(),i=!0),d.each(function(e,t){var i,a=t.id,s=n.oninit;a||(t.id=a=tinymce.DOM.uniqueId()),tinymce.get(a)||(i=new tinymce.Editor(a,n,tinymce.EditorManager),r.push(i),i.on("init",function(){var e,t=s;d.css("visibility",""),s&&++o==r.length&&("string"==typeof t&&(e=-1===t.indexOf(".")?null:tinymce.resolve(t.replace(/\.\w+$/,"")),t=tinymce.resolve(t)),t.apply(e||tinymce,r))}))}),e.each(r,function(e,t){t.render()})}var l,c,u,d=this,f="";if(!d.length)return d;if(!n)return window.tinymce?tinymce.get(d[0].id):null;if(d.css("visibility","hidden"),a.tinymce||r||!(l=n.script_url))1===r?o.push(s):s();else{r=1,c=l.substring(0,l.lastIndexOf("/")),-1!=l.indexOf(".min")&&(f=".min"),a.tinymce=a.tinyMCEPreInit||{base:c,suffix:f},-1!=l.indexOf("gzip")&&(u=n.language||"en",l=l+(/\?/.test(l)?"&":"?")+"js=true&core=true&suffix="+escape(f)+"&themes="+escape(n.theme||"modern")+"&plugins="+escape(n.plugins||"")+"&languages="+(u||""),a.tinyMCE_GZ||(a.tinyMCE_GZ={start:function(){function t(e){tinymce.ScriptLoader.markDone(tinymce.baseURI.toAbsolute(e))}t("langs/"+u+".js"),t("themes/"+n.theme+"/theme"+f+".js"),t("themes/"+n.theme+"/langs/"+u+".js"),e.each(n.plugins.split(","),function(e,n){
55252n&&(t("plugins/"+n+"/plugin"+f+".js"),t("plugins/"+n+"/langs/"+u+".js"))})},end:function(){}}));var h=document.createElement("script");h.type="text/javascript",h.onload=h.onreadystatechange=function(t){t=t||window.event,2===r||"load"!=t.type&&!/complete|loaded/.test(h.readyState)||(tinymce.dom.Event.domLoaded=1,r=2,n.script_loaded&&n.script_loaded(),s(),e.each(o,function(e,t){t()}))},h.src=l,document.body.appendChild(h)}return d},e.extend(e.expr[":"],{tinymce:function(e){var t;return e.id&&"tinymce"in window&&(t=tinymce.get(e.id),t&&t.editorManager===tinymce)?!0:!1}})}(jQuery); 55253 55254 55255 55256"use strict"; 55257 55258$.validator.addMethod("url_loose", function (val, elem) { 55259 // if no url, don't do anything 55260 if (val.length == 0) { 55261 return true; 55262 } 55263 return (/^(?:(http|https):\/\/)?(?:[\w-]+\.)+[a-z]{3,6}(\/(.+)?)?$/.test(val) 55264 ); 55265}, $.validator.format("Please enter a valid URL.")); 55266 55267$.validator.addMethod("editor_maxlength", function (val, elem, params) { 55268 if (typeof tinyMCE == 'undefined') return true; 55269 return tinyMCE.editors[elem.id].plugins["charactercount"].getCount() <= params; 55270}, $.validator.format('Maximum characters: {0}')); 55271 55272$.validator.addMethod("greater_than", function (value, elem, params) { 55273 if (!/Invalid|NaN/.test(new Date(value))) { 55274 return new Date(value) > new Date($(params).val()); 55275 } 55276 return isNaN(value) && isNaN($(params).val()) || Number(value) > Number($(params).val()); 55277}, 'Must be greater than start date.'); 55278 55279$.validator.addMethod("number_less_than", function (value, elem, params) { 55280 return Number(value) < Number($(params).val()); 55281}, 'Must be less than other field.'); 55282 55283$.validator.addMethod("number_less_than_or_equal_to", function (value, elem, params) { 55284 return Number(value) <= Number($(params).val()); 55285}, 'Must be less than or equal to other field.'); 55286 55287$.validator.addMethod("youtube_url", function (val, elem, params) { 55288 return val.indexOf('youtube.com') > -1 && val.indexOf('v=') > -1; 55289}, 'Must be a Youtube URL'); 55290 55291$.validator.addMethod("email", function (val, elem, params) { 55292 if (params['allowBlank'] && this.optional(elem)) { 55293 return true; 55294 } 55295 55296 var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/; 55297 return regex.test(val); 55298}, 'Must be a valid email address'); 55299 55300$.validator.addMethod("google_analytics_key", function (val, elem) { 55301 var regex = /^G-/; 55302 return regex.test(val); 55303}, 'Key must in the format of G-XXXXXXXXXX'); 55304 55305$.validator.addMethod("date_format", function (val, elem, params) { 55306 var valid = undefined; 55307 55308 try { 55309 $.datepicker.parseDate('mm/dd/yy', val); 55310 valid = true; 55311 } catch (e) { 55312 valid = false; 55313 } 55314 55315 return valid; 55316}, 'Must be a date: mm/dd/yyyy'); 55317 55318$.validator.addMethod("month_day", function (val, elem, params) { 55319 var field1 = $("[name=\"" + params[0] + "\"]").val(); 55320 var field2 = $("[name=\"" + params[1] + "\"]").val(); 55321 55322 return field1 && field2 || !field1 && !field2; 55323}, 'Month and day are required'); 55324 55325$.validator.addMethod("not_in", function (val, elem, params) { 55326 var formattedVal = val.toLowerCase(); 55327 var isValid = params[0].indexOf(formattedVal) === -1; 55328 return isValid; 55329}, $.validator.format('Cannot be any of the following: {0}')); 55330 55331$.validator.addMethod("file_required", function (value, elem, params) { 55332 if (elem.files.length) { 55333 return true; 55334 } 55335 55336 return $(elem).data('file-name') !== undefined; 55337}, 'File is required.'); 55338 55339$.validator.addMethod("filesize", function (value, element, params) { 55340 // In the validation call the supplied value should be in MB 55341 return this.optional(element) || element.files[0].size <= params * 1000000; 55342}, 'File size must be less than {0}MB'); 55343 55344$.validator.addMethod("currency", function (value, element) { 55345 return this.optional(element) || /^(\d{1,3}(\,\d{3})*|(\d+))(\.\d{2})?$/.test(value); 55346}, "Please specify a valid amount"); 55347 55348$.validator.addMethod("number_letter_hyphen", function (value, element) { 55349 return this.optional(element) || /^([a-z0-9-]+)?$/.test(value); 55350}, "Please specify a valid amount"); 55351'use strict'; 55352 55353if (typeof tinyMCE != 'undefined') { 55354 tinyMCE.textareaMaxlengthDefault = 600; 55355} 55356 55357// Define the errorPlacement handler independently 55358// so it can be included and / or overriden elsewhere. 55359window.formValidateErrorPlacement = function (error, element) { 55360 if (element.hasClass('no-error-label')) { 55361 return true; 55362 } else if (element.is(':checkbox')) { 55363 element.parents('.form-item--checkbox').first().append(error); 55364 } else if (element.is('select')) { 55365 element.parents('.form-item__container').first().after(error); 55366 } else if (element.parents('.form-item__container').length) { 55367 element.parents('.form-item__container').first().after(error); 55368 } else { 55369 error.insertAfter(element); 55370 } 55371}; 55372 55373window.formValidateDefaults = { 55374 errorPlacement: window.formValidateErrorPlacement, 55375 55376 onfocusout: function onfocusout(element) { 55377 if (!$(element).is('.datepicker')) { 55378 $(element).valid(); 55379 } 55380 }, 55381 55382 submitHandler: function submitHandler(form) { 55383 var text = $(form).attr('data-loading-text') || 'Please wait...'; 55384 var $submit = $(form).find('[type="submit"]'); 55385 55386 $(form).get(0).dispatchEvent(new CustomEvent('validated', { bubbles: true })); 55387 55388 $submit.prop('disabled', true); 55389 55390 if ($submit.is('button')) { 55391 $submit.html(text); 55392 } else { 55393 $submit.val(text); 55394 } 55395 55396 if ($(form).attr('data-remote')) { 55397 $.rails.handleRemote($(form)); 55398 } else { 55399 form.submit(); 55400 } 55401 } 55402}; 55403 55404if (window.nestedFormEvents) { 55405 window.nestedFormEvents.insertFields = function (content, assoc, link) { 55406 var target = $(link).data('target'); 55407 var closestParent = $(link).data('closest-parent'); 55408 if (target && closestParent) { 55409 return $(link).closest(closestParent).find(target).append(content); 55410 } else if (target) { 55411 return $(content).appendTo($(target)); 55412 } else { 55413 return $(content).insertBefore(link); 55414 } 55415 }; 55416} 55417
55418window.Modal = { 55419 open: function open($sel) { 55420 $sel.addClass('is-active'); 55421 $('body').addClass('modal-open'); 55422 }, 55423 55424 close: function close($sel) { 55425 $sel.removeClass('is-active'); 55426 $('body').removeClass('modal-open'); 55427 } 55428}; 55429 55430$.fn.currency = function () { 55431 return this.each(function () { 55432 $(this).inputmask('Regex', { regex: '[0-9]+\\.[0-9]{2}' }); 55433 }); 55434}; 55435 55436$.fn.numeric = function () { 55437 return this.each(function () { 55438 $(this).inputmask('Regex', { regex: '[0-9]+' }); 55439 }); 55440}; 55441 55442var closeMessage = function closeMessage() { 55443 var closeTrigger = $('[data-flash-message-close]'); 55444 55445 closeTrigger.bind('click', function (e) { 55446 e.preventDefault(); 55447 $(this).parent().fadeOut(200); 55448 }); 55449}; 55450 55451$(function () { 55452 closeMessage(); 55453}); 55454tinymce.PluginManager.add('charactercount', function (editor) { 55455 var self = this; 55456 55457 function update() { 55458 editor.theme.panel.find('#charactercount').text(display(self.getCount(), getMaxLength())); 55459 } 55460 55461 function display(count, max) { 55462 return ['Characters: {0} / {1}', count, max]; 55463 } 55464 55465 function getMaxLength() { 55466 if (editor.settings.charactercount_maxlength != undefined) { 55467 return editor.settings.charactercount_maxlength; 55468 } 55469 55470 return $(editor.getElement()).attr('data-editor-maxlength') || tinyMCE.textareaMaxlengthDefault; 55471 } 55472 55473 function decodeHtml(html) { 55474 var txt = document.createElement("textarea"); 55475 txt.innerHTML = html; 55476 return txt.value; 55477 } 55478 55479 self.getCount = function () { 55480 var tx = editor.getContent({ format: 'raw' }); 55481 var decoded = decodeHtml(tx); 55482 var decodedStripped = decoded.replace(/(<([^>]+)>)/ig, "").trim(); 55483 decodedStripped = decodedStripped.replace(/\s\s+/g, ' '); 55484 var tc = decodedStripped.length; 55485 return tc; 55486 }; 55487 55488 editor.getCharacterCount = function() { 55489 return self.getCount(); 55490 } 55491 55492 editor.on('init', function () { 55493 if(editor.settings.charactercount_enabled === false) { 55494 return; 55495 } 55496 55497 if(editor.settings.charactercount_enabled === undefined && $(editor.getElement()).attr('data-editor-maxlength') == undefined) { 55498 return; 55499 } 55500 55501 var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0]; 55502 55503 if (statusbar) { 55504 window.setTimeout(function () { 55505 statusbar.insert({ 55506 type: 'label', 55507 name: 'charactercount', 55508 text: display(self.getCount(), getMaxLength()), 55509 classes: 'charactercount', 55510 disabled: editor.settings.readonly 55511 }, 0); 55512 55513 editor.on('setcontent beforeaddundo', update); 55514 55515 editor.on('keyUp', function (e) { 55516 update(); 55517 }); 55518 }, 0); 55519 } 55520 }); 55521}); 55522'use strict'; 55523 55524var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55525 55526function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55527 55528var ConditionalInput = (function () { 55529 function ConditionalInput(args) { 55530 _classCallCheck(this, ConditionalInput); 55531 55532 if (args.control.length == 0) return; 55533 55534 this.args = args; 55535 this.$control = this.args.control; 55536 this.inputType = this.args.inputType || 'text'; 55537 this.setVisible(this.inputValue()); 55538 this.$control.on('change', this.onChange.bind(this)); 55539 } 55540 55541 _createClass(ConditionalInput, [{ 55542 key: 'onChange', 55543 value: function onChange(e) { 55544 var value = this.inputValue(this.$control); 55545 this.setVisible(value); 55546 } 55547 }, { 55548 key: 'setVisible', 55549 value: function setVisible(value) { 55550 for (var controlValue in this.args.visibility) { 55551 var possibleValues = controlValue.split('|'); 55552 if (possibleValues.indexOf(value) != -1) { 55553 this.args.visibility[controlValue].show(); 55554 } else { 55555 this.args.visibility[controlValue].hide(); 55556 } 55557 } 55558 } 55559 }, { 55560 key: 'inputValue', 55561 value: function inputValue() { 55562 var value = ''; 55563 switch (this.inputType) { 55564 case 'checkbox': 55565 value = this.$control.is(':checked') ? 'on' : 'off'; 55566 break; 55567 case 'radio': 55568 value = this.$control.filter(':checked').val(); 55569 break; 55570 default: 55571 value = this.$control.val(); 55572 } 55573 return value; 55574 } 55575 }]); 55576 55577 return ConditionalInput; 55578})(); 55579'use strict'; 55580 55581var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55582 55583function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55584 55585var DonorFormValidator = (function () { 55586 function DonorFormValidator(args) { 55587 var _this = this; 55588 55589 _classCallCheck(this, DonorFormValidator); 55590 55591 this.$form = args.form; 55592 this.submitHandler = args.submitHandler; 55593 55594 var formHasBeenValidated = false;
55595 55596 var formOptions = $.extend({}, formValidateDefaults, { 55597 rules: { 55598 'donation[gift_name_other]': { 55599 required: function required() { 55600 return _this.isGiftOther(); 55601 } 55602 }, 55603 'donation[state]': { 55604 required: function required() { 55605 return _this.stateRequired(); 55606 } 55607 }, 55608 'donation[postal]': { 55609 required: function required() { 55610 return _this.postalRequired(); 55611 } 55612 }, 55613 'donation[cc_number]': { 55614 required: function required() { 55615 return _this.isCreditCard(); 55616 }, 55617 creditcard: true 55618 }, 55619 'donation[cc_code]': { 55620 required: function required() { 55621 return _this.isCreditCard(); 55622 } 55623 }, 55624 55625 'donation[check_account_number]': { 55626 required: function required() { 55627 return _this.isEcheck(); 55628 } 55629 }, 55630 'donation[check_routing_number]': { 55631 required: function required() { 55632 return _this.isEcheck(); 55633 }, 55634 minlength: 9 55635 }, 55636 'donation[payment_method_check_name]': { 55637 required: function required() { 55638 return _this.isEcheck(); 55639 } 55640 }, 55641 'donation[payment_method_bank_name]': { 55642 required: function required() { 55643 return _this.isEcheck(); 55644 } 55645 }, 55646 'donation[donation_amount_custom]': { 55647 min: 1 55648 }, 55649 55650 'donation[tribute_attributes][tribute_name]': { 55651 required: function required() { 55652 return _this.tributeEnabled(); 55653 } 55654 }, 55655 'donation[tribute_attributes][email]': { 55656 required: function required() { 55657 return _this.tributeEnabled() && _this.tributeIsEmail(); 55658 } 55659 }, 55660 'donation[tribute_attributes][first_name]': { 55661 required: function required() { 55662 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55663 } 55664 }, 55665 'donation[tribute_attributes][last_name]': { 55666 required: function required() { 55667 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55668 } 55669 }, 55670 'donation[tribute_attributes][address_1]': { 55671 required: function required() { 55672 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55673 } 55674 }, 55675 'donation[tribute_attributes][city]': { 55676 required: function required() { 55677 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55678 } 55679 }, 55680 'donation[tribute_attributes][state]': { 55681 required: function required() { 55682 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55683 } 55684 }, 55685 'donation[tribute_attributes][postal]': { 55686 required: function required() { 55687 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55688 } 55689 }, 55690 'donation[tribute_attributes][country]': { 55691 required: function required() { 55692 return _this.tributeEnabled() && _this.tributeIsSnailmail(); 55693 } 55694 }, 55695 'hidden_recaptcha': { 55696 required: function required() { 55697 if (grecaptcha.getResponse() == '') { 55698 return true; 55699 } else { 55700 return false; 55701 } 55702 } 55703 } 55704 }, 55705 55706 messages: { 55707 'donation[donation_amount_custom]': { 55708 required: 'Amount is required', 55709 min: 'Amount must be at least $1.00' 55710 }, 55711 'donation[first_name]': 'First Name is required', 55712 'donation[last_name]': 'Last Name is required', 55713 'donation[email]': { 55714 required: 'Valid email is required', 55715 email: 'Valid email is required' 55716 }, 55717 'donation[address_1]': 'Address 1 is required', 55718 'donation[city]': 'City is required', 55719 'donation[state]': 'State / Province is required', 55720 'donation[postal]': 'Zip / Postal Code is required', 55721 55722 'donation[gift_name_other]': 'Other Gift Designation is required', 55723 55724 'donation[tribute_attributes][tribute_name]': 'Honoree Name is required', 55725 'donation[tribute_attributes][first_name]': 'Recipient First Name is required', 55726 'donation[tribute_attributes][last_name]': 'Recipient Last Name is required', 55727 'donation[tribute_attributes][email]': { 55728 required: 'Valid Recipient Email is required', 55729 email: 'Valid Recipient Email is required' 55730 }, 55731 'donation[tribute_attributes][address_1]': 'Recipient Address 1 is required', 55732 'donation[tribute_attributes][city]': 'Recipient City is required', 55733 'donation[tribute_attributes][state]': 'Recipient State / Province is required', 55734 'donation[tribute_attributes][postal]': 'Recipient Zip / Postal Code is required', 55735 55736 'donation[cc_number]': 'Valid Credit Card Number is required', 55737 'donation[cc_expiry_month]': 'Credit Card Expiration Month is required', 55738 'donation[cc_expiry_year]': 'Credit Card Expiration Year is required', 55739 'donation[cc_code]': 'Credit Card CVV Code is required', 55740 'donation[check_account_number]': 'Valid Bank Account Number is required', 55741 'donation[check_routing_number]': 'Valid Bank Routing Number is required', 55742 'donation[payment_method_check_name]': 'Name on Bank Account is required', 55743 'donation[payment_method_bank_name]': 'Bank Name is required', 55744 'donation[google_pay_token]': 'Google Pay button must be clicked before submitting', 55745 'hidden_recaptcha': 'To prove that you are a human, the CAPTCHA box must be checked' 55746 }, 55747 55748 focusInvalid: false, 55749
55750 invalidHandler: function invalidHandler(event, validator) { 55751 formHasBeenValidated = true; 55752 55753 var errors = validator.errorList.map(function (error) { 55754 return '<li>' + error.message + '</li>'; 55755 }); 55756 55757 var message = 'One or more fields are incomplete or incorrect. Please correct the following items: '; 55758 message += '<ul>' + errors.join('') + '</ul>'; 55759 $('#error-messages p').html(message); 55760 $('#error-messages').show(); 55761 55762 $('html, body').animate({ scrollTop: 0 }, 200); 55763 }, 55764 55765 submitHandler: function submitHandler(form, event) { 55766 return regeneratorRuntime.async(function submitHandler$(context$3$0) { 55767 while (1) switch (context$3$0.prev = context$3$0.next) { 55768 case 0: 55769 event.preventDefault(); 55770 55771 this.submitHandler(form, event); 55772 55773 case 2: 55774 case 'end': 55775 return context$3$0.stop(); 55776 } 55777 }, null, _this); 55778 } 55779 }); 55780 55781 this.$form.validate(formOptions); 55782 55783 this.$form.find('#donation_country').on('change', function () { 55784 if (formHasBeenValidated) { 55785 _this.$form.find('#donation_postal').valid(); 55786 } 55787 }); 55788 } 55789 55790 _createClass(DonorFormValidator, [{ 55791 key: 'isCreditCard', 55792 value: function isCreditCard() { 55793 return this.paymentMethodValue() == 'credit_card'; 55794 } 55795 }, { 55796 key: 'isEcheck', 55797 value: function isEcheck() { 55798 return this.paymentMethodValue() == 'echeck'; 55799 } 55800 }, { 55801 key: 'isGiftOther', 55802 value: function isGiftOther() { 55803 return this.$form.find('#donation_gift_name').val() == 'Other'; 55804 } 55805 }, { 55806 key: 'isGooglePay', 55807 value: function isGooglePay() { 55808 return this.paymentMethodValue() == 'google_pay'; 55809 } 55810 }, { 55811 key: 'paymentMethodValue', 55812 value: function paymentMethodValue() { 55813 if (this.$form.find('[name="donation[payment_method_type]"]:radio').length > 0) { 55814 return this.$form.find('[name="donation[payment_method_type]"]:checked').val(); 55815 } 55816 return this.$form.find('[name="donation[payment_method_type]"]').val(); 55817 } 55818 }, { 55819 key: 'tributeEnabled', 55820 value: function tributeEnabled() { 55821 return this.$form.find('[name="donation[tribute_enabled]"]').is(':checked'); 55822 } 55823 }, { 55824 key: 'tributeIsEmail', 55825 value: function tributeIsEmail() { 55826 return this.$form.find('#donation_tribute_attributes_notification_type_email').is(':checked'); 55827 } 55828 }, { 55829 key: 'tributeIsSnailmail', 55830 value: function tributeIsSnailmail() { 55831 return this.$form.find('#donation_tribute_attributes_notification_type_snailmail').is(':checked'); 55832 } 55833 }, { 55834 key: 'stateRequired', 55835 value: function stateRequired() { 55836 return ['US', 'CA'].indexOf(this.$form.find('#donation_country').val()) != -1; 55837 } 55838 }, { 55839 key: 'postalRequired', 55840 value: function postalRequired() { 55841 return ['US', 'CA'].indexOf(this.$form.find('#donation_country').val()) != -1; 55842 } 55843 }]); 55844 55845 return DonorFormValidator; 55846})(); 55847'use strict'; 55848 55849var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55850 55851function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55852 55853var CountrySelector = (function () { 55854 function CountrySelector(args) { 55855 _classCallCheck(this, CountrySelector); 55856 55857 this.$country = args.country; 55858 this.stateInputName = args.stateInputName; 55859 this.stateContainer = args.stateContainer, $(document).on('change', this.$country, this.onCountryChange.bind(this)); 55860 } 55861 55862 _createClass(CountrySelector, [{ 55863 key: 'onCountryChange', 55864 value: function onCountryChange(e) { 55865 this.checkState(); 55866 } 55867 }, { 55868 key: 'checkState', 55869 value: function checkState() { 55870 var country = $(this.$country).val(); 55871 $.get('/api/country_selector', { 55872 country: country, 55873 state_input_name: this.stateInputName, 55874 state_container: this.stateContainer 55875 }); 55876 } 55877 }]); 55878 55879 return CountrySelector; 55880})(); 55881'use strict'; 55882 55883var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55884 55885function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55886 55887var LabelSwitcher = (function () { 55888 function LabelSwitcher(args) { 55889 _classCallCheck(this, LabelSwitcher); 55890 55891 this.$control = args.control; 55892 this.$label = args.label; 55893 this.labelValueDefault = args.labelValueDefault; 55894 this.labelValues = args.labelValues; 55895 55896 $(document).on('change', this.$control, this.onChangeControl.bind(this)); 55897 this.setLabel(); 55898 } 55899 55900 _createClass(LabelSwitcher, [{ 55901 key: 'onChangeControl', 55902 value: function onChangeControl(e) { 55903 this.setLabel(); 55904 } 55905 }, { 55906 key: 'setLabel', 55907 value: function setLabel() { 55908 var controlValue = $(this.$control).val(); 55909 var index = Object.keys(this.labelValues).indexOf(controlValue); 55910 if (index > -1) { 55911 var labelValue = this.labelValues[controlValue]; 55912 $(this.$label).html(labelValue); 55913 } else { 55914 $(this.$label).html(this.labelValueDefault); 55915 } 55916 } 55917 }]); 55918 55919 return LabelSwitcher; 55920})(); 55921'use strict'; 55922 55923var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55924 55925function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55926 55927var PlaceholderSwitcher = (function () { 55928 function PlaceholderSwitcher(args) { 55929 _classCallCheck(this, PlaceholderSwitcher); 55930 55931 this.$control = args.control; 55932 this.$input = args.input; 55933 this.valueDefault = args.valueDefault; 55934 this.values = args.values; 55935 55936 $(document).on('change', this.$control, this.onChangeControl.bind(this)); 55937 this.setPlaceholder(); 55938 } 55939 55940 _createClass(PlaceholderSwitcher, [{ 55941 key: 'onChangeControl', 55942 value: function onChangeControl(e) { 55943 this.setPlaceholder(); 55944 } 55945 }, { 55946 key: 'setPlaceholder', 55947 value: function setPlaceholder() { 55948 var controlValue = $(this.$control).val(); 55949 var index = Object.keys(this.values).indexOf(controlValue); 55950 if (index > -1) { 55951 var placeholderValue = this.values[controlValue]; 55952 $(this.$input).prop('placeholder', placeholderValue); 55953 } else { 55954 $(this.$input).prop('placeholder', this.valueDefault); 55955 } 55956 } 55957 }]); 55958 55959 return PlaceholderSwitcher; 55960})(); 55961'use strict'; 55962 55963var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 55964 55965function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 55966 55967var Tabs = function Tabs() { 55968 _classCallCheck(this, Tabs); 55969 55970 $('.tabs[data-tab-group]').each(function (index, el) { 55971 new Tab($(el)); 55972 }); 55973}; 55974 55975var Tab = (function () { 55976 function Tab($el) { 55977 var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; 55978 55979 _classCallCheck(this, Tab); 55980 55981 this.$el = $el; 55982 this.tabGroup = this.$el.attr('data-tab-group'); 55983 this.$tabGroup = $('.tab-group[data-tab-group="' + this.tabGroup + '"]'); 55984 this.activeClass = options.activeClass || 'is-active'; 55985 this.$el.find('.tabs__item').on('click', this.onTabClick.bind(this)); 55986 55987 var $firstTab = this.$el.find('.tabs__item').first(); 55988 this.setActiveTab($firstTab); 55989 this.setActiveTabContent($firstTab); 55990 } 55991 55992 _createClass(Tab, [{ 55993 key: 'onTabClick', 55994 value: function onTabClick(e) { 55995 e.preventDefault(); 55996 var $tab = $(e.currentTarget); 55997 55998 if (this.isActive($tab)) { 55999 return; 56000 } 56001 56002 this.setActiveTab($tab); 56003 this.setActiveTabContent($tab); 56004 } 56005 }, { 56006 key: 'setActiveTab', 56007 value: function setActiveTab($tab) { 56008 this.$el.find('.tabs__item').removeClass(this.activeClass); 56009 $tab.addClass(this.activeClass); 56010 } 56011 }, { 56012 key: 'setActiveTabContent', 56013 value: function setActiveTabContent($tab) { 56014 var tabValue = $tab.attr('data-tab'); 56015 this.$tabGroup.find('[data-tab]').removeClass(this.activeClass); 56016 this.$tabGroup.find('[data-tab="' + tabValue + '"]').addClass(this.activeClass); 56017 } 56018 }, { 56019 key: 'isActive', 56020 value: function isActive($tab) { 56021 return $tab.hasClass(this.activeClass); 56022 } 56023 }]); 56024 56025 return Tab; 56026})(); 56027'use strict'; 56028 56029function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56030 56031var ModalHandler = function ModalHandler() { 56032 _classCallCheck(this, ModalHandler); 56033 56034 $(document).on('click', '[data-modal-trigger]', function (e) { 56035 e.preventDefault(); 56036 var selector = $(e.currentTarget).attr('data-modal-trigger'); 56037 Modal.open($(selector)); 56038 }); 56039
56040 $(document).on('click', '.modal-overlay, .modal-close, [data-modal-close]', function (e) { 56041 e.preventDefault(); 56042 Modal.close($(e.currentTarget).parents('.modal')); 56043 }); 56044 56045 $(document).on('keydown', function (event) { 56046 if (event.key == 'Escape') { 56047 Modal.close($('.modal')); 56048 } 56049 }); 56050}; 56051'use strict'; 56052 56053var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56054 56055function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56056 56057var FundraiserSearch = (function () { 56058 function FundraiserSearch(args) { 56059 _classCallCheck(this, FundraiserSearch); 56060 56061 this.$control = $(args.control); 56062 if (this.$control.length === 0) { 56063 return; 56064 } 56065 this.setup(); 56066 } 56067 56068 _createClass(FundraiserSearch, [{ 56069 key: 'setup', 56070 value: function setup() { 56071 var searchUrl = this.$control.attr('data-search-url'); 56072 56073 if (!searchUrl) { 56074 var msg = 'FundraiserSearch requires the attribute `data-search-url` '; 56075 msg += 'to be defined on the search input. '; 56076 msg += 'This value must point to a search endpoint'; 56077 console.error(msg); 56078 return; 56079 } 56080 56081 this.$control.autocomplete({ 56082 serviceUrl: searchUrl, 56083 onSelect: function onSelect(suggestion) { 56084 var redirectUrl = suggestion.data.url; 56085 if (redirectUrl) { 56086 window.location = redirectUrl; 56087 } else { 56088 var msg = 'FundraiserSearch error: no `url` was provided in '; 56089 msg += 'the `data` payload for the selected search result.'; 56090 console.error(msg); 56091 } 56092 } 56093 }); 56094 } 56095 }]); 56096 56097 return FundraiserSearch; 56098})(); 56099'use strict'; 56100 56101var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56102 56103function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56104 56105var MoreLoader = (function () { 56106 function MoreLoader(args) { 56107 _classCallCheck(this, MoreLoader); 56108 56109 this.$control = $(args.control); 56110 this.$container = $(args.container); 56111 56112 if (this.$control.length === 0 || this.$container.length === 0) { 56113 return; 56114 } 56115 56116 this.endpoint = args.endpoint; 56117 this.totalPages = args.totalPages; 56118 this.requestType = args.requestType || 'GET'; 56119 this.currentPage = args.currentPage || 1; 56120 this.loadingText = args.loadingText || 'Loading...'; 56121 this.originalText = this.$control.html(); 56122 56123 this.$control.on('click', this.onClickTrigger.bind(this)); 56124 } 56125 56126 _createClass(MoreLoader, [{ 56127 key: 'onClickTrigger', 56128 value: function onClickTrigger(e) { 56129 var _this = this; 56130 56131 e.preventDefault(); 56132 56133 this.disableControl(); 56134 56135 $.ajax({ 56136 url: this.endpoint, 56137 type: this.requestType, 56138 data: { page: this.getNextPage() } 56139 }).done(function (response) { 56140 if (response) { 56141 _this.$container.append(response); 56142 } else { 56143 _this.hideControl(); 56144 } 56145 }).fail(function (error) { 56146 console.error('MoreLoader error: ' + error); 56147 }).always(function () { 56148 _this.enableControl(); 56149 _this.checkForLastPage(); 56150 }); 56151 } 56152 }, { 56153 key: 'getNextPage', 56154 value: function getNextPage() { 56155 return ++this.currentPage; 56156 } 56157 }, { 56158 key: 'disableControl', 56159 value: function disableControl() { 56160 this.$control.prop('disabled', true).html(this.loadingText); 56161 } 56162 }, { 56163 key: 'enableControl', 56164 value: function enableControl() { 56165 this.$control.prop('disabled', false).html(this.originalText); 56166 } 56167 }, { 56168 key: 'hideControl', 56169 value: function hideControl() { 56170 this.$control.hide(); 56171 } 56172 }, { 56173 key: 'checkForLastPage', 56174 value: function checkForLastPage() { 56175 if (this.currentPage >= this.totalPages) { 56176 this.hideControl(); 56177 } 56178 } 56179 }]); 56180 56181 return MoreLoader; 56182})(); 56183'use strict'; 56184 56185var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56186 56187function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56188 56189var PasswordShow = (function () { 56190 function PasswordShow(args) { 56191 _classCallCheck(this, PasswordShow); 56192 56193 this.$passwordField = $(args.passwordField); 56194 this.$showControl = $(args.showControl); 56195 56196 if (this.$passwordField.length === 0 || this.$showControl.length === 0) { 56197 return; 56198 } 56199 56200 this.$showControl.on('click', this.onClickShowControl.bind(this)); 56201 } 56202 56203 _createClass(PasswordShow, [{ 56204 key: 'onClickShowControl', 56205 value: function onClickShowControl(e) { 56206 e.preventDefault(); 56207 56208 var currentType = this.$passwordField.attr('type'); 56209 if (currentType === 'password') { 56210 this.$passwordField.attr('type', 'text'); 56211 this.$showControl.html('Hide'); 56212 } else { 56213 this.$passwordField.attr('type', 'password'); 56214 this.$showControl.html('Show'); 56215 } 56216 } 56217 }]); 56218 56219 return PasswordShow; 56220})(); 56221'use strict'; 56222 56223var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56224 56225function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56226 56227var BillingInfoCookie = (function () { 56228 function BillingInfoCookie(args) { 56229 _classCallCheck(this, BillingInfoCookie); 56230 56231 this.modelName = args.modelName; 56232 this.initialize(); 56233 } 56234 56235 _createClass(BillingInfoCookie, [{ 56236 key: 'initialize', 56237 value: function initialize() { 56238 var _this = this; 56239 56240 $(document).on('click', '[data-clear-billing-info]', function (event) { 56241 event.preventDefault(); 56242 56243 var cookie = Cookies.get('fagc_billing_info'); 56244 56245 if (!cookie) { 56246 return; 56247 } 56248 56249 $('[data-clear-billing-info-wrapper]').hide(); 56250 56251 $.each(JSON.parse(cookie), function (inputName) { 56252 if (inputName === 'country') { 56253 $('[name="' + _this.modelName + '[' + inputName + ']"]').val('US').change(); 56254 } else { 56255 $('[name="' + _this.modelName + '[' + inputName + ']"]').val(''); 56256 } 56257 }); 56258 56259 Cookies.remove('fagc_billing_info'); 56260 }); 56261 } 56262 }]); 56263 56264 return BillingInfoCookie; 56265})(); 56266'use strict'; 56267 56268var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56269 56270function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56271 56272var GooglePay = (function () { 56273 function GooglePay(args) { 56274 _classCallCheck(this, GooglePay); 56275 56276 this.gateway = args.gateway; 56277 this.key = args.key; 56278 this.env = args.env; 56279 this.merchantId = args.merchantId; 56280 this.paymentsClient = null; 56281 this.tokenField = args.tokenField; 56282 this.lastDigitsField = args.lastDigitsField; 56283 this.expiryMonthField = args.expiryMonthField; 56284 this.expiryYearField = args.expiryYearField; 56285 this.nonprofitName = args.nonprofitName; 56286 this.onSubmit = args.onSubmit; 56287 this.getTotalAmount = args.getTotalAmount; 56288 this.$form = args.$form; 56289 56290 this.baseRequest = { 56291 apiVersion: 2, 56292 apiVersionMinor: 0 56293 }; 56294 56295 this.stripeTokenizationSpecification = { 56296 type: 'PAYMENT_GATEWAY', 56297 parameters: { 56298 'gateway': 'stripe', 56299 "stripe:version": "2018-10-31", 56300 'stripe:publishableKey': this.key 56301 } 56302 }; 56303 56304 this.authorizeNetTokenizationSpecification = { 56305 type: 'PAYMENT_GATEWAY', 56306 parameters: { 56307 'gateway': 'authorizenet', 56308 'gatewayMerchantId': this.key 56309 } 56310 }; 56311 56312 this.allowedCardNetworks = ["AMEX", "DISCOVER", "INTERAC", "JCB", "MASTERCARD", "VISA"]; 56313 this.allowedCardAuthMethods = ["PAN_ONLY", "CRYPTOGRAM_3DS"]; 56314 56315 this.baseCardPaymentMethod = { 56316 type: 'CARD', 56317 parameters: { 56318 allowedAuthMethods: this.allowedCardAuthMethods, 56319 allowedCardNetworks: this.allowedCardNetworks 56320 } 56321 }; 56322 56323 this.cardPaymentMethod = Object.assign({ tokenizationSpecification: this.getTokenizationSpecification() }, this.baseCardPaymentMethod); 56324 } 56325 56326 _createClass(GooglePay, [{ 56327 key: 'checkVisibility', 56328 value: function checkVisibility(args) { 56329 var paymentsClient = this.getGooglePaymentsClient(); 56330 paymentsClient.isReadyToPay(this.getGoogleIsReadyToPayRequest()).then(function (response) { 56331 if (response.result) { 56332 args.onSuccess(); 56333 } 56334 })['catch'](function (err) { 56335 console.error(err); 56336 }); 56337 } 56338 }, { 56339 key: 'getGoogleIsReadyToPayRequest', 56340 value: function getGoogleIsReadyToPayRequest() { 56341 return Object.assign({}, this.baseRequest, { 56342 allowedPaymentMethods: [this.baseCardPaymentMethod] 56343 }); 56344 } 56345 }, { 56346 key: 'getGooglePaymentsClient', 56347 value: function getGooglePaymentsClient() { 56348 if (this.paymentsClient === null) { 56349 this.paymentsClient = new google.payments.api.PaymentsClient({ environment: this.env }); 56350 } 56351 return this.paymentsClient; 56352 } 56353 }, { 56354 key: 'onGooglePaymentButtonClicked', 56355 value: function onGooglePaymentButtonClicked() { 56356 var _this = this; 56357 56358 var paymentDataRequest = this.getGooglePaymentDataRequest(); 56359 paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo(); 56360 56361 var paymentsClient = this.getGooglePaymentsClient(); 56362 paymentsClient.loadPaymentData(paymentDataRequest).then(function (paymentData) { 56363 _this.processPayment(paymentData); 56364 })['catch'](function (err) { 56365 console.error(err); 56366 }); 56367 } 56368 }, { 56369 key: 'getGooglePaymentDataRequest', 56370 value: function getGooglePaymentDataRequest() { 56371 var paymentDataRequest = Object.assign({}, this.baseRequest); 56372 paymentDataRequest.allowedPaymentMethods = [this.cardPaymentMethod]; 56373 paymentDataRequest.transactionInfo = this.getGoogleTransactionInfo(); 56374 paymentDataRequest.merchantInfo = { 56375 merchantName: this.nonprofitName, 56376 merchantId: this.merchantId 56377 }; 56378 return paymentDataRequest; 56379 } 56380 }, { 56381 key: 'getGoogleTransactionInfo', 56382 value: function getGoogleTransactionInfo() { 56383 return { 56384 countryCode: 'US', 56385 currencyCode: 'USD', 56386 totalPriceStatus: 'FINAL', 56387 totalPrice: '' + this.getTotalAmount() 56388 }; 56389 } 56390 }, { 56391 key: 'processPayment', 56392 value: function processPayment(paymentData) { 56393 var finalToken = null; 56394 var paymentToken = paymentData.paymentMethodData.tokenizationData.token; 56395 56396 switch (this.gateway) { 56397 case 'stripe': 56398 var parsedToken = JSON.parse(paymentToken); 56399 finalToken = parsedToken.id; 56400 $('#' + this.expiryMonthField).val(parsedToken.card.exp_month); 56401 $('#' + this.expiryYearField).val(parsedToken.card.exp_year); 56402 break; 56403 56404 case 'authorize_net': 56405 finalToken = window.btoa(paymentToken); 56406 break; 56407 } 56408
56409 var cardDetails = paymentData.paymentMethodData.info.cardDetails; 56410 56411 $('#' + this.lastDigitsField).val(cardDetails); 56412 $('#' + this.tokenField).val(finalToken); 56413 56414 this.onSubmit(); 56415 } 56416 }, { 56417 key: 'getTokenizationSpecification', 56418 value: function getTokenizationSpecification() { 56419 switch (this.gateway) { 56420 case 'authorize_net': 56421 return this.authorizeNetTokenizationSpecification; 56422 56423 case 'stripe': 56424 return this.stripeTokenizationSpecification; 56425 } 56426 } 56427 }]); 56428 56429 return GooglePay; 56430})(); 56431'use strict'; 56432 56433var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56434 56435function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56436 56437var ApplePay = (function () { 56438 function ApplePay(args) { 56439 _classCallCheck(this, ApplePay); 56440 56441 this.applePaymentSessionUrl = args.applePaymentSessionUrl; 56442 this.appleProcessUrl = args.appleProcessUrl; 56443 this.tokenField = args.tokenField; 56444 this.lastDigitsField = args.lastDigitsField; 56445 this.nonprofitName = args.nonprofitName; 56446 this.onSubmit = args.onSubmit; 56447 this.merchantIdentifier = args.merchantIdentifier; 56448 this.getTotalAmount = args.getTotalAmount; 56449 this.$form = args.$form; 56450 } 56451 56452 _createClass(ApplePay, [{ 56453 key: 'checkVisibility', 56454 value: function checkVisibility(args) { 56455 if (!window.ApplePaySession) { 56456 return; 56457 } 56458 56459 ApplePaySession.canMakePaymentsWithActiveCard(this.merchantIdentifier).then(function (canMakePayments) { 56460 console.log('[Apple Pay] Successful Apple Pay call to canMakePaymentsWithActiveCard'); 56461 console.log('[Apple Pay] canMakePayments: ', canMakePayments); 56462 if (canMakePayments) { 56463 args.onSuccess(); 56464 } 56465 })['catch'](function (err) { 56466 console.log('[Apple Pay] canMakePaymentsWithActiveCard returned an error'); 56467 console.error(err); 56468 }); 56469 } 56470 }, { 56471 key: 'onApplePayButtonClicked', 56472 value: function onApplePayButtonClicked() { 56473 var _this = this; 56474 56475 if (!window.ApplePaySession) { 56476 return; 56477 } 56478 56479 var request = { 56480 "countryCode": "US", 56481 "currencyCode": "USD", 56482 "merchantCapabilities": ["supports3DS"], 56483 "supportedNetworks": ["visa", "masterCard", "amex", "discover"], 56484 "total": { 56485 "label": this.nonprofitName, 56486 "type": "final", 56487 "amount": this.getTotalAmount() 56488 } 56489 }; 56490 56491 var session = new ApplePaySession(3, request); 56492 56493 session.onvalidatemerchant = function callee$2$0(event) { 56494 var merchantSession; 56495 return regeneratorRuntime.async(function callee$2$0$(context$3$0) { 56496 while (1) switch (context$3$0.prev = context$3$0.next) { 56497 case 0: 56498 context$3$0.prev = 0; 56499 56500 console.log('[Apple Pay] validating merchant, session url:', this.applePaymentSessionUrl); 56501 context$3$0.next = 4; 56502 return regeneratorRuntime.awrap(this.validateMerchant()); 56503 56504 case 4: 56505 merchantSession = context$3$0.sent; 56506 56507 console.log('[Apple Pay] merchant session:', merchantSession); 56508 session.completeMerchantValidation(merchantSession); 56509 context$3$0.next = 12; 56510 break; 56511 56512 case 9: 56513 context$3$0.prev = 9; 56514 context$3$0.t0 = context$3$0['catch'](0); 56515 56516 console.error('[Apple Pay] merchant validation failed:', context$3$0.t0); 56517 56518 case 12: 56519 case 'end': 56520 return context$3$0.stop(); 56521 } 56522 }, null, _this, [[0, 9]]); 56523 }; 56524 56525 session.onpaymentmethodselected = function (event) { 56526 console.log('[Apple Pay] onpaymentmethodselected:', event); 56527 try { 56528 var amount = _this.getTotalAmount(); 56529 console.log('[Apple Pay] getTotalAmount:', amount); 56530 56531 var update = { 56532 newTotal: { 56533 type: 'final', 56534 label: _this.nonprofitName, 56535 amount: amount 56536 } 56537 }; 56538 56539 session.completePaymentMethodSelection(update); 56540 } catch (err) { 56541 console.error('[Apple Pay] onpaymentmethodselected failed:', err); 56542 } 56543 }; 56544 56545 session.onpaymentauthorized = function (event) { 56546 console.log('[Apple Pay] onpaymentauthorized:', event); 56547 try { 56548 var finalToken = JSON.stringify(event.payment.token.paymentData); 56549 var lastFour = event.payment.token.paymentMethod.displayName.slice(-4); 56550 56551 $('#' + _this.tokenField).val(finalToken); 56552 $('#' + _this.lastDigitsField).val(lastFour); 56553 56554 var result = { 56555 "status": ApplePaySession.STATUS_SUCCESS 56556 }; 56557 56558 session.completePayment(result); 56559 56560 _this.onSubmit(); 56561 } catch (err) { 56562 console.error('[Apple Pay] onpaymentauthorized failed:', err); 56563 } 56564 }; 56565 56566 session.oncancel = function (event) { 56567 console.warn('[Apple Pay] session.oncancel fired - WebKit aborted the sheet:', event); 56568 }; 56569 56570 console.log('[Apple Pay] page origin:', window.location.hostname); 56571 session.begin(); 56572 } 56573 }, { 56574 key: 'validateMerchant', 56575 value: function validateMerchant() { 56576 var response; 56577 return regeneratorRuntime.async(function validateMerchant$(context$2$0) { 56578 while (1) switch (context$2$0.prev = context$2$0.next) { 56579 case 0: 56580 context$2$0.prev = 0; 56581 context$2$0.next = 3; 56582 return regeneratorRuntime.awrap($.ajax({ url: this.applePaymentSessionUrl })); 56583 56584 case 3: 56585 response = context$2$0.sent; 56586 56587 console.log('[Apple Pay] validateMerchant response:', response); 56588 return context$2$0.abrupt('return', response); 56589 56590 case 8: 56591 context$2$0.prev = 8; 56592 context$2$0.t0 = context$2$0['catch'](0); 56593 56594 console.error('[Apple Pay] validateMerchant ajax error:', context$2$0.t0); 56595 throw context$2$0.t0; 56596 56597 case 12: 56598 case 'end': 56599 return context$2$0.stop(); 56600 } 56601 }, null, this, [[0, 8]]); 56602 } 56603 }]); 56604 56605 return ApplePay; 56606})(); 56607'use strict'; 56608 56609var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56610 56611function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56612 56613var CommercePage = (function () { 56614 function CommercePage() { 56615 _classCallCheck(this, CommercePage); 56616 56617 this.initialize(); 56618 } 56619 56620 _createClass(CommercePage, [{ 56621 key: 'initialize', 56622 value: function initialize() { 56623 var _this = this; 56624 56625 // Product: "Show / Hide Info" 56626 $('[data-toggle-trigger="product-info"]').on('click', function (event) { 56627 event.preventDefault(); 56628 var isShowing = $(this).attr('data-toggle-showing'); 56629 var $parent = $(this).closest('[data-product-choice]'); 56630 var $productInfo = $parent.find('[data-toggle="product-info"]'); 56631 56632 if (isShowing === 'true') { 56633 $productInfo.hide(); 56634 $(this).attr('data-toggle-showing', 'false').html('More Info'); 56635 } else { 56636 $productInfo.show(); 56637 $(this).attr('data-toggle-showing', 'true').html('Less Info'); 56638 } 56639 }); 56640 56641 // When any quantity value changes, update 56642 // quantity and cost counter. 56643 $('[data-product-cost]').on('change', function () { 56644 _this.updateTotalQuantity(); 56645 _this.updateTotalCost(); 56646 }); 56647 56648 // When any quantity value changes, update 56649 // quantity and cost counter. 56650 $('#cart_donation').on('keyup', function (event) { 56651 _this.updateTotalCost(); 56652 }); 56653 56654 // Coupon visibility handler 56655 $('[data-coupon="toggle"]').on('click', function (event) { 56656 event.preventDefault(); 56657 $(this).hide(); 56658 $('.cart_coupon_code').show(); 56659 }); 56660 56661 // If a coupon code exists, show the input 56662 // and hide the control. 56663 if ($('#cart_coupon_code').val()) { 56664 $('[data-coupon="toggle"]').hide(); 56665 } else { 56666 $('.cart_coupon_code').hide(); 56667 } 56668 56669 $('.product-form').validate(window.formValidateDefaults); 56670 56671 this.updateTotalQuantity(); 56672 this.updateTotalCost(); 56673 56674 // Reveals the hidden description on small screens 56675 $('[data-commerce-page-description-read-more]').on('click', function (event) { 56676 event.preventDefault(); 56677 $('[data-commerce-page-description-overlay]').hide(); 56678 $('[data-commerce-page-description]').addClass('donate-body__expanded'); 56679 }); 56680 } 56681 }, { 56682 key: 'updateTotalQuantity', 56683 value: function updateTotalQuantity() { 56684 var totalQuantity = 0; 56685 56686 $('[data-product-cost]').each(function (index, el) { 56687 totalQuantity += parseInt($(this).val()); 56688 }); 56689 56690 var formattedQuantity = numeral(totalQuantity).format('0,0'); 56691 $('[data-commerce-page-quantity]').html(formattedQuantity); 56692 } 56693 }, { 56694 key: 'updateTotalCost', 56695 value: function updateTotalCost() { 56696 var totalCost = 0; 56697 56698 $('[data-product-cost]').each(function (index, el) { 56699 var quantity = parseInt($(this).val()); 56700 var cost = parseFloat($(this).attr('data-product-cost')); 56701 totalCost += quantity * cost; 56702 }); 56703 56704 var donation = parseFloat($('#cart_donation').val()); 56705 if (donation) { 56706 totalCost += donation; 56707 } 56708 56709 var formattedCost = numeral(totalCost).format('$0,0.00'); 56710 $('[data-commerce-page-cost]').html(formattedCost); 56711 } 56712 }]); 56713 56714 return CommercePage; 56715})(); 56716'use strict'; 56717 56718var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 56719 56720function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 56721 56722var OrderPage = (function () { 56723 function OrderPage(args) { 56724 _classCallCheck(this, OrderPage); 56725 56726 this.formHasBeenValidated = false;
56727 this.paymentFieldsUrl = args.paymentFieldsUrl; 56728 this.changeProcessingFeeUrl = args.changeProcessingFeeUrl; 56729 this.$form = $('#new_order'); 56730 this.submitButtonSelector = '[data-donation-submit]'; 56731 this.stripeApiKey = args.stripeApiKey; 56732 this.stripePaymentType = null; 56733 this.initialTotalInCents = args.initialTotalInCents; 56734 this.gateway = args.gateway; 56735 this.requiresTransaction = args.requiresTransaction; 56736 this.googlePayEnabled = args.googlePayEnabled; 56737 this.googlePayKey = args.googlePayKey; 56738 this.googlePayEnv = args.googlePayEnv; 56739 this.googlePayMerchantId = args.googlePayMerchantId; 56740 this.applePayEnabled = args.applePayEnabled; 56741 this.applePaymentSessionUrl = args.applePaymentSessionUrl; 56742 this.appleProcessUrl = args.appleProcessUrl; 56743 this.nonprofitName = args.nonprofitName; 56744 this.applePayMerchantIdentifier = args.applePayMerchantIdentifier; 56745 this.mailingFieldsEnabled = args.mailingFieldsEnabled; 56746 this.payPalEnabled = args.payPalEnabled; 56747 this.payPalOrderUrl = args.payPalOrderUrl; 56748 56749 this.initialize(); 56750 } 56751 56752 _createClass(OrderPage, [{ 56753 key: 'initialize', 56754 value: function initialize() { 56755 var _this = this; 56756 56757 $(document).on('change', '#order_processing_fee_included', function (event) { 56758 var form = $('#new_order').get(0); 56759 56760 $.ajax({ 56761 url: _this.changeProcessingFeeUrl, 56762 method: 'post', 56763 dataType: 'script', 56764 data: new FormData(form), 56765 processData: false, 56766 contentType: false 56767 }); 56768 }); 56769 56770 $(document).on('payment-method-change', function (event, data) { 56771 if (data.paymentMethod == 'credit_card') { 56772 _this.initializeStripeFields(); 56773 } else if (data.paymentMethod == 'paypal') { 56774 _this.initializePayPal(); 56775 } 56776 }); 56777 56778 $(document).on('payment-processing-fee-change', function (event, data) { 56779 if (data.paymentMethod == 'paypal') { 56780 _this.initializePayPal(); 56781 } 56782 }); 56783 56784 // Handles payment terms visibility 56785 new ConditionalInput({ 56786 inputType: 'radio', 56787 control: $('[name="order[payment_method_type]"][type="radio"]'), 56788 visibility: { 56789 'invoice': $('[data-terms-unprocessed-payment]'), 56790 'credit_card|echeck': $('[data-terms-processed-payment]') 56791 } 56792 }); 56793 56794 // Handles Order comment visibility 56795 new ConditionalInput({ 56796 inputType: 'checkbox', 56797 control: $('#order_comment_enabled'), 56798 visibility: { 56799 'on': $('.form-item--comment-fields') 56800 } 56801 }); 56802 56803 // Handles the switching of countries and showing 56804 // appropriate state / province 56805 new CountrySelector({ 56806 country: '#order_country', 56807 stateInputName: 'order[state]', 56808 stateContainer: '.form-fieldset--donation-state' 56809 }); 56810 56811 new LabelSwitcher({ 56812 control: '#order_country', 56813 label: $('label[for="order_postal"]'), 56814 labelValueDefault: 'Postal Code', 56815 labelValues: { 56816 'US': 'Zip Code' 56817 } 56818 }); 56819 56820 new PlaceholderSwitcher({ 56821 control: '#order_country', 56822 input: '#order_postal', 56823 values: { 56824 'US': '', 56825 'CA': '' 56826 }, 56827 valueDefault: 'optional' 56828 }); 56829 56830 new BillingInfoCookie({ modelName: 'order' }); 56831 56832 this.initializeCountryValidation(); 56833 this.initializePaymentFieldSwitcher(); 56834 this.initializeStripeFields(); 56835 this.initializeGooglePay(); 56836 this.initializeApplePay(); 56837 this.initializePayPal(); 56838 this.validateForm(); 56839 } 56840 }, { 56841 key: 'disableButtons', 56842 value: function disableButtons(isDisabled) { 56843 var $submitButton = $(this.submitButtonSelector); 56844 56845 if (isDisabled) { 56846 $submitButton.prop('disabled', true); 56847 } else { 56848 $submitButton.prop('disabled', false); 56849 } 56850 } 56851 }, { 56852 key: 'disableScroll', 56853 value: function disableScroll() { 56854 $('body').addClass('no-scroll'); 56855 } 56856 }, { 56857 key: 'initializeCountryValidation', 56858 value: function initializeCountryValidation() { 56859 var _this2 = this; 56860 56861 $('#order_country').on('change', function () { 56862 if (_this2.formHasBeenValidated) { 56863 $('#order_postal').valid(); 56864 } 56865 }); 56866 } 56867 }, { 56868 key: 'initializePaymentFieldSwitcher', 56869 value: function initializePaymentFieldSwitcher() { 56870 var _this3 = this; 56871 56872 $(document).on('change', '[name="order[payment_method_type]"]', function () { 56873 var form = $('#new_order').get(0); 56874 56875 $.ajax({ 56876 url: _this3.paymentFieldsUrl, 56877 method: 'post',
56878 dataType: 'script', 56879 data: new FormData(form), 56880 processData: false, 56881 contentType: false 56882 }); 56883 }); 56884 } 56885 }, { 56886 key: 'initializeApplePay', 56887 value: function initializeApplePay() { 56888 var _this4 = this; 56889 56890 if (!this.applePayEnabled) { 56891 return; 56892 } 56893 56894 this.applePay = new ApplePay({ 56895 applePaymentSessionUrl: this.applePaymentSessionUrl, 56896 appleProcessUrl: this.appleProcessUrl, 56897 totalAmount: this.totalAmount(), 56898 tokenField: 'order_apple_pay_token', 56899 lastDigitsField: 'order_payment_method_last_digits', 56900 nonprofitName: this.nonprofitName, 56901 merchantIdentifier: this.applePayMerchantIdentifier, 56902 $form: this.$form, 56903 56904 getTotalAmount: function getTotalAmount() { 56905 return _this4.totalAmount(); 56906 }, 56907 56908 onSubmit: function onSubmit() { 56909 _this4.submitForm(); 56910 } 56911 }); 56912 56913 this.applePay.checkVisibility({ 56914 onSuccess: function onSuccess() { 56915 $('[data-payment-method-type="apple_pay"]').show(); 56916 $('#order_apple_pay_available').val('true'); 56917 } 56918 }); 56919 } 56920 }, { 56921 key: 'initializeGooglePay', 56922 value: function initializeGooglePay() { 56923 var _this5 = this; 56924 56925 if (!this.googlePayEnabled) { 56926 return; 56927 } 56928 56929 this.googlePay = new GooglePay({ 56930 gateway: this.gateway, 56931 key: this.googlePayKey, 56932 env: this.googlePayEnv, 56933 merchantId: this.googlePayMerchantId, 56934 tokenField: 'order_google_pay_token', 56935 lastDigitsField: 'order_payment_method_last_digits', 56936 expiryMonthField: 'order_cc_expiry_month', 56937 expiryYearField: 'order_cc_expiry_year', 56938 nonprofitName: this.nonprofitName, 56939 $form: this.$form, 56940 56941 getTotalAmount: function getTotalAmount() { 56942 return _this5.totalAmount(); 56943 }, 56944 56945 onSubmit: function onSubmit() { 56946 _this5.submitForm(); 56947 } 56948 }); 56949 56950 this.googlePay.checkVisibility({ 56951 onSuccess: function onSuccess() { 56952 $('[data-payment-method-type="google_pay"]').show(); 56953 $('#order_google_pay_available').val('true'); 56954 } 56955 }); 56956 } 56957 }, { 56958 key: 'initializePayPal', 56959 value: function initializePayPal() { 56960 var _this6 = this; 56961 56962 if (!this.payPalEnabled) { 56963 return; 56964 } 56965 if (!this.isPayPal()) { 56966 return; 56967 } 56968 56969 paypal.Buttons({ 56970 style: { 56971 layout: 'horizontal', 56972 color: 'gold', 56973 shape: 'rect', 56974 label: 'pay', 56975 height: 55, 56976 borderRadius: 8, 56977 disableMaxWidth: true, 56978 tagline: false 56979 }, 56980 56981 onClick: function onClick(data, actions) { 56982 var isValid = _this6.$form.valid(); 56983 56984 if (!isValid) { 56985 return actions.reject(); 56986 } 56987 }, 56988 56989 createOrder: function createOrder() { 56990 var form, formData, response; 56991 return regeneratorRuntime.async(function createOrder$(context$3$0) { 56992 while (1) switch (context$3$0.prev = context$3$0.next) { 56993 case 0: 56994 form = this.$form.get(0); 56995 formData = new FormData(form); 56996 context$3$0.next = 4; 56997 return regeneratorRuntime.awrap($.ajax({ 56998 url: this.payPalOrderUrl, 56999 method: 'post', 57000 data: formData, 57001 processData: false, 57002 contentType: false 57003 })); 57004 57005 case 4: 57006 response = context$3$0.sent; 57007 return context$3$0.abrupt('return', response['id']); 57008 57009 case 6: 57010 case 'end': 57011 return context$3$0.stop(); 57012 } 57013 }, null, _this6); 57014 }, 57015 57016 onApprove: function onApprove(data) { 57017 return regeneratorRuntime.async(function onApprove$(context$3$0) { 57018 while (1) switch (context$3$0.prev = context$3$0.next) { 57019 case 0: 57020 $('#order_paypal_order_id').val(data.orderID); 57021 57022 this.disableButtons(true); 57023 this.disableScroll(); 57024 this.showLoading(); 57025
57026 if (window.executeV3RecaptchaAndSubmit) { 57027 window.executeV3RecaptchaAndSubmit(this.$form.get(0)); 57028 } else { 57029 this.$form.get(0).submit(); 57030 } 57031 57032 case 5: 57033 case 'end': 57034 return context$3$0.stop(); 57035 } 57036 }, null, _this6); 57037 } 57038 }).render('#paypal-button-container'); 57039 } 57040 }, { 57041 key: 'initializeStripeFields', 57042 value: function initializeStripeFields() { 57043 var _this7 = this; 57044 57045 if (this.gateway !== 'stripe') { 57046 return; 57047 } 57048 if (this.requiresTransaction == false) { 57049 return; 57050 } 57051 if (!this.isPaymentMethodTypeCreditCard()) { 57052 return; 57053 } 57054 57055 if (!this.stripeApiKey) { 57056 $('#stripe-card-error').show(); 57057 57058 return; 57059 } 57060 57061 this.stripe = Stripe(this.stripeApiKey); 57062 57063 var options = { 57064 // We can use `payment` for every scenario (including subscriptions) 57065 // because only creating a payment method (not an actual payment) 57066 // does not require the type. 57067 mode: 'payment', 57068 57069 amount: this.initialTotalInCents, 57070 currency: 'usd', 57071 paymentMethodCreation: 'manual', 57072 57073 paymentMethodTypes: ['card'], 57074 57075 appearance: { 57076 theme: 'stripe', 57077 57078 variables: { 57079 fontFamily: '"DM Sans", Helvetica, arial, sans-serif', 57080 fontSizeBase: '16px', 57081 colorText: '#262626', 57082 colorDanger: '#ff0000' 57083 }, 57084 57085 rules: { 57086 '.Label': { 57087 marginBottom: '4px', 57088 lineHeight: '24px' 57089 }, 57090 '.Input': { 57091 borderRadius: '0.25rem', 57092 borderColor: '#6b7280', 57093 boxShadow: 'none', 57094 padding: '14px 12px', 57095 height: '48px' 57096 }, 57097 '.Input:focus': { 57098 borderColor: '#2563eb', 57099 boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 0px, rgb(37, 99, 235) 0px 0px 0px 1px, rgba(0, 0, 0, 0) 0px 0px 0px 0px', 57100 transition: 'none' 57101 }, 57102 '.Input--invalid': { 57103 boxShadow: 'none' 57104 } 57105 } 57106 } 57107 }; 57108 57109 this.stripeElements = this.stripe.elements(options); 57110 57111 this.stripePaymentElement = this.stripeElements.create('payment', { 57112 fields: { 57113 billingDetails: { 57114 address: { 57115 country: this.mailingFieldsEnabled ? 'never' : 'auto', 57116 postalCode: this.mailingFieldsEnabled ? 'never' : 'auto' 57117 } 57118 } 57119 }, 57120 wallets: { 57121 applePay: 'never', 57122 googlePay: 'never' 57123 } 57124 }); 57125 57126 this.stripePaymentElement.on('loaderror', function (event) { 57127 // event.error.message contains information 57128 $('#stripe-card-error').show(); 57129 }); 57130 57131 this.stripePaymentElement.on('change', function (event) { 57132 _this7.stripePaymentType = event.value.type; 57133 }); 57134 57135 this.stripePaymentElement.mount('#stripe-card-field'); 57136 } 57137 }, { 57138 key: 'isPaymentMethodTypeCreditCard', 57139 value: function isPaymentMethodTypeCreditCard() { 57140 return this.paymentMethodValue() === 'credit_card'; 57141 } 57142 }, { 57143 key: 'isApplePay', 57144 value: function isApplePay() { 57145 return this.paymentMethodValue() == 'apple_pay'; 57146 } 57147 }, { 57148 key: 'isGooglePay', 57149 value: function isGooglePay() { 57150 return this.paymentMethodValue() == 'google_pay'; 57151 } 57152 }, { 57153 key: 'isPayPal', 57154 value: function isPayPal() { 57155 return this.paymentMethodValue() === 'paypal'; 57156 } 57157 }, { 57158 key: 'isStripe', 57159 value: function isStripe() { 57160 return !!this.stripeApiKey && this.isPaymentMethodTypeCreditCard(); 57161 } 57162 }, { 57163 key: 'paymentMethodValue', 57164 value: function paymentMethodValue() { 57165 if (this.$form.find('[name="order[payment_method_type]"]').is(':radio')) { 57166 return this.$form.find('[name="order[payment_method_type]"]:checked').val(); 57167 } else { 57168 return this.$form.find('[name="order[payment_method_type]"]').val(); 57169 } 57170 } 57171 }, { 57172 key: 'processStripe', 57173 value: function processStripe() {
57174 var validate, createPaymentMethodParams, _ref, error, paymentMethod, message; 57175 57176 return regeneratorRuntime.async(function processStripe$(context$2$0) { 57177 while (1) switch (context$2$0.prev = context$2$0.next) { 57178 case 0: 57179 context$2$0.next = 2; 57180 return regeneratorRuntime.awrap(this.stripeElements.submit()); 57181 57182 case 2: 57183 validate = context$2$0.sent; 57184 57185 if (!validate.error) { 57186 context$2$0.next = 6; 57187 break; 57188 } 57189 57190 this.stripePaymentElement.focus(); 57191 57192 return context$2$0.abrupt('return', { error: 'Invalid credit card' }); 57193 57194 case 6: 57195 createPaymentMethodParams = { 57196 elements: this.stripeElements 57197 }; 57198 57199 if (this.mailingFieldsEnabled) { 57200 createPaymentMethodParams['params'] = { 57201 billing_details: { 57202 address: { 57203 country: this.$form.find('#order_country').val(), 57204 postal_code: this.$form.find('#order_postal').val() 57205 } 57206 } 57207 }; 57208 } 57209 57210 context$2$0.next = 10; 57211 return regeneratorRuntime.awrap(this.stripe.createPaymentMethod(createPaymentMethodParams)); 57212 57213 case 10: 57214 _ref = context$2$0.sent; 57215 error = _ref.error; 57216 paymentMethod = _ref.paymentMethod; 57217 57218 if (!error) { 57219 context$2$0.next = 17; 57220 break; 57221 } 57222 57223 message = 'Could not accept credit card: ' + error.message; 57224 57225 alert(message); 57226 57227 return context$2$0.abrupt('return', { error: message }); 57228 57229 case 17: 57230 57231 $('#order_stripe_payment_method_id').val(paymentMethod.id); 57232 57233 if (paymentMethod.type === 'card') { 57234 $('#order_payment_method_last_digits').val(paymentMethod.card.last4); 57235 } 57236 57237 return context$2$0.abrupt('return', {}); 57238 57239 case 20: 57240 case 'end': 57241 return context$2$0.stop(); 57242 } 57243 }, null, this); 57244 } 57245 }, { 57246 key: 'showLoading', 57247 value: function showLoading() { 57248 $('#loading').removeClass('hidden'); 57249 } 57250 }, { 57251 key: 'submitHandler', 57252 value: function submitHandler(form) { 57253 var stripeResponse; 57254 return regeneratorRuntime.async(function submitHandler$(context$2$0) { 57255 while (1) switch (context$2$0.prev = context$2$0.next) { 57256 case 0: 57257 if (!this.isStripe()) { 57258 context$2$0.next = 10; 57259 break; 57260 } 57261 57262 this.disableButtons(true); 57263 57264 context$2$0.next = 4; 57265 return regeneratorRuntime.awrap(this.processStripe()); 57266 57267 case 4: 57268 stripeResponse = context$2$0.sent; 57269 57270 if (!stripeResponse.error) { 57271 context$2$0.next = 8; 57272 break; 57273 } 57274 57275 this.disableButtons(false); 57276 57277 return context$2$0.abrupt('return'); 57278 57279 case 8: 57280 context$2$0.next = 18; 57281 break; 57282 57283 case 10: 57284 if (!this.isGooglePay()) { 57285 context$2$0.next = 15; 57286 break; 57287 } 57288 57289 this.googlePay.onGooglePaymentButtonClicked(); 57290 return context$2$0.abrupt('return'); 57291 57292 case 15: 57293 if (!this.isApplePay()) { 57294 context$2$0.next = 18; 57295 break; 57296 } 57297 57298 this.applePay.onApplePayButtonClicked(); 57299 return context$2$0.abrupt('return'); 57300 57301 case 18: 57302 57303 this.submitForm(); 57304 57305 case 19: 57306 case 'end': 57307 return context$2$0.stop(); 57308 } 57309 }, null, this); 57310 } 57311 }, { 57312 key: 'submitForm', 57313 value: function submitForm() { 57314 this.disableButtons(true); 57315 this.disableScroll(); 57316 this.showLoading(); 57317 57318 if (window.executeV3RecaptchaAndSubmit) { 57319 window.executeV3RecaptchaAndSubmit(this.$form.get(0)); 57320 } else { 57321 this.$form.get(0).submit(); 57322 } 57323 } 57324 }, { 57325 key: 'totalAmount', 57326 value: function totalAmount() { 57327 var amount = this.$form.find('#order_subtotal').val(); 57328 var markup = this.$form.find('#order_markup').val() || 0; 57329 var markdown = this.$form.find('#order_markdown').val() || 0; 57330 var donation = this.$form.find('#order_donation').val() || 0; 57331 return (parseFloat(amount) + parseFloat(markup) + parseFloat(donation) + parseFloat(markdown)).toFixed(2); 57332 } 57333 }, { 57334 key: 'validateForm', 57335 value: function validateForm() { 57336 var _this8 = this; 57337 57338 this.$form.validate($.extend(formValidateDefaults, { 57339 rules: { 57340 'order[state]': { 57341 required: function required() { 57342 return ['US', 'CA'].indexOf($('#order_country').val()) != -1; 57343 } 57344 }, 57345 'order[postal]': { 57346 required: function required() { 57347 return ['US', 'CA'].indexOf($('#order_country').val()) != -1; 57348 } 57349 }, 57350 'order[email]': 'email', 57351 'order[cc_number]': 'creditcard', 57352 'order[google_pay_token]': { 57353 required: function required() { 57354 if (!_this8.isGooglePay()) { 57355 return false;
57356 } 57357 57358 return $('#order_google_pay_token').val().length; 57359 } 57360 }, 57361 'hidden_recaptcha': { 57362 required: function required() { 57363 if (grecaptcha.getResponse() == '') { 57364 return true; 57365 } else { 57366 return false; 57367 } 57368 } 57369 } 57370 }, 57371 57372 messages: { 57373 'order[subtotal]': 'Amount is required', 57374 'order[first_name]': 'First Name is required', 57375 'order[last_name]': 'Last Name is required', 57376 'order[email]': { 57377 required: 'Valid email is required', 57378 email: 'Valid email is required' 57379 }, 57380 'order[address_1]': 'Address 1 is required', 57381 'order[city]': 'City is required', 57382 'order[state]': 'State / Province is required', 57383 'order[postal]': 'Zip / Postal Code is required', 57384 57385 'order[cc_number]': 'Valid Credit Card Number is required', 57386 'order[cc_expiry_month]': 'Credit Card Expiration Month is required', 57387 'order[cc_expiry_year]': 'Credit Card Expiration Year is required', 57388 'order[cc_code]': 'Credit Card CVV Code is required', 57389 'order[check_account_number]': 'Valid Bank Account Number is required', 57390 'order[check_routing_number]': 'Valid Bank Routing Number is required', 57391 'order[payment_method_check_name]': 'Name on Bank Account is required', 57392 'order[payment_method_bank_name]': 'Bank Name is required', 57393 'hidden_recaptcha': 'To prove that you are a human, the CAPTCHA box must be checked' 57394 }, 57395 57396 focusInvalid: false, 57397
57398 invalidHandler: function invalidHandler(event, validator) { 57399 this.formHasBeenValidated = true; 57400 57401 var errors = validator.errorList.map(function (error) { 57402 return '<li>' + error.message + '</li>'; 57403 }); 57404 57405 var message = 'One or more fields are incomplete or incorrect. Please correct the following items: '; 57406 message += '<ul>' + errors.join('') + '</ul>'; 57407 $('#error-messages p').html(message); 57408 $('#error-messages').show(); 57409 57410 $('html, body').animate({ scrollTop: 0 }, 200); 57411 }, 57412 57413 submitHandler: function submitHandler(form, event) { 57414 _this8.submitHandler(form, event); 57415 } 57416 })); 57417 } 57418 }]); 57419 57420 return OrderPage; 57421})(); 57422'use strict'; 57423 57424var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 57425 57426function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 57427 57428var DonorsOrderShowPage = (function () { 57429 function DonorsOrderShowPage() { 57430 _classCallCheck(this, DonorsOrderShowPage); 57431 57432 this.initialize(); 57433 } 57434 57435 _createClass(DonorsOrderShowPage, [{ 57436 key: 'initialize', 57437 value: function initialize() { 57438 $('[data-fundraiser-show-login]').on('click', function (e) { 57439 e.preventDefault(); 57440 $('#fundraiser_user_session_wrapper').show(); 57441 $('#fundraiser_user_registration_wrapper').hide(); 57442 }); 57443 57444 $('[data-fundraiser-show-register]').on('click', function (e) { 57445 e.preventDefault(); 57446 $('#fundraiser_user_session_wrapper').hide(); 57447 $('#fundraiser_user_registration_wrapper').show(); 57448 }); 57449 57450 $('form').on('submit', function (e) { 57451 $(this).find('input[type="submit"]').prop('disabled', true).val('Loading...'); 57452 }); 57453 } 57454 }]); 57455 57456 return DonorsOrderShowPage; 57457})(); 57458'use strict'; 57459 57460var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 57461 57462function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 57463 57464var FundraiserPage = (function () { 57465 function FundraiserPage() { 57466 _classCallCheck(this, FundraiserPage); 57467 57468 this.initialize(); 57469 } 57470 57471 _createClass(FundraiserPage, [{ 57472 key: 'initialize', 57473 value: function initialize() { 57474 $(document).on('ajax:beforeSend', '.pagination', function (event) { 57475 $(event.currentTarget).addClass('loading'); 57476 }); 57477 57478 $(document).on('ajax:complete', '.pagination', function (event) { 57479 $(event.currentTarget).removeClass('loading'); 57480 }); 57481 57482 new MoreLoader({ 57483 control: '[data-control="team-members-list"]', 57484 container: '[data-list="team-members"]', 57485 endpoint: $('[data-control="team-members-list"]').attr('data-url'), 57486 totalPages: $('[data-control="team-members-list"]').attr('data-total-pages') 57487 }); 57488 57489 new MoreLoader({ 57490 control: '[data-control="team-siblings-all-list"]', 57491 container: '[data-list="team-siblings-all"]', 57492 endpoint: $('[data-control="team-siblings-all-list"]').attr('data-url'),
57493 totalPages: $('[data-control="team-siblings-all-list"]').attr('data-total-pages') 57494 }); 57495 57496 new MoreLoader({ 57497 control: '[data-control="team-siblings-fundraisers-list"]', 57498 container: '[data-list="team-siblings-fundraisers"]', 57499 endpoint: $('[data-control="team-siblings-fundraisers-list"]').attr('data-url'), 57500 totalPages: $('[data-control="team-siblings-fundraisers-list"]').attr('data-total-pages') 57501 }); 57502 57503 new MoreLoader({ 57504 control: '[data-control="team-siblings-teams-list"]', 57505 container: '[data-list="team-siblings-teams"]', 57506 endpoint: $('[data-control="team-siblings-teams-list"]').attr('data-url'), 57507 totalPages: $('[data-control="team-siblings-teams-list"]').attr('data-total-pages') 57508 }); 57509 } 57510 }]); 57511 57512 return FundraiserPage; 57513})(); 57514'use strict'; 57515 57516var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 57517 57518function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 57519 57520var LandingPage = (function () { 57521 function LandingPage() { 57522 _classCallCheck(this, LandingPage); 57523 57524 this.initialize(); 57525 } 57526 57527 _createClass(LandingPage, [{ 57528 key: 'initialize', 57529 value: function initialize() { 57530 new MoreLoader({ 57531 control: '[data-control="team-siblings-all-list"]', 57532 container: '[data-list="team-siblings-all"]', 57533 endpoint: $('[data-control="team-siblings-all-list"]').attr('data-url'), 57534 totalPages: $('[data-control="team-siblings-all-list"]').attr('data-total-pages') 57535 }); 57536 57537 new MoreLoader({ 57538 control: '[data-control="team-siblings-fundraisers-list"]', 57539 container: '[data-list="team-siblings-fundraisers"]', 57540 endpoint: $('[data-control="team-siblings-fundraisers-list"]').attr('data-url'), 57541 totalPages: $('[data-control="team-siblings-fundraisers-list"]').attr('data-total-pages') 57542 }); 57543 57544 new MoreLoader({ 57545 control: '[data-control="team-siblings-teams-list"]', 57546 container: '[data-list="team-siblings-teams"]', 57547 endpoint: $('[data-control="team-siblings-teams-list"]').attr('data-url'), 57548 totalPages: $('[data-control="team-siblings-teams-list"]').attr('data-total-pages') 57549 }); 57550 } 57551 }]); 57552 57553 return LandingPage; 57554})(); 57555'use strict'; 57556 57557var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 57558 57559function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 57560 57561var DonorsDonationPage = (function () { 57562 function DonorsDonationPage(args) { 57563 _classCallCheck(this, DonorsDonationPage); 57564 57565 this.paymentFieldsUrl = args.paymentFieldsUrl; 57566 this.recurringConversionEnabled = args.recurringConversionEnabled; 57567 this.recurringConversionUrl = args.recurringConversionUrl; 57568 this.recurringConversionMin = args.recurringConversionMin; 57569 this.recurringConversionMax = args.recurringConversionMax; 57570 this.frequency = args.frequency; 57571 this.donationAmountsEnabled = args.donationAmountsEnabled; 57572 this.changeDonationAmountUrl = args.changeDonationAmountUrl; 57573 this.changeCustomAmountUrl = args.changeCustomAmountUrl; 57574 this.changeFrequencyUrl = args.changeFrequencyUrl; 57575 this.changeProcessingFeeUrl = args.changeProcessingFeeUrl; 57576 this.$form = $('#new_donation'); 57577 this.submitButtonSelector = '[data-donation-submit]'; 57578 this.stripeApiKey = args.stripeApiKey; 57579 this.stripePaymentType = null; 57580 this.initialTotalInCents = args.initialTotalInCents; 57581 this.gateway = args.gateway; 57582 this.googlePayEnabled = args.googlePayEnabled; 57583 this.googlePayKey = args.googlePayKey; 57584 this.googlePayEnv = args.googlePayEnv; 57585 this.googlePayMerchantId = args.googlePayMerchantId; 57586 this.applePayEnabled = args.applePayEnabled; 57587 this.applePaymentSessionUrl = args.applePaymentSessionUrl; 57588 this.appleProcessUrl = args.appleProcessUrl; 57589 this.nonprofitName = args.nonprofitName; 57590 this.applePayMerchantIdentifier = args.applePayMerchantIdentifier; 57591 this.mailingFieldsEnabled = args.mailingFieldsEnabled; 57592 this.payPalEnabled = args.payPalEnabled; 57593 this.payPalOrderUrl = args.payPalOrderUrl; 57594 this.payPalDeactivatePlanUrl = args.payPalDeactivatePlanUrl; 57595 57596 this.initialize(); 57597 } 57598 57599 _createClass(DonorsDonationPage, [{ 57600 key: 'initialize', 57601 value: function initialize() { 57602 var _this = this; 57603 57604 $(document).on('change', '[name="donation[frequency]"]', function (event) { 57605 $('#donation_amounts_loading').show(); 57606 57607 var form = $('#new_donation').get(0); 57608 57609 $.ajax({ 57610 url: _this.changeFrequencyUrl, 57611 method: 'post',
57612 dataType: 'script', 57613 data: new FormData(form), 57614 processData: false, 57615 contentType: false 57616 }); 57617 }); 57618 57619 $(document).on('click', '[name="donation[donation_amount_id]"]', function (event) { 57620 $('#donation_amounts_loading').show(); 57621 57622 var form = $('#new_donation').get(0); 57623 57624 $.ajax({ 57625 url: _this.changeDonationAmountUrl, 57626 method: 'post', 57627 dataType: 'script', 57628 data: new FormData(form), 57629 processData: false, 57630 contentType: false 57631 }); 57632 }); 57633 57634 $(document).on('keyup', '#donation_donation_amount_custom', function (event) { 57635 var form = $('#new_donation').get(0); 57636 57637 $.ajax({ 57638 url: _this.changeCustomAmountUrl, 57639 method: 'post', 57640 dataType: 'script', 57641 data: new FormData(form), 57642 processData: false, 57643 contentType: false 57644 }); 57645 }); 57646 57647 $(document).on('change', '#donation_processing_fee_included', function (event) { 57648 var form = $('#new_donation').get(0); 57649 57650 $.ajax({ 57651 url: _this.changeProcessingFeeUrl, 57652 method: 'post', 57653 dataType: 'script', 57654 data: new FormData(form), 57655 processData: false, 57656 contentType: false 57657 }); 57658 }); 57659 57660 $(document).on('change', '[name="donation[payment_method_type]"]', function () { 57661 var form = $('#new_donation').get(0); 57662 57663 $.ajax({ 57664 url: _this.paymentFieldsUrl, 57665 method: 'post', 57666 dataType: 'script', 57667 data: new FormData(form), 57668 processData: false, 57669 contentType: false 57670 }); 57671 }); 57672 57673 $(document).on('payment-method-change', function (event, data) { 57674 if (data.paymentMethod == 'credit_card') { 57675 _this.initializeStripeFields(); 57676 } else if (data.paymentMethod == 'paypal') { 57677 _this.initializePayPal(); 57678 } 57679 }); 57680 57681 $(document).on('payment-frequency-change', function (event, data) { 57682 if (data.paymentMethod == 'paypal') { 57683 _this.initializePayPal(); 57684 } 57685 }); 57686 57687 $(document).on('payment-amount-change', function (event, data) { 57688 if (data.paymentMethod == 'paypal') { 57689 _this.initializePayPal(); 57690 } 57691 }); 57692 57693 $(document).on('payment-custom-amount-change', function (event, data) { 57694 if (data.paymentMethod == 'paypal') { 57695 _this.initializePayPal(); 57696 } 57697 }); 57698 57699 $(document).on('payment-processing-fee-change', function (event, data) { 57700 if (data.paymentMethod == 'paypal') { 57701 _this.initializePayPal(); 57702 } 57703 }); 57704 57705 new DonorFormValidator({ 57706 form: this.$form, 57707 submitHandler: this.submitHandler.bind(this) 57708 }); 57709 57710 // Handles gift visibility 57711 new ConditionalInput({ 57712 control: $('select#donation_gift_name'), 57713 visibility: { 57714 'Other': $('.donation_gift_name_other') 57715 } 57716 }); 57717 57718 // Handles tribute visibility 57719 new ConditionalInput({ 57720 inputType: 'checkbox', 57721 control: $('#donation_tribute_enabled'), 57722 visibility: { 57723 'on': $('.form-item--tribute-group') 57724 } 57725 }); 57726 57727 // Handles tribute notification type visibility 57728 new ConditionalInput({ 57729 inputType: 'radio', 57730 control: $('[name="donation[tribute_attributes][notification_type]"][type="radio"]'), 57731 visibility: { 57732 'email': $('.form-item--tribute-email-group'), 57733 'snailmail': $('.form-item--tribute-snailmail-group') 57734 } 57735 }); 57736 57737 // Handles tribute notification message visibility 57738 new ConditionalInput({ 57739 inputType: 'radio', 57740 control: $('[name="donation[tribute_attributes][notification_type]"][type="radio"]'), 57741 visibility: { 57742 'email|snailmail': $('.donation_tribute_message') 57743 } 57744 }); 57745 57746 new BillingInfoCookie({ 57747 modelName: 'donation' 57748 }); 57749 57750 this.toggleDescriptionReadMore(); 57751 this.initializeRecurringConversion(); 57752 this.initializeDescriptionReadMore(); 57753 this.initializeStripeFields(); 57754 this.initializeGooglePay(); 57755 this.initializeApplePay(); 57756 this.initializePayPal(); 57757 57758 $(window).on('resize', function () { 57759 _this.toggleDescriptionReadMore(); 57760 }); 57761 } 57762 }, { 57763 key: 'disableButtons', 57764 value: function disableButtons(isDisabled) { 57765 var $submitButton = $(this.submitButtonSelector); 57766 57767 if (isDisabled) { 57768 $submitButton.prop('disabled', true); 57769 $('button[name="recurring_conversion_button"]').prop('disabled', true); 57770 } else { 57771 $submitButton.prop('disabled', false); 57772 $('button[name="recurring_conversion_button"]').prop('disabled', false); 57773 } 57774 } 57775 }, { 57776 key: 'disableScroll', 57777 value: function disableScroll() { 57778 $('body').addClass('no-scroll'); 57779 } 57780 }, { 57781 key: 'frequencyValue', 57782 value: function frequencyValue() { 57783 return this.$form.find('[name="donation[frequency]"]:checked').val(); 57784 } 57785 }, { 57786 key: 'initializeDescriptionReadMore', 57787 value: function initializeDescriptionReadMore() { 57788 // Reveals the hidden description on small screens 57789 $('[data-donation-page-description-read-more]').on('click', function (event) { 57790 event.preventDefault(); 57791 $('[data-donation-page-description-overlay]').hide(); 57792 $('[data-donation-page-description]').addClass('donate-body__expanded'); 57793 }); 57794 } 57795 }, { 57796 key: 'initializeApplePay', 57797 value: function initializeApplePay() { 57798 var _this2 = this; 57799 57800 if (!this.applePayEnabled) { 57801 return; 57802 } 57803 57804 this.applePay = new ApplePay({ 57805 applePaymentSessionUrl: this.applePaymentSessionUrl, 57806 appleProcessUrl: this.appleProcessUrl, 57807 tokenField: 'donation_apple_pay_token', 57808 lastDigitsField: 'donation_payment_method_last_digits', 57809 nonprofitName: this.nonprofitName, 57810 merchantIdentifier: this.applePayMerchantIdentifier, 57811 $form: this.$form, 57812 57813 getTotalAmount: function getTotalAmount() { 57814 return _this2.totalAmountWithConversion(); 57815 }, 57816 57817 onSubmit: function onSubmit() {
57818 _this2.submitForm(); 57819 } 57820 }); 57821 57822 this.applePay.checkVisibility({ 57823 onSuccess: function onSuccess() { 57824 $('[data-payment-method-type="apple_pay"]').show(); 57825 $('#donation_apple_pay_available').val('true'); 57826 } 57827 }); 57828 } 57829 }, { 57830 key: 'initializeGooglePay', 57831 value: function initializeGooglePay() { 57832 var _this3 = this; 57833 57834 if (!this.googlePayEnabled) { 57835 return; 57836 } 57837 57838 this.googlePay = new GooglePay({ 57839 gateway: this.gateway, 57840 key: this.googlePayKey, 57841 env: this.googlePayEnv, 57842 merchantId: this.googlePayMerchantId, 57843 tokenField: 'donation_google_pay_token', 57844 lastDigitsField: 'donation_payment_method_last_digits', 57845 expiryMonthField: 'donation_cc_expiry_month', 57846 expiryYearField: 'donation_cc_expiry_year', 57847 nonprofitName: this.nonprofitName, 57848 $form: this.$form, 57849 57850 getTotalAmount: function getTotalAmount() { 57851 return _this3.totalAmountWithConversion(); 57852 }, 57853 57854 onSubmit: function onSubmit() { 57855 _this3.submitForm(); 57856 } 57857 }); 57858 57859 this.googlePay.checkVisibility({ 57860 onSuccess: function onSuccess() { 57861 $('[data-payment-method-type="google_pay"]').show(); 57862 $('#donation_google_pay_available').val('true'); 57863 } 57864 }); 57865 } 57866 }, { 57867 key: 'initializePayPal', 57868 value: function initializePayPal() { 57869 var _this4 = this; 57870 57871 if (!this.payPalEnabled) { 57872 return; 57873 } 57874 if (!this.isPayPal()) { 57875 return; 57876 } 57877 57878 if (this.isFrequencySingle()) { 57879 paypal.Buttons({ 57880 style: { 57881 layout: 'horizontal', 57882 color: 'gold', 57883 shape: 'rect', 57884 label: 'pay', 57885 height: 55, 57886 borderRadius: 8, 57887 disableMaxWidth: true, 57888 tagline: false 57889 }, 57890 57891 onClick: function onClick(data, actions) { 57892 var isValid = _this4.$form.valid(); 57893 57894 if (!isValid) { 57895 return actions.reject(); 57896 } 57897 }, 57898 57899 createOrder: function createOrder() { 57900 var form, formData, response; 57901 return regeneratorRuntime.async(function createOrder$(context$3$0) { 57902 while (1) switch (context$3$0.prev = context$3$0.next) { 57903 case 0: 57904 form = this.$form.get(0); 57905 formData = new FormData(form); 57906 context$3$0.next = 4; 57907 return regeneratorRuntime.awrap($.ajax({ 57908 url: this.payPalOrderUrl, 57909 method: 'post', 57910 data: formData, 57911 processData: false, 57912 contentType: false 57913 })); 57914 57915 case 4: 57916 response = context$3$0.sent; 57917 return context$3$0.abrupt('return', response['id']); 57918 57919 case 6: 57920 case 'end': 57921 return context$3$0.stop(); 57922 } 57923 }, null, _this4); 57924 }, 57925 57926 onApprove: function onApprove(data) { 57927 return regeneratorRuntime.async(function onApprove$(context$3$0) { 57928 while (1) switch (context$3$0.prev = context$3$0.next) { 57929 case 0: 57930 $('#donation_paypal_order_id').val(data.orderID); 57931 57932 this.disableButtons(true); 57933 this.disableScroll(); 57934 this.showLoading(); 57935 57936 if (window.executeV3RecaptchaAndSubmit) { 57937 window.executeV3RecaptchaAndSubmit(this.$form.get(0)); 57938 } else { 57939 this.$form.get(0).submit(); 57940 } 57941 57942 case 5: 57943 case 'end': 57944 return context$3$0.stop(); 57945 } 57946 }, null, _this4); 57947 } 57948 }).render('#paypal-button-container'); 57949 } else { 57950 (function () { 57951 var paypalCancelToken = null; 57952 57953 paypal.Buttons({ 57954 style: { 57955 layout: 'horizontal', 57956 color: 'gold', 57957 shape: 'rect', 57958 label: 'pay', 57959 height: 55, 57960 borderRadius: 8, 57961 disableMaxWidth: true, 57962 tagline: false 57963 }, 57964 57965 onClick: function onClick(data, actions) { 57966 var isValid = _this4.$form.valid(); 57967 57968 if (!isValid) { 57969 return actions.reject(); 57970 } 57971 }, 57972 57973 createSubscription: function createSubscription(data, actions) { 57974 var form, formData, response; 57975 return regeneratorRuntime.async(function createSubscription$(context$4$0) { 57976 while (1) switch (context$4$0.prev = context$4$0.next) { 57977 case 0: 57978 form = this.$form.get(0); 57979 formData = new FormData(form); 57980 context$4$0.next = 4; 57981 return regeneratorRuntime.awrap($.ajax({ 57982 url: this.payPalOrderUrl, 57983 method: 'post', 57984 data: formData, 57985 processData: false, 57986 contentType: false 57987 })); 57988 57989 case 4: 57990 response = context$4$0.sent; 57991 57992 paypalCancelToken = response.cancel_token; 57993 57994 return context$4$0.abrupt('return', actions.subscription.create({ 57995 plan_id: response.id 57996 })); 57997 57998 case 7: 57999 case 'end': 58000 return context$4$0.stop(); 58001 } 58002 }, null, _this4); 58003 }, 58004 58005 onApprove: function onApprove(data) { 58006 return regeneratorRuntime.async(function onApprove$(context$4$0) { 58007 while (1) switch (context$4$0.prev = context$4$0.next) { 58008 case 0: 58009 $('#donation_paypal_order_id').val(data.orderID); 58010 $('#donation_paypal_subscription_id').val(data.subscriptionID); 58011 58012 this.disableButtons(true); 58013 this.disableScroll(); 58014 this.showLoading(); 58015
58016 if (window.executeV3RecaptchaAndSubmit) { 58017 window.executeV3RecaptchaAndSubmit(this.$form.get(0)); 58018 } else { 58019 this.$form.get(0).submit(); 58020 } 58021 58022 case 6: 58023 case 'end': 58024 return context$4$0.stop(); 58025 } 58026 }, null, _this4); 58027 }, 58028 58029 onCancel: function onCancel(data) { 58030 $('#donation_paypal_plan_id').val(''); 58031 58032 if (paypalCancelToken) { 58033 $.ajax({ 58034 url: _this4.payPalDeactivatePlanUrl, 58035 method: 'post', 58036 data: { cancel_token: paypalCancelToken } 58037 }); 58038 58039 paypalCancelToken = null; 58040 } 58041 } 58042 }).render('#paypal-button-container'); 58043 })(); 58044 } 58045 } 58046 }, { 58047 key: 'initializeRecurringConversion', 58048 value: function initializeRecurringConversion() { 58049 var _this5 = this; 58050 58051 $(document).on('click', 'button[name="recurring_conversion_button"]', function (event) { 58052 event.preventDefault(); 58053 58054 var value = $(event.target).val(); 58055 var total = $(event.target).data('total'); 58056 58057 if (value) { 58058 $('#recurring_conversion').val(value); 58059 } 58060 58061 if (total) { 58062 $('#recurring_conversion_total').val(total); 58063 } 58064 58065 _this5.submitHandler(_this5.$form.get(0)); 58066 }); 58067 } 58068 }, { 58069 key: 'initializeStripeFields', 58070 value: function initializeStripeFields() { 58071 if (this.gateway !== 'stripe') { 58072 return; 58073 }; 58074 if (!this.isPaymentMethodTypeCreditCard()) { 58075 return; 58076 }; 58077 58078 if (!this.stripeApiKey) { 58079 $('#stripe-card-error').show(); 58080 58081 return; 58082 } 58083 58084 this.stripe = Stripe(this.stripeApiKey); 58085 58086 var options = { 58087 // We can use `payment` for every scenario (including subscriptions) 58088 // because only creating a payment method (not an actual payment) 58089 // does not require the type. 58090 mode: 'payment', 58091 58092 amount: this.initialTotalInCents, 58093 currency: 'usd', 58094 paymentMethodCreation: 'manual', 58095 58096 paymentMethodTypes: ['card'], 58097 58098 appearance: { 58099 theme: 'stripe', 58100 58101 variables: { 58102 fontFamily: '"DM Sans", Helvetica, arial, sans-serif', 58103 fontSizeBase: '16px', 58104 colorText: '#262626', 58105 colorDanger: '#ff0000' 58106 }, 58107 58108 rules: { 58109 '.Label': { 58110 marginBottom: '4px', 58111 lineHeight: '24px' 58112 }, 58113 '.Input': { 58114 borderRadius: '0.25rem', 58115 borderColor: '#6b7280', 58116 boxShadow: 'none', 58117 padding: '14px 12px', 58118 height: '48px' 58119 }, 58120 '.Input:focus': { 58121 borderColor: '#2563eb', 58122 boxShadow: 'rgb(255, 255, 255) 0px 0px 0px 0px, rgb(37, 99, 235) 0px 0px 0px 1px, rgba(0, 0, 0, 0) 0px 0px 0px 0px', 58123 transition: 'none' 58124 }, 58125 '.Input--invalid': { 58126 boxShadow: 'none' 58127 } 58128 } 58129 } 58130 }; 58131 58132 this.stripeElements = this.stripe.elements(options); 58133 58134 this.stripePaymentElement = this.stripeElements.create('payment', { 58135 disableLink: true, 58136 fields: { 58137 billingDetails: { 58138 address: { 58139 country: this.mailingFieldsEnabled ? 'never' : 'auto', 58140 postalCode: this.mailingFieldsEnabled ? 'never' : 'auto' 58141 } 58142 } 58143 }, 58144 wallets: { 58145 googlePay: 'never', 58146 applePay: 'never' 58147 } 58148 }); 58149 58150 this.stripePaymentElement.on('loaderror', function (event) { 58151 // event.error.message contains information 58152 $('#stripe-card-error').show(); 58153 }); 58154 58155 this.stripePaymentElement.mount('#stripe-card-field'); 58156 } 58157 }, { 58158 key: 'isAllowedAmountForRecurringConversion', 58159 value: function isAllowedAmountForRecurringConversion() { 58160 return this.totalAmount() > this.recurringConversionMin && this.totalAmount() < this.recurringConversionMax; 58161 } 58162 }, { 58163 key: 'isApplePay', 58164 value: function isApplePay() { 58165 return this.paymentMethodTypeValue() == 'apple_pay'; 58166 } 58167 }, { 58168 key: 'isGooglePay', 58169 value: function isGooglePay() { 58170 return this.paymentMethodTypeValue() == 'google_pay'; 58171 } 58172 }, { 58173 key: 'isStripe', 58174 value: function isStripe() { 58175 return !!this.stripeApiKey && this.isPaymentMethodTypeCredit
58175Card(); 58176 } 58177 }, { 58178 key: 'isFrequencySingle', 58179 value: function isFrequencySingle() { 58180 return this.frequencyValue() === 'single'; 58181 } 58182 }, { 58183 key: 'isPaymentMethodTypeCreditCard', 58184 value: function isPaymentMethodTypeCreditCard() { 58185 return this.paymentMethodTypeValue() === 'credit_card'; 58186 } 58187 }, { 58188 key: 'isPayPal', 58189 value: function isPayPal() { 58190 return this.paymentMethodTypeValue() === 'paypal'; 58191 } 58192 }, { 58193 key: 'isRecurringConversionEnabled', 58194 value: function isRecurringConversionEnabled() { 58195 return this.recurringConversionEnabled === true; 58196 } 58197 }, { 58198 key: 'paymentMethodTypeValue', 58199 value: function paymentMethodTypeValue() { 58200 if (this.$form.find('[name="donation[payment_method_type]"]').is(':radio')) { 58201 return this.$form.find('[name="donation[payment_method_type]"]:checked').val(); 58202 } else { 58203 return this.$form.find('[name="donation[payment_method_type]"]').val(); 58204 } 58205 } 58206 }, { 58207 key: 'processStripe', 58208 value: function processStripe() { 58209 var validate, createPaymentMethodParams, _ref, error, paymentMethod, message; 58210 58211 return regeneratorRuntime.async(function processStripe$(context$2$0) { 58212 while (1) switch (context$2$0.prev = context$2$0.next) { 58213 case 0: 58214 context$2$0.next = 2; 58215 return regeneratorRuntime.awrap(this.stripeElements.submit()); 58216 58217 case 2: 58218 validate = context$2$0.sent; 58219 58220 if (!validate.error) { 58221 context$2$0.next = 6; 58222 break; 58223 } 58224 58225 this.stripePaymentElement.focus(); 58226 58227 return context$2$0.abrupt('return', { error: 'Invalid credit card' }); 58228 58229 case 6: 58230 createPaymentMethodParams = { 58231 elements: this.stripeElements 58232 }; 58233 58234 if (this.mailingFieldsEnabled) { 58235 createPaymentMethodParams['params'] = { 58236 billing_details: { 58237 address: { 58238 country: this.$form.find('#donation_country').val(), 58239 postal_code: this.$form.find('#donation_postal').val() 58240 } 58241 } 58242 }; 58243 } 58244 58245 context$2$0.next = 10; 58246 return regeneratorRuntime.awrap(this.stripe.createPaymentMethod(createPaymentMethodParams)); 58247 58248 case 10: 58249 _ref = context$2$0.sent; 58250 error = _ref.error; 58251 paymentMethod = _ref.paymentMethod; 58252 58253 if (!error) { 58254 context$2$0.next = 17; 58255 break; 58256 } 58257 58258 message = 'Could not accept credit card: ' + error.message; 58259 58260 alert(message); 58261 58262 return context$2$0.abrupt('return', { error: message }); 58263 58264 case 17: 58265 58266 $('#donation_stripe_payment_method_id').val(paymentMethod.id); 58267 58268 if (paymentMethod.type === 'card') { 58269 $('#donation_payment_method_last_digits').val(paymentMethod.card.last4); 58270 $('#donation_cc_expiry_month').val(paymentMethod.card.exp_month); 58271 $('#donation_cc_expiry_year').val(paymentMethod.card.exp_year); 58272 } 58273 58274 return context$2$0.abrupt('return', {}); 58275 58276 case 20: 58277 case 'end': 58278 return context$2$0.stop(); 58279 } 58280 }, null, this); 58281 } 58282 }, { 58283 key: 'recurringModalIsOpen', 58284 value: function recurringModalIsOpen() { 58285 return $('.recurring-modal.is-active').length > 0; 58286 } 58287 }, { 58288 key: 'shouldShowRecurringConversion', 58289 value: function shouldShowRecurringConversion() { 58290 return !this.recurringModalIsOpen() && this.isFrequencySingle() && this.isRecurringConversionEnabled() && this.isAllowedAmountForRecurringConversion(); 58291 } 58292 }, { 58293 key: 'showLoading', 58294 value: function showLoading() { 58295 $('#loading').removeClass('hidden'); 58296 } 58297 }, { 58298 key: 'submitHandler', 58299 value: function submitHandler(form) { 58300 var stripeResponse; 58301 return regeneratorRuntime.async(function submitHandler$(context$2$0) { 58302 while (1) switch (context$2$0.prev = context$2$0.next) { 58303 case 0: 58304 if (!this.shouldShowRecurringConversion()) { 58305 context$2$0.next = 3; 58306 break; 58307 } 58308 58309 $.ajax({ 58310 url: this.recurringConversionUrl, 58311 method: 'post',
58312 dataType: 'script', 58313 data: new FormData(form), 58314 processData: false, 58315 contentType: false 58316 }); 58317 58318 return context$2$0.abrupt('return'); 58319 58320 case 3: 58321 if (!this.isStripe()) { 58322 context$2$0.next = 13; 58323 break; 58324 } 58325 58326 this.disableButtons(true); 58327 context$2$0.next = 7; 58328 return regeneratorRuntime.awrap(this.processStripe()); 58329 58330 case 7: 58331 stripeResponse = context$2$0.sent; 58332 58333 if (!stripeResponse.error) { 58334 context$2$0.next = 11; 58335 break; 58336 } 58337 58338 this.disableButtons(false); 58339 return context$2$0.abrupt('return'); 58340 58341 case 11: 58342 context$2$0.next = 21; 58343 break; 58344 58345 case 13: 58346 if (!this.isGooglePay()) { 58347 context$2$0.next = 18; 58348 break; 58349 } 58350 58351 this.googlePay.onGooglePaymentButtonClicked(); 58352 return context$2$0.abrupt('return'); 58353 58354 case 18: 58355 if (!this.isApplePay()) { 58356 context$2$0.next = 21; 58357 break; 58358 } 58359 58360 this.applePay.onApplePayButtonClicked(); 58361 return context$2$0.abrupt('return'); 58362 58363 case 21: 58364 58365 this.submitForm(); 58366 58367 case 22: 58368 case 'end': 58369 return context$2$0.stop(); 58370 } 58371 }, null, this); 58372 } 58373 }, { 58374 key: 'submitForm', 58375 value: function submitForm() { 58376 this.disableButtons(true); 58377 this.disableScroll(); 58378 this.showLoading(); 58379 58380 if (window.executeV3RecaptchaAndSubmit) { 58381 window.executeV3RecaptchaAndSubmit(this.$form.get(0)); 58382 } else { 58383 this.$form.get(0).submit(); 58384 } 58385 } 58386 }, { 58387 key: 'toggleDescriptionReadMore', 58388 value: function toggleDescriptionReadMore() { 58389 if ($('.donate-body__expanded').length) { 58390 return; 58391 } 58392 58393 var width = $(window).width(); 58394 var height = $('[data-donation-page-description]').height(); 58395 if (width < 740 && height > 74) { 58396 $('[data-donation-page-description-overlay]').show(); 58397 } else { 58398 $('[data-donation-page-description-overlay]').hide(); 58399 } 58400 } 58401 }, { 58402 key: 'totalAmount', 58403 value: function totalAmount() { 58404 var amount = this.$form.find('#donation_subtotal').val(); 58405 var markup = this.$form.find('#donation_markup').val() || 0; 58406 return (parseFloat(amount) + parseFloat(markup)).toFixed(2); 58407 } 58408 }, { 58409 key: 'totalAmountWithConversion', 58410 value: function totalAmountWithConversion() { 58411 var conversionTotal = this.$form.find('#recurring_conversion_total').val(); 58412 var conversionType = this.$form.find('#recurring_conversion').val(); 58413 58414 if (conversionTotal && conversionType != 'none') { 58415 return conversionTotal; 58416 } 58417 58418 return this.totalAmount(); 58419 } 58420 }]); 58421 58422 return DonorsDonationPage; 58423})(); 58424'use strict'; 58425 58426var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 58427 58428function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 58429 58430var DonorsDonationSuccessPage = (function () { 58431 function DonorsDonationSuccessPage() { 58432 _classCallCheck(this, DonorsDonationSuccessPage); 58433 58434 this.initialize(); 58435 } 58436 58437 _createClass(DonorsDonationSuccessPage, [{ 58438 key: 'initialize', 58439 value: function initialize() { 58440 $('[data-fundraiser-show-login]').on('click', function (e) { 58441 e.preventDefault(); 58442 $('#fundraiser_user_session_wrapper').show(); 58443 $('#fundraiser_user_registration_wrapper').hide(); 58444 }); 58445 58446 $('[data-fundraiser-show-register]').on('click', function (e) { 58447 e.preventDefault(); 58448 $('#fundraiser_user_session_wrapper').hide(); 58449 $('#fundraiser_user_registration_wrapper').show(); 58450 }); 58451 58452 $('form').on('submit', function (e) { 58453 $(this).find('input[type="submit"]').prop('disabled', true).val('Loading...'); 58454 }); 58455 } 58456 }]); 58457 58458 return DonorsDonationSuccessPage; 58459})(); 58460'use strict'; 58461 58462$(function () { 58463 new ConditionalInput({ 58464 inputType: 'select', 58465 control: $('[name="fundraiser_page[hero_media_type]"]'),
58466 visibility: { 58467 'image': $('.fundraiser_page_hero_image'), 58468 'video': $('.fundraiser_page_hero_video_url') 58469 } 58470 }); 58471 58472 new ConditionalInput({ 58473 inputType: 'checkbox', 58474 control: $('#fundraiser_page_hero_media_override'), 58475 visibility: { 58476 'on': $('.form-item--hero-media-group') 58477 } 58478 }); 58479 58480 new ConditionalInput({ 58481 inputType: 'checkbox', 58482 control: $('#donation_comment_enabled'), 58483 visibility: { 58484 'on': $('.form-item--comment-fields') 58485 } 58486 }); 58487 58488 new ConditionalInput({ 58489 inputType: 'select', 58490 control: $('#fundraiser_page_fundraiser_page_type'), 58491 visibility: { 58492 'team_member': $('.fundraiser_page_fundraiser_team_id'), 58493 'team_owner': $('.fundraiser_page_fundraiser_team_name') 58494 } 58495 }); 58496 58497 new ConditionalInput({ 58498 inputType: 'select', 58499 control: $('#fundraiser_page_fundraiser_page_type'), 58500 visibility: { 58501 'individual|team_member': $('.form-item--fundraiser_page_title') 58502 } 58503 }); 58504 58505 new ConditionalInput({ 58506 inputType: 'checkbox', 58507 control: $('#donation_double_the_donation_check'), 58508 visibility: { 58509 'on': $('#dd-wrapper') 58510 } 58511 }); 58512 58513 new CountrySelector({ 58514 country: '#donation_country', 58515 stateInputName: 'donation[state]', 58516 stateContainer: '.form-fieldset--donation-state' 58517 }); 58518 58519 new CountrySelector({ 58520 country: '#donation_tribute_attributes_country', 58521 stateInputName: 'donation[tribute_attributes][state]', 58522 stateContainer: '.form-fieldset--tribute-state' 58523 }); 58524 58525 new CountrySelector({ 58526 country: '#fundraiser_user_country', 58527 stateInputName: 'fundraiser_user[state]', 58528 stateContainer: '.form-fieldset--fundraiser-user-state' 58529 }); 58530 58531 var labelSwitcherDefaults = { 58532 labelValueDefault: 'Postal Code', 58533 labelValues: { 58534 'US': 'Zip Code' 58535 } 58536 }; 58537 58538 new LabelSwitcher($.extend(labelSwitcherDefaults, { 58539 control: '#donation_country', 58540 label: 'label[for="donation_postal"]' 58541 })); 58542 58543 new LabelSwitcher($.extend(labelSwitcherDefaults, { 58544 control: '#donation_tribute_attributes_country', 58545 label: 'label[for="donation_tribute_attributes_postal"]' 58546 })); 58547 58548 new LabelSwitcher($.extend(labelSwitcherDefaults, { 58549 control: '#fundraiser_user_country', 58550 label: 'label[for="fundraiser_user_postal"]' 58551 })); 58552 58553 new PlaceholderSwitcher({ 58554 control: '#donation_country', 58555 input: '#donation_postal', 58556 values: { 58557 'US': '', 58558 'CA': '' 58559 }, 58560 valueDefault: 'optional' 58561 }); 58562 58563 new FundraiserSearch({ 58564 control: '.fundraiser-search__input, [data-fundraiser-search]' 58565 }); 58566 58567 new MoreLoader({ 58568 control: '.donation-list--control', 58569 container: '.donation-list--donations', 58570 endpoint: $('.donation-list--control').attr('data-donations-url'), 58571 totalPages: $('.donation-list--control').attr('data-total-pages') 58572 }); 58573 58574 new MoreLoader({ 58575 control: '[data-control="donations-orders-list"]', 58576 container: '[data-list="donations-orders"]', 58577 endpoint: $('[data-control="donations-orders-list"]').attr('data-url'), 58578 totalPages: $('[data-control="donations-orders-list"]').attr('data-total-pages') 58579 }); 58580 58581 new MoreLoader({ 58582 control: '[data-control="donations-list"]', 58583 container: '[data-list="donations"]', 58584 endpoint: $('[data-control="donations-list"]').attr('data-url'), 58585 totalPages: $('[data-control="donations-list"]').attr('data-total-pages') 58586 }); 58587 58588 new MoreLoader({ 58589 control: '[data-control="orders-list"]', 58590 container: '[data-list="orders"]', 58591 endpoint: $('[data-control="orders-list"]').attr('data-url'), 58592 totalPages: $('[data-control="orders-list"]').attr('data-total-pages') 58593 }); 58594 58595 new PasswordShow({ 58596 passwordField: '[name="fundraiser_user[password]"]', 58597 showControl: '[data-password-show]' 58598 }); 58599 58600 new ModalHandler(); 58601 new Tabs(); 58602 58603 // Attach jQuery Validation number rule 58604 // to all inputs with class 'numeric' 58605 $.validator.addClassRules("numeric", { 58606 number: true 58607 }); 58608 58609 $.validator.addClassRules("non_zero", { 58610 min: 1 58611 }); 58612 58613 // Attach jQuery Validation email rule 58614 // to all inputs with class 'email' 58615 $.validator.addClassRules("email", { 58616 email: true 58617 }); 58618 58619 $('#new_fundraiser_user').validate($.extend({}, formValidateDefaults, { 58620 rules: { 58621 'fundraiser_user[email]': { 58622 email: true 58623 } 58624 } 58625 })); 58626 58627 $('.currency').currency(); 58628 $('.numeric').numeric(); 58629 58630 // Forces a `focusout` event when a select changes 58631 // so that validation can occur on change 58632 $('select').on('change', function () { 58633 $(this).focusout(); 58634 }); 58635 58636 tinyMCE.init({ 58637 selector: 'textarea.editor', 58638 toolbar: 'formatselect | bold italic | link | bullist numlist', 58639 plugins: 'charactercount,link,paste', 58640 menubar: false, 58641 content_style: '* { font-family: "Arial"; font-size: 14px; line-height: 18px; }', 58642 block_formats: 'Paragraph=p;Header 1=h1;Header 2=h2;Header 3=h3', 58643 paste_as_text: true, 58644 min_height: 240, 58645 entity_encoding: 'raw', 58646 elementpath: false, 58647 58648 setup: function setup(editor) {
58649 editor.on('keyUp change', function (ed) { 58650 tinyMCE.triggerSave(); 58651 }); 58652 } 58653 }); 58654 58655 var fundraiserSearchMobile = function fundraiserSearchMobile() { 58656 var header = $('.header'); 58657 var searchTrigger = $('.fundraiser-search__btn'); 58658 var searchForm = $('.fundraiser-search__form'); 58659 var searchIcon = $('.fundraiser-search__icon'); 58660 58661 searchTrigger.bind('click', function (e) { 58662 e.preventDefault(); 58663 $(this).parent().find(searchForm).toggleClass('is-active'); 58664 header.toggleClass('search-active'); 58665 searchIcon.toggle(); 58666 }); 58667 }; 58668 58669 fundraiserSearchMobile(); 58670}); 58671// ========================================================== 58672// VENDOR 58673// ========================================================== 58674 58675 58676 58677 58678 58679 58680 58681 58682 58683 58684 58685 58686 58687 58688// ========================================================== 58689// CUSTOM 58690// ========================================================== 58691 58692 58693 58694 58695 58696 58697 58698 58699 58700 58701 58702 58703 58704 58705 58706 58707 58708 58709 58710 58711 58712 58713 58714;
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.