vendor: 7,684 bytes, lines 1-210
1/** 2 * TinyMCE version 7.9.1 (2025-05-29) 3 */ 4 5(function () { 6 'use strict'; 7 8 var global$3 = tinymce.util.Tools.resolve('tinymce.PluginManager'); 9 10 /* eslint-disable @typescript-eslint/no-wrapper-object-types */ 11 const hasProto = (v, constructor, predicate) => { 12 var _a; 13 if (predicate(v, constructor.prototype)) { 14 return true; 15 } 16 else { 17 // String-based fallback time 18 return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name; 19 } 20 }; 21 const typeOf = (x) => { 22 const t = typeof x; 23 if (x === null) { 24 return 'null'; 25 } 26 else if (t === 'object' && Array.isArray(x)) { 27 return 'array'; 28 } 29 else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) { 30 return 'string'; 31 } 32 else { 33 return t; 34 } 35 }; 36 const isType$1 = (type) => (value) => typeOf(value) === type; 37 const isSimpleType = (type) => (value) => typeof value === type; 38 const eq$1 = (t) => (a) => t === a; 39 const isString = isType$1('string'); 40 const isArray = isType$1('array'); 41 const isBoolean = isSimpleType('boolean'); 42 const isUndefined = eq$1(undefined); 43 const isNullable = (a) => a === null || a === undefined; 44 const isNonNullable = (a) => !isNullable(a); 45 const isFunction = isSimpleType('function'); 46 const isNumber = isSimpleType('number'); 47 48 const noop = () => { }; 49 /** Compose two unary functions. Similar to compose, but avoids using Function.prototype.apply. */ 50 const compose1 = (fbc, fab) => (a) => fbc(fab(a)); 51 const constant = (value) => { 52 return () => { 53 return value; 54 }; 55 }; 56 const identity = (x) => { 57 return x; 58 }; 59 const tripleEquals = (a, b) => { 60 return a === b; 61 }; 62 // eslint-disable-next-line prefer-arrow/prefer-arrow-functions 63 function curry(fn, ...initialArgs) { 64 return (...restArgs) => { 65 const all = initialArgs.concat(restArgs); 66 return fn.apply(null, all); 67 }; 68 } 69 const call = (f) => { 70 f(); 71 }; 72 const never = constant(false); 73 const always = constant(true); 74 75 /** 76 * The `Optional` type represents a value (of any type) that potentially does 77 * not exist. Any `Optional<T>` can either be a `Some<T>` (in which case the 78 * value does exist) or a `None` (in which case the value does not exist). This 79 * module defines a whole lot of FP-inspired utility functions for dealing with 80 * `Optional` objects. 81 * 82 * Comparison with null or undefined: 83 * - We don't get fancy null coalescing operators with `Optional` 84 * - We do get fancy helper functions with `Optional` 85 * - `Optional` support nesting, and allow for the type to still be nullable (or 86 * another `Optional`) 87 * - There is no option to turn off strict-optional-checks like there is for 88 * strict-null-checks 89 */ 90 class Optional { 91 // The internal representation has a `tag` and a `value`, but both are 92 // private: able to be console.logged, but not able to be accessed by code 93 constructor(tag, value) { 94 this.tag = tag; 95 this.value = value; 96 } 97 // --- Identities --- 98 /** 99 * Creates a new `Optional<T>` that **does** contain a value. 100 */ 101 static some(value) { 102 return new Optional(true, value); 103 } 104 /** 105 * Create a new `Optional<T>` that **does not** contain a value. `T` can be 106 * any type because we don't actually have a `T`. 107 */ 108 static none() { 109 return Optional.singletonNone; 110 } 111 /** 112 * Perform a transform on an `Optional` type. Regardless of whether this 113 * `Optional` contains a value or not, `fold` will return a value of type `U`. 114 * If this `Optional` does not contain a value, the `U` will be created by 115 * calling `onNone`. If this `Optional` does contain a value, the `U` will be 116 * created by calling `onSome`. 117 * 118 * For the FP enthusiasts in the room, this function: 119 * 1. Could be used to implement all of the functions below 120 * 2. Forms a catamorphism 121 */ 122 fold(onNone, onSome) { 123 if (this.tag) { 124 return onSome(this.value); 125 } 126 else { 127 return onNone(); 128 } 129 } 130 /** 131 * Determine if this `Optional` object contains a value. 132 */ 133 isSome() { 134 return this.tag; 135 } 136 /** 137 * Determine if this `Optional` object **does not** contain a value. 138 */ 139 isNone() { 140 return !this.tag; 141 } 142 // --- Functor (name stolen from Haskell / maths) --- 143 /** 144 * Perform a transform on an `Optional` object, **if** there is a value. If 145 * you provide a function to turn a T into a U, this is the function you use 146 * to turn an `Optional<T>` into an `Optional<U>`. If this **does** contain 147 * a value then the output will also contain a value (that value being the 148 * output of `mapper(this.value)`), and if this **does not** contain a value 149 * then neither will the output. 150 */ 151 map(mapper) { 152 if (this.tag) { 153 return Optional.some(mapper(this.value)); 154 } 155 else { 156 return Optional.none(); 157 } 158 } 159 // --- Monad (name stolen from Haskell / maths) --- 160 /** 161 * Perform a transform on an `Optional` object, **if** there is a value. 162 * Unlike `map`, here the transform itself also returns an `Optional`. 163 */ 164 bind(binder) { 165 if (this.tag) { 166 return binder(this.value); 167 } 168 else { 169 return Optional.none(); 170 } 171 } 172 // --- Traversable (name stolen from Haskell / maths) --- 173 /** 174 * For a given predicate, this function finds out if there **exists** a value 175 * inside this `Optional` object that meets the predicate. In practice, this 176 * means that for `Optional`s that do not contain a value it returns false (as 177 * no predicate-meeting value exists). 178 */ 179 exists(predicate) { 180 return this.tag && predicate(this.value); 181 } 182 /** 183 * For a given predicate, this function finds out if **all** the values inside 184 * this `Optional` object meet the predicate. In practice, this means that 185 * for `Optional`s that do not contain a value it returns true (as all 0 186 * objects do meet the predicate). 187 */ 188 forall(predicate) { 189 return !this.tag || predicate(this.value); 190 } 191 filter(predicate) { 192 if (!this.tag || predicate(this.value)) { 193 return this; 194 } 195 else { 196 return Optional.none(); 197 } 198 } 199 // --- Getters --- 200 /** 201 * Get the value out of the inside of the `Optional` object, using a default 202 * `replacement` value if the provided `Optional` object does not contain a 203 * value. 204 */ 205 getOr(replacement) { 206 return this.tag ? this.value : replacement; 207 } 208 /** 209 * Get the value out of the inside of the `Optional` object, using a default 210 * `replacement` value if the provided `Optional` object does not contain a
211 * value. Unlike `getOr`, in this method the `replacement` object is also 212 * `Optional` - meaning that this method will always return an `Optional`. 213 */ 214 or(replacement) { 215 return this.tag ? this : replacement; 216 } 217 /** 218 * Get the value out of the inside of the `Optional` object, using a default 219 * `replacement` value if the provided `Optional` object does not contain a 220 * value. Unlike `getOr`, in this method the `replacement` value is 221 * "thunked" - that is to say that you don't pass a value to `getOrThunk`, you 222 * pass a function which (if called) will **return** the `value` you want to 223 * use. 224 */ 225 getOrThunk(thunk) { 226 return this.tag ? this.value : thunk(); 227 } 228 /** 229 * Get the value out of the inside of the `Optional` object, using a default 230 * `replacement` value if the provided Optional object does not contain a 231 * value. 232 * 233 * Unlike `or`, in this method the `replacement` value is "thunked" - that is 234 * to say that you don't pass a value to `orThunk`, you pass a function which 235 * (if called) will **return** the `value` you want to use. 236 * 237 * Unlike `getOrThunk`, in this method the `replacement` value is also 238 * `Optional`, meaning that this method will always return an `Optional`. 239 */ 240 orThunk(thunk) { 241 return this.tag ? this : thunk(); 242 } 243 /** 244 * Get the value out of the inside of the `Optional` object, throwing an 245 * exception if the provided `Optional` object does not contain a value. 246 * 247 * WARNING: 248 * You should only be using this function if you know that the `Optional` 249 * object **is not** empty (otherwise you're throwing exceptions in production 250 * code, which is bad). 251 * 252 * In tests this is more acceptable. 253 * 254 * Prefer other methods to this, such as `.each`. 255 */ 256 getOrDie(message) { 257 if (!this.tag) { 258 throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None'); 259 } 260 else { 261 return this.value; 262 } 263 } 264 // --- Interop with null and undefined --- 265 /**
vendor: 5,993 bytes, lines 266-434
266 * Creates an `Optional` value from a nullable (or undefined-able) input. 267 * Null, or undefined, is converted to `None`, and anything else is converted 268 * to `Some`. 269 */ 270 static from(value) { 271 return isNonNullable(value) ? Optional.some(value) : Optional.none(); 272 } 273 /** 274 * Converts an `Optional` to a nullable type, by getting the value if it 275 * exists, or returning `null` if it does not. 276 */ 277 getOrNull() { 278 return this.tag ? this.value : null; 279 } 280 /** 281 * Converts an `Optional` to an undefined-able type, by getting the value if 282 * it exists, or returning `undefined` if it does not. 283 */ 284 getOrUndefined() { 285 return this.value; 286 } 287 // --- Utilities --- 288 /** 289 * If the `Optional` contains a value, perform an action on that value. 290 * Unlike the rest of the methods on this type, `.each` has side-effects. If 291 * you want to transform an `Optional<T>` **into** something, then this is not 292 * the method for you. If you want to use an `Optional<T>` to **do** 293 * something, then this is the method for you - provided you're okay with not 294 * doing anything in the case where the `Optional` doesn't have a value inside 295 * it. If you're not sure whether your use-case fits into transforming 296 * **into** something or **doing** something, check whether it has a return 297 * value. If it does, you should be performing a transform. 298 */ 299 each(worker) { 300 if (this.tag) { 301 worker(this.value); 302 } 303 } 304 /** 305 * Turn the `Optional` object into an array that contains all of the values 306 * stored inside the `Optional`. In practice, this means the output will have 307 * either 0 or 1 elements. 308 */ 309 toArray() { 310 return this.tag ? [this.value] : []; 311 } 312 /** 313 * Turn the `Optional` object into a string for debugging or printing. Not 314 * recommended for production code, but good for debugging. Also note that 315 * these days an `Optional` object can be logged to the console directly, and 316 * its inner value (if it exists) will be visible. 317 */ 318 toString() { 319 return this.tag ? `some(${this.value})` : 'none()'; 320 } 321 } 322 // Sneaky optimisation: every instance of Optional.none is identical, so just 323 // reuse the same object 324 Optional.singletonNone = new Optional(false); 325 326 /* eslint-disable @typescript-eslint/unbound-method */ 327 const nativeSlice = Array.prototype.slice; 328 const nativeIndexOf = Array.prototype.indexOf; 329 const nativePush = Array.prototype.push; 330 /* eslint-enable */ 331 const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t); 332 const contains = (xs, x) => rawIndexOf(xs, x) > -1; 333 const exists = (xs, pred) => { 334 for (let i = 0, len = xs.length; i < len; i++) { 335 const x = xs[i]; 336 if (pred(x, i)) { 337 return true; 338 } 339 } 340 return false; 341 }; 342 const range = (num, f) => { 343 const r = []; 344 for (let i = 0; i < num; i++) { 345 r.push(f(i)); 346 } 347 return r; 348 }; 349 const map = (xs, f) => { 350 // pre-allocating array size when it's guaranteed to be known 351 // http://jsperf.com/push-allocated-vs-dynamic/22 352 const len = xs.length; 353 const r = new Array(len); 354 for (let i = 0; i < len; i++) { 355 const x = xs[i]; 356 r[i] = f(x, i); 357 } 358 return r; 359 }; 360 // Unwound implementing other functions in terms of each. 361 // The code size is roughly the same, and it should allow for better optimisation. 362 // const each = function<T, U>(xs: T[], f: (x: T, i?: number, xs?: T[]) => void): void { 363 const each$1 = (xs, f) => { 364 for (let i = 0, len = xs.length; i < len; i++) { 365 const x = xs[i]; 366 f(x, i); 367 } 368 }; 369 const eachr = (xs, f) => { 370 for (let i = xs.length - 1; i >= 0; i--) { 371 const x = xs[i]; 372 f(x, i); 373 } 374 }; 375 const partition = (xs, pred) => { 376 const pass = []; 377 const fail = []; 378 for (let i = 0, len = xs.length; i < len; i++) { 379 const x = xs[i]; 380 const arr = pred(x, i) ? pass : fail; 381 arr.push(x); 382 } 383 return { pass, fail }; 384 }; 385 const filter$1 = (xs, pred) => { 386 const r = []; 387 for (let i = 0, len = xs.length; i < len; i++) { 388 const x = xs[i]; 389 if (pred(x, i)) { 390 r.push(x); 391 } 392 } 393 return r; 394 }; 395 const foldr = (xs, f, acc) => { 396 eachr(xs, (x, i) => { 397 acc = f(acc, x, i); 398 }); 399 return acc; 400 }; 401 const foldl = (xs, f, acc) => { 402 each$1(xs, (x, i) => { 403 acc = f(acc, x, i); 404 }); 405 return acc; 406 }; 407 const findUntil = (xs, pred, until) => { 408 for (let i = 0, len = xs.length; i < len; i++) { 409 const x = xs[i]; 410 if (pred(x, i)) { 411 return Optional.some(x); 412 } 413 else if (until(x, i)) { 414 break; 415 } 416 } 417 return Optional.none(); 418 }; 419 const find = (xs, pred) => { 420 return findUntil(xs, pred, never); 421 }; 422 const findIndex = (xs, pred) => { 423 for (let i = 0, len = xs.length; i < len; i++) { 424 const x = xs[i]; 425 if (pred(x, i)) { 426 return Optional.some(i); 427 } 428 } 429 return Optional.none(); 430 }; 431 const flatten$1 = (xs) => { 432 // Note, this is possible because push supports multiple arguments: 433 // http://jsperf.com/concat-push/6 434 // Note that in the past, concat() would silently work (very slowly) for array-like objects.
vendor: 32,234 bytes, lines 435-1275
435 // With this change it will throw an error. 436 const r = []; 437 for (let i = 0, len = xs.length; i < len; ++i) { 438 // Ensure that each value is an array itself 439 if (!isArray(xs[i])) { 440 throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs); 441 } 442 nativePush.apply(r, xs[i]); 443 } 444 return r; 445 }; 446 const bind = (xs, f) => flatten$1(map(xs, f)); 447 const forall = (xs, pred) => { 448 for (let i = 0, len = xs.length; i < len; ++i) { 449 const x = xs[i]; 450 if (pred(x, i) !== true) { 451 return false; 452 } 453 } 454 return true; 455 }; 456 const mapToObject = (xs, f) => { 457 const r = {}; 458 for (let i = 0, len = xs.length; i < len; i++) { 459 const x = xs[i]; 460 r[String(x)] = f(x, i); 461 } 462 return r; 463 }; 464 const get$4 = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none(); 465 const head = (xs) => get$4(xs, 0); 466 const last = (xs) => get$4(xs, xs.length - 1); 467 isFunction(Array.from) ? Array.from : (x) => nativeSlice.call(x); 468 const findMap = (arr, f) => { 469 for (let i = 0; i < arr.length; i++) { 470 const r = f(arr[i], i); 471 if (r.isSome()) { 472 return r; 473 } 474 } 475 return Optional.none(); 476 }; 477 478 // There are many variations of Object iteration that are faster than the 'for-in' style: 479 // http://jsperf.com/object-keys-iteration/107 480 // 481 // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering 482 const keys = Object.keys; 483 // eslint-disable-next-line @typescript-eslint/unbound-method 484 const hasOwnProperty = Object.hasOwnProperty; 485 const each = (obj, f) => { 486 const props = keys(obj); 487 for (let k = 0, len = props.length; k < len; k++) { 488 const i = props[k]; 489 const x = obj[i]; 490 f(x, i); 491 } 492 }; 493 const objAcc = (r) => (x, i) => { 494 r[i] = x; 495 }; 496 const internalFilter = (obj, pred, onTrue, onFalse) => { 497 each(obj, (x, i) => { 498 (pred(x, i) ? onTrue : onFalse)(x, i); 499 }); 500 }; 501 const filter = (obj, pred) => { 502 const t = {}; 503 internalFilter(obj, pred, objAcc(t), noop); 504 return t; 505 }; 506 const mapToArray = (obj, f) => { 507 const r = []; 508 each(obj, (value, name) => { 509 r.push(f(value, name)); 510 }); 511 return r; 512 }; 513 const values = (obj) => { 514 return mapToArray(obj, identity); 515 }; 516 const size = (obj) => { 517 return keys(obj).length; 518 }; 519 const get$3 = (obj, key) => { 520 return has(obj, key) ? Optional.from(obj[key]) : Optional.none(); 521 }; 522 const has = (obj, key) => hasOwnProperty.call(obj, key); 523 const hasNonNullableKey = (obj, key) => has(obj, key) && obj[key] !== undefined && obj[key] !== null; 524 const isEmpty$1 = (r) => { 525 for (const x in r) { 526 if (hasOwnProperty.call(r, x)) { 527 return false; 528 } 529 } 530 return true; 531 }; 532 533 const Cell = (initial) => { 534 let value = initial; 535 const get = () => { 536 return value; 537 }; 538 const set = (v) => { 539 value = v; 540 }; 541 return { 542 get, 543 set 544 }; 545 }; 546 547 /** 548 * **Is** the value stored inside this Optional object equal to `rhs`? 549 */ 550 const is$2 = (lhs, rhs, comparator = tripleEquals) => lhs.exists((left) => comparator(left, rhs)); 551 const cat = (arr) => { 552 const r = []; 553 const push = (x) => { 554 r.push(x); 555 }; 556 for (let i = 0; i < arr.length; i++) { 557 arr[i].each(push); 558 } 559 return r; 560 }; 561 /* 562 Notes on the lift functions: 563 - We used to have a generic liftN, but we were concerned about its type-safety, and the below variants were faster in microbenchmarks. 564 - The getOrDie calls are partial functions, but are checked beforehand. This is faster and more convenient (but less safe) than folds. 565 - && is used instead of a loop for simplicity and performance. 566 */ 567 const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none(); 568 const flatten = (oot) => oot.bind(identity); 569 // This can help with type inference, by specifying the type param on the none case, so the caller doesn't have to. 570 const someIf = (b, a) => b ? Optional.some(a) : Optional.none(); 571 572 const singleton = (doRevoke) => { 573 const subject = Cell(Optional.none()); 574 const revoke = () => subject.get().each(doRevoke); 575 const clear = () => { 576 revoke(); 577 subject.set(Optional.none()); 578 }; 579 const isSet = () => subject.get().isSome(); 580 const get = () => subject.get(); 581 const set = (s) => { 582 revoke(); 583 subject.set(Optional.some(s)); 584 }; 585 return { 586 clear, 587 isSet, 588 get, 589 set 590 }; 591 }; 592 const unbindable = () => singleton((s) => s.unbind()); 593 594 const removeFromStart = (str, numChars) => { 595 return str.substring(numChars); 596 }; 597 598 const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr; 599 const removeLeading = (str, prefix) => { 600 return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str; 601 }; 602 /** Does 'str' start with 'prefix'? 603 * Note: all strings start with the empty string. 604 * More formally, for all strings x, startsWith(x, ""). 605 * This is so that for all strings x and y, startsWith(y + x, y) 606 */ 607 const startsWith = (str, prefix) => { 608 return checkRange(str, prefix, 0); 609 }; 610 const blank = (r) => (s) => s.replace(r, ''); 611 /** removes all leading and trailing spaces */ 612 const trim = blank(/^\s+|\s+$/g); 613 const isNotEmpty = (s) => s.length > 0; 614 const isEmpty = (s) => !isNotEmpty(s); 615 const toInt = (value, radix = 10) => { 616 const num = parseInt(value, radix); 617 return isNaN(num) ? Optional.none() : Optional.some(num); 618 }; 619 const toFloat = (value) => { 620 const num = parseFloat(value); 621 return isNaN(num) ? Optional.none() : Optional.some(num); 622 }; 623 624 const cached = (f) => { 625 let called = false; 626 let r; 627 return (...args) => { 628 if (!called) { 629 called = true; 630 r = f.apply(null, args); 631 } 632 return r; 633 }; 634 }; 635 636 const fromHtml = (html, scope) => { 637 const doc = scope || document; 638 const div = doc.createElement('div'); 639 div.innerHTML = html; 640 if (!div.hasChildNodes() || div.childNodes.length > 1) { 641 const message = 'HTML does not have a single root node'; 642 // eslint-disable-next-line no-console 643 console.error(message, html); 644 throw new Error(message); 645 } 646 return fromDom$1(div.childNodes[0]); 647 }; 648 const fromTag = (tag, scope) => { 649 const doc = scope || document; 650 const node = doc.createElement(tag); 651 return fromDom$1(node); 652 }; 653 const fromText = (text, scope) => { 654 const doc = scope || document; 655 const node = doc.createTextNode(text); 656 return fromDom$1(node); 657 }; 658 const fromDom$1 = (node) => { 659 // TODO: Consider removing this check, but left atm for safety 660 if (node === null || node === undefined) { 661 throw new Error('Node cannot be null or undefined'); 662 } 663 return { 664 dom: node 665 }; 666 }; 667 const fromPoint = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom$1); 668 // tslint:disable-next-line:variable-name 669 const SugarElement = { 670 fromHtml, 671 fromTag, 672 fromText, 673 fromDom: fromDom$1, 674 fromPoint 675 }; 676 677 const COMMENT = 8; 678 const DOCUMENT = 9; 679 const DOCUMENT_FRAGMENT = 11; 680 const ELEMENT = 1; 681 const TEXT = 3; 682 683 const is$1 = (element, selector) => { 684 const dom = element.dom; 685 if (dom.nodeType !== ELEMENT) { 686 return false; 687 } 688 else { 689 const elem = dom; 690 if (elem.matches !== undefined) { 691 return elem.matches(selector); 692 } 693 else if (elem.msMatchesSelector !== undefined) { 694 return elem.msMatchesSelector(selector); 695 } 696 else if (elem.webkitMatchesSelector !== undefined) { 697 return elem.webkitMatchesSelector(selector); 698 } 699 else if (elem.mozMatchesSelector !== undefined) { 700 // cast to any as mozMatchesSelector doesn't exist in TS DOM lib 701 return elem.mozMatchesSelector(selector); 702 } 703 else { 704 throw new Error('Browser lacks native selectors'); 705 } // unfortunately we can't throw this on startup :( 706 } 707 }; 708 const bypassSelector = (dom) => 709 // Only elements, documents and shadow roots support querySelector 710 // shadow root element type is DOCUMENT_FRAGMENT 711 dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT || 712 // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/ 713 dom.childElementCount === 0; 714 const all$1 = (selector, scope) => { 715 const base = scope === undefined ? document : scope.dom; 716 return bypassSelector(base) ? [] : map(base.querySelectorAll(selector), SugarElement.fromDom); 717 }; 718 const one = (selector, scope) => { 719 const base = scope === undefined ? document : scope.dom; 720 return bypassSelector(base) ? Optional.none() : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom); 721 }; 722 723 const eq = (e1, e2) => e1.dom === e2.dom; 724 const is = is$1; 725 726 const name = (element) => { 727 const r = element.dom.nodeName; 728 return r.toLowerCase(); 729 }; 730 const type = (element) => element.dom.nodeType; 731 const isType = (t) => (element) => type(element) === t; 732 const isComment = (element) => type(element) === COMMENT || name(element) === '#comment'; 733 const isElement = isType(ELEMENT); 734 const isText = isType(TEXT); 735 const isDocument = isType(DOCUMENT); 736 const isDocumentFragment = isType(DOCUMENT_FRAGMENT); 737 const isTag = (tag) => (e) => isElement(e) && name(e) === tag; 738 739 /** 740 * The document associated with the current element 741 * NOTE: this will throw if the owner is null. 742 */ 743 const owner = (element) => SugarElement.fromDom(element.dom.ownerDocument); 744 /** 745 * If the element is a document, return it. Otherwise, return its ownerDocument. 746 * @param dos 747 */ 748 const documentOrOwner = (dos) => isDocument(dos) ? dos : owner(dos); 749 const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom); 750 const parents = (element, isRoot) => { 751 const stop = isFunction(isRoot) ? isRoot : never; 752 // This is used a *lot* so it needs to be performant, not recursive 753 let dom = element.dom; 754 const ret = []; 755 while (dom.parentNode !== null && dom.parentNode !== undefined) { 756 const rawParent = dom.parentNode; 757 const p = SugarElement.fromDom(rawParent); 758 ret.push(p); 759 if (stop(p) === true) { 760 break; 761 } 762 else { 763 dom = rawParent; 764 } 765 } 766 return ret; 767 }; 768 const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom); 769 const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom); 770 const children$3 = (element) => map(element.dom.childNodes, SugarElement.fromDom); 771 const child$3 = (element, index) => { 772 const cs = element.dom.childNodes; 773 return Optional.from(cs[index]).map(SugarElement.fromDom); 774 }; 775 const firstChild = (element) => child$3(element, 0); 776 777 /** 778 * Is the element a ShadowRoot? 779 * 780 * Note: this is insufficient to test if any element is a shadow root, but it is sufficient to differentiate between 781 * a Document and a ShadowRoot. 782 */ 783 const isShadowRoot = (dos) => isDocumentFragment(dos) && isNonNullable(dos.dom.host); 784 const getRootNode = (e) => SugarElement.fromDom(e.dom.getRootNode()); 785 /** If this element is in a ShadowRoot, return it. */ 786 const getShadowRoot = (e) => { 787 const r = getRootNode(e); 788 return isShadowRoot(r) ? Optional.some(r) : Optional.none(); 789 }; 790 /** Return the host of a ShadowRoot. 791 * 792 * This function will throw if Shadow DOM is unsupported in the browser, or if the host is null. 793 * If you actually have a ShadowRoot, this shouldn't happen. 794 */ 795 const getShadowHost = (e) => SugarElement.fromDom(e.dom.host); 796 797 const before = (marker, element) => { 798 const parent$1 = parent(marker); 799 parent$1.each((v) => { 800 v.dom.insertBefore(element.dom, marker.dom); 801 }); 802 }; 803 const after$1 = (marker, element) => { 804 const sibling = nextSibling(marker); 805 sibling.fold(() => { 806 const parent$1 = parent(marker); 807 parent$1.each((v) => { 808 append$1(v, element); 809 }); 810 }, (v) => { 811 before(v, element); 812 }); 813 }; 814 const prepend = (parent, element) => { 815 const firstChild$1 = firstChild(parent); 816 firstChild$1.fold(() => { 817 append$1(parent, element); 818 }, (v) => { 819 parent.dom.insertBefore(element.dom, v.dom); 820 }); 821 }; 822 const append$1 = (parent, element) => { 823 parent.dom.appendChild(element.dom); 824 }; 825 const wrap = (element, wrapper) => { 826 before(element, wrapper); 827 append$1(wrapper, element); 828 }; 829 830 const after = (marker, elements) => { 831 each$1(elements, (x, i) => { 832 const e = i === 0 ? marker : elements[i - 1]; 833 after$1(e, x); 834 }); 835 }; 836 const append = (parent, elements) => { 837 each$1(elements, (x) => { 838 append$1(parent, x); 839 }); 840 }; 841 842 const rawSet = (dom, key, value) => { 843 /* 844 * JQuery coerced everything to a string, and silently did nothing on text node/null/undefined. 845 * 846 * We fail on those invalid cases, only allowing numbers and booleans. 847 */ 848 if (isString(value) || isBoolean(value) || isNumber(value)) { 849 dom.setAttribute(key, value + ''); 850 } 851 else { 852 // eslint-disable-next-line no-console 853 console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom); 854 throw new Error('Attribute value was not simple'); 855 } 856 }; 857 const set$2 = (element, key, value) => { 858 rawSet(element.dom, key, value); 859 }; 860 const setAll = (element, attrs) => { 861 const dom = element.dom; 862 each(attrs, (v, k) => { 863 rawSet(dom, k, v); 864 }); 865 }; 866 const get$2 = (element, key) => { 867 const v = element.dom.getAttribute(key); 868 // undefined is the more appropriate value for JS, and this matches JQuery 869 return v === null ? undefined : v; 870 }; 871 const getOpt = (element, key) => Optional.from(get$2(element, key)); 872 const remove$2 = (element, key) => { 873 element.dom.removeAttribute(key); 874 }; 875 const clone = (element) => foldl(element.dom.attributes, (acc, attr) => { 876 acc[attr.name] = attr.value; 877 return acc; 878 }, {}); 879 880 const remove$1 = (element) => { 881 const dom = element.dom; 882 if (dom.parentNode !== null) { 883 dom.parentNode.removeChild(dom); 884 } 885 }; 886 const unwrap = (wrapper) => { 887 const children = children$3(wrapper); 888 if (children.length > 0) { 889 after(wrapper, children); 890 } 891 remove$1(wrapper); 892 }; 893 894 const fromDom = (nodes) => map(nodes, SugarElement.fromDom); 895 896 // some elements, such as mathml, don't have style attributes 897 // others, such as angular elements, have style attributes that aren't a CSSStyleDeclaration 898 const isSupported = (dom) => 899 // eslint-disable-next-line @typescript-eslint/unbound-method 900 dom.style !== undefined && isFunction(dom.style.getPropertyValue); 901 902 // Node.contains() is very, very, very good performance 903 // http://jsperf.com/closest-vs-contains/5 904 const inBody = (element) => { 905 // Technically this is only required on IE, where contains() returns false for text nodes. 906 // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet). 907 const dom = isText(element) ? element.dom.parentNode : element.dom; 908 // use ownerDocument.body to ensure this works inside iframes. 909 // Normally contains is bad because an element "contains" itself, but here we want that. 910 if (dom === undefined || dom === null || dom.ownerDocument === null) { 911 return false; 912 } 913 const doc = dom.ownerDocument; 914 return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost)); 915 }; 916 917 const internalSet = (dom, property, value) => { 918 // This is going to hurt. Apologies. 919 // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through. 920 // we're going to be explicit; strings only. 921 if (!isString(value)) { 922 // eslint-disable-next-line no-console 923 console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom); 924 throw new Error('CSS value must be a string: ' + value); 925 } 926 // removed: support for dom().style[property] where prop is camel case instead of normal property name 927 if (isSupported(dom)) { 928 dom.style.setProperty(property, value); 929 } 930 }; 931 const internalRemove = (dom, property) => { 932 /* 933 * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims 934 * 935 * http://help.dottoro.com/ljopsjck.php 936 * http://stackoverflow.com/a/7901886/7546 937 */ 938 if (isSupported(dom)) { 939 dom.style.removeProperty(property); 940 } 941 }; 942 const set$1 = (element, property, value) => { 943 const dom = element.dom; 944 internalSet(dom, property, value); 945 }; 946 /* 947 * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle). 948 * Blame CSS 2.0. 949 * 950 * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value 951 */ 952 const get$1 = (element, property) => { 953 const dom = element.dom; 954 /* 955 * IE9 and above per 956 * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle 957 * 958 * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous. 959 * 960 * JQuery has some magic here for IE popups, but we don't really need that. 961 * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6. 962 */ 963 const styles = window.getComputedStyle(dom); 964 const r = styles.getPropertyValue(property); 965 // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value. 966 // Turns out we do this a lot. 967 return (r === '' && !inBody(element)) ? getUnsafeProperty(dom, property) : r; 968 }; 969 // removed: support for dom().style[property] where prop is camel case instead of normal property name 970 // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists. 971 const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : ''; 972 /* 973 * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM: 974 * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value 975 * 976 * Returns NONE if the property isn't set, or the value is an empty string. 977 */ 978 const getRaw$1 = (element, property) => { 979 const dom = element.dom; 980 const raw = getUnsafeProperty(dom, property); 981 return Optional.from(raw).filter((r) => r.length > 0); 982 }; 983 const remove = (element, property) => { 984 const dom = element.dom; 985 internalRemove(dom, property); 986 if (is$2(getOpt(element, 'style').map(trim), '')) { 987 // No more styles left, remove the style attribute as well 988 remove$2(element, 'style'); 989 } 990 }; 991 992 const Dimension = (name, getOffset) => { 993 const set = (element, h) => { 994 if (!isNumber(h) && !h.match(/^[0-9]+$/)) { 995 throw new Error(name + '.set accepts only positive integer values. Value was ' + h); 996 } 997 const dom = element.dom; 998 if (isSupported(dom)) { 999 dom.style[name] = h + 'px'; 1000 } 1001 }; 1002 /* 1003 * jQuery supports querying width and height on the document and window objects. 1004 * 1005 * TBIO doesn't do this, so the code is removed to save space, but left here just in case. 1006 */ 1007 /* 1008 var getDocumentWidth = (element) => { 1009 var dom = element.dom; 1010 if (Node.isDocument(element)) { 1011 var body = dom.body; 1012 var doc = dom.documentElement; 1013 return Math.max( 1014 body.scrollHeight, 1015 doc.scrollHeight, 1016 body.offsetHeight, 1017 doc.offsetHeight, 1018 doc.clientHeight 1019 ); 1020 } 1021 }; 1022 1023 var getWindowWidth = (element) => { 1024 var dom = element.dom; 1025 if (dom.window === dom) { 1026 // There is no offsetHeight on a window, so use the clientHeight of the document 1027 return dom.document.documentElement.clientHeight; 1028 } 1029 }; 1030 */ 1031 const get = (element) => { 1032 const r = getOffset(element); 1033 // zero or null means non-standard or disconnected, fall back to CSS 1034 if (r <= 0 || r === null) { 1035 const css = get$1(element, name); 1036 // ugh this feels dirty, but it saves cycles 1037 return parseFloat(css) || 0; 1038 } 1039 return r; 1040 }; 1041 // in jQuery, getOuter replicates (or uses) box-sizing: border-box calculations 1042 // although these calculations only seem relevant for quirks mode, and edge cases TBIO doesn't rely on 1043 const getOuter = get; 1044 const aggregate = (element, properties) => foldl(properties, (acc, property) => { 1045 const val = get$1(element, property); 1046 const value = val === undefined ? 0 : parseInt(val, 10); 1047 return isNaN(value) ? acc : acc + value; 1048 }, 0); 1049 const max = (element, value, properties) => { 1050 const cumulativeInclusions = aggregate(element, properties); 1051 // if max-height is 100px and your cumulativeInclusions is 150px, there is no way max-height can be 100px, so we return 0. 1052 const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0; 1053 return absoluteMax; 1054 }; 1055 return { 1056 set, 1057 get, 1058 getOuter, 1059 aggregate, 1060 max 1061 }; 1062 }; 1063 1064 const toNumber = (px, fallback) => toFloat(px).getOr(fallback); 1065 const getProp = (element, name, fallback) => toNumber(get$1(element, name), fallback); 1066 const calcContentBoxSize = (element, size, upper, lower) => { 1067 const paddingUpper = getProp(element, `padding-${upper}`, 0); 1068 const paddingLower = getProp(element, `padding-${lower}`, 0); 1069 const borderUpper = getProp(element, `border-${upper}-width`, 0); 1070 const borderLower = getProp(element, `border-${lower}-width`, 0); 1071 return size - paddingUpper - paddingLower - borderUpper - borderLower; 1072 }; 1073 const getCalculatedWidth = (element, boxSizing) => { 1074 const dom = element.dom; 1075 const width = dom.getBoundingClientRect().width || dom.offsetWidth; 1076 return boxSizing === 'border-box' ? width : calcContentBoxSize(element, width, 'left', 'right'); 1077 }; 1078 const getInnerWidth = (element) => getCalculatedWidth(element, 'content-box'); 1079 1080 Dimension('width', (element) => 1081 // IMO passing this function is better than using dom['offset' + 'width'] 1082 element.dom.offsetWidth); 1083 Dimension('width', (element) => { 1084 const dom = element.dom; 1085 return inBody(element) ? dom.getBoundingClientRect().width : dom.offsetWidth; 1086 }); 1087 const getInner = getInnerWidth; 1088 1089 const NodeValue = (is, name) => { 1090 const get = (element) => { 1091 if (!is(element)) { 1092 throw new Error('Can only get ' + name + ' value of a ' + name + ' node'); 1093 } 1094 return getOption(element).getOr(''); 1095 }; 1096 const getOption = (element) => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none(); 1097 const set = (element, value) => { 1098 if (!is(element)) { 1099 throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node'); 1100 } 1101 element.dom.nodeValue = value; 1102 }; 1103 return { 1104 get, 1105 getOption, 1106 set 1107 }; 1108 }; 1109 1110 const api = NodeValue(isText, 'text'); 1111 const get = (element) => api.get(element); 1112 const set = (element, value) => api.set(element, value); 1113 1114 var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => { 1115 if (is(scope, a)) { 1116 return Optional.some(scope); 1117 } 1118 else if (isFunction(isRoot) && isRoot(scope)) { 1119 return Optional.none(); 1120 } 1121 else { 1122 return ancestor(scope, a, isRoot); 1123 } 1124 }; 1125 1126 const ancestor$1 = (scope, predicate, isRoot) => { 1127 let element = scope.dom; 1128 const stop = isFunction(isRoot) ? isRoot : never; 1129 while (element.parentNode) { 1130 element = element.parentNode; 1131 const el = SugarElement.fromDom(element); 1132 if (predicate(el)) { 1133 return Optional.some(el); 1134 } 1135 else if (stop(el)) { 1136 break; 1137 } 1138 } 1139 return Optional.none(); 1140 }; 1141 const closest$2 = (scope, predicate, isRoot) => { 1142 // This is required to avoid ClosestOrAncestor passing the predicate to itself 1143 const is = (s, test) => test(s); 1144 return ClosestOrAncestor(is, ancestor$1, scope, predicate, isRoot); 1145 }; 1146 const child$2 = (scope, predicate) => { 1147 const pred = (node) => predicate(SugarElement.fromDom(node)); 1148 const result = find(scope.dom.childNodes, pred); 1149 return result.map(SugarElement.fromDom); 1150 }; 1151 1152 const ancestor = (scope, selector, isRoot) => ancestor$1(scope, (e) => is$1(e, selector), isRoot); 1153 const child$1 = (scope, selector) => child$2(scope, (e) => is$1(e, selector)); 1154 const descendant = (scope, selector) => one(selector, scope); 1155 // Returns Some(closest ancestor element (sugared)) matching 'selector' up to isRoot, or None() otherwise 1156 const closest$1 = (scope, selector, isRoot) => { 1157 const is = (element, selector) => is$1(element, selector); 1158 return ClosestOrAncestor(is, ancestor, scope, selector, isRoot); 1159 }; 1160 1161 const closest = (target) => closest$1(target, '[contenteditable]'); 1162 const isEditable = (element, assumeEditable = false) => { 1163 if (inBody(element)) { 1164 return element.dom.isContentEditable; 1165 } 1166 else { 1167 // Find the closest contenteditable element and check if it's editable 1168 return closest(element).fold(constant(assumeEditable), (editable) => getRaw(editable) === 'true'); 1169 } 1170 }; 1171 const getRaw = (element) => element.dom.contentEditable; 1172 1173 const children$2 = (scope, predicate) => filter$1(children$3(scope), predicate); 1174 const descendants$1 = (scope, predicate) => { 1175 let result = []; 1176 // Recurse.toArray() might help here 1177 each$1(children$3(scope), (x) => { 1178 if (predicate(x)) { 1179 result = result.concat([x]); 1180 } 1181 result = result.concat(descendants$1(x, predicate)); 1182 }); 1183 return result; 1184 }; 1185 1186 const children$1 = (scope, selector) => 1187 // It may surprise you to learn this is exactly what JQuery does 1188 // TODO: Avoid all the wrapping and unwrapping 1189 children$2(scope, (e) => is$1(e, selector)); 1190 const descendants = (scope, selector) => all$1(selector, scope); 1191 1192 const child = (scope, selector) => child$1(scope, selector).isSome(); 1193 1194 /* 1195 NOTE: This file is partially duplicated in the following locations: 1196 - models/dom/table/core/TableUtils.ts 1197 - advtable 1198 Make sure that if making changes to this file, the other files are updated as well 1199 */ 1200 const getNodeName = (elm) => elm.nodeName.toLowerCase(); 1201 const getBody = (editor) => SugarElement.fromDom(editor.getBody()); 1202 const getIsRoot = (editor) => (element) => eq(element, getBody(editor)); 1203 const removePxSuffix = (size) => size ? size.replace(/px$/, '') : ''; 1204 const addPxSuffix = (size) => /^\d+(\.\d+)?$/.test(size) ? size + 'px' : size; 1205 const getSelectionStart = (editor) => SugarElement.fromDom(editor.selection.getStart()); 1206 const getSelectionEnd = (editor) => SugarElement.fromDom(editor.selection.getEnd()); 1207 const isInEditableContext = (cell) => closest$2(cell, isTag('table')).forall(isEditable); 1208 1209 const validSectionList = ['tfoot', 'thead', 'tbody', 'colgroup']; 1210 const isValidSection = (parentName) => contains(validSectionList, parentName); 1211 const grid = (rows, columns) => ({ 1212 rows, 1213 columns 1214 }); 1215 const detail = (element, rowspan, colspan) => ({ 1216 element, 1217 rowspan, 1218 colspan 1219 }); 1220 const extended = (element, rowspan, colspan, row, column, isLocked) => ({ 1221 element, 1222 rowspan, 1223 colspan, 1224 row, 1225 column, 1226 isLocked 1227 }); 1228 const rowdetail = (element, cells, section) => ({ 1229 element, 1230 cells, 1231 section 1232 }); 1233 const bounds = (startRow, startCol, finishRow, finishCol) => ({ 1234 startRow, 1235 startCol, 1236 finishRow, 1237 finishCol 1238 }); 1239 const columnext = (element, colspan, column) => ({ 1240 element, 1241 colspan, 1242 column 1243 }); 1244 const colgroup = (element, columns) => ({ 1245 element, 1246 columns 1247 }); 1248 1249 const getAttrValue = (cell, name, fallback = 0) => getOpt(cell, name).map((value) => parseInt(value, 10)).getOr(fallback); 1250 1251 const firstLayer = (scope, selector) => { 1252 return filterFirstLayer(scope, selector, always); 1253 }; 1254 const filterFirstLayer = (scope, selector, predicate) => { 1255 return bind(children$3(scope), (x) => { 1256 if (is$1(x, selector)) { 1257 return predicate(x) ? [x] : []; 1258 } 1259 else { 1260 return filterFirstLayer(x, selector, predicate); 1261 } 1262 }); 1263 }; 1264 1265 // lookup inside this table 1266 const lookup = (tags, element, isRoot = never) => { 1267 // If the element we're inspecting is the root, we definitely don't want it. 1268 if (isRoot(element)) { 1269 return Optional.none(); 1270 } 1271 // This looks a lot like SelectorFind.closest, with one big exception - the isRoot check. 1272 // The code here will look for parents if passed a table, SelectorFind.closest with that specific isRoot check won't. 1273 if (contains(tags, name(element))) { 1274 return Optional.some(element); 1275 }
1276 const isRootOrUpperTable = (elm) => is$1(elm, 'table') || isRoot(elm); 1277 return ancestor(element, tags.join(','), isRootOrUpperTable); 1278 }; 1279 /* 1280 * Identify the optional cell that element represents. 1281 */ 1282 const cell = (element, isRoot) => lookup(['td', 'th'], element, isRoot); 1283 const cells = (ancestor) => firstLayer(ancestor, 'th,td'); 1284 const columns = (ancestor) => { 1285 if (is$1(ancestor, 'colgroup')) { 1286 return children$1(ancestor, 'col'); 1287 } 1288 else { 1289 return bind(columnGroups(ancestor), (columnGroup) => children$1(columnGroup, 'col')); 1290 } 1291 }; 1292 const table = (element, isRoot) => closest$1(element, 'table', isRoot); 1293 const rows = (ancestor) => firstLayer(ancestor, 'tr'); 1294 const columnGroups = (ancestor) => table(ancestor).fold(constant([]), (table) => children$1(table, 'colgroup')); 1295 1296 const isHeaderCell = isTag('th'); 1297 const getRowHeaderType = (isHeaderRow, isHeaderCells) => { 1298 if (isHeaderRow && isHeaderCells) { 1299 return 'sectionCells'; 1300 } 1301 else if (isHeaderRow) { 1302 return 'section'; 1303 } 1304 else { 1305 return 'cells'; 1306 } 1307 }; 1308 const getRowType$1 = (row) => { 1309 // Header rows can use a combination of theads and ths - want to detect the different combinations 1310 const isHeaderRow = row.section === 'thead'; 1311 const isHeaderCells = is$2(findCommonCellType(row.cells), 'th'); 1312 if (row.section === 'tfoot') { 1313 return { type: 'footer' }; 1314 } 1315 else if (isHeaderRow || isHeaderCells) { 1316 return { type: 'header', subType: getRowHeaderType(isHeaderRow, isHeaderCells) }; 1317 } 1318 else { 1319 return { type: 'body' }; 1320 } 1321 }; 1322 const findCommonCellType = (cells) => { 1323 const headerCells = filter$1(cells, (cell) => isHeaderCell(cell.element)); 1324 if (headerCells.length === 0) { 1325 return Optional.some('td'); 1326 } 1327 else if (headerCells.length === cells.length) { 1328 return Optional.some('th'); 1329 } 1330 else { 1331 return Optional.none(); 1332 } 1333 }; 1334 const findCommonRowType = (rows) => { 1335 const rowTypes = map(rows, (row) => getRowType$1(row).type); 1336 const hasHeader = contains(rowTypes, 'header'); 1337 const hasFooter = contains(rowTypes, 'footer'); 1338 if (!hasHeader && !hasFooter) { 1339 return Optional.some('body'); 1340 } 1341 else { 1342 const hasBody = contains(rowTypes, 'body'); 1343 if (hasHeader && !hasBody && !hasFooter) { 1344 return Optional.some('header'); 1345 } 1346 else if (!hasHeader && !hasBody && hasFooter) { 1347 return Optional.some('footer'); 1348 } 1349 else { 1350 return Optional.none(); 1351 } 1352 } 1353 }; 1354 1355 const fromRowsOrColGroups = (elems, getSection) => map(elems, (row) => { 1356 if (name(row) === 'colgroup') { 1357 const cells = map(columns(row), (column) => { 1358 const colspan = getAttrValue(column, 'span', 1); 1359 return detail(column, 1, colspan); 1360 }); 1361 return rowdetail(row, cells, 'colgroup'); 1362 } 1363 else { 1364 const cells$1 = map(cells(row), (cell) => { 1365 const rowspan = getAttrValue(cell, 'rowspan', 1); 1366 const colspan = getAttrValue(cell, 'colspan', 1); 1367 return detail(cell, rowspan, colspan); 1368 }); 1369 return rowdetail(row, cells$1, getSection(row)); 1370 } 1371 }); 1372 const getParentSection = (group) => parent(group).map((parent) => { 1373 const parentName = name(parent); 1374 return isValidSection(parentName) ? parentName : 'tbody'; 1375 }).getOr('tbody'); 1376 /* 1377 * Takes a DOM table and returns a list of list of: 1378 element: row element 1379 cells: (id, rowspan, colspan) structs 1380 */ 1381 const fromTable$1 = (table) => { 1382 const rows$1 = rows(table); 1383 const columnGroups$1 = columnGroups(table); 1384 const elems = [...columnGroups$1, ...rows$1]; 1385 return fromRowsOrColGroups(elems, getParentSection); 1386 }; 1387 1388 const LOCKED_COL_ATTR = 'data-snooker-locked-cols'; 1389 const getLockedColumnsFromTable = (table) => getOpt(table, LOCKED_COL_ATTR) 1390 .bind((lockedColStr) => Optional.from(lockedColStr.match(/\d+/g))) 1391 .map((lockedCols) => mapToObject(lockedCols, always)); 1392
1393 const key = (row, column) => { 1394 return row + ',' + column; 1395 }; 1396 const getAt = (warehouse, row, column) => Optional.from(warehouse.access[key(row, column)]); 1397 const findItem = (warehouse, item, comparator) => { 1398 const filtered = filterItems(warehouse, (detail) => { 1399 return comparator(item, detail.element); 1400 }); 1401 return filtered.length > 0 ? Optional.some(filtered[0]) : Optional.none(); 1402 }; 1403 const filterItems = (warehouse, predicate) => { 1404 const all = bind(warehouse.all, (r) => { 1405 return r.cells; 1406 }); 1407 return filter$1(all, predicate); 1408 }; 1409 const generateColumns = (rowData) => { 1410 const columnsGroup = {}; 1411 let index = 0; 1412 each$1(rowData.cells, (column) => { 1413 const colspan = column.colspan; 1414 range(colspan, (columnIndex) => { 1415 const colIndex = index + columnIndex; 1416 columnsGroup[colIndex] = columnext(column.element, colspan, colIndex); 1417 }); 1418 index += colspan; 1419 }); 1420 return columnsGroup; 1421 }; 1422 /* 1423 * From a list of list of Detail, generate three pieces of information: 1424 * 1. the grid size 1425 * 2. a data structure which can efficiently identify which cell is in which row,column position 1426 * 3. a list of all cells in order left-to-right, top-to-bottom 1427 */ 1428 const generate = (list) => { 1429 // list is an array of objects, made by cells and elements 1430 // elements: is the TR 1431 // cells: is an array of objects representing the cells in the row. 1432 // It is made of: 1433 // colspan (merge cell) 1434 // element 1435 // rowspan (merge cols) 1436 const access = {}; 1437 const cells = []; 1438 const tableOpt = head(list).map((rowData) => rowData.element).bind(table); 1439 const lockedColumns = tableOpt.bind(getLockedColumnsFromTable).getOr({}); 1440 let maxRows = 0; 1441 let maxColumns = 0; 1442 let rowCount = 0; 1443 const { pass: colgroupRows, fail: rows } = partition(list, (rowData) => rowData.section === 'colgroup'); 1444 // Handle rows first 1445 each$1(rows, (rowData) => { 1446 const currentRow = []; 1447 each$1(rowData.cells, (rowCell) => { 1448 let start = 0; 1449 // If this spot has been taken by a previous rowspan, skip it. 1450 while (access[key(rowCount, start)] !== undefined) { 1451 start++; 1452 } 1453 const isLocked = hasNonNullableKey(lockedColumns, start.toString()); 1454 const current = extended(rowCell.element, rowCell.rowspan, rowCell.colspan, rowCount, start, isLocked); 1455 // Occupy all the (row, column) positions that this cell spans for. 1456 for (let occupiedColumnPosition = 0; occupiedColumnPosition < rowCell.colspan; occupiedColumnPosition++) { 1457 for (let occupiedRowPosition = 0; occupiedRowPosition < rowCell.rowspan; occupiedRowPosition++) { 1458 const rowPosition = rowCount + occupiedRowPosition; 1459 const columnPosition = start + occupiedColumnPosition; 1460 const newpos = key(rowPosition, columnPosition); 1461 access[newpos] = current; 1462 maxColumns = Math.max(maxColumns, columnPosition + 1); 1463 } 1464 } 1465 currentRow.push(current); 1466 }); 1467 maxRows++; 1468 cells.push(rowdetail(rowData.element, currentRow, rowData.section)); 1469 rowCount++; 1470 }); 1471 // Handle colgroups 1472 // Note: Currently only a single colgroup is supported so just use the last one 1473 const { columns, colgroups } = last(colgroupRows).map((rowData) => { 1474 const columns = generateColumns(rowData); 1475 const colgroup$1 = colgroup(rowData.element, values(columns)); 1476 return { 1477 colgroups: [colgroup$1], 1478 columns 1479 }; 1480 }).getOrThunk(() => ({ 1481 colgroups: [], 1482 columns: {} 1483 })); 1484 const grid$1 = grid(maxRows, maxColumns); 1485 return { 1486 grid: grid$1, 1487 access, 1488 all: cells, 1489 columns, 1490 colgroups 1491 }; 1492 }; 1493 const fromTable = (table) => { 1494 const list = fromTable$1(table); 1495 return generate(list); 1496 }; 1497 const justCells = (warehouse) => bind(warehouse.all, (w) => w.cells); 1498 const justColumns = (warehouse) => values(warehouse.columns); 1499 const hasColumns = (warehouse) => keys(warehouse.columns).length > 0; 1500 const getColumnAt = (warehouse, columnIndex) => Optional.from(warehouse.columns[columnIndex]); 1501 const Warehouse = { 1502 fromTable, 1503 generate, 1504 getAt, 1505 findItem, 1506 filterItems, 1507 justCells, 1508 justColumns, 1509 hasColumns, 1510 getColumnAt 1511 }; 1512 1513 const findInWarehouse = (warehouse, element) => findMap(warehouse.all, (r) =>
1513 find(r.cells, (e) => eq(element, e.element))); 1514 const extractCells = (warehouse, target, predicate) => { 1515 const details = map(target.selection, (cell$1) => { 1516 return cell(cell$1) 1517 .bind((lc) => findInWarehouse(warehouse, lc)) 1518 .filter(predicate); 1519 }); 1520 const cells = cat(details); 1521 return someIf(cells.length > 0, cells); 1522 }; 1523 const onMergable = (_warehouse, target) => target.mergable; 1524 const onUnmergable = (_warehouse, target) => target.unmergable; 1525 const onCells = (warehouse, target) => extractCells(warehouse, target, always); 1526 const isUnlockedTableCell = (warehouse, cell) => findInWarehouse(warehouse, cell).exists((detail) => !detail.isLocked); 1527 const allUnlocked = (warehouse, cells) => forall(cells, (cell) => isUnlockedTableCell(warehouse, cell)); 1528 // If any locked columns are present in the selection, then don't want to be able to merge 1529 const onUnlockedMergable = (warehouse, target) => onMergable(warehouse, target).filter((mergeable) => allUnlocked(warehouse, mergeable.cells)); 1530 // If any locked columns are present in the selection, then don't want to be able to unmerge 1531 const onUnlockedUnmergable = (warehouse, target) => onUnmergable(warehouse, target).filter((cells) => allUnlocked(warehouse, cells)); 1532 1533 const isCol = isTag('col'); 1534 const isColgroup = isTag('colgroup'); 1535 const isRow = (element) => name(element) === 'tr' || isColgroup(element); 1536 const elementToData = (element) => { 1537 const colspan = getAttrValue(element, 'colspan', 1); 1538 const rowspan = getAttrValue(element, 'rowspan', 1); 1539 return { 1540 element, 1541 colspan, 1542 rowspan 1543 }; 1544 }; 1545 // note that `toData` seems to be only for testing 1546 const modification = (generators, toData = elementToData) => { 1547 const nuCell = (data) => isCol(data.element) ? generators.col(data) : generators.cell(data); 1548 const nuRow = (data) => isColgroup(data.element) ? generators.colgroup(data) : generators.row(data); 1549 const add = (element) => { 1550 if (isRow(element)) { 1551 return nuRow({ element }); 1552 } 1553 else { 1554 const cell = element; 1555 const replacement = nuCell(toData(cell)); 1556 recent = Optional.some({ item: cell, replacement }); 1557 return replacement; 1558 } 1559 }; 1560 let recent = Optional.none(); 1561 const getOrInit = (element, comparator) => { 1562 return recent.fold(() => { 1563 return add(element); 1564 }, (p) => { 1565 return comparator(element, p.item) ? p.replacement : add(element); 1566 }); 1567 }; 1568 return { 1569 getOrInit 1570 }; 1571 }; 1572 const transform = (tag) => { 1573 return (generators) => { 1574 const list = []; 1575 const find$1 = (element, comparator) => { 1576 return find(list, (x) => { 1577 return comparator(x.item, element); 1578 }); 1579 }; 1580 const makeNew = (element) => { 1581 // Ensure scope is never set on a td element as it's a deprecated attribute 1582 const attrs = tag === 'td' ? { scope: null } : {}; 1583 const cell = generators.replace(element, tag, attrs); 1584 list.push({ 1585 item: element, 1586 sub: cell 1587 }); 1588 return cell; 1589 }; 1590 const replaceOrInit = (element, comparator) => { 1591 if (isRow(element) || isCol(element)) { 1592 return element; 1593 } 1594 else { 1595 const cell = element; 1596 return find$1(cell, comparator).fold(() => { 1597 return makeNew(cell); 1598 }, (p) => { 1599 return comparator(element, p.item) ? p.sub : makeNew(cell); 1600 }); 1601 } 1602 }; 1603 return { 1604 replaceOrInit 1605 }; 1606 }; 1607 }; 1608 const getScopeAttribute = (cell) => getOpt(cell, 'scope').map( 1609 // Attribute can be col, colgroup, row, and rowgroup. 1610 // As col and colgroup are to be treated as if they are the same, lob off everything after the first three characters and there is no difference. 1611 (attribute) => attribute.substr(0, 3)); 1612 const merging = (generators) => { 1613 const unmerge = (cell) => { 1614 const scope = getScopeAttribute(cell); 1615 scope.each((attribute) => set$2(cell, 'scope', attribute)); 1616 return () => { 1617 const raw = generators.cell({ 1618 element: cell, 1619 colspan: 1, 1620 rowspan: 1 1621 }); 1622 // Remove any width calculations because they are no longer relevant. 1623 remove(raw, 'width'); 1624 remove(cell, 'width'); 1625 scope.each((attribute) => set$2(raw, 'scope', attribute)); 1626 return raw; 1627 }; 1628 }; 1629 const merge = (cells) => { 1630 const getScopeProperty = () => { 1631 const stringAttributes = cat(map(cells, getScopeAttribute)); 1632 if (stringAttributes.length === 0) { 1633 return Optional.none(); 1634 } 1635 else { 1636 const baseScope = stringAttributes[0]; 1637 const scopes = ['row', 'col']; 1638 const isMixed = exists(stringAttributes, (attribute) => { 1639 return attribute !== baseScope && contains(scopes, attribute); 1640 }); 1641 return isMixed ? Optional.none() : Optional.from(baseScope); 1642 } 1643 }; 1644 remove(cells[0], 'width'); 1645 getScopeProperty().fold(() => remove$2(cells[0], 'scope'), (attribute) => set$2(cells[0], 'scope', attribute + 'group')); 1646 return constant(cells[0]); 1647 }; 1648 return { 1649 unmerge, 1650 merge 1651 }; 1652 }; 1653 const Generators = { 1654 modification, 1655 transform, 1656 merging 1657 }; 1658 1659 var TagBoundaries = [ 1660 'body', 1661 'p', 1662 'div', 1663 'article', 1664 'aside', 1665 'figcaption', 1666 'figure', 1667 'footer', 1668 'header', 1669 'nav', 1670 'section', 1671 'ol', 1672 'ul', 1673 'li', 1674 'table', 1675 'thead', 1676 'tbody', 1677 'tfoot', 1678 'caption', 1679 'tr', 1680 'td', 1681 'th', 1682 'h1', 1683 'h2', 1684 'h3', 1685 'h4', 1686 'h5', 1687 'h6', 1688 'blockquote', 1689 'pre', 1690 'address' 1691 ]; 1692 1693 var DomUniverse = () => { 1694 const clone$1 = (element) => { 1695 return SugarElement.fromDom(element.dom.cloneNode(false)); 1696 }; 1697 const document = (element) => documentOrOwner(element).dom; 1698 const isBoundary = (element) => { 1699 if (!isElement(element)) { 1700 return false;
vendor: 4,334 bytes, lines 1701-1830
1701 } 1702 if (name(element) === 'body') { 1703 return true; 1704 } 1705 return contains(TagBoundaries, name(element)); 1706 }; 1707 const isEmptyTag = (element) => { 1708 if (!isElement(element)) { 1709 return false; 1710 } 1711 return contains(['br', 'img', 'hr', 'input'], name(element)); 1712 }; 1713 const isNonEditable = (element) => isElement(element) && get$2(element, 'contenteditable') === 'false'; 1714 const comparePosition = (element, other) => { 1715 return element.dom.compareDocumentPosition(other.dom); 1716 }; 1717 const copyAttributesTo = (source, destination) => { 1718 const as = clone(source); 1719 setAll(destination, as); 1720 }; 1721 const isSpecial = (element) => { 1722 const tag = name(element); 1723 return contains([ 1724 'script', 'noscript', 'iframe', 'noframes', 'noembed', 'title', 'style', 'textarea', 'xmp' 1725 ], tag); 1726 }; 1727 const getLanguage = (element) => isElement(element) ? getOpt(element, 'lang') : Optional.none(); 1728 return { 1729 up: constant({ 1730 selector: ancestor, 1731 closest: closest$1, 1732 predicate: ancestor$1, 1733 all: parents 1734 }), 1735 down: constant({ 1736 selector: descendants, 1737 predicate: descendants$1 1738 }), 1739 styles: constant({ 1740 get: get$1, 1741 getRaw: getRaw$1, 1742 set: set$1, 1743 remove: remove 1744 }), 1745 attrs: constant({ 1746 get: get$2, 1747 set: set$2, 1748 remove: remove$2, 1749 copyTo: copyAttributesTo 1750 }), 1751 insert: constant({ 1752 before: before, 1753 after: after$1, 1754 afterAll: after, 1755 append: append$1, 1756 appendAll: append, 1757 prepend: prepend, 1758 wrap: wrap 1759 }), 1760 remove: constant({ 1761 unwrap: unwrap, 1762 remove: remove$1 1763 }), 1764 create: constant({ 1765 nu: SugarElement.fromTag, 1766 clone: clone$1, 1767 text: SugarElement.fromText 1768 }), 1769 query: constant({ 1770 comparePosition, 1771 prevSibling: prevSibling, 1772 nextSibling: nextSibling 1773 }), 1774 property: constant({ 1775 children: children$3, 1776 name: name, 1777 parent: parent, 1778 document, 1779 isText: isText, 1780 isComment: isComment, 1781 isElement: isElement, 1782 isSpecial, 1783 getLanguage, 1784 getText: get, 1785 setText: set, 1786 isBoundary, 1787 isEmptyTag, 1788 isNonEditable 1789 }), 1790 eq: eq, 1791 is: is 1792 }; 1793 }; 1794 1795 const leftRight = (left, right) => ({ 1796 left, 1797 right 1798 }); 1799 const brokenPath = (first, second, splits) => ({ 1800 first, 1801 second, 1802 splits 1803 }); 1804 const bisect = (universe, parent, child) => { 1805 const children = universe.property().children(parent); 1806 const index = findIndex(children, curry(universe.eq, child)); 1807 return index.map((ind) => { 1808 return { 1809 before: children.slice(0, ind), 1810 after: children.slice(ind + 1) 1811 }; 1812 }); 1813 }; 1814 /** 1815 * Clone parent to the RIGHT and move everything after child in the parent element into 1816 * a clone of the parent (placed after parent). 1817 */ 1818 const breakToRight = (universe, parent, child) => { 1819 return bisect(universe, parent, child).map((parts) => { 1820 const second = universe.create().clone(parent); 1821 universe.insert().appendAll(second, parts.after); 1822 universe.insert().after(parent, second); 1823 return leftRight(parent, second); 1824 }); 1825 }; 1826 /** 1827 * Clone parent to the LEFT and move everything before and including child into 1828 * the a clone of the parent (placed before parent) 1829 */ 1830 const breakToLeft = (universe, parent, child) => {
1831 return bisect(universe, parent, child).map((parts) => { 1832 const prior = universe.create().clone(parent); 1833 universe.insert().appendAll(prior, parts.before.concat([child])); 1834 universe.insert().appendAll(parent, parts.after); 1835 universe.insert().before(parent, prior); 1836 return leftRight(prior, parent); 1837 }); 1838 }; 1839 /* 1840 * Using the breaker, break from the child up to the top element defined by the predicate. 1841 * It returns three values: 1842 * first: the top level element that completed the break 1843 * second: the optional element representing second part of the top-level split if the breaking completed successfully to the top 1844 * splits: a list of (Element, Element) pairs that represent the splits that have occurred on the way to the top. 1845 */ 1846 const breakPath = (universe, item, isTop, breaker) => { 1847 const next = (child, group, splits) => { 1848 const fallback = brokenPath(child, Optional.none(), splits); 1849 // Found the top, so stop. 1850 if (isTop(child)) { 1851 return brokenPath(child, group, splits); 1852 } 1853 else { 1854 // Split the child at parent, and keep going 1855 return universe.property().parent(child).bind((parent) => { 1856 return breaker(universe, parent, child).map((breakage) => { 1857 const extra = [{ first: breakage.left, second: breakage.right }]; 1858 // Our isTop is based on the left-side parent, so keep it regardless of split. 1859 const nextChild = isTop(parent) ? parent : breakage.left; 1860 return next(nextChild, Optional.some(breakage.right), splits.concat(extra)); 1861 }); 1862 }).getOr(fallback); 1863 } 1864 }; 1865 return next(item, Optional.none(), []); 1866 }; 1867 1868 const all = (universe, look, elements, f) => { 1869 const head = elements[0]; 1870 const tail = elements.slice(1); 1871 return f(universe, look, head, tail); 1872 }; 1873 /** 1874 * Check if look returns the same element for all elements, and return it if it exists. 1875 */ 1876 const oneAll = (universe, look, elements) => { 1877 return elements.length > 0 ? 1878 all(universe, look, elements, unsafeOne) : 1879 Optional.none(); 1880 }; 1881 const unsafeOne = (universe, look, head, tail) => { 1882 const start = look(universe, head); 1883 return foldr(tail, (b, a) => { 1884 const current = look(universe, a); 1885 return commonElement(universe, b, current); 1886 }, start); 1887 }; 1888 const commonElement = (universe, start, end) => { 1889 return start.bind((s) => { 1890 return end.filter(curry(universe.eq, s)); 1891 }); 1892 }; 1893 1894 const sharedOne$1 = oneAll; 1895 breakToLeft; 1896 breakToRight; 1897 breakPath; 1898 1899 const universe = DomUniverse(); 1900 const sharedOne = (look, elements) => { 1901 return sharedOne$1(universe, (_universe, element) => { 1902 return look(element); 1903 }, elements); 1904 }; 1905 1906 const opGetRowsType = (table, target) => { 1907 const house = Warehouse.fromTable(table); 1908 const details = onCells(house, target); 1909 return details.bind((selectedCells) => { 1910 const lastSelectedCell = selectedCells[selectedCells.length - 1]; 1911 const minRowRange = selectedCells[0].row; 1912 const maxRowRange = lastSelectedCell.row + lastSelectedCell.rowspan; 1913 const selectedRows = house.all.slice(minRowRange, maxRowRange); 1914 return findCommonRowType(selectedRows); 1915 }).getOr(''); 1916 }; 1917 Generators.transform('th'); 1918 Generators.transform('td'); 1919 const getRowsType = opGetRowsType; 1920 1921 // Note, something is *within* if it is completely contained within the bounds. 1922 const isWithin = (bounds, detail) => { 1923 return (detail.column >= bounds.startCol && 1924 (detail.column + detail.colspan - 1) <= bounds.finishCol && 1925 detail.row >= bounds.startRow && 1926 (detail.row + detail.rowspan - 1) <= bounds.finishRow); 1927 }; 1928 const isRectangular = (warehouse, bounds) => { 1929 let isRect = true; 1930 const detailIsWithin = curry(isWithin, bounds); 1931 for (let i = bounds.startRow; i <= bounds.finishRow; i++) { 1932 for (let j = bounds.startCol; j <= bounds.finishCol; j++) { 1933 isRect = isRect && Warehouse.getAt(warehouse, i, j).exists(detailIsWithin); 1934 } 1935 } 1936 return isRect ? Optional.some(bounds) : Optional.none(); 1937 }; 1938 1939 const getBounds = (detailA, detailB) => { 1940 return bounds(Math.min(detailA.row, detailB.row), Math.min(detailA.column, detailB.column), Math.max(detailA.row + detailA.rowspan - 1, detailB.row + detailB.rowspan - 1), Math.max(detailA.column + detailA.colspan - 1, detailB.column + detailB.colspan - 1)); 1941 }; 1942 const getAnyBox = (warehouse, startCell, finishCell) => { 1943 const startCoords = Warehouse.findItem(warehouse, startCell, eq); 1944 const finishCoords = Warehouse.findItem(warehouse, finishCell, eq); 1945 return startCoords.bind((sc) => { 1946 return finishCoords.map((fc) => { 1947 return getBounds(sc, fc); 1948 }); 1949 }); 1950 }; 1951 const getBox$1 = (warehouse, startCell, finishCell) => { 1952 return getAnyBox(warehouse, startCell, finishCell).bind((bounds) => { 1953 return isRectangular(warehouse, bounds); 1954 }); 1955 }; 1956 1957 const getBox = (table, first, last) => { 1958 const warehouse = getWarehouse(table); 1959 return getBox$1(warehouse, first, last); 1960 }; 1961 // Private method ... keep warehouse in snooker, please. 1962 const getWarehouse = Warehouse.fromTable; 1963 1964 var global$2 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 1965 1966 const getTDTHOverallStyle = (dom, elm, name) => { 1967 const cells = dom.select('td,th', elm); 1968 let firstChildStyle; 1969 for (let i = 0; i < cells.length; i++) { 1970 const currentStyle = dom.getStyle(cells[i], name); 1971 if (isUndefined(firstChildStyle)) { 1972 firstChildStyle = currentStyle; 1973 } 1974 if (firstChildStyle !== currentStyle) { 1975 return ''; 1976 } 1977 } 1978 return firstChildStyle; 1979 }; 1980 const setAlign = (editor, elm, name) => { 1981 // Alignment formats may not use the same styles so ensure to remove any existing horizontal alignment format first 1982 global$2.each('left center right'.split(' '), (align) => { 1983 if (align !== name) { 1984 editor.formatter.remove('align' + align, {}, elm); 1985 } 1986 }); 1987 if (name) { 1988 editor.formatter.apply('align' + name, {}, elm); 1989 } 1990 }; 1991 const setVAlign = (editor, elm, name) => { 1992 // Alignment formats may not use the same styles so ensure to remove any existing vertical alignment format first 1993 global$2.each('top middle bottom'.split(' '), (align) => { 1994 if (align !== name) { 1995 editor.formatter.remove('valign' + align, {}, elm); 1996 } 1997 }); 1998 if (name) { 1999 editor.formatter.apply('valign' + name, {}, elm); 2000 } 2001 }; 2002 2003 /* 2004 NOTE: This file is duplicated in the following locations: 2005 - core/api/TableEvents.ts 2006 - models/dom/table/api/Events.ts 2007 - advtable 2008 Make sure that if making changes to this file, the other files are updated as well 2009 */ 2010 const fireTableModified = (editor, table, data) => { 2011 editor.dispatch('TableModified', { ...data, table }); 2012 }; 2013 2014 var global$1 = tinymce.util.Tools.resolve('tinymce.Env'); 2015 2016 const defaultTableToolbar = 'tableprops tabledelete | tableinsertrowbefore tableinsertrowafter tabledeleterow | tableinsertcolbefore tableinsertcolafter tabledelete
2016col'; 2017 const defaultCellBorderWidths = range(5, (i) => { 2018 const size = `${i + 1}px`; 2019 return { title: size, value: size }; 2020 }); 2021 const defaultCellBorderStyles = map(['Solid', 'Dotted', 'Dashed', 'Double', 'Groove', 'Ridge', 'Inset', 'Outset', 'None', 'Hidden'], (type) => { 2022 return { title: type, value: type.toLowerCase() }; 2023 }); 2024 // Note: This is also contained in the core Options.ts file 2025 const defaultWidth = '100%'; 2026 const getPixelForcedWidth = (editor) => { 2027 var _a; 2028 // Determine the inner size of the parent block element where the table will be inserted 2029 const dom = editor.dom; 2030 const parentBlock = (_a = dom.getParent(editor.selection.getStart(), dom.isBlock)) !== null && _a !== void 0 ? _a : editor.getBody(); 2031 return getInner(SugarElement.fromDom(parentBlock)) + 'px'; 2032 }; 2033 // Note: This is also contained in the core Options.ts file 2034 const determineDefaultStyles = (editor, defaultStyles) => { 2035 if (isResponsiveForced(editor) || !shouldStyleWithCss(editor)) { 2036 return defaultStyles; 2037 } 2038 else if (isPixelsForced(editor)) { 2039 return { ...defaultStyles, width: getPixelForcedWidth(editor) }; 2040 } 2041 else { 2042 return { ...defaultStyles, width: defaultWidth }; 2043 } 2044 }; 2045 // Note: This is also contained in the core Options.ts file 2046 const determineDefaultAttributes = (editor, defaultAttributes) => { 2047 if (isResponsiveForced(editor) || shouldStyleWithCss(editor)) { 2048 return defaultAttributes; 2049 } 2050 else if (isPixelsForced(editor)) { 2051 return { ...defaultAttributes, width: getPixelForcedWidth(editor) }; 2052 } 2053 else { 2054 return { ...defaultAttributes, width: defaultWidth }; 2055 } 2056 }; 2057 const option = (name) => (editor) => editor.options.get(name); 2058 const register = (editor) => { 2059 const registerOption = editor.options.register; 2060 registerOption('table_border_widths', { 2061 processor: 'object[]', 2062 default: defaultCellBorderWidths 2063 }); 2064 registerOption('table_border_styles', { 2065 processor: 'object[]', 2066 default: defaultCellBorderStyles 2067 }); 2068 registerOption('table_cell_advtab', { 2069 processor: 'boolean', 2070 default: true 2071 }); 2072 registerOption('table_row_advtab', { 2073 processor: 'boolean', 2074 default: true 2075 }); 2076 registerOption('table_advtab', { 2077 processor: 'boolean', 2078 default: true 2079 }); 2080 registerOption('table_appearance_options', { 2081 processor: 'boolean', 2082 default: true 2083 }); 2084 registerOption('table_grid', { 2085 processor: 'boolean', 2086 // Table grid relies on hover, which isn't available on touch devices so use the dialog instead 2087 default: !global$1.deviceType.isTouch() 2088 }); 2089 registerOption('table_cell_class_list', { 2090 processor: 'object[]', 2091 default: [] 2092 }); 2093 registerOption('table_row_class_list', { 2094 processor: 'object[]', 2095 default: [] 2096 }); 2097 registerOption('table_class_list', { 2098 processor: 'object[]', 2099 default: [] 2100 }); 2101 registerOption('table_toolbar', { 2102 processor: 'string', 2103 default: defaultTableToolbar 2104 }); 2105 registerOption('table_background_color_map', { 2106 processor: 'object[]', 2107 default: [] 2108 }); 2109 registerOption('table_border_color_map', { 2110 processor: 'object[]', 2111 default: [] 2112 }); 2113 }; 2114 const getTableSizingMode = option('table_sizing_mode'); 2115 const getTableBorderWidths = option('table_border_widths'); 2116 const getTableBorderStyles = option('table_border_styles'); 2117 const hasAdvancedCellTab = option('table_cell_advtab'); 2118 const hasAdvancedRowTab = option('table_row_advtab'); 2119 const hasAdvancedTableTab = option('table_advtab'); 2120 const hasAppearanceOptions = option('table_appearance_options'); 2121 const hasTableGrid = option('table_grid'); 2122 const shouldStyleWithCss = option('table_style_by_css'); 2123 const getCellClassList = option('table_cell_class_list'); 2124 const getRowClassList = option('table_row_class_list'); 2125 const getTableClassList = option('table_class_list'); 2126 const getToolbar = option('table_toolbar');
2127 const getTableBackgroundColorMap = option('table_background_color_map'); 2128 const getTableBorderColorMap = option('table_border_color_map'); 2129 const isPixelsForced = (editor) => getTableSizingMode(editor) === 'fixed'; 2130 const isResponsiveForced = (editor) => getTableSizingMode(editor) === 'responsive'; 2131 const getDefaultStyles = (editor) => { 2132 // Note: The we don't rely on the default here as we need to dynamically lookup the widths based on the current editor state 2133 const options = editor.options; 2134 const defaultStyles = options.get('table_default_styles'); 2135 return options.isSet('table_default_styles') ? defaultStyles : determineDefaultStyles(editor, defaultStyles); 2136 }; 2137 const getDefaultAttributes = (editor) => { 2138 // Note: The we don't rely on the default here as we need to dynamically lookup the widths based on the current editor state 2139 const options = editor.options; 2140 const defaultAttributes = options.get('table_default_attributes'); 2141 return options.isSet('table_default_attributes') ? defaultAttributes : determineDefaultAttributes(editor, defaultAttributes); 2142 }; 2143 2144 const lookupTable = (container) => { 2145 return ancestor(container, 'table'); 2146 }; 2147 const retrieve$1 = (container, selector) => { 2148 const sels = descendants(container, selector); 2149 return sels.length > 0 ? Optional.some(sels) : Optional.none(); 2150 }; 2151 const getEdges = (container, firstSelectedSelector, lastSelectedSelector) => { 2152 return descendant(container, firstSelectedSelector).bind((first) => { 2153 return descendant(container, lastSelectedSelector).bind((last) => { 2154 return sharedOne(lookupTable, [first, last]).map((table) => { 2155 return { 2156 first, 2157 last, 2158 table 2159 }; 2160 }); 2161 }); 2162 }); 2163 }; 2164 2165 // Explicitly calling CellSelection.retrieve so that we can see the API signature. 2166 const retrieve = (container, selector) => { 2167 return retrieve$1(container, selector); 2168 }; 2169 const retrieveBox = (container, firstSelectedSelector, lastSelectedSelector) => { 2170 return getEdges(container, firstSelectedSelector, lastSelectedSelector).bind((edges) => { 2171 const isRoot = (ancestor) => { 2172 return eq(container, ancestor); 2173 }; 2174 const sectionSelector = 'thead,tfoot,tbody,table'; 2175 const firstAncestor = ancestor(edges.first, sectionSelector, isRoot); 2176 const lastAncestor = ancestor(edges.last, sectionSelector, isRoot); 2177 return firstAncestor.bind((fA) => { 2178 return lastAncestor.bind((lA) => { 2179 return eq(fA, lA) ? getBox(edges.table, edges.first, edges.last) : Optional.none(); 2180 }); 2181 }); 2182 }); 2183 }; 2184 2185 const selection = identity; 2186 const unmergable = (selectedCells) => { 2187 const hasSpan = (elem, type) => getOpt(elem, type).exists((span) => parseInt(span, 10) > 1); 2188 const hasRowOrColSpan = (elem) => hasSpan(elem, 'rowspan') || hasSpan(elem, 'colspan'); 2189 return selectedCells.length > 0 && forall(selectedCells, hasRowOrColSpan) ? Optional.some(selectedCells) : Optional.none(); 2190 }; 2191 const mergable = (table, selectedCells, ephemera) => { 2192 if (selectedCells.length <= 1) { 2193 return Optional.none(); 2194 } 2195 else { 2196 return retrieveBox(table, ephemera.firstSelectedSelector, ephemera.lastSelectedSelector) 2197 .map((bounds) => ({ bounds, cells: selectedCells })); 2198 } 2199 }; 2200 2201 /* 2202 NOTE: This file is duplicated in the following locations: 2203 - models/dom/table/selection/Ephemera.ts 2204 - advtable 2205 Make sure that if making changes to this file, the other files are updated as well 2206 */ 2207 const strSelected = 'data-mce-selected'; 2208 const strSelectedSelector = 'td[' + strSelected + '],th[' + strSelected + ']'; 2209 const strFirstSelected = 'data-mce-first-selected'; 2210 const strFirstSelectedSelector = 'td[' + strFirstSelected + '],th[' + strFirstSelected + ']'; 2211 const strLastSelected = 'data-mce-last-selected'; 2212 const strLastSelectedSelector = 'td[' + strLastSelected + '],th[' + strLastSelected + ']';
2213 const ephemera = { 2214 selected: strSelected, 2215 selectedSelector: strSelectedSelector, 2216 firstSelected: strFirstSelected, 2217 firstSelectedSelector: strFirstSelectedSelector, 2218 lastSelected: strLastSelected, 2219 lastSelectedSelector: strLastSelectedSelector 2220 }; 2221 2222 /* 2223 NOTE: This file is partially duplicated in the following locations: 2224 - models/dom/table/selection/TableSelection.ts 2225 - advtable 2226 Make sure that if making changes to this file, the other files are updated as well 2227 */ 2228 const getSelectionCellFallback = (element) => table(element).bind((table) => retrieve(table, ephemera.firstSelectedSelector)).fold(constant(element), (cells) => cells[0]); 2229 const getSelectionFromSelector = (selector) => (initCell, isRoot) => { 2230 const cellName = name(initCell); 2231 const cell = cellName === 'col' || cellName === 'colgroup' ? getSelectionCellFallback(initCell) : initCell; 2232 return closest$1(cell, selector, isRoot); 2233 }; 2234 const getSelectionCellOrCaption = getSelectionFromSelector('th,td,caption'); 2235 const getSelectionCell = getSelectionFromSelector('th,td'); 2236 const getCellsFromSelection = (editor) => fromDom(editor.model.table.getSelectedCells()); 2237 const getRowsFromSelection = (selected, selector) => { 2238 const cellOpt = getSelectionCell(selected); 2239 const rowsOpt = cellOpt.bind((cell) => table(cell)) 2240 .map((table) => rows(table)); 2241 return lift2(cellOpt, rowsOpt, (cell, rows) => filter$1(rows, (row) => exists(fromDom(row.dom.cells), (rowCell) => get$2(rowCell, selector) === '1' || eq(rowCell, cell)))).getOr([]); 2242 }; 2243 2244 const verticalAlignValues = [ 2245 { 2246 text: 'None', 2247 value: '' 2248 }, 2249 { 2250 text: 'Top', 2251 value: 'top' 2252 }, 2253 { 2254 text: 'Middle', 2255 value: 'middle' 2256 }, 2257 { 2258 text: 'Bottom', 2259 value: 'bottom' 2260 } 2261 ]; 2262 2263 const hexColour = (value) => ({ 2264 value: normalizeHex(value) 2265 }); 2266 const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 2267 const longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i; 2268 const isHexString = (hex) => shorthandRegex.test(hex) || longformRegex.test(hex); 2269 const normalizeHex = (hex) => removeLeading(hex, '#').toUpperCase(); 2270 const fromString$1 = (hex) => isHexString(hex) ? Optional.some({ value: normalizeHex(hex) }) : Optional.none(); 2271 const toHex = (component) => { 2272 const hex = component.toString(16); 2273 return (hex.length === 1 ? '0' + hex : hex).toUpperCase(); 2274 }; 2275 const fromRgba = (rgbaColour) => { 2276 const value = toHex(rgbaColour.red) + toHex(rgbaColour.green) + toHex(rgbaColour.blue); 2277 return hexColour(value); 2278 }; 2279 2280 /* eslint-disable no-console */ 2281 const rgbRegex = /^\s*rgb\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*\)\s*$/i; 2282 // This regex will match rgba(0, 0, 0, 0.5) or rgba(0, 0, 0, 50%) , or without commas 2283 const rgbaRegex = /^\s*rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*((?:\d?\.\d+|\d+)%?)\s*\)\s*$/i; 2284 const rgbaColour = (red, green, blue, alpha) => ({ 2285 red, 2286 green, 2287 blue, 2288 alpha 2289 }); 2290 const fromStringValues = (red, green, blue, alpha) => { 2291 const r = parseInt(red, 10); 2292 const g = parseInt(green, 10); 2293 const b = parseInt(blue, 10); 2294 const a = parseFloat(alpha); 2295 return rgbaColour(r, g, b, a); 2296 }; 2297 const fromString = (rgbaString) => { 2298 const rgbMatch = rgbRegex.exec(rgbaString); 2299 if (rgbMatch !== null) { 2300 return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], rgbMatch[3], '1')); 2301 } 2302 const rgbaMatch = rgbaRegex.exec(rgbaString); 2303 if (rgbaMatch !== null) { 2304 return Optional.some(fromStringValues(rgbaMatch[1], rgbaMatch[2], rgbaMatch[3], rgbaMatch[4])); 2305 } 2306 return Optional.none(); 2307 }; 2308 2309 const anyToHex = (color) => fromString$1(color) 2310 .orThunk(() => fromString(color).map(fromRgba)) 2311 .getOrThunk(() => { 2312 // Not dealing with Hex or RGBA so use a canvas to parse the color 2313 const canvas = document.createElement('canvas'); 2314 canvas.height = 1; 2315 canvas.width = 1; 2316 const canvasContext = canvas.getContext('2d'); 2317 // all valid colors after this point 2318 canvasContext.clearRect(0, 0, canvas.width, canvas.height); 2319 // invalid colors will be shown as white - the first assignment will pass and the second may be ignored 2320 canvasContext.fillStyle = '#FFFFFF'; 2321 canvasContext.fillStyle = color; 2322 canvasContext.fillRect(0, 0, 1, 1); 2323 const rgba = canvasContext.getImageData(0, 0, 1, 1).data; 2324 const r = rgba[0]; 2325 const g = rgba[1]; 2326 const b = rgba[2]; 2327 const a = rgba[3]; 2328 return fromRgba(rgbaColour(r, g, b, a)); 2329 }); 2330 const rgbaToHexString = (color) => fromString(color) 2331 .map(fromRgba) 2332 .map((h) => '#' + h.value) 2333 .getOr(color); 2334 2335 const onSetupToggle = (editor, formatName, formatValue) => { 2336 return (api) => { 2337 const boundCallback = unbindable(); 2338 const isNone = isEmpty(formatValue); 2339 const init = () => { 2340 const selectedCells = getCellsFromSelection(editor); 2341 const checkNode = (cell) => editor.formatter.match(formatName, { value: formatValue }, cell.dom, isNone); 2342 // If value is empty (A None-entry in the list), check if the format is not set at all. Otherwise, check if the format is set to the correct value. 2343 if (isNone) { 2344 api.setActive(!exists(selectedCells, checkNode)); 2345 boundCallback.set(editor.formatter.formatChanged(formatName, (match) => api.setActive(!match), true)); 2346 } 2347 else { 2348 api.setActive(forall(selectedCells, checkNode)); 2349 boundCallback.set(editor.formatter.formatChanged(formatName, api.setActive, false, { value: formatValue })); 2350 } 2351 }; 2352 // The editor may or may not have been setup yet, so check for that 2353 editor.initialized ? init() : editor.on('init', init); 2354 return boundCallback.clear; 2355 }; 2356 }; 2357 const isListGroup = (item) => hasNonNullableKey(item, 'menu'); 2358 const buildListItems = (items) => map(items, (item) => { 2359 // item.text is not documented - maybe deprecated option we can delete?? 2360 const text = item.text || item.title || ''; 2361 if (isListGroup(item)) { 2362 return { 2363 text, 2364 items: buildListItems(item.menu) 2365 }; 2366 } 2367 else { 2368 return { 2369 text, 2370 value: item.value 2371 }; 2372 } 2373 }); 2374 const buildClassList = (classList) => { 2375 if (!classList.length) { 2376 return Optional.none(); 2377 } 2378 return Optional.some(buildListItems([{ text: 'Select...', value: 'mce-no-match' }, ...classList])); 2379 }; 2380 const buildMenuItems = (editor, items, format, onAction) => map(items, (item) => { 2381 // item.text is not documented - maybe deprecated option we can delete?? 2382 const text = item.text || item.title; 2383 if (isListGroup(item)) { 2384 return { 2385 type: 'nestedmenuitem', 2386 text, 2387 getSubmenuItems: () => buildMenuItems(editor, item.menu, format, onAction) 2388 }; 2389 } 2390 else { 2391 return { 2392 text, 2393 type: 'togglemenuitem', 2394 onAction: () => onAction(item.value), 2395 onSetup: onSetupToggle(editor, format, item.value) 2396 }; 2397 } 2398 }); 2399 const applyTableCellStyle = (editor, style) => (value) => { 2400 editor.execCommand('mceTableApplyCellStyle', false, { [style]: value }); 2401 }; 2402 const filterNoneItem = (list) => bind(list, (item) => { 2403 if (isListGroup(item)) { 2404 return [{ ...item, menu: filterNoneItem(item.menu) }]; 2405 } 2406 else { 2407 return isNotEmpty(item.value) ? [item] : []; 2408 } 2409 }); 2410 const generateMenuItemsCallback = (editor, items, format, onAction) => (callback) => callback(buildMenuItems(editor, items, format, onAction)); 2411 const buildColorMenu = (editor, colorList, style) => { 2412 const colorMap = map(colorList, (entry) => ({ 2413 text: entry.title, 2414 value: '#' + anyToHex(entry.value).value, 2415 type: 'choiceitem' 2416 })); 2417 return [{ 2418 type: 'fancymenuitem', 2419 fancytype: 'colorswatch', 2420 initData: { 2421 colors: colorMap.length > 0 ? colorMap : undefined, 2422 allowCustomColors: false 2423 }, 2424 onAction: (data) => { 2425 const value = data.value === 'remove' ? '' : data.value; 2426 editor.execCommand('mceTableApplyCellStyle', false, { [style]: value }); 2427 } 2428 }]; 2429 }; 2430 const changeRowHeader = (editor) => () => { 2431 const currentType = editor.queryCommandValue('mceTableRowType'); 2432 const newType = currentType === 'header' ? 'body' : 'header'; 2433 editor.execCommand('mceTableRowType', false, { type: newType }); 2434 }; 2435 const changeColumnHeader = (editor) => () => { 2436 const currentType = editor.queryCommandValue('mceTableColType'); 2437 const newType = currentType === 'th' ? 'td' : 'th'; 2438 editor.execCommand('mceTableColType', false, { type: newType }); 2439 }; 2440 2441 const getClassList$1 = (editor) => buildClassList(getCellClassList(editor)) 2442 .map((items) => ({ 2443 name: 'class', 2444 type: 'listbox', 2445 label: 'Class', 2446 items 2447 })); 2448 const children = [ 2449 { 2450 name: 'width', 2451 type: 'input', 2452 label: 'Width' 2453 }, 2454 { 2455 name: 'celltype', 2456 type: 'listbox', 2457 label: 'Cell type', 2458 items: [ 2459 { text: 'Cell', value: 'td' }, 2460 { text: 'Header cell', value: 'th' } 2461 ] 2462 }, 2463 { 2464 name: 'scope', 2465 type: 'listbox', 2466 label: 'Scope', 2467 items: [ 2468 { text: 'None', value: '' }, 2469 { text: 'Row', value: 'row' }, 2470 { text: 'Column', value: 'col' }, 2471 { text: 'Row group', value: 'rowgroup' }, 2472 { text: 'Column group', value: 'colgroup' } 2473 ] 2474 }, 2475 { 2476 name: 'halign', 2477 type: 'listbox', 2478 label: 'Horizontal align', 2479 items: [ 2480 { text: 'None', value: '' }, 2481 { text: 'Left', value: 'left' }, 2482 { text: 'Center', value: 'center' }, 2483 { text: 'Right', value: 'right' } 2484 ] 2485 }, 2486 { 2487 name: 'valign', 2488 type: 'listbox', 2489 label: 'Vertical align', 2490 items: verticalAlignValues 2491 } 2492 ]; 2493 const getItems$2 = (editor) => children.concat(getClassList$1(editor).toArray()); 2494 2495 const getAdvancedTab = (editor, dialogName) => { 2496 const emptyBorderStyle = [{ text: 'Select...', value: '' }]; 2497 const advTabItems = [ 2498 { 2499 name: 'borderstyle', 2500 type: 'listbox', 2501 label: 'Border style', 2502 items: emptyBorderStyle.concat(buildListItems(getTableBorderStyles(editor))) 2503 }, 2504 { 2505 name: 'bordercolor', 2506 type: 'colorinput', 2507 label: 'Border color' 2508 }, 2509 { 2510 name: 'backgroundcolor', 2511 type: 'colorinput',
2512 label: 'Background color' 2513 } 2514 ]; 2515 const borderWidth = { 2516 name: 'borderwidth', 2517 type: 'input', 2518 label: 'Border width' 2519 }; 2520 const items = dialogName === 'cell' ? [borderWidth].concat(advTabItems) : advTabItems; 2521 return { 2522 title: 'Advanced', 2523 name: 'advanced', 2524 items 2525 }; 2526 }; 2527 2528 // The get node is required here because it can be transformed 2529 // when switching between tags (e.g. th and td) 2530 const normal = (editor, element) => { 2531 const dom = editor.dom; 2532 const setAttrib = (attr, value) => { 2533 dom.setAttrib(element, attr, value); 2534 }; 2535 const setStyle = (prop, value) => { 2536 dom.setStyle(element, prop, value); 2537 }; 2538 const setFormat = (formatName, value) => { 2539 // Remove format if given an empty string 2540 if (value === '') { 2541 editor.formatter.remove(formatName, { value: null }, element, true); 2542 } 2543 else { 2544 editor.formatter.apply(formatName, { value }, element); 2545 } 2546 }; 2547 return { 2548 setAttrib, 2549 setStyle, 2550 setFormat 2551 }; 2552 }; 2553 const DomModifier = { 2554 normal 2555 }; 2556 2557 const rgbToHex = (value) => startsWith(value, 'rgb') ? rgbaToHexString(value) : value; 2558 const extractAdvancedStyles = (elm) => { 2559 const element = SugarElement.fromDom(elm); 2560 return { 2561 borderwidth: getRaw$1(element, 'border-width').getOr(''), 2562 borderstyle: getRaw$1(element, 'border-style').getOr(''), 2563 bordercolor: getRaw$1(element, 'border-color').map(rgbToHex).getOr(''), 2564 backgroundcolor: getRaw$1(element, 'background-color').map(rgbToHex).getOr('') 2565 }; 2566 }; 2567 const getSharedValues = (data) => { 2568 // TODO surely there's a better way to do this?? 2569 // Mutates baseData to return an object that contains only the values 2570 // that were the same across all objects in data 2571 const baseData = data[0]; 2572 const comparisonData = data.slice(1); 2573 each$1(comparisonData, (items) => { 2574 each$1(keys(baseData), (key) => { 2575 each(items, (itemValue, itemKey) => { 2576 const comparisonValue = baseData[key]; 2577 if (comparisonValue !== '' && key === itemKey) { 2578 if (comparisonValue !== itemValue) { 2579 baseData[key] = key === 'class' ? 'mce-no-match' : ''; 2580 } 2581 } 2582 }); 2583 }); 2584 }); 2585 return baseData; 2586 }; 2587 // The extractDataFrom... functions are in this file partly for code reuse and partly so we can test them, 2588 // because some of these are crazy complicated 2589 const getAlignment = (formats, formatName, editor, elm) => find(formats, (name) => !isUndefined(editor.formatter.matchNode(elm, formatName + name))).getOr(''); 2590 const getHAlignment = curry(getAlignment, ['left', 'center', 'right'], 'align'); 2591 const getVAlignment = curry(getAlignment, ['top', 'middle', 'bottom'], 'valign'); 2592 const extractDataFromSettings = (editor, hasAdvTableTab) => { 2593 const style = getDefaultStyles(editor); 2594 const attrs = getDefaultAttributes(editor); 2595 const extractAdvancedStyleData = () => ({ 2596 borderstyle: get$3(style, 'border-style').getOr(''), 2597 bordercolor: rgbToHex(get$3(style, 'border-color').getOr('')), 2598 backgroundcolor: rgbToHex(get$3(style, 'background-color').getOr('')) 2599 }); 2600 const defaultData = { 2601 height: '', 2602 width: '100%', 2603 cellspacing: '', 2604 cellpadding: '', 2605 caption: false, 2606 class: '', 2607 align: '', 2608 border: '' 2609 }; 2610 const getBorder = () => { 2611 const borderWidth = style['border-width']; 2612 if (shouldStyleWithCss(editor) && borderWidth) { 2613 return { border: borderWidth }; 2614 }
2615 return get$3(attrs, 'border').fold(() => ({}), (border) => ({ border })); 2616 }; 2617 const advStyle = (hasAdvTableTab ? extractAdvancedStyleData() : {}); 2618 const getCellPaddingCellSpacing = () => { 2619 const spacing = get$3(style, 'border-spacing').or(get$3(attrs, 'cellspacing')).fold(() => ({}), (cellspacing) => ({ cellspacing })); 2620 const padding = get$3(style, 'border-padding').or(get$3(attrs, 'cellpadding')).fold(() => ({}), (cellpadding) => ({ cellpadding })); 2621 return { 2622 ...spacing, 2623 ...padding 2624 }; 2625 }; 2626 const data = { 2627 ...defaultData, 2628 ...style, 2629 ...attrs, 2630 ...advStyle, 2631 ...getBorder(), 2632 ...getCellPaddingCellSpacing() 2633 }; 2634 return data; 2635 }; 2636 const getRowType = (elm) => table(SugarElement.fromDom(elm)).map((table) => { 2637 const target = { selection: fromDom(elm.cells) }; 2638 return getRowsType(table, target); 2639 }).getOr(''); 2640 const extractDataFromTableElement = (editor, elm, hasAdvTableTab) => { 2641 const getBorder = (dom, elm) => { 2642 // Cases (in order to check): 2643 // 1. shouldStyleWithCss - extract border-width style if it exists 2644 // 2. !shouldStyleWithCss && border attribute - set border attribute as value 2645 // 3. !shouldStyleWithCss && nothing on the table - grab styles from the first th or td 2646 const optBorderWidth = getRaw$1(SugarElement.fromDom(elm), 'border-width'); 2647 if (shouldStyleWithCss(editor) && optBorderWidth.isSome()) { 2648 return optBorderWidth.getOr(''); 2649 } 2650 return dom.getAttrib(elm, 'border') || getTDTHOverallStyle(editor.dom, elm, 'border-width') 2651 || getTDTHOverallStyle(editor.dom, elm, 'border') || ''; 2652 }; 2653 const dom = editor.dom; 2654 const cellspacing = shouldStyleWithCss(editor) ? 2655 dom.getStyle(elm, 'border-spacing') || dom.getAttrib(elm, 'cellspacing') : 2656 dom.getAttrib(elm, 'cellspacing') || dom.getStyle(elm, 'border-spacing'); 2657 const cellpadding = shouldStyleWithCss(editor) ? 2658 getTDTHOverallStyle(dom, elm, 'padding') || dom.getAttrib(elm, 'cellpadding') : 2659 dom.getAttrib(elm, 'cellpadding') || getTDTHOverallStyle(dom, elm, 'padding'); 2660 return { 2661 width: dom.getStyle(elm, 'width') || dom.getAttrib(elm, 'width'), 2662 height: dom.getStyle(elm, 'height') || dom.getAttrib(elm, 'height'), 2663 cellspacing: cellspacing !== null && cellspacing !== void 0 ? cellspacing : '', 2664 cellpadding: cellpadding !== null && cellpadding !== void 0 ? cellpadding : '', 2665 border: getBorder(dom, elm), 2666 caption: !!dom.select('caption', elm)[0], 2667 class: dom.getAttrib(elm, 'class', ''), 2668 align: getHAlignment(editor, elm), 2669 ...(hasAdvTableTab ? extractAdvancedStyles(elm) : {}) 2670 }; 2671 }; 2672 const extractDataFromRowElement = (editor, elm, hasAdvancedRowTab) => { 2673 const dom = editor.dom; 2674 return { 2675 height: dom.getStyle(elm, 'height') || dom.getAttrib(elm, 'height'), 2676 class: dom.getAttrib(elm, 'class', ''), 2677 type: getRowType(elm), 2678 align: getHAlignment(editor, elm), 2679 ...(hasAdvancedRowTab ? extractAdvancedStyles(elm) : {}) 2680 }; 2681 }; 2682 const extractDataFromCellElement = (editor, cell, hasAdvancedCellTab, column) => { 2683 const dom = editor.dom; 2684 const colElm = column.getOr(cell); 2685 const getStyle = (element, style) => dom.getStyle(element, style) || dom.getAttrib(element, style); 2686 return { 2687 width: getStyle(colElm, 'width'), 2688 scope: dom.getAttrib(cell, 'scope'), 2689 celltype: getNodeName(cell), 2690 class: dom.getAttrib(cell, 'class', ''), 2691 halign: getHAlignment(editor, cell), 2692 valign: getVAlignment(editor, cell), 2693 ...(hasAdvancedCellTab ? extractAdvancedStyles(cell) : {}) 2694 }; 2695 }; 2696 2697 const getSelectedCells = (table, cells) => { 2698 const warehouse = Warehouse.fromTable(table); 2699 const allCells = Warehouse.justCells(warehouse); 2700 const filtered = filter$1(allCells, (cellA) => exists(cells, (cellB) => eq(cellA.element, cellB))); 2701 return map(filtered, (cell) => ({ 2702 element: cell.element.dom, 2703 column: Warehouse.getColumnAt(warehouse, cell.column).map((col) => col.element.dom) 2704 })); 2705 }; 2706 const updateSimpleProps$1 = (modifier, colModifier, data, shouldUpdate) => { 2707 if (shouldUpdate('scope')) { 2708 modifier.setAttrib('scope', data.scope); 2709 } 2710 if (shouldUpdate('class') && data.class !== 'mce-no-match') { 2711 modifier.setAttrib('class', data.class); 2712 } 2713 if (shouldUpdate('width')) { 2714 colModifier.setStyle('width', addPxSuffix(data.width)); 2715 } 2716 }; 2717 const updateAdvancedProps$1 = (modifier, data, shouldUpdate) => { 2718 if (shouldUpdate('backgroundcolor')) { 2719 modifier.setFormat('tablecellbackgroundcolor', data.backgroundcolor); 2720 } 2721 if (shouldUpdate('bordercolor')) { 2722 modifier.setFormat('tablecellbordercolor', data.bordercolor); 2723 } 2724 if (shouldUpdate('borderstyle')) { 2725 modifier.setFormat('tablecellborderstyle', data.borderstyle); 2726 } 2727 if (shouldUpdate('borderwidth')) { 2728 modifier.setFormat('tablecellborderwidth', addPxSuffix(data.borderwidth)); 2729 } 2730 }; 2731 const applyStyleData$1 = (editor, cells, data, wasChanged) => { 2732 const isSingleCell = cells.length === 1; 2733 each$1(cells, (item) => { 2734 const cellElm = item.element; 2735 const shouldOverrideCurrentValue = isSingleCell ? always : wasChanged; 2736 const modifier = DomModifier.normal(editor, cellElm); 2737 const colModifier = item.column.map((col) => DomModifier.normal(editor, col)).getOr(modifier); 2738 updateSimpleProps$1(modifier, colModifier, data, shouldOverrideCurrentValue); 2739 if (hasAdvancedCellTab(editor)) { 2740 updateAdvancedProps$1(modifier, data, shouldOverrideCurrentValue); 2741 } 2742 // Apply alignment 2743 if (wasChanged('halign')) { 2744 setAlign(editor, cellElm, data.halign); 2745 } 2746 // Apply vertical alignment 2747 if (wasChanged('valign')) { 2748 setVAlign(editor, cellElm, data.valign); 2749 } 2750 }); 2751 }; 2752 const applyStructureData$1 = (editor, data) => { 2753 // Switch cell type if applicable. Note that we specifically tell the command to not fire events 2754 // as we'll batch the events and fire a `TableModified` event at the end of the updates. 2755 editor.execCommand('mceTableCellType', false, { type: data.celltype, no_events: true }); 2756 }; 2757 const applyCellData = (editor, cells, oldData, data) => { 2758 const modifiedData = filter(data, (value, key) => oldData[key] !== value); 2759 if (size(modifiedData) > 0 && cells.length >= 1) { 2760 // Retrieve the table before the cells are modified as there is a case where cells 2761 // are replaced and the reference will be lost when trying to fire events. 2762 table(cells[0]).each((table) => { 2763 const selectedCells = getSelectedCells(table, cells); 2764 // style modified if there's at least one other change apart from 'celltype' and 'scope' 2765 const styleModified = size(filter(modifiedData, (_value, key) => key !== 'scope' && key !== 'celltype')) > 0; 2766 const structureModified = has(modifiedData, 'celltype'); 2767 // Update the cells styling using the dialog data 2768 if (styleModified || has(modifiedData, 'scope')) { 2769 applyStyleData$1(editor, selectedCells, data, curry(has, modifiedData)); 2770 } 2771 // Update the cells structure using the dialog data 2772 if (structureModified) { 2773 applyStructureData$1(editor, data); 2774 } 2775 fireTableModified(editor, table.dom, { 2776 structure: structureModified, 2777 style: styleModified, 2778 }); 2779 }); 2780 } 2781 }; 2782 const onSubmitCellForm = (editor, cells, oldData, api) => { 2783 const data = api.getData(); 2784 api.close(); 2785 editor.undoManager.transact(() => { 2786 applyCellData(editor, cells, oldData, data); 2787 editor.focus(); 2788 }); 2789 }; 2790 const getData$1 = (editor, cells) => { 2791 const cellsData = table(cells[0]).map((table) => map(getSelectedCells(table, cells), (item) => extractDataFromCellElement(editor, item.element, hasAdvancedCellTab(editor), item.column))); 2792 return getSharedValues(cellsData.getOrDie()); 2793 }; 2794 const open$2 = (editor) => { 2795 const cells = getCellsFromSelection(editor); 2796 // Check if there are any cells to operate on 2797 if (cells.length === 0) { 2798 return; 2799 } 2800 const data = getData$1(editor, cells); 2801 const dialogTabPanel = { 2802 type: 'tabpanel', 2803 tabs: [ 2804 { 2805 title: 'General', 2806 name: 'general', 2807 items: getItems$2(editor) 2808 }, 2809 getAdvancedTab(editor, 'cell') 2810 ] 2811 }; 2812 const dialogPanel = { 2813 type: 'panel', 2814 items: [ 2815 { 2816 type: 'grid', 2817 columns: 2, 2818 items: getItems$2(editor) 2819 } 2820 ] 2821 }; 2822 editor.windowManager.open({ 2823 title: 'Cell Properties', 2824 size: 'normal', 2825 body: hasAdvancedCellTab(editor) ? dialogTabPanel : dialogPanel, 2826 buttons: [ 2827 { 2828 type: 'cancel', 2829 name: 'cancel', 2830 text: 'Cancel' 2831 }, 2832 { 2833 type: 'submit', 2834 name: 'save', 2835 text: 'Save', 2836 primary: true 2837 } 2838 ], 2839 initialData: data, 2840 onSubmit: curry(onSubmitCellForm, editor, cells, data) 2841 }); 2842 }; 2843 2844 const getClassList = (editor) => buildClassList(getRowClassList(editor)) 2845 .map((items) => ({ 2846 name: 'class', 2847 type: 'listbox', 2848 label: 'Class', 2849 items 2850 })); 2851 const formChildren = [ 2852 { 2853 type: 'listbox', 2854 name: 'type', 2855 label: 'Row type', 2856 items: [ 2857 { text: 'Header', value: 'header' }, 2858 { text: 'Body', value: 'body' }, 2859 { text: 'Footer', value: 'footer' } 2860 ] 2861 }, 2862 { 2863 type: 'listbox', 2864 name: 'align', 2865 label: 'Alignment', 2866 items: [ 2867 { text: 'None', value: '' }, 2868 { text: 'Left', value: 'left' }, 2869 { text: 'Center', value: 'center' }, 2870 { text: 'Right', value: 'right' } 2871 ] 2872 }, 2873 { 2874 label: 'Height', 2875 name: 'height', 2876 type: 'input' 2877 } 2878 ]; 2879 const getItems$1 = (editor) => formChildren.concat(getClassList(editor).toArray()); 2880 2881 const updateSimpleProps = (modifier, data, shouldUpdate) => { 2882 if (shouldUpdate('class') && data.class !== 'mce-no-match') { 2883 modifier.setAttrib('class', data.class); 2884 } 2885 if (shouldUpdate('height')) { 2886 modifier.setStyle('height', addPxSuffix(data.height)); 2887 } 2888 }; 2889 const updateAdvancedProps = (modifier, data, shouldUpdate) => { 2890 if (shouldUpdate('backgroundcolor')) { 2891 modifier.setStyle('background-color', data.backgroundcolor); 2892 } 2893 if (shouldUpdate('bordercolor')) { 2894 modifier.setStyle('border-color', data.bordercolor); 2895 } 2896 if (shouldUpdate('borderstyle')) { 2897 modifier.setStyle('border-style', data.borderstyle); 2898 } 2899 }; 2900 const applyStyleData = (editor, rows, data, wasChanged) => { 2901 const isSingleRow = rows.length === 1; 2902 const shouldOverrideCurrentValue = isSingleRow ? always : wasChanged; 2903 each$1(rows, (rowElm) => { 2904 const rowCells = children$1(SugarElement.fromDom(rowElm), 'td,th'); 2905 const modifier = DomModifier.normal(editor, rowElm); 2906 updateSimpleProps(modifier, data, shouldOverrideCurrentValue); 2907 if (hasAdvancedRowTab(editor)) { 2908 updateAdvancedProps(modifier, data, shouldOverrideCurrentValue); 2909 } 2910 // TINY-10617: Simplify number of height styles when applying height on tr 2911 if (wasChanged('height')) { 2912 each$1(rowCells, (cell) => { 2913 editor.dom.setStyle(cell.dom, 'height', null); 2914 }); 2915 } 2916 if (wasChanged('align')) { 2917 setAlign(editor, rowElm, data.align); 2918 } 2919 }); 2920 }; 2921 const applyStructureData = (editor, data) => { 2922 // Switch cell type if applicable. Note that we specifically tell the command to not fire events 2923 // as we'll batch the events and fire a `TableModified` event at the end of the updates. 2924 editor.execCommand('mceTableRowType', false, { type: data.type, no_events: true }); 2925 }; 2926 const applyRowData = (editor, rows, oldData, data) => { 2927 const modifiedData = filter(data, (value, key) => oldData[key] !== value); 2928 if (size(modifiedData) > 0) { 2929 const typeModified = has(modifiedData, 'type'); 2930 // style modified if there's at least one other change apart from 'type' 2931 const styleModified = typeModified ? size(modifiedData) > 1 : true; 2932 // Update the rows styling using the dialog data 2933 if (styleModified) { 2934 applyStyleData(editor, rows, data, curry(has, modifiedData)); 2935 } 2936 // Update the rows structure using the dialog data 2937 if (typeModified) { 2938 applyStructureData(editor, data); 2939 } 2940 table(SugarElement.fromDom(rows[0])).each((table) =>
2940 fireTableModified(editor, table.dom, { 2941 structure: typeModified, 2942 style: styleModified 2943 })); 2944 } 2945 }; 2946 const onSubmitRowForm = (editor, rows, oldData, api) => { 2947 const data = api.getData(); 2948 api.close(); 2949 editor.undoManager.transact(() => { 2950 applyRowData(editor, rows, oldData, data); 2951 editor.focus(); 2952 }); 2953 }; 2954 const open$1 = (editor) => { 2955 const rows = getRowsFromSelection(getSelectionStart(editor), ephemera.selected); 2956 // Check if there are any rows to operate on 2957 if (rows.length === 0) { 2958 return; 2959 } 2960 // Get current data and find shared values between rows 2961 const rowsData = map(rows, (rowElm) => extractDataFromRowElement(editor, rowElm.dom, hasAdvancedRowTab(editor))); 2962 const data = getSharedValues(rowsData); 2963 const dialogTabPanel = { 2964 type: 'tabpanel', 2965 tabs: [ 2966 { 2967 title: 'General', 2968 name: 'general', 2969 items: getItems$1(editor) 2970 }, 2971 getAdvancedTab(editor, 'row') 2972 ] 2973 }; 2974 const dialogPanel = { 2975 type: 'panel', 2976 items: [ 2977 { 2978 type: 'grid', 2979 columns: 2, 2980 items: getItems$1(editor) 2981 } 2982 ] 2983 }; 2984 editor.windowManager.open({ 2985 title: 'Row Properties', 2986 size: 'normal', 2987 body: hasAdvancedRowTab(editor) ? dialogTabPanel : dialogPanel, 2988 buttons: [ 2989 { 2990 type: 'cancel', 2991 name: 'cancel', 2992 text: 'Cancel' 2993 }, 2994 { 2995 type: 'submit', 2996 name: 'save', 2997 text: 'Save', 2998 primary: true 2999 } 3000 ], 3001 initialData: data, 3002 onSubmit: curry(onSubmitRowForm, editor, map(rows, (r) => r.dom), data) 3003 }); 3004 }; 3005 3006 const getItems = (editor, classes, insertNewTable) => { 3007 const rowColCountItems = !insertNewTable ? [] : [ 3008 { 3009 type: 'input', 3010 name: 'cols', 3011 label: 'Cols', 3012 inputMode: 'numeric' 3013 }, 3014 { 3015 type: 'input', 3016 name: 'rows', 3017 label: 'Rows', 3018 inputMode: 'numeric' 3019 } 3020 ]; 3021 const alwaysItems = [ 3022 { 3023 type: 'input', 3024 name: 'width', 3025 label: 'Width' 3026 }, 3027 { 3028 type: 'input', 3029 name: 'height', 3030 label: 'Height' 3031 } 3032 ]; 3033 const appearanceItems = hasAppearanceOptions(editor) ? [ 3034 { 3035 type: 'input', 3036 name: 'cellspacing', 3037 label: 'Cell spacing', 3038 inputMode: 'numeric' 3039 }, 3040 { 3041 type: 'input', 3042 name: 'cellpadding', 3043 label: 'Cell padding', 3044 inputMode: 'numeric' 3045 }, 3046 { 3047 type: 'input', 3048 name: 'border', 3049 label: 'Border width' 3050 }, 3051 { 3052 type: 'label', 3053 label: 'Caption', 3054 items: [ 3055 { 3056 type: 'checkbox', 3057 name: 'caption', 3058 label: 'Show caption' 3059 } 3060 ] 3061 } 3062 ] : []; 3063 const alignmentItem = [ 3064 { 3065 type: 'listbox', 3066 name: 'align', 3067 label: 'Alignment', 3068 items: [ 3069 { text: 'None', value: '' }, 3070 { text: 'Left', value: 'left' }, 3071 { text: 'Center', value: 'center' }, 3072 { text: 'Right', value: 'right' } 3073 ] 3074 } 3075 ]; 3076 const classListItem = classes.length > 0 ? [ 3077 { 3078 name: 'class', 3079 type: 'listbox', 3080 label: 'Class', 3081 items: classes 3082 } 3083 ] : []; 3084 return rowColCountItems.concat(alwaysItems).concat(appearanceItems).concat(alignmentItem).concat(classListItem); 3085 }; 3086 3087 // Explore the layers of the table till we find the first layer of tds or ths 3088 const styleTDTH = (dom, elm, name, value) => { 3089 if (elm.tagName === 'TD' || elm.tagName === 'TH') { 3090 if (isString(name) && isNonNullable(value)) { 3091 dom.setStyle(elm, name, value); 3092 } 3093 else { 3094 dom.setStyles(elm, name); 3095 } 3096 } 3097 else { 3098 if (elm.children) { 3099 for (let i = 0; i < elm.children.length; i++) { 3100 styleTDTH(dom, elm.children[i], name, value); 3101 } 3102 } 3103 } 3104 }; 3105 const applyDataToElement = (editor, tableElm, data, shouldApplyOnCell) => { 3106 const dom = editor.dom; 3107 const attrs = {}; 3108 const styles = {}; 3109 const shouldStyleWithCss$1 = shouldStyleWithCss(editor); 3110 const hasAdvancedTableTab$1 = hasAdvancedTableTab(editor); 3111 const borderIsZero = parseFloat(data.border) === 0; 3112 if (!isUndefined(data.class) && data.class !== 'mce-no-match') { 3113 attrs.class = data.class; 3114 } 3115 styles.height = addPxSuffix(data.height); 3116 if (shouldStyleWithCss$1) { 3117 styles.width = addPxSuffix(data.width); 3118 } 3119 else if (dom.getAttrib(tableElm, 'width')) { 3120 attrs.width = removePxSuffix(data.width); 3121 } 3122 if (shouldStyleWithCss$1) {
3123 if (borderIsZero) { 3124 attrs.border = 0; 3125 styles['border-width'] = ''; 3126 } 3127 else { 3128 styles['border-width'] = addPxSuffix(data.border); 3129 attrs.border = 1; 3130 } 3131 styles['border-spacing'] = addPxSuffix(data.cellspacing); 3132 } 3133 else { 3134 attrs.border = borderIsZero ? 0 : data.border; 3135 attrs.cellpadding = data.cellpadding; 3136 attrs.cellspacing = data.cellspacing; 3137 } 3138 // TINY-9837: Relevant data are applied on child TD/THs only if they have been modified since the previous dialog submission 3139 if (shouldStyleWithCss$1 && tableElm.children) { 3140 const cellStyles = {}; 3141 if (borderIsZero) { 3142 cellStyles['border-width'] = ''; 3143 } 3144 else if (shouldApplyOnCell.border) { 3145 cellStyles['border-width'] = addPxSuffix(data.border); 3146 } 3147 if (shouldApplyOnCell.cellpadding) { 3148 cellStyles.padding = addPxSuffix(data.cellpadding); 3149 } 3150 if (hasAdvancedTableTab$1 && shouldApplyOnCell.bordercolor) { 3151 cellStyles['border-color'] = data.bordercolor; 3152 } 3153 if (!isEmpty$1(cellStyles)) { 3154 for (let i = 0; i < tableElm.children.length; i++) { 3155 styleTDTH(dom, tableElm.children[i], cellStyles); 3156 } 3157 } 3158 } 3159 if (hasAdvancedTableTab$1) { 3160 const advData = data; 3161 styles['background-color'] = advData.backgroundcolor; 3162 styles['border-color'] = advData.bordercolor; 3163 styles['border-style'] = advData.borderstyle; 3164 } 3165 dom.setStyles(tableElm, { ...getDefaultStyles(editor), ...styles }); 3166 dom.setAttribs(tableElm, { ...getDefaultAttributes(editor), ...attrs }); 3167 }; 3168 const onSubmitTableForm = (editor, tableElm, oldData, api) => { 3169 const dom = editor.dom; 3170 const data = api.getData(); 3171 const modifiedData = filter(data, (value, key) => oldData[key] !== value); 3172 api.close(); 3173 editor.undoManager.transact(() => { 3174 if (!tableElm) { 3175 const cols = toInt(data.cols).getOr(1); 3176 const rows = toInt(data.rows).getOr(1); 3177 // Cases 1 & 3 - inserting a table 3178 editor.execCommand('mceInsertTable', false, { rows, columns: cols }); 3179 tableElm = getSelectionCell(getSelectionStart(editor), getIsRoot(editor)) 3180 .bind((cell) => table(cell, getIsRoot(editor))) 3181 .map((table) => table.dom) 3182 .getOrDie(); 3183 } 3184 if (size(modifiedData) > 0) { 3185 const applicableCellProperties = { 3186 border: has(modifiedData, 'border'), 3187 bordercolor: has(modifiedData, 'bordercolor'), 3188 cellpadding: has(modifiedData, 'cellpadding') 3189 }; 3190 applyDataToElement(editor, tableElm, data, applicableCellProperties); 3191 // Toggle caption on/off 3192 const captionElm = dom.select('caption', tableElm)[0]; 3193 if (captionElm && !data.caption || !captionElm && data.caption) { 3194 editor.execCommand('mceTableToggleCaption'); 3195 } 3196 setAlign(editor, tableElm, data.align); 3197 } 3198 editor.focus(); 3199 editor.addVisual(); 3200 if (size(modifiedData) > 0) { 3201 const captionModified = has(modifiedData, 'caption'); 3202 // style modified if there's at least one other change apart from 'caption' 3203 const styleModified = captionModified ? size(modifiedData) > 1 : true; 3204 fireTableModified(editor, tableElm, { structure: captionModified, style: styleModified }); 3205 } 3206 }); 3207 }; 3208 const open = (editor, insertNewTable) => { 3209 const dom = editor.dom; 3210 let tableElm; 3211 let data = extractDataFromSettings(editor, hasAdvancedTableTab(editor)); 3212 // Cases for creation/update of tables: 3213 // 1. isNew == true - called by mceInsertTable - we are inserting a new table so we don't care what the selection's parent is, 3214 // and we need to add cols and rows input fields to the dialog 3215 // 2. isNew == false && selection parent is a table - update the table 3216 // 3. isNew == false && selection parent isn't a table - open dialog with default values and insert a table 3217 if (insertNewTable) { 3218 // Case 1 - isNew == true. We're inserting a new table so use defaults and add cols and rows + adv properties. 3219 data.cols = '1'; 3220 data.rows = '1'; 3221 if (hasAdvancedTableTab(editor)) { 3222 data.borderstyle = ''; 3223 data.bordercolor = ''; 3224 data.backgroundcolor = ''; 3225 } 3226 } 3227 else { 3228 tableElm = dom.getParent(editor.selection.getStart(), 'table', editor.getBody()); 3229 if (tableElm) { 3230 // Case 2 - isNew == false && table parent 3231 data = extractDataFromTableElement(editor, tableElm, hasAdvancedTableTab(editor)); 3232 } 3233 else { 3234 // Case 3 - isNew == false && non-table parent. data is set to basic defaults so just add the adv properties if needed 3235 if (hasAdvancedTableTab(editor)) { 3236 data.borderstyle = ''; 3237 data.bordercolor = ''; 3238 data.backgroundcolor = ''; 3239 } 3240 } 3241 } 3242 const classes = buildClassList(getTableClassList(editor)); 3243 if (classes.isSome()) { 3244 if (data.class) { 3245 data.class = data.class.replace(/\s*mce\-item\-table\s*/g, ''); 3246 } 3247 } 3248 const generalPanel = { 3249 type: 'grid', 3250 columns: 2, 3251 items: getItems(editor, classes.getOr([]), insertNewTable) 3252 }; 3253 const nonAdvancedForm = () => ({ 3254 type: 'panel', 3255 items: [generalPanel] 3256 }); 3257 const advancedForm = () => ({ 3258 type: 'tabpanel', 3259 tabs: [ 3260 { 3261 title: 'General', 3262 name: 'general', 3263 items: [generalPanel] 3264 }, 3265 getAdvancedTab(editor, 'table') 3266 ] 3267 }); 3268 const dialogBody = hasAdvancedTableTab(editor) ? advancedForm() : nonAdvancedForm(); 3269 editor.windowManager.open({ 3270 title: 'Table Properties', 3271 size: 'normal', 3272 body: dialogBody, 3273 onSubmit: curry(onSubmitTableForm, editor, tableElm, data), 3274 buttons: [ 3275 { 3276 type: 'cancel', 3277 name: 'cancel', 3278 text: 'Cancel' 3279 }, 3280 { 3281 type: 'submit', 3282 name: 'save', 3283 text: 'Save', 3284 primary: true 3285 } 3286 ], 3287 initialData: data 3288 }); 3289 }; 3290 3291 const registerCommands = (editor) => { 3292 const runAction = (f) => { 3293 if (isInEditableContext(getSelectionStart(editor))) { 3294 f(); 3295 } 3296 }; 3297 // Register dialog commands 3298 each({ 3299 // AP-101 TableDialog.open renders a slightly different dialog if isNew is true 3300 mceTableProps: curry(open, editor, false), 3301 mceTableRowProps: curry(open$1, editor), 3302 mceTableCellProps: curry(open$2, editor), 3303 mceInsertTableDialog: curry(open, editor, true), 3304 }, (func, name) => editor.addCommand(name, () => runAction(func))); 3305 }; 3306 3307 /* 3308 NOTE: This file is partially duplicated in the following locations: 3309 - models/dom/table/queries/TableTargets.ts 3310 - advtable 3311 Make sure that if making changes to this file, the other files are updated as well 3312 */ 3313 const noMenu = (cell) => ({ 3314 element: cell, 3315 mergable: Optional.none(), 3316 unmergable: Optional.none(), 3317 selection: [cell] 3318 }); 3319 const forMenu = (selectedCells, table, cell) => ({ 3320 element: cell, 3321 mergable: mergable(table, selectedCells, ephemera), 3322 unmergable: unmergable(selectedCells), 3323 selection: selection(selectedCells) 3324 }); 3325 3326 const getSelectionTargets = (editor) => { 3327 const targets = Cell(Optional.none()); 3328 const changeHandlers = Cell([]);
3329 let selectionDetails = Optional.none(); 3330 const isCaption = isTag('caption'); 3331 const isDisabledForSelection = (key) => selectionDetails.forall((details) => !details[key]); 3332 const getStart = () => getSelectionCellOrCaption(getSelectionStart(editor), getIsRoot(editor)); 3333 const getEnd = () => getSelectionCellOrCaption(getSelectionEnd(editor), getIsRoot(editor)); 3334 const findTargets = () => getStart().bind((startCellOrCaption) => flatten(lift2(table(startCellOrCaption), getEnd().bind(table), (startTable, endTable) => { 3335 if (eq(startTable, endTable)) { 3336 if (isCaption(startCellOrCaption)) { 3337 return Optional.some(noMenu(startCellOrCaption)); 3338 } 3339 else { 3340 return Optional.some(forMenu(getCellsFromSelection(editor), startTable, startCellOrCaption)); 3341 } 3342 } 3343 return Optional.none(); 3344 }))); 3345 const getExtractedDetails = (targets) => { 3346 const tableOpt = table(targets.element); 3347 return tableOpt.map((table) => { 3348 const warehouse = Warehouse.fromTable(table); 3349 const selectedCells = onCells(warehouse, targets).getOr([]); 3350 const locked = foldl(selectedCells, (acc, cell) => { 3351 if (cell.isLocked) { 3352 acc.onAny = true; 3353 if (cell.column === 0) { 3354 acc.onFirst = true; 3355 } 3356 else if (cell.column + cell.colspan >= warehouse.grid.columns) { 3357 acc.onLast = true; 3358 } 3359 } 3360 return acc; 3361 }, { onAny: false, onFirst: false, onLast: false }); 3362 return { 3363 mergeable: onUnlockedMergable(warehouse, targets).isSome(), 3364 unmergeable: onUnlockedUnmergable(warehouse, targets).isSome(), 3365 locked 3366 }; 3367 }); 3368 }; 3369 const resetTargets = () => { 3370 // Reset the targets 3371 targets.set(cached(findTargets)()); 3372 // Reset the selection details 3373 selectionDetails = targets.get().bind(getExtractedDetails); 3374 // Trigger change handlers 3375 each$1(changeHandlers.get(), call); 3376 }; 3377 const setupHandler = (handler) => { 3378 // Execute the handler to set the initial state 3379 handler(); 3380 // Register the handler so we can update the state when resetting targets 3381 changeHandlers.set(changeHandlers.get().concat([handler])); 3382 return () => { 3383 changeHandlers.set(filter$1(changeHandlers.get(), (h) => h !== handler)); 3384 }; 3385 }; 3386 const onSetup = (api, isDisabled) => setupHandler(() => targets.get().fold(() => { 3387 api.setEnabled(false); 3388 }, (targets) => { 3389 api.setEnabled(!isDisabled(targets) && editor.selection.isEditable()); 3390 })); 3391 const onSetupWithToggle = (api, isDisabled, isActive) => setupHandler(() => targets.get().fold(() => { 3392 api.setEnabled(false); 3393 api.setActive(false); 3394 }, (targets) => { 3395 api.setEnabled(!isDisabled(targets) && editor.selection.isEditable()); 3396 api.setActive(isActive(targets)); 3397 })); 3398 const isDisabledFromLocked = (lockedDisable) => selectionDetails.exists((details) => details.locked[lockedDisable]); 3399 const onSetupTable = (api) => onSetup(api, (_) => false); 3400 const onSetupCellOrRow = (api) => onSetup(api, (targets) => isCaption(targets.element)); 3401 const onSetupColumn = (lockedDisable) => (api) => onSetup(api, (targets) => isCaption(targets.element) || isDisabledFromLocked(lockedDisable)); 3402 const onSetupPasteable = (getClipboardData) => (api) => onSetup(api, (targets) => isCaption(targets.element) || getClipboardData().isNone()); 3403 const onSetupPasteableColumn = (getClipboardData, lockedDisable) => (api) => onSetup(api, (targets) => isCaption(targets.element) || getClipboardData().isNone() || isDisabledFromLocked(lockedDisable)); 3404 const onSetupMergeable = (api) => onSetup(api, (_targets) => isDisabledForSelection('mergeable')); 3405 const onSetupUnmergeable = (api) => onSetup(api, (_targets) => isDisabledForSelection('unmergeable')); 3406 const onSetupTableWithCaption = (api) => { 3407 return onSetupWithToggle(api, never, (targets) => { 3408 const tableOpt = table(targets.element, getIsRoot(editor)); 3409 return tableOpt.exists((table) => child(table, 'caption')); 3410 }); 3411 }; 3412 const onSetupTableHeaders = (command, headerType) => (api) => { 3413 return onSetupWithToggle(api, (targets) => isCaption(targets.element), () => editor.queryCommandValue(command) === headerType); 3414 }; 3415 const onSetupTableRowHeaders = onSetupTableHeaders('mceTableRowType', 'header'); 3416 const onSetupTableColumnHeaders = onSetupTableHeaders('mceTableColType', 'th'); 3417 editor.on('NodeChange ExecCommand TableSelectorChange', resetTargets); 3418 return { 3419 onSetupTable, 3420 onSetupCellOrRow, 3421 onSetupColumn, 3422 onSetupPasteable, 3423 onSetupPasteableColumn, 3424 onSetupMergeable, 3425 onSetupUnmergeable, 3426 resetTargets, 3427 onSetupTableWithCaption, 3428 onSetupTableRowHeaders, 3429 onSetupTableColumnHeaders, 3430 targets: targets.get 3431 }; 3432 }; 3433 3434 var global = tinymce.util.Tools.resolve('tinymce.FakeClipboard'); 3435 3436 /* 3437 NOTE: This file is duplicated in the following locations: 3438 - models/dom/table/api/Clipboard.ts 3439 Make sure that if making changes to this file, the other files are updated as well 3440 */ 3441 const tableTypeBase = 'x-tinymce/dom-table-'; 3442 const tableTypeRow = tableTypeBase + 'rows'; 3443 const tableTypeColumn = tableTypeBase + 'columns'; 3444 const getData = (type) => { 3445 var _a; 3446 const items = (_a = global.read()) !== null && _a !== void 0 ? _a : []; 3447 return findMap(items, (item) => Optional.from(item.getType(type))); 3448 }; 3449 const getRows = () => getData(tableTypeRow); 3450 const getColumns = () => getData(tableTypeColumn); 3451 3452 const onSetupEditable$1 = (editor) => (api) => { 3453 const nodeChanged = () => { 3454 api.setEnabled(editor.selection.isEditable()); 3455 }; 3456 editor.on('NodeChange', nodeChanged); 3457 nodeChanged(); 3458 return () => { 3459 editor.off('NodeChange', nodeChanged); 3460 }; 3461 }; 3462 const addButtons = (editor, selectionTargets) => { 3463 editor.ui.registry.addMenuButton('table', { 3464 tooltip: 'Table', 3465 icon: 'table', 3466 onSetup: onSetupEditable$1(editor), 3467 fetch: (callback) => callback('inserttable | cell row column | advtablesort | tableprops deletetable') 3468 }); 3469 const cmd = (command) => () => editor.execCommand(command); 3470 // TODO: TINY-8172 Unwind this when an alternative solution is found 3471 const addButtonIfRegistered = (name, spec) => { 3472 if (editor.queryCommandSupported(spec.command)) { 3473 editor.ui.registry.addButton(name, { 3474 ...spec, 3475 onAction: isFunction(spec.onAction) ? spec.onAction : cmd(spec.command) 3476 }); 3477 } 3478 }; 3479 // TODO: TINY-8172 Unwind this when an alternative solution is found 3480 const addToggleButtonIfRegistered = (name, spec) => { 3481 if (editor.queryCommandSupported(spec.command)) { 3482 editor.ui.registry.addToggleButton(name, { 3483 ...spec, 3484 onAction: isFunction(spec.onAction) ? spec.onAction : cmd(spec.command) 3485 }); 3486 } 3487 }; 3488 addButtonIfRegistered('tableprops', { 3489 tooltip: 'Table properties',
3490 command: 'mceTableProps', 3491 icon: 'table', 3492 onSetup: selectionTargets.onSetupTable 3493 }); 3494 addButtonIfRegistered('tabledelete', { 3495 tooltip: 'Delete table', 3496 command: 'mceTableDelete', 3497 icon: 'table-delete-table', 3498 onSetup: selectionTargets.onSetupTable 3499 }); 3500 addButtonIfRegistered('tablecellprops', { 3501 tooltip: 'Cell properties', 3502 command: 'mceTableCellProps', 3503 icon: 'table-cell-properties', 3504 onSetup: selectionTargets.onSetupCellOrRow 3505 }); 3506 addButtonIfRegistered('tablemergecells', { 3507 tooltip: 'Merge cells', 3508 command: 'mceTableMergeCells', 3509 icon: 'table-merge-cells', 3510 onSetup: selectionTargets.onSetupMergeable 3511 }); 3512 addButtonIfRegistered('tablesplitcells', { 3513 tooltip: 'Split cell', 3514 command: 'mceTableSplitCells', 3515 icon: 'table-split-cells', 3516 onSetup: selectionTargets.onSetupUnmergeable 3517 }); 3518 addButtonIfRegistered('tableinsertrowbefore', { 3519 tooltip: 'Insert row before', 3520 command: 'mceTableInsertRowBefore', 3521 icon: 'table-insert-row-above', 3522 onSetup: selectionTargets.onSetupCellOrRow 3523 }); 3524 addButtonIfRegistered('tableinsertrowafter', { 3525 tooltip: 'Insert row after', 3526 command: 'mceTableInsertRowAfter', 3527 icon: 'table-insert-row-after', 3528 onSetup: selectionTargets.onSetupCellOrRow 3529 }); 3530 addButtonIfRegistered('tabledeleterow', { 3531 tooltip: 'Delete row', 3532 command: 'mceTableDeleteRow', 3533 icon: 'table-delete-row', 3534 onSetup: selectionTargets.onSetupCellOrRow 3535 }); 3536 addButtonIfRegistered('tablerowprops', { 3537 tooltip: 'Row properties', 3538 command: 'mceTableRowProps', 3539 icon: 'table-row-properties', 3540 onSetup: selectionTargets.onSetupCellOrRow 3541 }); 3542 addButtonIfRegistered('tableinsertcolbefore', { 3543 tooltip: 'Insert column before', 3544 command: 'mceTableInsertColBefore', 3545 icon: 'table-insert-column-before', 3546 onSetup: selectionTargets.onSetupColumn("onFirst" /* LockedDisable.onFirst */) 3547 }); 3548 addButtonIfRegistered('tableinsertcolafter', { 3549 tooltip: 'Insert column after', 3550 command: 'mceTableInsertColAfter', 3551 icon: 'table-insert-column-after', 3552 onSetup: selectionTargets.onSetupColumn("onLast" /* LockedDisable.onLast */) 3553 }); 3554 addButtonIfRegistered('tabledeletecol', { 3555 tooltip: 'Delete column', 3556 command: 'mceTableDeleteCol', 3557 icon: 'table-delete-column', 3558 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3559 }); 3560 addButtonIfRegistered('tablecutrow', { 3561 tooltip: 'Cut row', 3562 command: 'mceTableCutRow', 3563 icon: 'cut-row', 3564 onSetup: selectionTargets.onSetupCellOrRow 3565 }); 3566 addButtonIfRegistered('tablecopyrow', { 3567 tooltip: 'Copy row', 3568 command: 'mceTableCopyRow', 3569 icon: 'duplicate-row', 3570 onSetup: selectionTargets.onSetupCellOrRow 3571 }); 3572 addButtonIfRegistered('tablepasterowbefore', { 3573 tooltip: 'Paste row before', 3574 command: 'mceTablePasteRowBefore', 3575 icon: 'paste-row-before', 3576 onSetup: selectionTargets.onSetupPasteable(getRows) 3577 }); 3578 addButtonIfRegistered('tablepasterowafter', { 3579 tooltip: 'Paste row after', 3580 command: 'mceTablePasteRowAfter', 3581 icon: 'paste-row-after', 3582 onSetup: selectionTargets.onSetupPasteable(getRows) 3583 }); 3584 addButtonIfRegistered('tablecutcol', { 3585 tooltip: 'Cut column', 3586 command: 'mceTableCutCol', 3587 icon: 'cut-column', 3588 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3589 }); 3590 addButtonIfRegistered('tablecopycol', { 3591 tooltip: 'Copy column',
3592 command: 'mceTableCopyCol', 3593 icon: 'duplicate-column', 3594 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3595 }); 3596 addButtonIfRegistered('tablepastecolbefore', { 3597 tooltip: 'Paste column before', 3598 command: 'mceTablePasteColBefore', 3599 icon: 'paste-column-before', 3600 onSetup: selectionTargets.onSetupPasteableColumn(getColumns, "onFirst" /* LockedDisable.onFirst */) 3601 }); 3602 addButtonIfRegistered('tablepastecolafter', { 3603 tooltip: 'Paste column after', 3604 command: 'mceTablePasteColAfter', 3605 icon: 'paste-column-after', 3606 onSetup: selectionTargets.onSetupPasteableColumn(getColumns, "onLast" /* LockedDisable.onLast */) 3607 }); 3608 addButtonIfRegistered('tableinsertdialog', { 3609 tooltip: 'Insert table', 3610 command: 'mceInsertTableDialog', 3611 icon: 'table', 3612 onSetup: onSetupEditable$1(editor) 3613 }); 3614 const tableClassList = filterNoneItem(getTableClassList(editor)); 3615 if (tableClassList.length !== 0 && editor.queryCommandSupported('mceTableToggleClass')) { 3616 editor.ui.registry.addMenuButton('tableclass', { 3617 icon: 'table-classes', 3618 tooltip: 'Table styles', 3619 fetch: generateMenuItemsCallback(editor, tableClassList, 'tableclass', (value) => editor.execCommand('mceTableToggleClass', false, value)), 3620 onSetup: selectionTargets.onSetupTable 3621 }); 3622 } 3623 const tableCellClassList = filterNoneItem(getCellClassList(editor)); 3624 if (tableCellClassList.length !== 0 && editor.queryCommandSupported('mceTableCellToggleClass')) { 3625 editor.ui.registry.addMenuButton('tablecellclass', { 3626 icon: 'table-cell-classes', 3627 tooltip: 'Cell styles', 3628 fetch: generateMenuItemsCallback(editor, tableCellClassList, 'tablecellclass', (value) => editor.execCommand('mceTableCellToggleClass', false, value)), 3629 onSetup: selectionTargets.onSetupCellOrRow 3630 }); 3631 } 3632 // TODO: TINY-8172 Unwind this when an alternative solution is found 3633 if (editor.queryCommandSupported('mceTableApplyCellStyle')) { 3634 editor.ui.registry.addMenuButton('tablecellvalign', { 3635 icon: 'vertical-align', 3636 tooltip: 'Vertical align', 3637 fetch: generateMenuItemsCallback(editor, verticalAlignValues, 'tablecellverticalalign', applyTableCellStyle(editor, 'vertical-align')), 3638 onSetup: selectionTargets.onSetupCellOrRow 3639 }); 3640 editor.ui.registry.addMenuButton('tablecellborderwidth', { 3641 icon: 'border-width', 3642 tooltip: 'Border width', 3643 fetch: generateMenuItemsCallback(editor, getTableBorderWidths(editor), 'tablecellborderwidth', applyTableCellStyle(editor, 'border-width')), 3644 onSetup: selectionTargets.onSetupCellOrRow 3645 }); 3646 editor.ui.registry.addMenuButton('tablecellborderstyle', { 3647 icon: 'border-style', 3648 tooltip: 'Border style', 3649 fetch: generateMenuItemsCallback(editor, getTableBorderStyles(editor), 'tablecellborderstyle', applyTableCellStyle(editor, 'border-style')), 3650 onSetup: selectionTargets.onSetupCellOrRow 3651 }); 3652 editor.ui.registry.addMenuButton('tablecellbackgroundcolor', { 3653 icon: 'cell-background-color', 3654 tooltip: 'Background color', 3655 fetch: (callback) => callback(buildColorMenu(editor, getTableBackgroundColorMap(editor), 'background-color')), 3656 onSetup: selectionTargets.onSetupCellOrRow 3657 }); 3658 editor.ui.registry.addMenuButton('tablecellbordercolor', { 3659 icon: 'cell-border-color', 3660 tooltip: 'Border color', 3661 fetch: (callback) => callback(buildColorMenu(editor, getTableBorderColorMap(editor), 'border-color')), 3662 onSetup: selectionTargets.onSetupCellOrRow 3663 }); 3664 } 3665 addToggleButtonIfRegistered('tablecaption', { 3666 tooltip: 'Table caption', 3667 icon: 'table-caption',
3668 command: 'mceTableToggleCaption', 3669 onSetup: selectionTargets.onSetupTableWithCaption 3670 }); 3671 addToggleButtonIfRegistered('tablerowheader', { 3672 tooltip: 'Row header', 3673 icon: 'table-top-header', 3674 command: 'mceTableRowType', 3675 onAction: changeRowHeader(editor), 3676 onSetup: selectionTargets.onSetupTableRowHeaders 3677 }); 3678 addToggleButtonIfRegistered('tablecolheader', { 3679 tooltip: 'Column header', 3680 icon: 'table-left-header', 3681 command: 'mceTableColType', 3682 onAction: changeColumnHeader(editor), 3683 onSetup: selectionTargets.onSetupTableColumnHeaders 3684 }); 3685 }; 3686 const addToolbars = (editor) => { 3687 const isEditableTable = (table) => editor.dom.is(table, 'table') && editor.getBody().contains(table) && editor.dom.isEditable(table.parentNode); 3688 const toolbar = getToolbar(editor); 3689 if (toolbar.length > 0) { 3690 editor.ui.registry.addContextToolbar('table', { 3691 predicate: isEditableTable, 3692 items: toolbar, 3693 scope: 'node', 3694 position: 'node' 3695 }); 3696 } 3697 }; 3698 3699 const onSetupEditable = (editor) => (api) => { 3700 const nodeChanged = () => { 3701 api.setEnabled(editor.selection.isEditable()); 3702 }; 3703 editor.on('NodeChange', nodeChanged); 3704 nodeChanged(); 3705 return () => { 3706 editor.off('NodeChange', nodeChanged); 3707 }; 3708 }; 3709 const addMenuItems = (editor, selectionTargets) => { 3710 const cmd = (command) => () => editor.execCommand(command); 3711 // TODO: TINY-8172 Unwind this when an alternative solution is found 3712 const addMenuIfRegistered = (name, spec) => { 3713 if (editor.queryCommandSupported(spec.command)) { 3714 editor.ui.registry.addMenuItem(name, { 3715 ...spec, 3716 onAction: isFunction(spec.onAction) ? spec.onAction : cmd(spec.command) 3717 }); 3718 return true; 3719 } 3720 else { 3721 return false; 3722 } 3723 }; 3724 // TODO: TINY-8172 Unwind this when an alternative solution is found 3725 const addToggleMenuIfRegistered = (name, spec) => { 3726 if (editor.queryCommandSupported(spec.command)) { 3727 editor.ui.registry.addToggleMenuItem(name, { 3728 ...spec, 3729 onAction: isFunction(spec.onAction) ? spec.onAction : cmd(spec.command) 3730 }); 3731 } 3732 }; 3733 const insertTableAction = (data) => { 3734 editor.execCommand('mceInsertTable', false, { 3735 rows: data.numRows, 3736 columns: data.numColumns 3737 }); 3738 }; 3739 const hasRowMenuItems = [ 3740 addMenuIfRegistered('tableinsertrowbefore', { 3741 text: 'Insert row before', 3742 icon: 'table-insert-row-above', 3743 command: 'mceTableInsertRowBefore', 3744 onSetup: selectionTargets.onSetupCellOrRow 3745 }), 3746 addMenuIfRegistered('tableinsertrowafter', { 3747 text: 'Insert row after', 3748 icon: 'table-insert-row-after', 3749 command: 'mceTableInsertRowAfter', 3750 onSetup: selectionTargets.onSetupCellOrRow 3751 }), 3752 addMenuIfRegistered('tabledeleterow', { 3753 text: 'Delete row', 3754 icon: 'table-delete-row', 3755 command: 'mceTableDeleteRow', 3756 onSetup: selectionTargets.onSetupCellOrRow 3757 }), 3758 addMenuIfRegistered('tablerowprops', { 3759 text: 'Row properties', 3760 icon: 'table-row-properties', 3761 command: 'mceTableRowProps', 3762 onSetup: selectionTargets.onSetupCellOrRow 3763 }), 3764 addMenuIfRegistered('tablecutrow', { 3765 text: 'Cut row', 3766 icon: 'cut-row', 3767 command: 'mceTableCutRow', 3768 onSetup: selectionTargets.onSetupCellOrRow 3769 }), 3770 addMenuIfRegistered('tablecopyrow', { 3771 text: 'Copy row', 3772 icon: 'duplicate-row',
3773 command: 'mceTableCopyRow', 3774 onSetup: selectionTargets.onSetupCellOrRow 3775 }), 3776 addMenuIfRegistered('tablepasterowbefore', { 3777 text: 'Paste row before', 3778 icon: 'paste-row-before', 3779 command: 'mceTablePasteRowBefore', 3780 onSetup: selectionTargets.onSetupPasteable(getRows) 3781 }), 3782 addMenuIfRegistered('tablepasterowafter', { 3783 text: 'Paste row after', 3784 icon: 'paste-row-after', 3785 command: 'mceTablePasteRowAfter', 3786 onSetup: selectionTargets.onSetupPasteable(getRows) 3787 }), 3788 ]; 3789 const hasColumnMenuItems = [ 3790 addMenuIfRegistered('tableinsertcolumnbefore', { 3791 text: 'Insert column before', 3792 icon: 'table-insert-column-before', 3793 command: 'mceTableInsertColBefore', 3794 onSetup: selectionTargets.onSetupColumn("onFirst" /* LockedDisable.onFirst */) 3795 }), 3796 addMenuIfRegistered('tableinsertcolumnafter', { 3797 text: 'Insert column after', 3798 icon: 'table-insert-column-after', 3799 command: 'mceTableInsertColAfter', 3800 onSetup: selectionTargets.onSetupColumn("onLast" /* LockedDisable.onLast */) 3801 }), 3802 addMenuIfRegistered('tabledeletecolumn', { 3803 text: 'Delete column', 3804 icon: 'table-delete-column', 3805 command: 'mceTableDeleteCol', 3806 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3807 }), 3808 addMenuIfRegistered('tablecutcolumn', { 3809 text: 'Cut column', 3810 icon: 'cut-column', 3811 command: 'mceTableCutCol', 3812 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3813 }), 3814 addMenuIfRegistered('tablecopycolumn', { 3815 text: 'Copy column', 3816 icon: 'duplicate-column', 3817 command: 'mceTableCopyCol', 3818 onSetup: selectionTargets.onSetupColumn("onAny" /* LockedDisable.onAny */) 3819 }), 3820 addMenuIfRegistered('tablepastecolumnbefore', { 3821 text: 'Paste column before', 3822 icon: 'paste-column-before', 3823 command: 'mceTablePasteColBefore', 3824 onSetup: selectionTargets.onSetupPasteableColumn(getColumns, "onFirst" /* LockedDisable.onFirst */) 3825 }), 3826 addMenuIfRegistered('tablepastecolumnafter', { 3827 text: 'Paste column after', 3828 icon: 'paste-column-after', 3829 command: 'mceTablePasteColAfter', 3830 onSetup: selectionTargets.onSetupPasteableColumn(getColumns, "onLast" /* LockedDisable.onLast */) 3831 }), 3832 ]; 3833 const hasCellMenuItems = [ 3834 addMenuIfRegistered('tablecellprops', { 3835 text: 'Cell properties', 3836 icon: 'table-cell-properties', 3837 command: 'mceTableCellProps', 3838 onSetup: selectionTargets.onSetupCellOrRow 3839 }), 3840 addMenuIfRegistered('tablemergecells', { 3841 text: 'Merge cells', 3842 icon: 'table-merge-cells', 3843 command: 'mceTableMergeCells', 3844 onSetup: selectionTargets.onSetupMergeable 3845 }), 3846 addMenuIfRegistered('tablesplitcells', { 3847 text: 'Split cell', 3848 icon: 'table-split-cells', 3849 command: 'mceTableSplitCells', 3850 onSetup: selectionTargets.onSetupUnmergeable 3851 }), 3852 ]; 3853 if (!hasTableGrid(editor)) { 3854 editor.ui.registry.addMenuItem('inserttable', { 3855 text: 'Table', 3856 icon: 'table', 3857 onAction: cmd('mceInsertTableDialog'), 3858 onSetup: onSetupEditable(editor) 3859 }); 3860 } 3861 else { 3862 editor.ui.registry.addNestedMenuItem('inserttable', { 3863 text: 'Table', 3864 icon: 'table', 3865 getSubmenuItems: () => [{ type: 'fancymenuitem', fancytype: 'inserttable', onAction: insertTableAction }], 3866 onSetup: onSetupEditable(editor) 3867 }); 3868 } 3869 // TINY-3636: We want a way to use the dialog even when tablegrid true. 3870 // If tablegrid false then inserttable and inserttabledialog are the same, 3871 // but that's preferrable to breaking things at this point. 3872 editor.ui.registry.addMenuItem('inserttabledialog', { 3873 text: 'Insert table', 3874 icon: 'table', 3875 onAction: cmd('mceInsertTableDialog'), 3876 onSetup: onSetupEditable(editor) 3877 }); 3878 addMenuIfRegistered('tableprops', { 3879 text: 'Table properties', 3880 onSetup: selectionTargets.onSetupTable,
3881 command: 'mceTableProps' 3882 }); 3883 addMenuIfRegistered('deletetable', { 3884 text: 'Delete table', 3885 icon: 'table-delete-table', 3886 onSetup: selectionTargets.onSetupTable, 3887 command: 'mceTableDelete' 3888 }); 3889 // if any of the row menu items returned true 3890 if (contains(hasRowMenuItems, true)) { 3891 editor.ui.registry.addNestedMenuItem('row', { 3892 type: 'nestedmenuitem', 3893 text: 'Row', 3894 getSubmenuItems: constant('tableinsertrowbefore tableinsertrowafter tabledeleterow tablerowprops | tablecutrow tablecopyrow tablepasterowbefore tablepasterowafter') 3895 }); 3896 } 3897 if (contains(hasColumnMenuItems, true)) { 3898 editor.ui.registry.addNestedMenuItem('column', { 3899 type: 'nestedmenuitem', 3900 text: 'Column', 3901 getSubmenuItems: constant('tableinsertcolumnbefore tableinsertcolumnafter tabledeletecolumn | tablecutcolumn tablecopycolumn tablepastecolumnbefore tablepastecolumnafter') 3902 }); 3903 } 3904 if (contains(hasCellMenuItems, true)) { 3905 editor.ui.registry.addNestedMenuItem('cell', { 3906 type: 'nestedmenuitem', 3907 text: 'Cell', 3908 getSubmenuItems: constant('tablecellprops tablemergecells tablesplitcells') 3909 }); 3910 } 3911 editor.ui.registry.addContextMenu('table', { 3912 update: () => { 3913 // context menu fires before node change, so check the selection here first 3914 selectionTargets.resetTargets(); 3915 // ignoring element since it's monitored elsewhere 3916 return selectionTargets.targets().fold(constant(''), (targets) => { 3917 // If clicking in a caption, then we shouldn't show the cell/row/column options 3918 if (name(targets.element) === 'caption') { 3919 return 'tableprops deletetable'; 3920 } 3921 else { 3922 return 'cell row column | advtablesort | tableprops deletetable'; 3923 } 3924 }); 3925 } 3926 }); 3927 const tableClassList = filterNoneItem(getTableClassList(editor)); 3928 if (tableClassList.length !== 0 && editor.queryCommandSupported('mceTableToggleClass')) { 3929 editor.ui.registry.addNestedMenuItem('tableclass', { 3930 icon: 'table-classes', 3931 text: 'Table styles', 3932 getSubmenuItems: () => buildMenuItems(editor, tableClassList, 'tableclass', (value) => editor.execCommand('mceTableToggleClass', false, value)), 3933 onSetup: selectionTargets.onSetupTable 3934 }); 3935 } 3936 const tableCellClassList = filterNoneItem(getCellClassList(editor)); 3937 if (tableCellClassList.length !== 0 && editor.queryCommandSupported('mceTableCellToggleClass')) { 3938 editor.ui.registry.addNestedMenuItem('tablecellclass', { 3939 icon: 'table-cell-classes', 3940 text: 'Cell styles', 3941 getSubmenuItems: () => buildMenuItems(editor, tableCellClassList, 'tablecellclass', (value) => editor.execCommand('mceTableCellToggleClass', false, value)), 3942 onSetup: selectionTargets.onSetupCellOrRow 3943 }); 3944 } 3945 // TODO: TINY-8172 Unwind this when an alternative solution is found 3946 if (editor.queryCommandSupported('mceTableApplyCellStyle')) { 3947 editor.ui.registry.addNestedMenuItem('tablecellvalign', { 3948 icon: 'vertical-align', 3949 text: 'Vertical align', 3950 getSubmenuItems: () => buildMenuItems(editor, verticalAlignValues, 'tablecellverticalalign', applyTableCellStyle(editor, 'vertical-align')), 3951 onSetup: selectionTargets.onSetupCellOrRow 3952 }); 3953 editor.ui.registry.addNestedMenuItem('tablecellborderwidth', { 3954 icon: 'border-width', 3955 text: 'Border width', 3956 getSubmenuItems: () => buildMenuItems(editor, getTableBorderWidths(editor), 'tablecellborderwidth', applyTableCellStyle(editor, 'border-width')), 3957 onSetup: selectionTargets.onSetupCellOrRow 3958 }); 3959 editor.ui.registry.addNestedMenuItem('tablecellborderstyle', { 3960 icon: 'border-style', 3961 text: 'Border style', 3962 getSubmenuItems: () => buildMenuItems(editor, getTableBorderStyles(editor), 'tablecellborderstyle', applyTableCellStyle(editor, 'border-style')), 3963 onSetup: selectionTargets.onSetupCellOrRow 3964 }); 3965 editor.ui.registry.addNestedMenuItem('tablecellbackgroundcolor', { 3966 icon: 'cell-background-color',
3967 text: 'Background color', 3968 getSubmenuItems: () => buildColorMenu(editor, getTableBackgroundColorMap(editor), 'background-color'), 3969 onSetup: selectionTargets.onSetupCellOrRow 3970 }); 3971 editor.ui.registry.addNestedMenuItem('tablecellbordercolor', { 3972 icon: 'cell-border-color', 3973 text: 'Border color', 3974 getSubmenuItems: () => buildColorMenu(editor, getTableBorderColorMap(editor), 'border-color'), 3975 onSetup: selectionTargets.onSetupCellOrRow 3976 }); 3977 } 3978 addToggleMenuIfRegistered('tablecaption', { 3979 icon: 'table-caption', 3980 text: 'Table caption', 3981 command: 'mceTableToggleCaption', 3982 onSetup: selectionTargets.onSetupTableWithCaption 3983 }); 3984 addToggleMenuIfRegistered('tablerowheader', { 3985 text: 'Row header', 3986 icon: 'table-top-header', 3987 command: 'mceTableRowType', 3988 onAction: changeRowHeader(editor), 3989 onSetup: selectionTargets.onSetupTableRowHeaders 3990 }); 3991 addToggleMenuIfRegistered('tablecolheader', { 3992 text: 'Column header', 3993 icon: 'table-left-header', 3994 command: 'mceTableColType', 3995 onAction: changeColumnHeader(editor), 3996 onSetup: selectionTargets.onSetupTableRowHeaders 3997 }); 3998 }; 3999 4000 const Plugin = (editor) => { 4001 const selectionTargets = getSelectionTargets(editor); 4002 register(editor); 4003 registerCommands(editor); 4004 addMenuItems(editor, selectionTargets); 4005 addButtons(editor, selectionTargets); 4006 addToolbars(editor); 4007 }; 4008 var Plugin$1 = () => { 4009 global$3.add('table', Plugin); 4010 }; 4011 4012 Plugin$1(); 4013 /** ***** 4014 * DO NOT EXPORT ANYTHING 4015 * 4016 * IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE 4017 *******/ 4018 4019})();
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.