[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /******/ (() => { // webpackBootstrap 2 /******/ "use strict"; 3 /******/ // The require scope 4 /******/ var __webpack_require__ = {}; 5 /******/ 6 /************************************************************************/ 7 /******/ /* webpack/runtime/compat get default export */ 8 /******/ (() => { 9 /******/ // getDefaultExport function for compatibility with non-harmony modules 10 /******/ __webpack_require__.n = (module) => { 11 /******/ var getter = module && module.__esModule ? 12 /******/ () => (module['default']) : 13 /******/ () => (module); 14 /******/ __webpack_require__.d(getter, { a: getter }); 15 /******/ return getter; 16 /******/ }; 17 /******/ })(); 18 /******/ 19 /******/ /* webpack/runtime/define property getters */ 20 /******/ (() => { 21 /******/ // define getter functions for harmony exports 22 /******/ __webpack_require__.d = (exports, definition) => { 23 /******/ for(var key in definition) { 24 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 25 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 26 /******/ } 27 /******/ } 28 /******/ }; 29 /******/ })(); 30 /******/ 31 /******/ /* webpack/runtime/hasOwnProperty shorthand */ 32 /******/ (() => { 33 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 34 /******/ })(); 35 /******/ 36 /******/ /* webpack/runtime/make namespace object */ 37 /******/ (() => { 38 /******/ // define __esModule on exports 39 /******/ __webpack_require__.r = (exports) => { 40 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 41 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 42 /******/ } 43 /******/ Object.defineProperty(exports, '__esModule', { value: true }); 44 /******/ }; 45 /******/ })(); 46 /******/ 47 /************************************************************************/ 48 var __webpack_exports__ = {}; 49 // ESM COMPAT FLAG 50 __webpack_require__.r(__webpack_exports__); 51 52 // EXPORTS 53 __webpack_require__.d(__webpack_exports__, { 54 RichTextData: () => (/* reexport */ RichTextData), 55 __experimentalRichText: () => (/* reexport */ __experimentalRichText), 56 __unstableCreateElement: () => (/* reexport */ createElement), 57 __unstableToDom: () => (/* reexport */ toDom), 58 __unstableUseRichText: () => (/* reexport */ useRichText), 59 applyFormat: () => (/* reexport */ applyFormat), 60 concat: () => (/* reexport */ concat), 61 create: () => (/* reexport */ create), 62 getActiveFormat: () => (/* reexport */ getActiveFormat), 63 getActiveFormats: () => (/* reexport */ getActiveFormats), 64 getActiveObject: () => (/* reexport */ getActiveObject), 65 getTextContent: () => (/* reexport */ getTextContent), 66 insert: () => (/* reexport */ insert), 67 insertObject: () => (/* reexport */ insertObject), 68 isCollapsed: () => (/* reexport */ isCollapsed), 69 isEmpty: () => (/* reexport */ isEmpty), 70 join: () => (/* reexport */ join), 71 registerFormatType: () => (/* reexport */ registerFormatType), 72 remove: () => (/* reexport */ remove_remove), 73 removeFormat: () => (/* reexport */ removeFormat), 74 replace: () => (/* reexport */ replace_replace), 75 slice: () => (/* reexport */ slice), 76 split: () => (/* reexport */ split), 77 store: () => (/* reexport */ store), 78 toHTMLString: () => (/* reexport */ toHTMLString), 79 toggleFormat: () => (/* reexport */ toggleFormat), 80 unregisterFormatType: () => (/* reexport */ unregisterFormatType), 81 useAnchor: () => (/* reexport */ useAnchor), 82 useAnchorRef: () => (/* reexport */ useAnchorRef) 83 }); 84 85 // NAMESPACE OBJECT: ./node_modules/@wordpress/rich-text/build-module/store/selectors.js 86 var selectors_namespaceObject = {}; 87 __webpack_require__.r(selectors_namespaceObject); 88 __webpack_require__.d(selectors_namespaceObject, { 89 getFormatType: () => (getFormatType), 90 getFormatTypeForBareElement: () => (getFormatTypeForBareElement), 91 getFormatTypeForClassName: () => (getFormatTypeForClassName), 92 getFormatTypes: () => (getFormatTypes) 93 }); 94 95 // NAMESPACE OBJECT: ./node_modules/@wordpress/rich-text/build-module/store/actions.js 96 var actions_namespaceObject = {}; 97 __webpack_require__.r(actions_namespaceObject); 98 __webpack_require__.d(actions_namespaceObject, { 99 addFormatTypes: () => (addFormatTypes), 100 removeFormatTypes: () => (removeFormatTypes) 101 }); 102 103 ;// CONCATENATED MODULE: external ["wp","data"] 104 const external_wp_data_namespaceObject = window["wp"]["data"]; 105 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/reducer.js 106 /** 107 * WordPress dependencies 108 */ 109 110 111 /** 112 * Reducer managing the format types 113 * 114 * @param {Object} state Current state. 115 * @param {Object} action Dispatched action. 116 * 117 * @return {Object} Updated state. 118 */ 119 function formatTypes(state = {}, action) { 120 switch (action.type) { 121 case 'ADD_FORMAT_TYPES': 122 return { 123 ...state, 124 // Key format types by their name. 125 ...action.formatTypes.reduce((newFormatTypes, type) => ({ 126 ...newFormatTypes, 127 [type.name]: type 128 }), {}) 129 }; 130 case 'REMOVE_FORMAT_TYPES': 131 return Object.fromEntries(Object.entries(state).filter(([key]) => !action.names.includes(key))); 132 } 133 return state; 134 } 135 /* harmony default export */ const reducer = ((0,external_wp_data_namespaceObject.combineReducers)({ 136 formatTypes 137 })); 138 139 ;// CONCATENATED MODULE: ./node_modules/rememo/rememo.js 140 141 142 /** @typedef {(...args: any[]) => *[]} GetDependants */ 143 144 /** @typedef {() => void} Clear */ 145 146 /** 147 * @typedef {{ 148 * getDependants: GetDependants, 149 * clear: Clear 150 * }} EnhancedSelector 151 */ 152 153 /** 154 * Internal cache entry. 155 * 156 * @typedef CacheNode 157 * 158 * @property {?CacheNode|undefined} [prev] Previous node. 159 * @property {?CacheNode|undefined} [next] Next node. 160 * @property {*[]} args Function arguments for cache entry. 161 * @property {*} val Function result. 162 */ 163 164 /** 165 * @typedef Cache 166 * 167 * @property {Clear} clear Function to clear cache. 168 * @property {boolean} [isUniqueByDependants] Whether dependants are valid in 169 * considering cache uniqueness. A cache is unique if dependents are all arrays 170 * or objects. 171 * @property {CacheNode?} [head] Cache head. 172 * @property {*[]} [lastDependants] Dependants from previous invocation. 173 */ 174 175 /** 176 * Arbitrary value used as key for referencing cache object in WeakMap tree. 177 * 178 * @type {{}} 179 */ 180 var LEAF_KEY = {}; 181 182 /** 183 * Returns the first argument as the sole entry in an array. 184 * 185 * @template T 186 * 187 * @param {T} value Value to return. 188 * 189 * @return {[T]} Value returned as entry in array. 190 */ 191 function arrayOf(value) { 192 return [value]; 193 } 194 195 /** 196 * Returns true if the value passed is object-like, or false otherwise. A value 197 * is object-like if it can support property assignment, e.g. object or array. 198 * 199 * @param {*} value Value to test. 200 * 201 * @return {boolean} Whether value is object-like. 202 */ 203 function isObjectLike(value) { 204 return !!value && 'object' === typeof value; 205 } 206 207 /** 208 * Creates and returns a new cache object. 209 * 210 * @return {Cache} Cache object. 211 */ 212 function createCache() { 213 /** @type {Cache} */ 214 var cache = { 215 clear: function () { 216 cache.head = null; 217 }, 218 }; 219 220 return cache; 221 } 222 223 /** 224 * Returns true if entries within the two arrays are strictly equal by 225 * reference from a starting index. 226 * 227 * @param {*[]} a First array. 228 * @param {*[]} b Second array. 229 * @param {number} fromIndex Index from which to start comparison. 230 * 231 * @return {boolean} Whether arrays are shallowly equal. 232 */ 233 function isShallowEqual(a, b, fromIndex) { 234 var i; 235 236 if (a.length !== b.length) { 237 return false; 238 } 239 240 for (i = fromIndex; i < a.length; i++) { 241 if (a[i] !== b[i]) { 242 return false; 243 } 244 } 245 246 return true; 247 } 248 249 /** 250 * Returns a memoized selector function. The getDependants function argument is 251 * called before the memoized selector and is expected to return an immutable 252 * reference or array of references on which the selector depends for computing 253 * its own return value. The memoize cache is preserved only as long as those 254 * dependant references remain the same. If getDependants returns a different 255 * reference(s), the cache is cleared and the selector value regenerated. 256 * 257 * @template {(...args: *[]) => *} S 258 * 259 * @param {S} selector Selector function. 260 * @param {GetDependants=} getDependants Dependant getter returning an array of 261 * references used in cache bust consideration. 262 */ 263 /* harmony default export */ function rememo(selector, getDependants) { 264 /** @type {WeakMap<*,*>} */ 265 var rootCache; 266 267 /** @type {GetDependants} */ 268 var normalizedGetDependants = getDependants ? getDependants : arrayOf; 269 270 /** 271 * Returns the cache for a given dependants array. When possible, a WeakMap 272 * will be used to create a unique cache for each set of dependants. This 273 * is feasible due to the nature of WeakMap in allowing garbage collection 274 * to occur on entries where the key object is no longer referenced. Since 275 * WeakMap requires the key to be an object, this is only possible when the 276 * dependant is object-like. The root cache is created as a hierarchy where 277 * each top-level key is the first entry in a dependants set, the value a 278 * WeakMap where each key is the next dependant, and so on. This continues 279 * so long as the dependants are object-like. If no dependants are object- 280 * like, then the cache is shared across all invocations. 281 * 282 * @see isObjectLike 283 * 284 * @param {*[]} dependants Selector dependants. 285 * 286 * @return {Cache} Cache object. 287 */ 288 function getCache(dependants) { 289 var caches = rootCache, 290 isUniqueByDependants = true, 291 i, 292 dependant, 293 map, 294 cache; 295 296 for (i = 0; i < dependants.length; i++) { 297 dependant = dependants[i]; 298 299 // Can only compose WeakMap from object-like key. 300 if (!isObjectLike(dependant)) { 301 isUniqueByDependants = false; 302 break; 303 } 304 305 // Does current segment of cache already have a WeakMap? 306 if (caches.has(dependant)) { 307 // Traverse into nested WeakMap. 308 caches = caches.get(dependant); 309 } else { 310 // Create, set, and traverse into a new one. 311 map = new WeakMap(); 312 caches.set(dependant, map); 313 caches = map; 314 } 315 } 316 317 // We use an arbitrary (but consistent) object as key for the last item 318 // in the WeakMap to serve as our running cache. 319 if (!caches.has(LEAF_KEY)) { 320 cache = createCache(); 321 cache.isUniqueByDependants = isUniqueByDependants; 322 caches.set(LEAF_KEY, cache); 323 } 324 325 return caches.get(LEAF_KEY); 326 } 327 328 /** 329 * Resets root memoization cache. 330 */ 331 function clear() { 332 rootCache = new WeakMap(); 333 } 334 335 /* eslint-disable jsdoc/check-param-names */ 336 /** 337 * The augmented selector call, considering first whether dependants have 338 * changed before passing it to underlying memoize function. 339 * 340 * @param {*} source Source object for derivation. 341 * @param {...*} extraArgs Additional arguments to pass to selector. 342 * 343 * @return {*} Selector result. 344 */ 345 /* eslint-enable jsdoc/check-param-names */ 346 function callSelector(/* source, ...extraArgs */) { 347 var len = arguments.length, 348 cache, 349 node, 350 i, 351 args, 352 dependants; 353 354 // Create copy of arguments (avoid leaking deoptimization). 355 args = new Array(len); 356 for (i = 0; i < len; i++) { 357 args[i] = arguments[i]; 358 } 359 360 dependants = normalizedGetDependants.apply(null, args); 361 cache = getCache(dependants); 362 363 // If not guaranteed uniqueness by dependants (primitive type), shallow 364 // compare against last dependants and, if references have changed, 365 // destroy cache to recalculate result. 366 if (!cache.isUniqueByDependants) { 367 if ( 368 cache.lastDependants && 369 !isShallowEqual(dependants, cache.lastDependants, 0) 370 ) { 371 cache.clear(); 372 } 373 374 cache.lastDependants = dependants; 375 } 376 377 node = cache.head; 378 while (node) { 379 // Check whether node arguments match arguments 380 if (!isShallowEqual(node.args, args, 1)) { 381 node = node.next; 382 continue; 383 } 384 385 // At this point we can assume we've found a match 386 387 // Surface matched node to head if not already 388 if (node !== cache.head) { 389 // Adjust siblings to point to each other. 390 /** @type {CacheNode} */ (node.prev).next = node.next; 391 if (node.next) { 392 node.next.prev = node.prev; 393 } 394 395 node.next = cache.head; 396 node.prev = null; 397 /** @type {CacheNode} */ (cache.head).prev = node; 398 cache.head = node; 399 } 400 401 // Return immediately 402 return node.val; 403 } 404 405 // No cached value found. Continue to insertion phase: 406 407 node = /** @type {CacheNode} */ ({ 408 // Generate the result from original function 409 val: selector.apply(null, args), 410 }); 411 412 // Avoid including the source object in the cache. 413 args[0] = null; 414 node.args = args; 415 416 // Don't need to check whether node is already head, since it would 417 // have been returned above already if it was 418 419 // Shift existing head down list 420 if (cache.head) { 421 cache.head.prev = node; 422 node.next = cache.head; 423 } 424 425 cache.head = node; 426 427 return node.val; 428 } 429 430 callSelector.getDependants = normalizedGetDependants; 431 callSelector.clear = clear; 432 clear(); 433 434 return /** @type {S & EnhancedSelector} */ (callSelector); 435 } 436 437 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/selectors.js 438 /** 439 * External dependencies 440 */ 441 442 443 /** 444 * Returns all the available format types. 445 * 446 * @param {Object} state Data state. 447 * 448 * @example 449 * ```js 450 * import { __, sprintf } from '@wordpress/i18n'; 451 * import { store as richTextStore } from '@wordpress/rich-text'; 452 * import { useSelect } from '@wordpress/data'; 453 * 454 * const ExampleComponent = () => { 455 * const { getFormatTypes } = useSelect( 456 * ( select ) => select( richTextStore ), 457 * [] 458 * ); 459 * 460 * const availableFormats = getFormatTypes(); 461 * 462 * return availableFormats ? ( 463 * <ul> 464 * { availableFormats?.map( ( format ) => ( 465 * <li>{ format.name }</li> 466 * ) ) } 467 * </ul> 468 * ) : ( 469 * __( 'No Formats available' ) 470 * ); 471 * }; 472 * ``` 473 * 474 * @return {Array} Format types. 475 */ 476 const getFormatTypes = rememo(state => Object.values(state.formatTypes), state => [state.formatTypes]); 477 478 /** 479 * Returns a format type by name. 480 * 481 * @param {Object} state Data state. 482 * @param {string} name Format type name. 483 * 484 * @example 485 * ```js 486 * import { __, sprintf } from '@wordpress/i18n'; 487 * import { store as richTextStore } from '@wordpress/rich-text'; 488 * import { useSelect } from '@wordpress/data'; 489 * 490 * const ExampleComponent = () => { 491 * const { getFormatType } = useSelect( 492 * ( select ) => select( richTextStore ), 493 * [] 494 * ); 495 * 496 * const boldFormat = getFormatType( 'core/bold' ); 497 * 498 * return boldFormat ? ( 499 * <ul> 500 * { Object.entries( boldFormat )?.map( ( [ key, value ] ) => ( 501 * <li> 502 * { key } : { value } 503 * </li> 504 * ) ) } 505 * </ul> 506 * ) : ( 507 * __( 'Not Found' ) 508 * ; 509 * }; 510 * ``` 511 * 512 * @return {Object?} Format type. 513 */ 514 function getFormatType(state, name) { 515 return state.formatTypes[name]; 516 } 517 518 /** 519 * Gets the format type, if any, that can handle a bare element (without a 520 * data-format-type attribute), given the tag name of this element. 521 * 522 * @param {Object} state Data state. 523 * @param {string} bareElementTagName The tag name of the element to find a 524 * format type for. 525 * 526 * @example 527 * ```js 528 * import { __, sprintf } from '@wordpress/i18n'; 529 * import { store as richTextStore } from '@wordpress/rich-text'; 530 * import { useSelect } from '@wordpress/data'; 531 * 532 * const ExampleComponent = () => { 533 * const { getFormatTypeForBareElement } = useSelect( 534 * ( select ) => select( richTextStore ), 535 * [] 536 * ); 537 * 538 * const format = getFormatTypeForBareElement( 'strong' ); 539 * 540 * return format && <p>{ sprintf( __( 'Format name: %s' ), format.name ) }</p>; 541 * } 542 * ``` 543 * 544 * @return {?Object} Format type. 545 */ 546 function getFormatTypeForBareElement(state, bareElementTagName) { 547 const formatTypes = getFormatTypes(state); 548 return formatTypes.find(({ 549 className, 550 tagName 551 }) => { 552 return className === null && bareElementTagName === tagName; 553 }) || formatTypes.find(({ 554 className, 555 tagName 556 }) => { 557 return className === null && '*' === tagName; 558 }); 559 } 560 561 /** 562 * Gets the format type, if any, that can handle an element, given its classes. 563 * 564 * @param {Object} state Data state. 565 * @param {string} elementClassName The classes of the element to find a format 566 * type for. 567 * 568 * @example 569 * ```js 570 * import { __, sprintf } from '@wordpress/i18n'; 571 * import { store as richTextStore } from '@wordpress/rich-text'; 572 * import { useSelect } from '@wordpress/data'; 573 * 574 * const ExampleComponent = () => { 575 * const { getFormatTypeForClassName } = useSelect( 576 * ( select ) => select( richTextStore ), 577 * [] 578 * ); 579 * 580 * const format = getFormatTypeForClassName( 'has-inline-color' ); 581 * 582 * return format && <p>{ sprintf( __( 'Format name: %s' ), format.name ) }</p>; 583 * }; 584 * ``` 585 * 586 * @return {?Object} Format type. 587 */ 588 function getFormatTypeForClassName(state, elementClassName) { 589 return getFormatTypes(state).find(({ 590 className 591 }) => { 592 if (className === null) { 593 return false; 594 } 595 return ` $elementClassName} `.indexOf(` $className} `) >= 0; 596 }); 597 } 598 599 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/actions.js 600 /** 601 * Returns an action object used in signalling that format types have been 602 * added. 603 * Ignored from documentation as registerFormatType should be used instead from @wordpress/rich-text 604 * 605 * @ignore 606 * 607 * @param {Array|Object} formatTypes Format types received. 608 * 609 * @return {Object} Action object. 610 */ 611 function addFormatTypes(formatTypes) { 612 return { 613 type: 'ADD_FORMAT_TYPES', 614 formatTypes: Array.isArray(formatTypes) ? formatTypes : [formatTypes] 615 }; 616 } 617 618 /** 619 * Returns an action object used to remove a registered format type. 620 * 621 * Ignored from documentation as unregisterFormatType should be used instead from @wordpress/rich-text 622 * 623 * @ignore 624 * 625 * @param {string|Array} names Format name. 626 * 627 * @return {Object} Action object. 628 */ 629 function removeFormatTypes(names) { 630 return { 631 type: 'REMOVE_FORMAT_TYPES', 632 names: Array.isArray(names) ? names : [names] 633 }; 634 } 635 636 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/index.js 637 /** 638 * WordPress dependencies 639 */ 640 641 642 /** 643 * Internal dependencies 644 */ 645 646 647 648 const STORE_NAME = 'core/rich-text'; 649 650 /** 651 * Store definition for the rich-text namespace. 652 * 653 * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore 654 * 655 * @type {Object} 656 */ 657 const store = (0,external_wp_data_namespaceObject.createReduxStore)(STORE_NAME, { 658 reducer: reducer, 659 selectors: selectors_namespaceObject, 660 actions: actions_namespaceObject 661 }); 662 (0,external_wp_data_namespaceObject.register)(store); 663 664 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-format-equal.js 665 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 666 667 /** 668 * Optimised equality check for format objects. 669 * 670 * @param {?RichTextFormat} format1 Format to compare. 671 * @param {?RichTextFormat} format2 Format to compare. 672 * 673 * @return {boolean} True if formats are equal, false if not. 674 */ 675 function isFormatEqual(format1, format2) { 676 // Both not defined. 677 if (format1 === format2) { 678 return true; 679 } 680 681 // Either not defined. 682 if (!format1 || !format2) { 683 return false; 684 } 685 if (format1.type !== format2.type) { 686 return false; 687 } 688 const attributes1 = format1.attributes; 689 const attributes2 = format2.attributes; 690 691 // Both not defined. 692 if (attributes1 === attributes2) { 693 return true; 694 } 695 696 // Either not defined. 697 if (!attributes1 || !attributes2) { 698 return false; 699 } 700 const keys1 = Object.keys(attributes1); 701 const keys2 = Object.keys(attributes2); 702 if (keys1.length !== keys2.length) { 703 return false; 704 } 705 const length = keys1.length; 706 707 // Optimise for speed. 708 for (let i = 0; i < length; i++) { 709 const name = keys1[i]; 710 if (attributes1[name] !== attributes2[name]) { 711 return false; 712 } 713 } 714 return true; 715 } 716 717 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/normalise-formats.js 718 /** 719 * Internal dependencies 720 */ 721 722 723 724 /** @typedef {import('./types').RichTextValue} RichTextValue */ 725 726 /** 727 * Normalises formats: ensures subsequent adjacent equal formats have the same 728 * reference. 729 * 730 * @param {RichTextValue} value Value to normalise formats of. 731 * 732 * @return {RichTextValue} New value with normalised formats. 733 */ 734 function normaliseFormats(value) { 735 const newFormats = value.formats.slice(); 736 newFormats.forEach((formatsAtIndex, index) => { 737 const formatsAtPreviousIndex = newFormats[index - 1]; 738 if (formatsAtPreviousIndex) { 739 const newFormatsAtIndex = formatsAtIndex.slice(); 740 newFormatsAtIndex.forEach((format, formatIndex) => { 741 const previousFormat = formatsAtPreviousIndex[formatIndex]; 742 if (isFormatEqual(format, previousFormat)) { 743 newFormatsAtIndex[formatIndex] = previousFormat; 744 } 745 }); 746 newFormats[index] = newFormatsAtIndex; 747 } 748 }); 749 return { 750 ...value, 751 formats: newFormats 752 }; 753 } 754 755 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/apply-format.js 756 /** 757 * Internal dependencies 758 */ 759 760 761 762 /** @typedef {import('./types').RichTextValue} RichTextValue */ 763 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 764 765 function replace(array, index, value) { 766 array = array.slice(); 767 array[index] = value; 768 return array; 769 } 770 771 /** 772 * Apply a format object to a Rich Text value from the given `startIndex` to the 773 * given `endIndex`. Indices are retrieved from the selection if none are 774 * provided. 775 * 776 * @param {RichTextValue} value Value to modify. 777 * @param {RichTextFormat} format Format to apply. 778 * @param {number} [startIndex] Start index. 779 * @param {number} [endIndex] End index. 780 * 781 * @return {RichTextValue} A new value with the format applied. 782 */ 783 function applyFormat(value, format, startIndex = value.start, endIndex = value.end) { 784 const { 785 formats, 786 activeFormats 787 } = value; 788 const newFormats = formats.slice(); 789 790 // The selection is collapsed. 791 if (startIndex === endIndex) { 792 const startFormat = newFormats[startIndex]?.find(({ 793 type 794 }) => type === format.type); 795 796 // If the caret is at a format of the same type, expand start and end to 797 // the edges of the format. This is useful to apply new attributes. 798 if (startFormat) { 799 const index = newFormats[startIndex].indexOf(startFormat); 800 while (newFormats[startIndex] && newFormats[startIndex][index] === startFormat) { 801 newFormats[startIndex] = replace(newFormats[startIndex], index, format); 802 startIndex--; 803 } 804 endIndex++; 805 while (newFormats[endIndex] && newFormats[endIndex][index] === startFormat) { 806 newFormats[endIndex] = replace(newFormats[endIndex], index, format); 807 endIndex++; 808 } 809 } 810 } else { 811 // Determine the highest position the new format can be inserted at. 812 let position = +Infinity; 813 for (let index = startIndex; index < endIndex; index++) { 814 if (newFormats[index]) { 815 newFormats[index] = newFormats[index].filter(({ 816 type 817 }) => type !== format.type); 818 const length = newFormats[index].length; 819 if (length < position) { 820 position = length; 821 } 822 } else { 823 newFormats[index] = []; 824 position = 0; 825 } 826 } 827 for (let index = startIndex; index < endIndex; index++) { 828 newFormats[index].splice(position, 0, format); 829 } 830 } 831 return normaliseFormats({ 832 ...value, 833 formats: newFormats, 834 // Always revise active formats. This serves as a placeholder for new 835 // inputs with the format so new input appears with the format applied, 836 // and ensures a format of the same type uses the latest values. 837 activeFormats: [...(activeFormats?.filter(({ 838 type 839 }) => type !== format.type) || []), format] 840 }); 841 } 842 843 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/create-element.js 844 /** 845 * Parse the given HTML into a body element. 846 * 847 * Note: The current implementation will return a shared reference, reset on 848 * each call to `createElement`. Therefore, you should not hold a reference to 849 * the value to operate upon asynchronously, as it may have unexpected results. 850 * 851 * @param {HTMLDocument} document The HTML document to use to parse. 852 * @param {string} html The HTML to parse. 853 * 854 * @return {HTMLBodyElement} Body element with parsed HTML. 855 */ 856 function createElement({ 857 implementation 858 }, html) { 859 // Because `createHTMLDocument` is an expensive operation, and with this 860 // function being internal to `rich-text` (full control in avoiding a risk 861 // of asynchronous operations on the shared reference), a single document 862 // is reused and reset for each call to the function. 863 if (!createElement.body) { 864 createElement.body = implementation.createHTMLDocument('').body; 865 } 866 createElement.body.innerHTML = html; 867 return createElement.body; 868 } 869 870 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/special-characters.js 871 /** 872 * Object replacement character, used as a placeholder for objects. 873 */ 874 const OBJECT_REPLACEMENT_CHARACTER = '\ufffc'; 875 876 /** 877 * Zero width non-breaking space, used as padding in the editable DOM tree when 878 * it is empty otherwise. 879 */ 880 const ZWNBSP = '\ufeff'; 881 882 ;// CONCATENATED MODULE: external ["wp","escapeHtml"] 883 const external_wp_escapeHtml_namespaceObject = window["wp"]["escapeHtml"]; 884 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-active-formats.js 885 /** @typedef {import('./types').RichTextValue} RichTextValue */ 886 /** @typedef {import('./types').RichTextFormatList} RichTextFormatList */ 887 888 /** 889 * Internal dependencies 890 */ 891 892 893 /** 894 * Gets the all format objects at the start of the selection. 895 * 896 * @param {RichTextValue} value Value to inspect. 897 * @param {Array} EMPTY_ACTIVE_FORMATS Array to return if there are no 898 * active formats. 899 * 900 * @return {RichTextFormatList} Active format objects. 901 */ 902 function getActiveFormats(value, EMPTY_ACTIVE_FORMATS = []) { 903 const { 904 formats, 905 start, 906 end, 907 activeFormats 908 } = value; 909 if (start === undefined) { 910 return EMPTY_ACTIVE_FORMATS; 911 } 912 if (start === end) { 913 // For a collapsed caret, it is possible to override the active formats. 914 if (activeFormats) { 915 return activeFormats; 916 } 917 const formatsBefore = formats[start - 1] || EMPTY_ACTIVE_FORMATS; 918 const formatsAfter = formats[start] || EMPTY_ACTIVE_FORMATS; 919 920 // By default, select the lowest amount of formats possible (which means 921 // the caret is positioned outside the format boundary). The user can 922 // then use arrow keys to define `activeFormats`. 923 if (formatsBefore.length < formatsAfter.length) { 924 return formatsBefore; 925 } 926 return formatsAfter; 927 } 928 929 // If there's no formats at the start index, there are not active formats. 930 if (!formats[start]) { 931 return EMPTY_ACTIVE_FORMATS; 932 } 933 const selectedFormats = formats.slice(start, end); 934 935 // Clone the formats so we're not mutating the live value. 936 const _activeFormats = [...selectedFormats[0]]; 937 let i = selectedFormats.length; 938 939 // For performance reasons, start from the end where it's much quicker to 940 // realise that there are no active formats. 941 while (i--) { 942 const formatsAtIndex = selectedFormats[i]; 943 944 // If we run into any index without formats, we're sure that there's no 945 // active formats. 946 if (!formatsAtIndex) { 947 return EMPTY_ACTIVE_FORMATS; 948 } 949 let ii = _activeFormats.length; 950 951 // Loop over the active formats and remove any that are not present at 952 // the current index. 953 while (ii--) { 954 const format = _activeFormats[ii]; 955 if (!formatsAtIndex.find(_format => isFormatEqual(format, _format))) { 956 _activeFormats.splice(ii, 1); 957 } 958 } 959 960 // If there are no active formats, we can stop. 961 if (_activeFormats.length === 0) { 962 return EMPTY_ACTIVE_FORMATS; 963 } 964 } 965 return _activeFormats || EMPTY_ACTIVE_FORMATS; 966 } 967 968 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-format-type.js 969 /** 970 * WordPress dependencies 971 */ 972 973 /** 974 * Internal dependencies 975 */ 976 977 978 /** @typedef {import('./register-format-type').RichTextFormatType} RichTextFormatType */ 979 980 /** 981 * Returns a registered format type. 982 * 983 * @param {string} name Format name. 984 * 985 * @return {RichTextFormatType|undefined} Format type. 986 */ 987 function get_format_type_getFormatType(name) { 988 return (0,external_wp_data_namespaceObject.select)(store).getFormatType(name); 989 } 990 991 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/to-tree.js 992 /** 993 * Internal dependencies 994 */ 995 996 997 998 999 function restoreOnAttributes(attributes, isEditableTree) { 1000 if (isEditableTree) { 1001 return attributes; 1002 } 1003 const newAttributes = {}; 1004 for (const key in attributes) { 1005 let newKey = key; 1006 if (key.startsWith('data-disable-rich-text-')) { 1007 newKey = key.slice('data-disable-rich-text-'.length); 1008 } 1009 newAttributes[newKey] = attributes[key]; 1010 } 1011 return newAttributes; 1012 } 1013 1014 /** 1015 * Converts a format object to information that can be used to create an element 1016 * from (type, attributes and object). 1017 * 1018 * @param {Object} $1 Named parameters. 1019 * @param {string} $1.type The format type. 1020 * @param {string} $1.tagName The tag name. 1021 * @param {Object} $1.attributes The format attributes. 1022 * @param {Object} $1.unregisteredAttributes The unregistered format 1023 * attributes. 1024 * @param {boolean} $1.object Whether or not it is an object 1025 * format. 1026 * @param {boolean} $1.boundaryClass Whether or not to apply a boundary 1027 * class. 1028 * @param {boolean} $1.isEditableTree 1029 * 1030 * @return {Object} Information to be used for element creation. 1031 */ 1032 function fromFormat({ 1033 type, 1034 tagName, 1035 attributes, 1036 unregisteredAttributes, 1037 object, 1038 boundaryClass, 1039 isEditableTree 1040 }) { 1041 const formatType = get_format_type_getFormatType(type); 1042 let elementAttributes = {}; 1043 if (boundaryClass && isEditableTree) { 1044 elementAttributes['data-rich-text-format-boundary'] = 'true'; 1045 } 1046 if (!formatType) { 1047 if (attributes) { 1048 elementAttributes = { 1049 ...attributes, 1050 ...elementAttributes 1051 }; 1052 } 1053 return { 1054 type, 1055 attributes: restoreOnAttributes(elementAttributes, isEditableTree), 1056 object 1057 }; 1058 } 1059 elementAttributes = { 1060 ...unregisteredAttributes, 1061 ...elementAttributes 1062 }; 1063 for (const name in attributes) { 1064 const key = formatType.attributes ? formatType.attributes[name] : false; 1065 if (key) { 1066 elementAttributes[key] = attributes[name]; 1067 } else { 1068 elementAttributes[name] = attributes[name]; 1069 } 1070 } 1071 if (formatType.className) { 1072 if (elementAttributes.class) { 1073 elementAttributes.class = `$formatType.className} $elementAttributes.class}`; 1074 } else { 1075 elementAttributes.class = formatType.className; 1076 } 1077 } 1078 1079 // When a format is declared as non editable, make it non editable in the 1080 // editor. 1081 if (isEditableTree && formatType.contentEditable === false) { 1082 elementAttributes.contenteditable = 'false'; 1083 } 1084 return { 1085 type: tagName || formatType.tagName, 1086 object: formatType.object, 1087 attributes: restoreOnAttributes(elementAttributes, isEditableTree) 1088 }; 1089 } 1090 1091 /** 1092 * Checks if both arrays of formats up until a certain index are equal. 1093 * 1094 * @param {Array} a Array of formats to compare. 1095 * @param {Array} b Array of formats to compare. 1096 * @param {number} index Index to check until. 1097 */ 1098 function isEqualUntil(a, b, index) { 1099 do { 1100 if (a[index] !== b[index]) { 1101 return false; 1102 } 1103 } while (index--); 1104 return true; 1105 } 1106 function toTree({ 1107 value, 1108 preserveWhiteSpace, 1109 createEmpty, 1110 append, 1111 getLastChild, 1112 getParent, 1113 isText, 1114 getText, 1115 remove, 1116 appendText, 1117 onStartIndex, 1118 onEndIndex, 1119 isEditableTree, 1120 placeholder 1121 }) { 1122 const { 1123 formats, 1124 replacements, 1125 text, 1126 start, 1127 end 1128 } = value; 1129 const formatsLength = formats.length + 1; 1130 const tree = createEmpty(); 1131 const activeFormats = getActiveFormats(value); 1132 const deepestActiveFormat = activeFormats[activeFormats.length - 1]; 1133 let lastCharacterFormats; 1134 let lastCharacter; 1135 append(tree, ''); 1136 for (let i = 0; i < formatsLength; i++) { 1137 const character = text.charAt(i); 1138 const shouldInsertPadding = isEditableTree && ( 1139 // Pad the line if the line is empty. 1140 !lastCharacter || 1141 // Pad the line if the previous character is a line break, otherwise 1142 // the line break won't be visible. 1143 lastCharacter === '\n'); 1144 const characterFormats = formats[i]; 1145 let pointer = getLastChild(tree); 1146 if (characterFormats) { 1147 characterFormats.forEach((format, formatIndex) => { 1148 if (pointer && lastCharacterFormats && 1149 // Reuse the last element if all formats remain the same. 1150 isEqualUntil(characterFormats, lastCharacterFormats, formatIndex)) { 1151 pointer = getLastChild(pointer); 1152 return; 1153 } 1154 const { 1155 type, 1156 tagName, 1157 attributes, 1158 unregisteredAttributes 1159 } = format; 1160 const boundaryClass = isEditableTree && format === deepestActiveFormat; 1161 const parent = getParent(pointer); 1162 const newNode = append(parent, fromFormat({ 1163 type, 1164 tagName, 1165 attributes, 1166 unregisteredAttributes, 1167 boundaryClass, 1168 isEditableTree 1169 })); 1170 if (isText(pointer) && getText(pointer).length === 0) { 1171 remove(pointer); 1172 } 1173 pointer = append(newNode, ''); 1174 }); 1175 } 1176 1177 // If there is selection at 0, handle it before characters are inserted. 1178 if (i === 0) { 1179 if (onStartIndex && start === 0) { 1180 onStartIndex(tree, pointer); 1181 } 1182 if (onEndIndex && end === 0) { 1183 onEndIndex(tree, pointer); 1184 } 1185 } 1186 if (character === OBJECT_REPLACEMENT_CHARACTER) { 1187 const replacement = replacements[i]; 1188 if (!replacement) continue; 1189 const { 1190 type, 1191 attributes, 1192 innerHTML 1193 } = replacement; 1194 const formatType = get_format_type_getFormatType(type); 1195 if (!isEditableTree && type === 'script') { 1196 pointer = append(getParent(pointer), fromFormat({ 1197 type: 'script', 1198 isEditableTree 1199 })); 1200 append(pointer, { 1201 html: decodeURIComponent(attributes['data-rich-text-script']) 1202 }); 1203 } else if (formatType?.contentEditable === false) { 1204 // For non editable formats, render the stored inner HTML. 1205 pointer = append(getParent(pointer), fromFormat({ 1206 ...replacement, 1207 isEditableTree, 1208 boundaryClass: start === i && end === i + 1 1209 })); 1210 if (innerHTML) { 1211 append(pointer, { 1212 html: innerHTML 1213 }); 1214 } 1215 } else { 1216 pointer = append(getParent(pointer), fromFormat({ 1217 ...replacement, 1218 object: true, 1219 isEditableTree 1220 })); 1221 } 1222 // Ensure pointer is text node. 1223 pointer = append(getParent(pointer), ''); 1224 } else if (!preserveWhiteSpace && character === '\n') { 1225 pointer = append(getParent(pointer), { 1226 type: 'br', 1227 attributes: isEditableTree ? { 1228 'data-rich-text-line-break': 'true' 1229 } : undefined, 1230 object: true 1231 }); 1232 // Ensure pointer is text node. 1233 pointer = append(getParent(pointer), ''); 1234 } else if (!isText(pointer)) { 1235 pointer = append(getParent(pointer), character); 1236 } else { 1237 appendText(pointer, character); 1238 } 1239 if (onStartIndex && start === i + 1) { 1240 onStartIndex(tree, pointer); 1241 } 1242 if (onEndIndex && end === i + 1) { 1243 onEndIndex(tree, pointer); 1244 } 1245 if (shouldInsertPadding && i === text.length) { 1246 append(getParent(pointer), ZWNBSP); 1247 if (placeholder && text.length === 0) { 1248 append(getParent(pointer), { 1249 type: 'span', 1250 attributes: { 1251 'data-rich-text-placeholder': placeholder, 1252 // Necessary to prevent the placeholder from catching 1253 // selection and being editable. 1254 style: 'pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;' 1255 } 1256 }); 1257 } 1258 } 1259 lastCharacterFormats = characterFormats; 1260 lastCharacter = character; 1261 } 1262 return tree; 1263 } 1264 1265 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/to-html-string.js 1266 /** 1267 * WordPress dependencies 1268 */ 1269 1270 1271 1272 /** 1273 * Internal dependencies 1274 */ 1275 1276 1277 1278 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1279 1280 /** 1281 * Create an HTML string from a Rich Text value. 1282 * 1283 * @param {Object} $1 Named argements. 1284 * @param {RichTextValue} $1.value Rich text value. 1285 * @param {boolean} [$1.preserveWhiteSpace] Preserves newlines if true. 1286 * 1287 * @return {string} HTML string. 1288 */ 1289 function toHTMLString({ 1290 value, 1291 preserveWhiteSpace 1292 }) { 1293 const tree = toTree({ 1294 value, 1295 preserveWhiteSpace, 1296 createEmpty, 1297 append, 1298 getLastChild, 1299 getParent, 1300 isText, 1301 getText, 1302 remove, 1303 appendText 1304 }); 1305 return createChildrenHTML(tree.children); 1306 } 1307 function createEmpty() { 1308 return {}; 1309 } 1310 function getLastChild({ 1311 children 1312 }) { 1313 return children && children[children.length - 1]; 1314 } 1315 function append(parent, object) { 1316 if (typeof object === 'string') { 1317 object = { 1318 text: object 1319 }; 1320 } 1321 object.parent = parent; 1322 parent.children = parent.children || []; 1323 parent.children.push(object); 1324 return object; 1325 } 1326 function appendText(object, text) { 1327 object.text += text; 1328 } 1329 function getParent({ 1330 parent 1331 }) { 1332 return parent; 1333 } 1334 function isText({ 1335 text 1336 }) { 1337 return typeof text === 'string'; 1338 } 1339 function getText({ 1340 text 1341 }) { 1342 return text; 1343 } 1344 function remove(object) { 1345 const index = object.parent.children.indexOf(object); 1346 if (index !== -1) { 1347 object.parent.children.splice(index, 1); 1348 } 1349 return object; 1350 } 1351 function createElementHTML({ 1352 type, 1353 attributes, 1354 object, 1355 children 1356 }) { 1357 let attributeString = ''; 1358 for (const key in attributes) { 1359 if (!(0,external_wp_escapeHtml_namespaceObject.isValidAttributeName)(key)) { 1360 continue; 1361 } 1362 attributeString += ` $key}="${(0,external_wp_escapeHtml_namespaceObject.escapeAttribute)(attributes[key])}"`; 1363 } 1364 if (object) { 1365 return `<$type}$attributeString}>`; 1366 } 1367 return `<$type}$attributeString}>$createChildrenHTML(children)}</$type}>`; 1368 } 1369 function createChildrenHTML(children = []) { 1370 return children.map(child => { 1371 if (child.html !== undefined) { 1372 return child.html; 1373 } 1374 return child.text === undefined ? createElementHTML(child) : (0,external_wp_escapeHtml_namespaceObject.escapeEditableHTML)(child.text); 1375 }).join(''); 1376 } 1377 1378 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-text-content.js 1379 /** 1380 * Internal dependencies 1381 */ 1382 1383 1384 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1385 1386 /** 1387 * Get the textual content of a Rich Text value. This is similar to 1388 * `Element.textContent`. 1389 * 1390 * @param {RichTextValue} value Value to use. 1391 * 1392 * @return {string} The text content. 1393 */ 1394 function getTextContent({ 1395 text 1396 }) { 1397 return text.replace(OBJECT_REPLACEMENT_CHARACTER, ''); 1398 } 1399 1400 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/create.js 1401 /** 1402 * WordPress dependencies 1403 */ 1404 1405 1406 /** 1407 * Internal dependencies 1408 */ 1409 1410 1411 1412 1413 1414 1415 1416 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1417 1418 function createEmptyValue() { 1419 return { 1420 formats: [], 1421 replacements: [], 1422 text: '' 1423 }; 1424 } 1425 function toFormat({ 1426 tagName, 1427 attributes 1428 }) { 1429 let formatType; 1430 if (attributes && attributes.class) { 1431 formatType = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForClassName(attributes.class); 1432 if (formatType) { 1433 // Preserve any additional classes. 1434 attributes.class = ` $attributes.class} `.replace(` $formatType.className} `, ' ').trim(); 1435 if (!attributes.class) { 1436 delete attributes.class; 1437 } 1438 } 1439 } 1440 if (!formatType) { 1441 formatType = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForBareElement(tagName); 1442 } 1443 if (!formatType) { 1444 return attributes ? { 1445 type: tagName, 1446 attributes 1447 } : { 1448 type: tagName 1449 }; 1450 } 1451 if (formatType.__experimentalCreatePrepareEditableTree && !formatType.__experimentalCreateOnChangeEditableValue) { 1452 return null; 1453 } 1454 if (!attributes) { 1455 return { 1456 formatType, 1457 type: formatType.name, 1458 tagName 1459 }; 1460 } 1461 const registeredAttributes = {}; 1462 const unregisteredAttributes = {}; 1463 const _attributes = { 1464 ...attributes 1465 }; 1466 for (const key in formatType.attributes) { 1467 const name = formatType.attributes[key]; 1468 registeredAttributes[key] = _attributes[name]; 1469 1470 // delete the attribute and what's left is considered 1471 // to be unregistered. 1472 delete _attributes[name]; 1473 if (typeof registeredAttributes[key] === 'undefined') { 1474 delete registeredAttributes[key]; 1475 } 1476 } 1477 for (const name in _attributes) { 1478 unregisteredAttributes[name] = attributes[name]; 1479 } 1480 if (formatType.contentEditable === false) { 1481 delete unregisteredAttributes.contenteditable; 1482 } 1483 return { 1484 formatType, 1485 type: formatType.name, 1486 tagName, 1487 attributes: registeredAttributes, 1488 unregisteredAttributes 1489 }; 1490 } 1491 1492 /** 1493 * The RichTextData class is used to instantiate a wrapper around rich text 1494 * values, with methods that can be used to transform or manipulate the data. 1495 * 1496 * - Create an empty instance: `new RichTextData()`. 1497 * - Create one from an HTML string: `RichTextData.fromHTMLString( 1498 * '<em>hello</em>' )`. 1499 * - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( 1500 * document.querySelector( 'p' ) )`. 1501 * - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. 1502 * - Create one from a rich text value: `new RichTextData( { text: '...', 1503 * formats: [ ... ] } )`. 1504 * 1505 * @todo Add methods to manipulate the data, such as applyFormat, slice etc. 1506 */ 1507 class RichTextData { 1508 #value; 1509 static empty() { 1510 return new RichTextData(); 1511 } 1512 static fromPlainText(text) { 1513 return new RichTextData(create({ 1514 text 1515 })); 1516 } 1517 static fromHTMLString(html) { 1518 return new RichTextData(create({ 1519 html 1520 })); 1521 } 1522 static fromHTMLElement(htmlElement, options = {}) { 1523 const { 1524 preserveWhiteSpace = false 1525 } = options; 1526 const element = preserveWhiteSpace ? htmlElement : collapseWhiteSpace(htmlElement); 1527 const richTextData = new RichTextData(create({ 1528 element 1529 })); 1530 Object.defineProperty(richTextData, 'originalHTML', { 1531 value: htmlElement.innerHTML 1532 }); 1533 return richTextData; 1534 } 1535 constructor(init = createEmptyValue()) { 1536 this.#value = init; 1537 } 1538 toPlainText() { 1539 return getTextContent(this.#value); 1540 } 1541 // We could expose `toHTMLElement` at some point as well, but we'd only use 1542 // it internally. 1543 toHTMLString({ 1544 preserveWhiteSpace 1545 } = {}) { 1546 return this.originalHTML || toHTMLString({ 1547 value: this.#value, 1548 preserveWhiteSpace 1549 }); 1550 } 1551 valueOf() { 1552 return this.toHTMLString(); 1553 } 1554 toString() { 1555 return this.toHTMLString(); 1556 } 1557 toJSON() { 1558 return this.toHTMLString(); 1559 } 1560 get length() { 1561 return this.text.length; 1562 } 1563 get formats() { 1564 return this.#value.formats; 1565 } 1566 get replacements() { 1567 return this.#value.replacements; 1568 } 1569 get text() { 1570 return this.#value.text; 1571 } 1572 } 1573 for (const name of Object.getOwnPropertyNames(String.prototype)) { 1574 if (RichTextData.prototype.hasOwnProperty(name)) { 1575 continue; 1576 } 1577 Object.defineProperty(RichTextData.prototype, name, { 1578 value(...args) { 1579 // Should we convert back to RichTextData? 1580 return this.toHTMLString()[name](...args); 1581 } 1582 }); 1583 } 1584 1585 /** 1586 * Create a RichText value from an `Element` tree (DOM), an HTML string or a 1587 * plain text string, with optionally a `Range` object to set the selection. If 1588 * called without any input, an empty value will be created. The optional 1589 * functions can be used to filter out content. 1590 * 1591 * A value will have the following shape, which you are strongly encouraged not 1592 * to modify without the use of helper functions: 1593 * 1594 * ```js 1595 * { 1596 * text: string, 1597 * formats: Array, 1598 * replacements: Array, 1599 * ?start: number, 1600 * ?end: number, 1601 * } 1602 * ``` 1603 * 1604 * As you can see, text and formatting are separated. `text` holds the text, 1605 * including any replacement characters for objects and lines. `formats`, 1606 * `objects` and `lines` are all sparse arrays of the same length as `text`. It 1607 * holds information about the formatting at the relevant text indices. Finally 1608 * `start` and `end` state which text indices are selected. They are only 1609 * provided if a `Range` was given. 1610 * 1611 * @param {Object} [$1] Optional named arguments. 1612 * @param {Element} [$1.element] Element to create value from. 1613 * @param {string} [$1.text] Text to create value from. 1614 * @param {string} [$1.html] HTML to create value from. 1615 * @param {Range} [$1.range] Range to create value from. 1616 * @param {boolean} [$1.__unstableIsEditableTree] 1617 * @return {RichTextValue} A rich text value. 1618 */ 1619 function create({ 1620 element, 1621 text, 1622 html, 1623 range, 1624 __unstableIsEditableTree: isEditableTree 1625 } = {}) { 1626 if (html instanceof RichTextData) { 1627 return { 1628 text: html.text, 1629 formats: html.formats, 1630 replacements: html.replacements 1631 }; 1632 } 1633 if (typeof text === 'string' && text.length > 0) { 1634 return { 1635 formats: Array(text.length), 1636 replacements: Array(text.length), 1637 text 1638 }; 1639 } 1640 if (typeof html === 'string' && html.length > 0) { 1641 // It does not matter which document this is, we're just using it to 1642 // parse. 1643 element = createElement(document, html); 1644 } 1645 if (typeof element !== 'object') { 1646 return createEmptyValue(); 1647 } 1648 return createFromElement({ 1649 element, 1650 range, 1651 isEditableTree 1652 }); 1653 } 1654 1655 /** 1656 * Helper to accumulate the value's selection start and end from the current 1657 * node and range. 1658 * 1659 * @param {Object} accumulator Object to accumulate into. 1660 * @param {Node} node Node to create value with. 1661 * @param {Range} range Range to create value with. 1662 * @param {Object} value Value that is being accumulated. 1663 */ 1664 function accumulateSelection(accumulator, node, range, value) { 1665 if (!range) { 1666 return; 1667 } 1668 const { 1669 parentNode 1670 } = node; 1671 const { 1672 startContainer, 1673 startOffset, 1674 endContainer, 1675 endOffset 1676 } = range; 1677 const currentLength = accumulator.text.length; 1678 1679 // Selection can be extracted from value. 1680 if (value.start !== undefined) { 1681 accumulator.start = currentLength + value.start; 1682 // Range indicates that the current node has selection. 1683 } else if (node === startContainer && node.nodeType === node.TEXT_NODE) { 1684 accumulator.start = currentLength + startOffset; 1685 // Range indicates that the current node is selected. 1686 } else if (parentNode === startContainer && node === startContainer.childNodes[startOffset]) { 1687 accumulator.start = currentLength; 1688 // Range indicates that the selection is after the current node. 1689 } else if (parentNode === startContainer && node === startContainer.childNodes[startOffset - 1]) { 1690 accumulator.start = currentLength + value.text.length; 1691 // Fallback if no child inside handled the selection. 1692 } else if (node === startContainer) { 1693 accumulator.start = currentLength; 1694 } 1695 1696 // Selection can be extracted from value. 1697 if (value.end !== undefined) { 1698 accumulator.end = currentLength + value.end; 1699 // Range indicates that the current node has selection. 1700 } else if (node === endContainer && node.nodeType === node.TEXT_NODE) { 1701 accumulator.end = currentLength + endOffset; 1702 // Range indicates that the current node is selected. 1703 } else if (parentNode === endContainer && node === endContainer.childNodes[endOffset - 1]) { 1704 accumulator.end = currentLength + value.text.length; 1705 // Range indicates that the selection is before the current node. 1706 } else if (parentNode === endContainer && node === endContainer.childNodes[endOffset]) { 1707 accumulator.end = currentLength; 1708 // Fallback if no child inside handled the selection. 1709 } else if (node === endContainer) { 1710 accumulator.end = currentLength + endOffset; 1711 } 1712 } 1713 1714 /** 1715 * Adjusts the start and end offsets from a range based on a text filter. 1716 * 1717 * @param {Node} node Node of which the text should be filtered. 1718 * @param {Range} range The range to filter. 1719 * @param {Function} filter Function to use to filter the text. 1720 * 1721 * @return {Object|void} Object containing range properties. 1722 */ 1723 function filterRange(node, range, filter) { 1724 if (!range) { 1725 return; 1726 } 1727 const { 1728 startContainer, 1729 endContainer 1730 } = range; 1731 let { 1732 startOffset, 1733 endOffset 1734 } = range; 1735 if (node === startContainer) { 1736 startOffset = filter(node.nodeValue.slice(0, startOffset)).length; 1737 } 1738 if (node === endContainer) { 1739 endOffset = filter(node.nodeValue.slice(0, endOffset)).length; 1740 } 1741 return { 1742 startContainer, 1743 startOffset, 1744 endContainer, 1745 endOffset 1746 }; 1747 } 1748 1749 /** 1750 * Collapse any whitespace used for HTML formatting to one space character, 1751 * because it will also be displayed as such by the browser. 1752 * 1753 * We need to strip it from the content because we use white-space: pre-wrap for 1754 * displaying editable rich text. Without using white-space: pre-wrap, the 1755 * browser will litter the content with non breaking spaces, among other issues. 1756 * See packages/rich-text/src/component/use-default-style.js. 1757 * 1758 * @see 1759 * https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space 1760 * 1761 * @param {HTMLElement} element 1762 * @param {boolean} isRoot 1763 * 1764 * @return {HTMLElement} New element with collapsed whitespace. 1765 */ 1766 function collapseWhiteSpace(element, isRoot = true) { 1767 const clone = element.cloneNode(true); 1768 clone.normalize(); 1769 Array.from(clone.childNodes).forEach((node, i, nodes) => { 1770 if (node.nodeType === node.TEXT_NODE) { 1771 let newNodeValue = node.nodeValue; 1772 if (/[\n\t\r\f]/.test(newNodeValue)) { 1773 newNodeValue = newNodeValue.replace(/[\n\t\r\f]+/g, ' '); 1774 } 1775 if (newNodeValue.indexOf(' ') !== -1) { 1776 newNodeValue = newNodeValue.replace(/ {2,}/g, ' '); 1777 } 1778 if (i === 0 && newNodeValue.startsWith(' ')) { 1779 newNodeValue = newNodeValue.slice(1); 1780 } else if (isRoot && i === nodes.length - 1 && newNodeValue.endsWith(' ')) { 1781 newNodeValue = newNodeValue.slice(0, -1); 1782 } 1783 node.nodeValue = newNodeValue; 1784 } else if (node.nodeType === node.ELEMENT_NODE) { 1785 collapseWhiteSpace(node, false); 1786 } 1787 }); 1788 return clone; 1789 } 1790 1791 /** 1792 * We need to normalise line breaks to `\n` so they are consistent across 1793 * platforms and serialised properly. Not removing \r would cause it to 1794 * linger and result in double line breaks when whitespace is preserved. 1795 */ 1796 const CARRIAGE_RETURN = '\r'; 1797 1798 /** 1799 * Removes reserved characters used by rich-text (zero width non breaking spaces 1800 * added by `toTree` and object replacement characters). 1801 * 1802 * @param {string} string 1803 */ 1804 function removeReservedCharacters(string) { 1805 // with the global flag, note that we should create a new regex each time OR 1806 // reset lastIndex state. 1807 return string.replace(new RegExp(`[$ZWNBSP}$OBJECT_REPLACEMENT_CHARACTER}$CARRIAGE_RETURN}]`, 'gu'), ''); 1808 } 1809 1810 /** 1811 * Creates a Rich Text value from a DOM element and range. 1812 * 1813 * @param {Object} $1 Named argements. 1814 * @param {Element} [$1.element] Element to create value from. 1815 * @param {Range} [$1.range] Range to create value from. 1816 * @param {boolean} [$1.isEditableTree] 1817 * 1818 * @return {RichTextValue} A rich text value. 1819 */ 1820 function createFromElement({ 1821 element, 1822 range, 1823 isEditableTree 1824 }) { 1825 const accumulator = createEmptyValue(); 1826 if (!element) { 1827 return accumulator; 1828 } 1829 if (!element.hasChildNodes()) { 1830 accumulateSelection(accumulator, element, range, createEmptyValue()); 1831 return accumulator; 1832 } 1833 const length = element.childNodes.length; 1834 1835 // Optimise for speed. 1836 for (let index = 0; index < length; index++) { 1837 const node = element.childNodes[index]; 1838 const tagName = node.nodeName.toLowerCase(); 1839 if (node.nodeType === node.TEXT_NODE) { 1840 const text = removeReservedCharacters(node.nodeValue); 1841 range = filterRange(node, range, removeReservedCharacters); 1842 accumulateSelection(accumulator, node, range, { 1843 text 1844 }); 1845 // Create a sparse array of the same length as `text`, in which 1846 // formats can be added. 1847 accumulator.formats.length += text.length; 1848 accumulator.replacements.length += text.length; 1849 accumulator.text += text; 1850 continue; 1851 } 1852 if (node.nodeType !== node.ELEMENT_NODE) { 1853 continue; 1854 } 1855 if (isEditableTree && 1856 // Ignore any line breaks that are not inserted by us. 1857 tagName === 'br' && !node.getAttribute('data-rich-text-line-break')) { 1858 accumulateSelection(accumulator, node, range, createEmptyValue()); 1859 continue; 1860 } 1861 if (tagName === 'script') { 1862 const value = { 1863 formats: [,], 1864 replacements: [{ 1865 type: tagName, 1866 attributes: { 1867 'data-rich-text-script': node.getAttribute('data-rich-text-script') || encodeURIComponent(node.innerHTML) 1868 } 1869 }], 1870 text: OBJECT_REPLACEMENT_CHARACTER 1871 }; 1872 accumulateSelection(accumulator, node, range, value); 1873 mergePair(accumulator, value); 1874 continue; 1875 } 1876 if (tagName === 'br') { 1877 accumulateSelection(accumulator, node, range, createEmptyValue()); 1878 mergePair(accumulator, create({ 1879 text: '\n' 1880 })); 1881 continue; 1882 } 1883 const format = toFormat({ 1884 tagName, 1885 attributes: getAttributes({ 1886 element: node 1887 }) 1888 }); 1889 1890 // When a format type is declared as not editable, replace it with an 1891 // object replacement character and preserve the inner HTML. 1892 if (format?.formatType?.contentEditable === false) { 1893 delete format.formatType; 1894 accumulateSelection(accumulator, node, range, createEmptyValue()); 1895 mergePair(accumulator, { 1896 formats: [,], 1897 replacements: [{ 1898 ...format, 1899 innerHTML: node.innerHTML 1900 }], 1901 text: OBJECT_REPLACEMENT_CHARACTER 1902 }); 1903 continue; 1904 } 1905 if (format) delete format.formatType; 1906 const value = createFromElement({ 1907 element: node, 1908 range, 1909 isEditableTree 1910 }); 1911 accumulateSelection(accumulator, node, range, value); 1912 1913 // Ignore any placeholders, but keep their content since the browser 1914 // might insert text inside them when the editable element is flex. 1915 if (!format || node.getAttribute('data-rich-text-placeholder')) { 1916 mergePair(accumulator, value); 1917 } else if (value.text.length === 0) { 1918 if (format.attributes) { 1919 mergePair(accumulator, { 1920 formats: [,], 1921 replacements: [format], 1922 text: OBJECT_REPLACEMENT_CHARACTER 1923 }); 1924 } 1925 } else { 1926 // Indices should share a reference to the same formats array. 1927 // Only create a new reference if `formats` changes. 1928 function mergeFormats(formats) { 1929 if (mergeFormats.formats === formats) { 1930 return mergeFormats.newFormats; 1931 } 1932 const newFormats = formats ? [format, ...formats] : [format]; 1933 mergeFormats.formats = formats; 1934 mergeFormats.newFormats = newFormats; 1935 return newFormats; 1936 } 1937 1938 // Since the formats parameter can be `undefined`, preset 1939 // `mergeFormats` with a new reference. 1940 mergeFormats.newFormats = [format]; 1941 mergePair(accumulator, { 1942 ...value, 1943 formats: Array.from(value.formats, mergeFormats) 1944 }); 1945 } 1946 } 1947 return accumulator; 1948 } 1949 1950 /** 1951 * Gets the attributes of an element in object shape. 1952 * 1953 * @param {Object} $1 Named argements. 1954 * @param {Element} $1.element Element to get attributes from. 1955 * 1956 * @return {Object|void} Attribute object or `undefined` if the element has no 1957 * attributes. 1958 */ 1959 function getAttributes({ 1960 element 1961 }) { 1962 if (!element.hasAttributes()) { 1963 return; 1964 } 1965 const length = element.attributes.length; 1966 let accumulator; 1967 1968 // Optimise for speed. 1969 for (let i = 0; i < length; i++) { 1970 const { 1971 name, 1972 value 1973 } = element.attributes[i]; 1974 if (name.indexOf('data-rich-text-') === 0) { 1975 continue; 1976 } 1977 const safeName = /^on/i.test(name) ? 'data-disable-rich-text-' + name : name; 1978 accumulator = accumulator || {}; 1979 accumulator[safeName] = value; 1980 } 1981 return accumulator; 1982 } 1983 1984 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/concat.js 1985 /** 1986 * Internal dependencies 1987 */ 1988 1989 1990 1991 1992 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1993 1994 /** 1995 * Concats a pair of rich text values. Not that this mutates `a` and does NOT 1996 * normalise formats! 1997 * 1998 * @param {Object} a Value to mutate. 1999 * @param {Object} b Value to add read from. 2000 * 2001 * @return {Object} `a`, mutated. 2002 */ 2003 function mergePair(a, b) { 2004 a.formats = a.formats.concat(b.formats); 2005 a.replacements = a.replacements.concat(b.replacements); 2006 a.text += b.text; 2007 return a; 2008 } 2009 2010 /** 2011 * Combine all Rich Text values into one. This is similar to 2012 * `String.prototype.concat`. 2013 * 2014 * @param {...RichTextValue} values Objects to combine. 2015 * 2016 * @return {RichTextValue} A new value combining all given records. 2017 */ 2018 function concat(...values) { 2019 return normaliseFormats(values.reduce(mergePair, create())); 2020 } 2021 2022 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-active-format.js 2023 /** 2024 * Internal dependencies 2025 */ 2026 2027 2028 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2029 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2030 2031 /** 2032 * Gets the format object by type at the start of the selection. This can be 2033 * used to get e.g. the URL of a link format at the current selection, but also 2034 * to check if a format is active at the selection. Returns undefined if there 2035 * is no format at the selection. 2036 * 2037 * @param {RichTextValue} value Value to inspect. 2038 * @param {string} formatType Format type to look for. 2039 * 2040 * @return {RichTextFormat|undefined} Active format object of the specified 2041 * type, or undefined. 2042 */ 2043 function getActiveFormat(value, formatType) { 2044 return getActiveFormats(value).find(({ 2045 type 2046 }) => type === formatType); 2047 } 2048 2049 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-active-object.js 2050 /** 2051 * Internal dependencies 2052 */ 2053 2054 2055 2056 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2057 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2058 2059 /** 2060 * Gets the active object, if there is any. 2061 * 2062 * @param {RichTextValue} value Value to inspect. 2063 * 2064 * @return {RichTextFormat|void} Active object, or undefined. 2065 */ 2066 function getActiveObject({ 2067 start, 2068 end, 2069 replacements, 2070 text 2071 }) { 2072 if (start + 1 !== end || text[start] !== OBJECT_REPLACEMENT_CHARACTER) { 2073 return; 2074 } 2075 return replacements[start]; 2076 } 2077 2078 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-collapsed.js 2079 /** 2080 * Internal dependencies 2081 */ 2082 2083 /** 2084 * Check if the selection of a Rich Text value is collapsed or not. Collapsed 2085 * means that no characters are selected, but there is a caret present. If there 2086 * is no selection, `undefined` will be returned. This is similar to 2087 * `window.getSelection().isCollapsed()`. 2088 * 2089 * @param props The rich text value to check. 2090 * @param props.start 2091 * @param props.end 2092 * @return True if the selection is collapsed, false if not, undefined if there is no selection. 2093 */ 2094 function isCollapsed({ 2095 start, 2096 end 2097 }) { 2098 if (start === undefined || end === undefined) { 2099 return; 2100 } 2101 return start === end; 2102 } 2103 2104 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-empty.js 2105 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2106 2107 /** 2108 * Check if a Rich Text value is Empty, meaning it contains no text or any 2109 * objects (such as images). 2110 * 2111 * @param {RichTextValue} value Value to use. 2112 * 2113 * @return {boolean} True if the value is empty, false if not. 2114 */ 2115 function isEmpty({ 2116 text 2117 }) { 2118 return text.length === 0; 2119 } 2120 2121 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/join.js 2122 /** 2123 * Internal dependencies 2124 */ 2125 2126 2127 2128 2129 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2130 2131 /** 2132 * Combine an array of Rich Text values into one, optionally separated by 2133 * `separator`, which can be a Rich Text value, HTML string, or plain text 2134 * string. This is similar to `Array.prototype.join`. 2135 * 2136 * @param {Array<RichTextValue>} values An array of values to join. 2137 * @param {string|RichTextValue} [separator] Separator string or value. 2138 * 2139 * @return {RichTextValue} A new combined value. 2140 */ 2141 function join(values, separator = '') { 2142 if (typeof separator === 'string') { 2143 separator = create({ 2144 text: separator 2145 }); 2146 } 2147 return normaliseFormats(values.reduce((accumlator, { 2148 formats, 2149 replacements, 2150 text 2151 }) => ({ 2152 formats: accumlator.formats.concat(separator.formats, formats), 2153 replacements: accumlator.replacements.concat(separator.replacements, replacements), 2154 text: accumlator.text + separator.text + text 2155 }))); 2156 } 2157 2158 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/register-format-type.js 2159 /** 2160 * WordPress dependencies 2161 */ 2162 2163 /** 2164 * Internal dependencies 2165 */ 2166 2167 /** 2168 * @typedef {Object} WPFormat 2169 * 2170 * @property {string} name A string identifying the format. Must be 2171 * unique across all registered formats. 2172 * @property {string} tagName The HTML tag this format will wrap the 2173 * selection with. 2174 * @property {boolean} interactive Whether format makes content interactive or not. 2175 * @property {string | null} [className] A class to match the format. 2176 * @property {string} title Name of the format. 2177 * @property {Function} edit Should return a component for the user to 2178 * interact with the new registered format. 2179 */ 2180 2181 /** 2182 * Registers a new format provided a unique name and an object defining its 2183 * behavior. 2184 * 2185 * @param {string} name Format name. 2186 * @param {WPFormat} settings Format settings. 2187 * 2188 * @return {WPFormat|undefined} The format, if it has been successfully 2189 * registered; otherwise `undefined`. 2190 */ 2191 function registerFormatType(name, settings) { 2192 settings = { 2193 name, 2194 ...settings 2195 }; 2196 if (typeof settings.name !== 'string') { 2197 window.console.error('Format names must be strings.'); 2198 return; 2199 } 2200 if (!/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test(settings.name)) { 2201 window.console.error('Format names must contain a namespace prefix, include only lowercase alphanumeric characters or dashes, and start with a letter. Example: my-plugin/my-custom-format'); 2202 return; 2203 } 2204 if ((0,external_wp_data_namespaceObject.select)(store).getFormatType(settings.name)) { 2205 window.console.error('Format "' + settings.name + '" is already registered.'); 2206 return; 2207 } 2208 if (typeof settings.tagName !== 'string' || settings.tagName === '') { 2209 window.console.error('Format tag names must be a string.'); 2210 return; 2211 } 2212 if ((typeof settings.className !== 'string' || settings.className === '') && settings.className !== null) { 2213 window.console.error('Format class names must be a string, or null to handle bare elements.'); 2214 return; 2215 } 2216 if (!/^[_a-zA-Z]+[a-zA-Z0-9_-]*$/.test(settings.className)) { 2217 window.console.error('A class name must begin with a letter, followed by any number of hyphens, underscores, letters, or numbers.'); 2218 return; 2219 } 2220 if (settings.className === null) { 2221 const formatTypeForBareElement = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForBareElement(settings.tagName); 2222 if (formatTypeForBareElement && formatTypeForBareElement.name !== 'core/unknown') { 2223 window.console.error(`Format "$formatTypeForBareElement.name}" is already registered to handle bare tag name "$settings.tagName}".`); 2224 return; 2225 } 2226 } else { 2227 const formatTypeForClassName = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForClassName(settings.className); 2228 if (formatTypeForClassName) { 2229 window.console.error(`Format "$formatTypeForClassName.name}" is already registered to handle class name "$settings.className}".`); 2230 return; 2231 } 2232 } 2233 if (!('title' in settings) || settings.title === '') { 2234 window.console.error('The format "' + settings.name + '" must have a title.'); 2235 return; 2236 } 2237 if ('keywords' in settings && settings.keywords.length > 3) { 2238 window.console.error('The format "' + settings.name + '" can have a maximum of 3 keywords.'); 2239 return; 2240 } 2241 if (typeof settings.title !== 'string') { 2242 window.console.error('Format titles must be strings.'); 2243 return; 2244 } 2245 (0,external_wp_data_namespaceObject.dispatch)(store).addFormatTypes(settings); 2246 return settings; 2247 } 2248 2249 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/remove-format.js 2250 /** 2251 * Internal dependencies 2252 */ 2253 2254 2255 2256 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2257 2258 /** 2259 * Remove any format object from a Rich Text value by type from the given 2260 * `startIndex` to the given `endIndex`. Indices are retrieved from the 2261 * selection if none are provided. 2262 * 2263 * @param {RichTextValue} value Value to modify. 2264 * @param {string} formatType Format type to remove. 2265 * @param {number} [startIndex] Start index. 2266 * @param {number} [endIndex] End index. 2267 * 2268 * @return {RichTextValue} A new value with the format applied. 2269 */ 2270 function removeFormat(value, formatType, startIndex = value.start, endIndex = value.end) { 2271 const { 2272 formats, 2273 activeFormats 2274 } = value; 2275 const newFormats = formats.slice(); 2276 2277 // If the selection is collapsed, expand start and end to the edges of the 2278 // format. 2279 if (startIndex === endIndex) { 2280 const format = newFormats[startIndex]?.find(({ 2281 type 2282 }) => type === formatType); 2283 if (format) { 2284 while (newFormats[startIndex]?.find(newFormat => newFormat === format)) { 2285 filterFormats(newFormats, startIndex, formatType); 2286 startIndex--; 2287 } 2288 endIndex++; 2289 while (newFormats[endIndex]?.find(newFormat => newFormat === format)) { 2290 filterFormats(newFormats, endIndex, formatType); 2291 endIndex++; 2292 } 2293 } 2294 } else { 2295 for (let i = startIndex; i < endIndex; i++) { 2296 if (newFormats[i]) { 2297 filterFormats(newFormats, i, formatType); 2298 } 2299 } 2300 } 2301 return normaliseFormats({ 2302 ...value, 2303 formats: newFormats, 2304 activeFormats: activeFormats?.filter(({ 2305 type 2306 }) => type !== formatType) || [] 2307 }); 2308 } 2309 function filterFormats(formats, index, formatType) { 2310 const newFormats = formats[index].filter(({ 2311 type 2312 }) => type !== formatType); 2313 if (newFormats.length) { 2314 formats[index] = newFormats; 2315 } else { 2316 delete formats[index]; 2317 } 2318 } 2319 2320 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/insert.js 2321 /** 2322 * Internal dependencies 2323 */ 2324 2325 2326 2327 2328 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2329 2330 /** 2331 * Insert a Rich Text value, an HTML string, or a plain text string, into a 2332 * Rich Text value at the given `startIndex`. Any content between `startIndex` 2333 * and `endIndex` will be removed. Indices are retrieved from the selection if 2334 * none are provided. 2335 * 2336 * @param {RichTextValue} value Value to modify. 2337 * @param {RichTextValue|string} valueToInsert Value to insert. 2338 * @param {number} [startIndex] Start index. 2339 * @param {number} [endIndex] End index. 2340 * 2341 * @return {RichTextValue} A new value with the value inserted. 2342 */ 2343 function insert(value, valueToInsert, startIndex = value.start, endIndex = value.end) { 2344 const { 2345 formats, 2346 replacements, 2347 text 2348 } = value; 2349 if (typeof valueToInsert === 'string') { 2350 valueToInsert = create({ 2351 text: valueToInsert 2352 }); 2353 } 2354 const index = startIndex + valueToInsert.text.length; 2355 return normaliseFormats({ 2356 formats: formats.slice(0, startIndex).concat(valueToInsert.formats, formats.slice(endIndex)), 2357 replacements: replacements.slice(0, startIndex).concat(valueToInsert.replacements, replacements.slice(endIndex)), 2358 text: text.slice(0, startIndex) + valueToInsert.text + text.slice(endIndex), 2359 start: index, 2360 end: index 2361 }); 2362 } 2363 2364 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/remove.js 2365 /** 2366 * Internal dependencies 2367 */ 2368 2369 2370 2371 2372 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2373 2374 /** 2375 * Remove content from a Rich Text value between the given `startIndex` and 2376 * `endIndex`. Indices are retrieved from the selection if none are provided. 2377 * 2378 * @param {RichTextValue} value Value to modify. 2379 * @param {number} [startIndex] Start index. 2380 * @param {number} [endIndex] End index. 2381 * 2382 * @return {RichTextValue} A new value with the content removed. 2383 */ 2384 function remove_remove(value, startIndex, endIndex) { 2385 return insert(value, create(), startIndex, endIndex); 2386 } 2387 2388 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/replace.js 2389 /** 2390 * Internal dependencies 2391 */ 2392 2393 2394 2395 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2396 2397 /** 2398 * Search a Rich Text value and replace the match(es) with `replacement`. This 2399 * is similar to `String.prototype.replace`. 2400 * 2401 * @param {RichTextValue} value The value to modify. 2402 * @param {RegExp|string} pattern A RegExp object or literal. Can also be 2403 * a string. It is treated as a verbatim 2404 * string and is not interpreted as a 2405 * regular expression. Only the first 2406 * occurrence will be replaced. 2407 * @param {Function|string} replacement The match or matches are replaced with 2408 * the specified or the value returned by 2409 * the specified function. 2410 * 2411 * @return {RichTextValue} A new value with replacements applied. 2412 */ 2413 function replace_replace({ 2414 formats, 2415 replacements, 2416 text, 2417 start, 2418 end 2419 }, pattern, replacement) { 2420 text = text.replace(pattern, (match, ...rest) => { 2421 const offset = rest[rest.length - 2]; 2422 let newText = replacement; 2423 let newFormats; 2424 let newReplacements; 2425 if (typeof newText === 'function') { 2426 newText = replacement(match, ...rest); 2427 } 2428 if (typeof newText === 'object') { 2429 newFormats = newText.formats; 2430 newReplacements = newText.replacements; 2431 newText = newText.text; 2432 } else { 2433 newFormats = Array(newText.length); 2434 newReplacements = Array(newText.length); 2435 if (formats[offset]) { 2436 newFormats = newFormats.fill(formats[offset]); 2437 } 2438 } 2439 formats = formats.slice(0, offset).concat(newFormats, formats.slice(offset + match.length)); 2440 replacements = replacements.slice(0, offset).concat(newReplacements, replacements.slice(offset + match.length)); 2441 if (start) { 2442 start = end = offset + newText.length; 2443 } 2444 return newText; 2445 }); 2446 return normaliseFormats({ 2447 formats, 2448 replacements, 2449 text, 2450 start, 2451 end 2452 }); 2453 } 2454 2455 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/insert-object.js 2456 /** 2457 * Internal dependencies 2458 */ 2459 2460 2461 2462 2463 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2464 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2465 2466 /** 2467 * Insert a format as an object into a Rich Text value at the given 2468 * `startIndex`. Any content between `startIndex` and `endIndex` will be 2469 * removed. Indices are retrieved from the selection if none are provided. 2470 * 2471 * @param {RichTextValue} value Value to modify. 2472 * @param {RichTextFormat} formatToInsert Format to insert as object. 2473 * @param {number} [startIndex] Start index. 2474 * @param {number} [endIndex] End index. 2475 * 2476 * @return {RichTextValue} A new value with the object inserted. 2477 */ 2478 function insertObject(value, formatToInsert, startIndex, endIndex) { 2479 const valueToInsert = { 2480 formats: [,], 2481 replacements: [formatToInsert], 2482 text: OBJECT_REPLACEMENT_CHARACTER 2483 }; 2484 return insert(value, valueToInsert, startIndex, endIndex); 2485 } 2486 2487 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/slice.js 2488 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2489 2490 /** 2491 * Slice a Rich Text value from `startIndex` to `endIndex`. Indices are 2492 * retrieved from the selection if none are provided. This is similar to 2493 * `String.prototype.slice`. 2494 * 2495 * @param {RichTextValue} value Value to modify. 2496 * @param {number} [startIndex] Start index. 2497 * @param {number} [endIndex] End index. 2498 * 2499 * @return {RichTextValue} A new extracted value. 2500 */ 2501 function slice(value, startIndex = value.start, endIndex = value.end) { 2502 const { 2503 formats, 2504 replacements, 2505 text 2506 } = value; 2507 if (startIndex === undefined || endIndex === undefined) { 2508 return { 2509 ...value 2510 }; 2511 } 2512 return { 2513 formats: formats.slice(startIndex, endIndex), 2514 replacements: replacements.slice(startIndex, endIndex), 2515 text: text.slice(startIndex, endIndex) 2516 }; 2517 } 2518 2519 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/split.js 2520 /** 2521 * Internal dependencies 2522 */ 2523 2524 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2525 2526 /** 2527 * Split a Rich Text value in two at the given `startIndex` and `endIndex`, or 2528 * split at the given separator. This is similar to `String.prototype.split`. 2529 * Indices are retrieved from the selection if none are provided. 2530 * 2531 * @param {RichTextValue} value 2532 * @param {number|string} [string] Start index, or string at which to split. 2533 * 2534 * @return {Array<RichTextValue>|undefined} An array of new values. 2535 */ 2536 function split({ 2537 formats, 2538 replacements, 2539 text, 2540 start, 2541 end 2542 }, string) { 2543 if (typeof string !== 'string') { 2544 return splitAtSelection(...arguments); 2545 } 2546 let nextStart = 0; 2547 return text.split(string).map(substring => { 2548 const startIndex = nextStart; 2549 const value = { 2550 formats: formats.slice(startIndex, startIndex + substring.length), 2551 replacements: replacements.slice(startIndex, startIndex + substring.length), 2552 text: substring 2553 }; 2554 nextStart += string.length + substring.length; 2555 if (start !== undefined && end !== undefined) { 2556 if (start >= startIndex && start < nextStart) { 2557 value.start = start - startIndex; 2558 } else if (start < startIndex && end > startIndex) { 2559 value.start = 0; 2560 } 2561 if (end >= startIndex && end < nextStart) { 2562 value.end = end - startIndex; 2563 } else if (start < nextStart && end > nextStart) { 2564 value.end = substring.length; 2565 } 2566 } 2567 return value; 2568 }); 2569 } 2570 function splitAtSelection({ 2571 formats, 2572 replacements, 2573 text, 2574 start, 2575 end 2576 }, startIndex = start, endIndex = end) { 2577 if (start === undefined || end === undefined) { 2578 return; 2579 } 2580 const before = { 2581 formats: formats.slice(0, startIndex), 2582 replacements: replacements.slice(0, startIndex), 2583 text: text.slice(0, startIndex) 2584 }; 2585 const after = { 2586 formats: formats.slice(endIndex), 2587 replacements: replacements.slice(endIndex), 2588 text: text.slice(endIndex), 2589 start: 0, 2590 end: 0 2591 }; 2592 return [before, after]; 2593 } 2594 2595 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-range-equal.js 2596 /** 2597 * Returns true if two ranges are equal, or false otherwise. Ranges are 2598 * considered equal if their start and end occur in the same container and 2599 * offset. 2600 * 2601 * @param {Range|null} a First range object to test. 2602 * @param {Range|null} b First range object to test. 2603 * 2604 * @return {boolean} Whether the two ranges are equal. 2605 */ 2606 function isRangeEqual(a, b) { 2607 return a === b || a && b && a.startContainer === b.startContainer && a.startOffset === b.startOffset && a.endContainer === b.endContainer && a.endOffset === b.endOffset; 2608 } 2609 2610 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/to-dom.js 2611 /** 2612 * Internal dependencies 2613 */ 2614 2615 2616 2617 2618 2619 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2620 2621 /** 2622 * Creates a path as an array of indices from the given root node to the given 2623 * node. 2624 * 2625 * @param {Node} node Node to find the path of. 2626 * @param {HTMLElement} rootNode Root node to find the path from. 2627 * @param {Array} path Initial path to build on. 2628 * 2629 * @return {Array} The path from the root node to the node. 2630 */ 2631 function createPathToNode(node, rootNode, path) { 2632 const parentNode = node.parentNode; 2633 let i = 0; 2634 while (node = node.previousSibling) { 2635 i++; 2636 } 2637 path = [i, ...path]; 2638 if (parentNode !== rootNode) { 2639 path = createPathToNode(parentNode, rootNode, path); 2640 } 2641 return path; 2642 } 2643 2644 /** 2645 * Gets a node given a path (array of indices) from the given node. 2646 * 2647 * @param {HTMLElement} node Root node to find the wanted node in. 2648 * @param {Array} path Path (indices) to the wanted node. 2649 * 2650 * @return {Object} Object with the found node and the remaining offset (if any). 2651 */ 2652 function getNodeByPath(node, path) { 2653 path = [...path]; 2654 while (node && path.length > 1) { 2655 node = node.childNodes[path.shift()]; 2656 } 2657 return { 2658 node, 2659 offset: path[0] 2660 }; 2661 } 2662 function to_dom_append(element, child) { 2663 if (child.html !== undefined) { 2664 return element.innerHTML += child.html; 2665 } 2666 if (typeof child === 'string') { 2667 child = element.ownerDocument.createTextNode(child); 2668 } 2669 const { 2670 type, 2671 attributes 2672 } = child; 2673 if (type) { 2674 child = element.ownerDocument.createElement(type); 2675 for (const key in attributes) { 2676 child.setAttribute(key, attributes[key]); 2677 } 2678 } 2679 return element.appendChild(child); 2680 } 2681 function to_dom_appendText(node, text) { 2682 node.appendData(text); 2683 } 2684 function to_dom_getLastChild({ 2685 lastChild 2686 }) { 2687 return lastChild; 2688 } 2689 function to_dom_getParent({ 2690 parentNode 2691 }) { 2692 return parentNode; 2693 } 2694 function to_dom_isText(node) { 2695 return node.nodeType === node.TEXT_NODE; 2696 } 2697 function to_dom_getText({ 2698 nodeValue 2699 }) { 2700 return nodeValue; 2701 } 2702 function to_dom_remove(node) { 2703 return node.parentNode.removeChild(node); 2704 } 2705 function toDom({ 2706 value, 2707 prepareEditableTree, 2708 isEditableTree = true, 2709 placeholder, 2710 doc = document 2711 }) { 2712 let startPath = []; 2713 let endPath = []; 2714 if (prepareEditableTree) { 2715 value = { 2716 ...value, 2717 formats: prepareEditableTree(value) 2718 }; 2719 } 2720 2721 /** 2722 * Returns a new instance of a DOM tree upon which RichText operations can be 2723 * applied. 2724 * 2725 * Note: The current implementation will return a shared reference, reset on 2726 * each call to `createEmpty`. Therefore, you should not hold a reference to 2727 * the value to operate upon asynchronously, as it may have unexpected results. 2728 * 2729 * @return {Object} RichText tree. 2730 */ 2731 const createEmpty = () => createElement(doc, ''); 2732 const tree = toTree({ 2733 value, 2734 createEmpty, 2735 append: to_dom_append, 2736 getLastChild: to_dom_getLastChild, 2737 getParent: to_dom_getParent, 2738 isText: to_dom_isText, 2739 getText: to_dom_getText, 2740 remove: to_dom_remove, 2741 appendText: to_dom_appendText, 2742 onStartIndex(body, pointer) { 2743 startPath = createPathToNode(pointer, body, [pointer.nodeValue.length]); 2744 }, 2745 onEndIndex(body, pointer) { 2746 endPath = createPathToNode(pointer, body, [pointer.nodeValue.length]); 2747 }, 2748 isEditableTree, 2749 placeholder 2750 }); 2751 return { 2752 body: tree, 2753 selection: { 2754 startPath, 2755 endPath 2756 } 2757 }; 2758 } 2759 2760 /** 2761 * Create an `Element` tree from a Rich Text value and applies the difference to 2762 * the `Element` tree contained by `current`. 2763 * 2764 * @param {Object} $1 Named arguments. 2765 * @param {RichTextValue} $1.value Value to apply. 2766 * @param {HTMLElement} $1.current The live root node to apply the element tree to. 2767 * @param {Function} [$1.prepareEditableTree] Function to filter editorable formats. 2768 * @param {boolean} [$1.__unstableDomOnly] Only apply elements, no selection. 2769 * @param {string} [$1.placeholder] Placeholder text. 2770 */ 2771 function apply({ 2772 value, 2773 current, 2774 prepareEditableTree, 2775 __unstableDomOnly, 2776 placeholder 2777 }) { 2778 // Construct a new element tree in memory. 2779 const { 2780 body, 2781 selection 2782 } = toDom({ 2783 value, 2784 prepareEditableTree, 2785 placeholder, 2786 doc: current.ownerDocument 2787 }); 2788 applyValue(body, current); 2789 if (value.start !== undefined && !__unstableDomOnly) { 2790 applySelection(selection, current); 2791 } 2792 } 2793 function applyValue(future, current) { 2794 let i = 0; 2795 let futureChild; 2796 while (futureChild = future.firstChild) { 2797 const currentChild = current.childNodes[i]; 2798 if (!currentChild) { 2799 current.appendChild(futureChild); 2800 } else if (!currentChild.isEqualNode(futureChild)) { 2801 if (currentChild.nodeName !== futureChild.nodeName || currentChild.nodeType === currentChild.TEXT_NODE && currentChild.data !== futureChild.data) { 2802 current.replaceChild(futureChild, currentChild); 2803 } else { 2804 const currentAttributes = currentChild.attributes; 2805 const futureAttributes = futureChild.attributes; 2806 if (currentAttributes) { 2807 let ii = currentAttributes.length; 2808 2809 // Reverse loop because `removeAttribute` on `currentChild` 2810 // changes `currentAttributes`. 2811 while (ii--) { 2812 const { 2813 name 2814 } = currentAttributes[ii]; 2815 if (!futureChild.getAttribute(name)) { 2816 currentChild.removeAttribute(name); 2817 } 2818 } 2819 } 2820 if (futureAttributes) { 2821 for (let ii = 0; ii < futureAttributes.length; ii++) { 2822 const { 2823 name, 2824 value 2825 } = futureAttributes[ii]; 2826 if (currentChild.getAttribute(name) !== value) { 2827 currentChild.setAttribute(name, value); 2828 } 2829 } 2830 } 2831 applyValue(futureChild, currentChild); 2832 future.removeChild(futureChild); 2833 } 2834 } else { 2835 future.removeChild(futureChild); 2836 } 2837 i++; 2838 } 2839 while (current.childNodes[i]) { 2840 current.removeChild(current.childNodes[i]); 2841 } 2842 } 2843 function applySelection({ 2844 startPath, 2845 endPath 2846 }, current) { 2847 const { 2848 node: startContainer, 2849 offset: startOffset 2850 } = getNodeByPath(current, startPath); 2851 const { 2852 node: endContainer, 2853 offset: endOffset 2854 } = getNodeByPath(current, endPath); 2855 const { 2856 ownerDocument 2857 } = current; 2858 const { 2859 defaultView 2860 } = ownerDocument; 2861 const selection = defaultView.getSelection(); 2862 const range = ownerDocument.createRange(); 2863 range.setStart(startContainer, startOffset); 2864 range.setEnd(endContainer, endOffset); 2865 const { 2866 activeElement 2867 } = ownerDocument; 2868 if (selection.rangeCount > 0) { 2869 // If the to be added range and the live range are the same, there's no 2870 // need to remove the live range and add the equivalent range. 2871 if (isRangeEqual(range, selection.getRangeAt(0))) { 2872 return; 2873 } 2874 selection.removeAllRanges(); 2875 } 2876 selection.addRange(range); 2877 2878 // This function is not intended to cause a shift in focus. Since the above 2879 // selection manipulations may shift focus, ensure that focus is restored to 2880 // its previous state. 2881 if (activeElement !== ownerDocument.activeElement) { 2882 // The `instanceof` checks protect against edge cases where the focused 2883 // element is not of the interface HTMLElement (does not have a `focus` 2884 // or `blur` property). 2885 // 2886 // See: https://github.com/Microsoft/TypeScript/issues/5901#issuecomment-431649653 2887 if (activeElement instanceof defaultView.HTMLElement) { 2888 activeElement.focus(); 2889 } 2890 } 2891 } 2892 2893 ;// CONCATENATED MODULE: external ["wp","a11y"] 2894 const external_wp_a11y_namespaceObject = window["wp"]["a11y"]; 2895 ;// CONCATENATED MODULE: external ["wp","i18n"] 2896 const external_wp_i18n_namespaceObject = window["wp"]["i18n"]; 2897 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/toggle-format.js 2898 /** 2899 * WordPress dependencies 2900 */ 2901 2902 2903 2904 2905 /** 2906 * Internal dependencies 2907 */ 2908 2909 2910 2911 2912 2913 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2914 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2915 2916 /** 2917 * Toggles a format object to a Rich Text value at the current selection. 2918 * 2919 * @param {RichTextValue} value Value to modify. 2920 * @param {RichTextFormat} format Format to apply or remove. 2921 * 2922 * @return {RichTextValue} A new value with the format applied or removed. 2923 */ 2924 function toggleFormat(value, format) { 2925 if (getActiveFormat(value, format.type)) { 2926 // For screen readers, will announce if formatting control is disabled. 2927 if (format.title) { 2928 // translators: %s: title of the formatting control 2929 (0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.sprintf)((0,external_wp_i18n_namespaceObject.__)('%s removed.'), format.title), 'assertive'); 2930 } 2931 return removeFormat(value, format.type); 2932 } 2933 // For screen readers, will announce if formatting control is enabled. 2934 if (format.title) { 2935 // translators: %s: title of the formatting control 2936 (0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.sprintf)((0,external_wp_i18n_namespaceObject.__)('%s applied.'), format.title), 'assertive'); 2937 } 2938 return applyFormat(value, format); 2939 } 2940 2941 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/unregister-format-type.js 2942 /** 2943 * WordPress dependencies 2944 */ 2945 2946 2947 /** 2948 * Internal dependencies 2949 */ 2950 2951 2952 /** @typedef {import('./register-format-type').WPFormat} WPFormat */ 2953 2954 /** 2955 * Unregisters a format. 2956 * 2957 * @param {string} name Format name. 2958 * 2959 * @return {WPFormat|undefined} The previous format value, if it has 2960 * been successfully unregistered; 2961 * otherwise `undefined`. 2962 */ 2963 function unregisterFormatType(name) { 2964 const oldFormat = (0,external_wp_data_namespaceObject.select)(store).getFormatType(name); 2965 if (!oldFormat) { 2966 window.console.error(`Format $name} is not registered.`); 2967 return; 2968 } 2969 (0,external_wp_data_namespaceObject.dispatch)(store).removeFormatTypes(name); 2970 return oldFormat; 2971 } 2972 2973 ;// CONCATENATED MODULE: external ["wp","element"] 2974 const external_wp_element_namespaceObject = window["wp"]["element"]; 2975 ;// CONCATENATED MODULE: external ["wp","deprecated"] 2976 const external_wp_deprecated_namespaceObject = window["wp"]["deprecated"]; 2977 var external_wp_deprecated_default = /*#__PURE__*/__webpack_require__.n(external_wp_deprecated_namespaceObject); 2978 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-anchor-ref.js 2979 /** 2980 * WordPress dependencies 2981 */ 2982 2983 2984 2985 /** 2986 * Internal dependencies 2987 */ 2988 2989 2990 /** 2991 * @template T 2992 * @typedef {import('@wordpress/element').RefObject<T>} RefObject<T> 2993 */ 2994 /** @typedef {import('../register-format-type').WPFormat} WPFormat */ 2995 /** @typedef {import('../types').RichTextValue} RichTextValue */ 2996 2997 /** 2998 * This hook, to be used in a format type's Edit component, returns the active 2999 * element that is formatted, or the selection range if no format is active. 3000 * The returned value is meant to be used for positioning UI, e.g. by passing it 3001 * to the `Popover` component. 3002 * 3003 * @param {Object} $1 Named parameters. 3004 * @param {RefObject<HTMLElement>} $1.ref React ref of the element 3005 * containing the editable content. 3006 * @param {RichTextValue} $1.value Value to check for selection. 3007 * @param {WPFormat} $1.settings The format type's settings. 3008 * 3009 * @return {Element|Range} The active element or selection range. 3010 */ 3011 function useAnchorRef({ 3012 ref, 3013 value, 3014 settings = {} 3015 }) { 3016 external_wp_deprecated_default()('`useAnchorRef` hook', { 3017 since: '6.1', 3018 alternative: '`useAnchor` hook' 3019 }); 3020 const { 3021 tagName, 3022 className, 3023 name 3024 } = settings; 3025 const activeFormat = name ? getActiveFormat(value, name) : undefined; 3026 return (0,external_wp_element_namespaceObject.useMemo)(() => { 3027 if (!ref.current) return; 3028 const { 3029 ownerDocument: { 3030 defaultView 3031 } 3032 } = ref.current; 3033 const selection = defaultView.getSelection(); 3034 if (!selection.rangeCount) { 3035 return; 3036 } 3037 const range = selection.getRangeAt(0); 3038 if (!activeFormat) { 3039 return range; 3040 } 3041 let element = range.startContainer; 3042 3043 // If the caret is right before the element, select the next element. 3044 element = element.nextElementSibling || element; 3045 while (element.nodeType !== element.ELEMENT_NODE) { 3046 element = element.parentNode; 3047 } 3048 return element.closest(tagName + (className ? '.' + className : '')); 3049 }, [activeFormat, value.start, value.end, tagName, className]); 3050 } 3051 3052 ;// CONCATENATED MODULE: external ["wp","compose"] 3053 const external_wp_compose_namespaceObject = window["wp"]["compose"]; 3054 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-anchor.js 3055 /** 3056 * WordPress dependencies 3057 */ 3058 3059 3060 3061 /** @typedef {import('../register-format-type').WPFormat} WPFormat */ 3062 /** @typedef {import('../types').RichTextValue} RichTextValue */ 3063 3064 /** 3065 * Given a range and a format tag name and class name, returns the closest 3066 * format element. 3067 * 3068 * @param {Range} range The Range to check. 3069 * @param {HTMLElement} editableContentElement The editable wrapper. 3070 * @param {string} tagName The tag name of the format element. 3071 * @param {string} className The class name of the format element. 3072 * 3073 * @return {HTMLElement|undefined} The format element, if found. 3074 */ 3075 function getFormatElement(range, editableContentElement, tagName, className) { 3076 let element = range.startContainer; 3077 3078 // Even if the active format is defined, the actualy DOM range's start 3079 // container may be outside of the format's DOM element: 3080 // `a‸<strong>b</strong>` (DOM) while visually it's `a<strong>‸b</strong>`. 3081 // So at a given selection index, start with the deepest format DOM element. 3082 if (element.nodeType === element.TEXT_NODE && range.startOffset === element.length && element.nextSibling) { 3083 element = element.nextSibling; 3084 while (element.firstChild) { 3085 element = element.firstChild; 3086 } 3087 } 3088 if (element.nodeType !== element.ELEMENT_NODE) { 3089 element = element.parentElement; 3090 } 3091 if (!element) return; 3092 if (element === editableContentElement) return; 3093 if (!editableContentElement.contains(element)) return; 3094 const selector = tagName + (className ? '.' + className : ''); 3095 3096 // .closest( selector ), but with a boundary. Check if the element matches 3097 // the selector. If it doesn't match, try the parent element if it's not the 3098 // editable wrapper. We don't want to try to match ancestors of the editable 3099 // wrapper, which is what .closest( selector ) would do. When the element is 3100 // the editable wrapper (which is most likely the case because most text is 3101 // unformatted), this never runs. 3102 while (element !== editableContentElement) { 3103 if (element.matches(selector)) { 3104 return element; 3105 } 3106 element = element.parentElement; 3107 } 3108 } 3109 3110 /** 3111 * @typedef {Object} VirtualAnchorElement 3112 * @property {() => DOMRect} getBoundingClientRect A function returning a DOMRect 3113 * @property {HTMLElement} contextElement The actual DOM element 3114 */ 3115 3116 /** 3117 * Creates a virtual anchor element for a range. 3118 * 3119 * @param {Range} range The range to create a virtual anchor element for. 3120 * @param {HTMLElement} editableContentElement The editable wrapper. 3121 * 3122 * @return {VirtualAnchorElement} The virtual anchor element. 3123 */ 3124 function createVirtualAnchorElement(range, editableContentElement) { 3125 return { 3126 contextElement: editableContentElement, 3127 getBoundingClientRect() { 3128 return editableContentElement.contains(range.startContainer) ? range.getBoundingClientRect() : editableContentElement.getBoundingClientRect(); 3129 } 3130 }; 3131 } 3132 3133 /** 3134 * Get the anchor: a format element if there is a matching one based on the 3135 * tagName and className or a range otherwise. 3136 * 3137 * @param {HTMLElement} editableContentElement The editable wrapper. 3138 * @param {string} tagName The tag name of the format 3139 * element. 3140 * @param {string} className The class name of the format 3141 * element. 3142 * 3143 * @return {HTMLElement|VirtualAnchorElement|undefined} The anchor. 3144 */ 3145 function getAnchor(editableContentElement, tagName, className) { 3146 if (!editableContentElement) return; 3147 const { 3148 ownerDocument 3149 } = editableContentElement; 3150 const { 3151 defaultView 3152 } = ownerDocument; 3153 const selection = defaultView.getSelection(); 3154 if (!selection) return; 3155 if (!selection.rangeCount) return; 3156 const range = selection.getRangeAt(0); 3157 if (!range || !range.startContainer) return; 3158 const formatElement = getFormatElement(range, editableContentElement, tagName, className); 3159 if (formatElement) return formatElement; 3160 return createVirtualAnchorElement(range, editableContentElement); 3161 } 3162 3163 /** 3164 * This hook, to be used in a format type's Edit component, returns the active 3165 * element that is formatted, or a virtual element for the selection range if 3166 * no format is active. The returned value is meant to be used for positioning 3167 * UI, e.g. by passing it to the `Popover` component via the `anchor` prop. 3168 * 3169 * @param {Object} $1 Named parameters. 3170 * @param {HTMLElement|null} $1.editableContentElement The element containing 3171 * the editable content. 3172 * @param {WPFormat=} $1.settings The format type's settings. 3173 * @return {Element|VirtualAnchorElement|undefined|null} The active element or selection range. 3174 */ 3175 function useAnchor({ 3176 editableContentElement, 3177 settings = {} 3178 }) { 3179 const { 3180 tagName, 3181 className, 3182 isActive 3183 } = settings; 3184 const [anchor, setAnchor] = (0,external_wp_element_namespaceObject.useState)(() => getAnchor(editableContentElement, tagName, className)); 3185 const wasActive = (0,external_wp_compose_namespaceObject.usePrevious)(isActive); 3186 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 3187 if (!editableContentElement) return; 3188 function callback() { 3189 setAnchor(getAnchor(editableContentElement, tagName, className)); 3190 } 3191 function attach() { 3192 ownerDocument.addEventListener('selectionchange', callback); 3193 } 3194 function detach() { 3195 ownerDocument.removeEventListener('selectionchange', callback); 3196 } 3197 const { 3198 ownerDocument 3199 } = editableContentElement; 3200 if (editableContentElement === ownerDocument.activeElement || 3201 // When a link is created, we need to attach the popover to the newly created anchor. 3202 !wasActive && isActive || 3203 // Sometimes we're _removing_ an active anchor, such as the inline color popover. 3204 // When we add the color, it switches from a virtual anchor to a `<mark>` element. 3205 // When we _remove_ the color, it switches from a `<mark>` element to a virtual anchor. 3206 wasActive && !isActive) { 3207 setAnchor(getAnchor(editableContentElement, tagName, className)); 3208 attach(); 3209 } 3210 editableContentElement.addEventListener('focusin', attach); 3211 editableContentElement.addEventListener('focusout', detach); 3212 return () => { 3213 detach(); 3214 editableContentElement.removeEventListener('focusin', attach); 3215 editableContentElement.removeEventListener('focusout', detach); 3216 }; 3217 }, [editableContentElement, tagName, className, isActive, wasActive]); 3218 return anchor; 3219 } 3220 3221 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-default-style.js 3222 /** 3223 * WordPress dependencies 3224 */ 3225 3226 3227 /** 3228 * In HTML, leading and trailing spaces are not visible, and multiple spaces 3229 * elsewhere are visually reduced to one space. This rule prevents spaces from 3230 * collapsing so all space is visible in the editor and can be removed. It also 3231 * prevents some browsers from inserting non-breaking spaces at the end of a 3232 * line to prevent the space from visually disappearing. Sometimes these non 3233 * breaking spaces can linger in the editor causing unwanted non breaking spaces 3234 * in between words. If also prevent Firefox from inserting a trailing `br` node 3235 * to visualise any trailing space, causing the element to be saved. 3236 * 3237 * > Authors are encouraged to set the 'white-space' property on editing hosts 3238 * > and on markup that was originally created through these editing mechanisms 3239 * > to the value 'pre-wrap'. Default HTML whitespace handling is not well 3240 * > suited to WYSIWYG editing, and line wrapping will not work correctly in 3241 * > some corner cases if 'white-space' is left at its default value. 3242 * 3243 * https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors 3244 * 3245 * @type {string} 3246 */ 3247 const whiteSpace = 'pre-wrap'; 3248 3249 /** 3250 * A minimum width of 1px will prevent the rich text container from collapsing 3251 * to 0 width and hiding the caret. This is useful for inline containers. 3252 */ 3253 const minWidth = '1px'; 3254 function useDefaultStyle() { 3255 return (0,external_wp_element_namespaceObject.useCallback)(element => { 3256 if (!element) return; 3257 element.style.whiteSpace = whiteSpace; 3258 element.style.minWidth = minWidth; 3259 }, []); 3260 } 3261 3262 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-boundary-style.js 3263 /** 3264 * WordPress dependencies 3265 */ 3266 3267 3268 /* 3269 * Calculates and renders the format boundary style when the active formats 3270 * change. 3271 */ 3272 function useBoundaryStyle({ 3273 record 3274 }) { 3275 const ref = (0,external_wp_element_namespaceObject.useRef)(); 3276 const { 3277 activeFormats = [], 3278 replacements, 3279 start 3280 } = record.current; 3281 const activeReplacement = replacements[start]; 3282 (0,external_wp_element_namespaceObject.useEffect)(() => { 3283 // There's no need to recalculate the boundary styles if no formats are 3284 // active, because no boundary styles will be visible. 3285 if ((!activeFormats || !activeFormats.length) && !activeReplacement) { 3286 return; 3287 } 3288 const boundarySelector = '*[data-rich-text-format-boundary]'; 3289 const element = ref.current.querySelector(boundarySelector); 3290 if (!element) { 3291 return; 3292 } 3293 const { 3294 ownerDocument 3295 } = element; 3296 const { 3297 defaultView 3298 } = ownerDocument; 3299 const computedStyle = defaultView.getComputedStyle(element); 3300 const newColor = computedStyle.color.replace(')', ', 0.2)').replace('rgb', 'rgba'); 3301 const selector = `.rich-text:focus $boundarySelector}`; 3302 const rule = `background-color: $newColor}`; 3303 const style = `$selector} {$rule}}`; 3304 const globalStyleId = 'rich-text-boundary-style'; 3305 let globalStyle = ownerDocument.getElementById(globalStyleId); 3306 if (!globalStyle) { 3307 globalStyle = ownerDocument.createElement('style'); 3308 globalStyle.id = globalStyleId; 3309 ownerDocument.head.appendChild(globalStyle); 3310 } 3311 if (globalStyle.innerHTML !== style) { 3312 globalStyle.innerHTML = style; 3313 } 3314 }, [activeFormats, activeReplacement]); 3315 return ref; 3316 } 3317 3318 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-copy-handler.js 3319 /** 3320 * WordPress dependencies 3321 */ 3322 3323 3324 3325 /** 3326 * Internal dependencies 3327 */ 3328 3329 3330 3331 3332 function useCopyHandler(props) { 3333 const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); 3334 propsRef.current = props; 3335 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3336 function onCopy(event) { 3337 const { 3338 record 3339 } = propsRef.current; 3340 const { 3341 ownerDocument 3342 } = element; 3343 if (isCollapsed(record.current) || !element.contains(ownerDocument.activeElement)) { 3344 return; 3345 } 3346 const selectedRecord = slice(record.current); 3347 const plainText = getTextContent(selectedRecord); 3348 const html = toHTMLString({ 3349 value: selectedRecord 3350 }); 3351 event.clipboardData.setData('text/plain', plainText); 3352 event.clipboardData.setData('text/html', html); 3353 event.clipboardData.setData('rich-text', 'true'); 3354 event.preventDefault(); 3355 if (event.type === 'cut') { 3356 ownerDocument.execCommand('delete'); 3357 } 3358 } 3359 element.addEventListener('copy', onCopy); 3360 element.addEventListener('cut', onCopy); 3361 return () => { 3362 element.removeEventListener('copy', onCopy); 3363 element.removeEventListener('cut', onCopy); 3364 }; 3365 }, []); 3366 } 3367 3368 ;// CONCATENATED MODULE: external ["wp","keycodes"] 3369 const external_wp_keycodes_namespaceObject = window["wp"]["keycodes"]; 3370 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-format-boundaries.js 3371 /** 3372 * WordPress dependencies 3373 */ 3374 3375 3376 3377 3378 /** 3379 * Internal dependencies 3380 */ 3381 3382 const EMPTY_ACTIVE_FORMATS = []; 3383 function useFormatBoundaries(props) { 3384 const [, forceRender] = (0,external_wp_element_namespaceObject.useReducer)(() => ({})); 3385 const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); 3386 propsRef.current = props; 3387 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3388 function onKeyDown(event) { 3389 const { 3390 keyCode, 3391 shiftKey, 3392 altKey, 3393 metaKey, 3394 ctrlKey 3395 } = event; 3396 if ( 3397 // Only override left and right keys without modifiers pressed. 3398 shiftKey || altKey || metaKey || ctrlKey || keyCode !== external_wp_keycodes_namespaceObject.LEFT && keyCode !== external_wp_keycodes_namespaceObject.RIGHT) { 3399 return; 3400 } 3401 const { 3402 record, 3403 applyRecord 3404 } = propsRef.current; 3405 const { 3406 text, 3407 formats, 3408 start, 3409 end, 3410 activeFormats: currentActiveFormats = [] 3411 } = record.current; 3412 const collapsed = isCollapsed(record.current); 3413 const { 3414 ownerDocument 3415 } = element; 3416 const { 3417 defaultView 3418 } = ownerDocument; 3419 // To do: ideally, we should look at visual position instead. 3420 const { 3421 direction 3422 } = defaultView.getComputedStyle(element); 3423 const reverseKey = direction === 'rtl' ? external_wp_keycodes_namespaceObject.RIGHT : external_wp_keycodes_namespaceObject.LEFT; 3424 const isReverse = event.keyCode === reverseKey; 3425 3426 // If the selection is collapsed and at the very start, do nothing if 3427 // navigating backward. 3428 // If the selection is collapsed and at the very end, do nothing if 3429 // navigating forward. 3430 if (collapsed && currentActiveFormats.length === 0) { 3431 if (start === 0 && isReverse) { 3432 return; 3433 } 3434 if (end === text.length && !isReverse) { 3435 return; 3436 } 3437 } 3438 3439 // If the selection is not collapsed, let the browser handle collapsing 3440 // the selection for now. Later we could expand this logic to set 3441 // boundary positions if needed. 3442 if (!collapsed) { 3443 return; 3444 } 3445 const formatsBefore = formats[start - 1] || EMPTY_ACTIVE_FORMATS; 3446 const formatsAfter = formats[start] || EMPTY_ACTIVE_FORMATS; 3447 const destination = isReverse ? formatsBefore : formatsAfter; 3448 const isIncreasing = currentActiveFormats.every((format, index) => format === destination[index]); 3449 let newActiveFormatsLength = currentActiveFormats.length; 3450 if (!isIncreasing) { 3451 newActiveFormatsLength--; 3452 } else if (newActiveFormatsLength < destination.length) { 3453 newActiveFormatsLength++; 3454 } 3455 if (newActiveFormatsLength === currentActiveFormats.length) { 3456 record.current._newActiveFormats = destination; 3457 return; 3458 } 3459 event.preventDefault(); 3460 const origin = isReverse ? formatsAfter : formatsBefore; 3461 const source = isIncreasing ? destination : origin; 3462 const newActiveFormats = source.slice(0, newActiveFormatsLength); 3463 const newValue = { 3464 ...record.current, 3465 activeFormats: newActiveFormats 3466 }; 3467 record.current = newValue; 3468 applyRecord(newValue); 3469 forceRender(); 3470 } 3471 element.addEventListener('keydown', onKeyDown); 3472 return () => { 3473 element.removeEventListener('keydown', onKeyDown); 3474 }; 3475 }, []); 3476 } 3477 3478 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-select-object.js 3479 /** 3480 * WordPress dependencies 3481 */ 3482 3483 function useSelectObject() { 3484 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3485 function onClick(event) { 3486 const { 3487 target 3488 } = event; 3489 3490 // If the child element has no text content, it must be an object. 3491 if (target === element || target.textContent && target.isContentEditable) { 3492 return; 3493 } 3494 const { 3495 ownerDocument 3496 } = target; 3497 const { 3498 defaultView 3499 } = ownerDocument; 3500 const selection = defaultView.getSelection(); 3501 3502 // If it's already selected, do nothing and let default behavior 3503 // happen. This means it's "click-through". 3504 if (selection.containsNode(target)) return; 3505 const range = ownerDocument.createRange(); 3506 // If the target is within a non editable element, select the non 3507 // editable element. 3508 const nodeToSelect = target.isContentEditable ? target : target.closest('[contenteditable]'); 3509 range.selectNode(nodeToSelect); 3510 selection.removeAllRanges(); 3511 selection.addRange(range); 3512 event.preventDefault(); 3513 } 3514 function onFocusIn(event) { 3515 // When there is incoming focus from a link, select the object. 3516 if (event.relatedTarget && !element.contains(event.relatedTarget) && event.relatedTarget.tagName === 'A') { 3517 onClick(event); 3518 } 3519 } 3520 element.addEventListener('click', onClick); 3521 element.addEventListener('focusin', onFocusIn); 3522 return () => { 3523 element.removeEventListener('click', onClick); 3524 element.removeEventListener('focusin', onFocusIn); 3525 }; 3526 }, []); 3527 } 3528 3529 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/update-formats.js 3530 /** 3531 * Internal dependencies 3532 */ 3533 3534 3535 3536 /** @typedef {import('./types').RichTextValue} RichTextValue */ 3537 3538 /** 3539 * Efficiently updates all the formats from `start` (including) until `end` 3540 * (excluding) with the active formats. Mutates `value`. 3541 * 3542 * @param {Object} $1 Named paramentes. 3543 * @param {RichTextValue} $1.value Value te update. 3544 * @param {number} $1.start Index to update from. 3545 * @param {number} $1.end Index to update until. 3546 * @param {Array} $1.formats Replacement formats. 3547 * 3548 * @return {RichTextValue} Mutated value. 3549 */ 3550 function updateFormats({ 3551 value, 3552 start, 3553 end, 3554 formats 3555 }) { 3556 // Start and end may be switched in case of delete. 3557 const min = Math.min(start, end); 3558 const max = Math.max(start, end); 3559 const formatsBefore = value.formats[min - 1] || []; 3560 const formatsAfter = value.formats[max] || []; 3561 3562 // First, fix the references. If any format right before or after are 3563 // equal, the replacement format should use the same reference. 3564 value.activeFormats = formats.map((format, index) => { 3565 if (formatsBefore[index]) { 3566 if (isFormatEqual(format, formatsBefore[index])) { 3567 return formatsBefore[index]; 3568 } 3569 } else if (formatsAfter[index]) { 3570 if (isFormatEqual(format, formatsAfter[index])) { 3571 return formatsAfter[index]; 3572 } 3573 } 3574 return format; 3575 }); 3576 while (--end >= start) { 3577 if (value.activeFormats.length > 0) { 3578 value.formats[end] = value.activeFormats; 3579 } else { 3580 delete value.formats[end]; 3581 } 3582 } 3583 return value; 3584 } 3585 3586 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-input-and-selection.js 3587 /** 3588 * WordPress dependencies 3589 */ 3590 3591 3592 3593 /** 3594 * Internal dependencies 3595 */ 3596 3597 3598 3599 /** 3600 * All inserting input types that would insert HTML into the DOM. 3601 * 3602 * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes 3603 * 3604 * @type {Set} 3605 */ 3606 const INSERTION_INPUT_TYPES_TO_IGNORE = new Set(['insertParagraph', 'insertOrderedList', 'insertUnorderedList', 'insertHorizontalRule', 'insertLink']); 3607 const use_input_and_selection_EMPTY_ACTIVE_FORMATS = []; 3608 const PLACEHOLDER_ATTR_NAME = 'data-rich-text-placeholder'; 3609 3610 /** 3611 * If the selection is set on the placeholder element, collapse the selection to 3612 * the start (before the placeholder). 3613 * 3614 * @param {Window} defaultView 3615 */ 3616 function fixPlaceholderSelection(defaultView) { 3617 const selection = defaultView.getSelection(); 3618 const { 3619 anchorNode, 3620 anchorOffset 3621 } = selection; 3622 if (anchorNode.nodeType !== anchorNode.ELEMENT_NODE) { 3623 return; 3624 } 3625 const targetNode = anchorNode.childNodes[anchorOffset]; 3626 if (!targetNode || targetNode.nodeType !== targetNode.ELEMENT_NODE || !targetNode.hasAttribute(PLACEHOLDER_ATTR_NAME)) { 3627 return; 3628 } 3629 selection.collapseToStart(); 3630 } 3631 function useInputAndSelection(props) { 3632 const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); 3633 propsRef.current = props; 3634 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3635 const { 3636 ownerDocument 3637 } = element; 3638 const { 3639 defaultView 3640 } = ownerDocument; 3641 let isComposing = false; 3642 function onInput(event) { 3643 // Do not trigger a change if characters are being composed. 3644 // Browsers will usually emit a final `input` event when the 3645 // characters are composed. 3646 // As of December 2019, Safari doesn't support 3647 // nativeEvent.isComposing. 3648 if (isComposing) { 3649 return; 3650 } 3651 let inputType; 3652 if (event) { 3653 inputType = event.inputType; 3654 } 3655 const { 3656 record, 3657 applyRecord, 3658 createRecord, 3659 handleChange 3660 } = propsRef.current; 3661 3662 // The browser formatted something or tried to insert HTML. 3663 // Overwrite it. It will be handled later by the format library if 3664 // needed. 3665 if (inputType && (inputType.indexOf('format') === 0 || INSERTION_INPUT_TYPES_TO_IGNORE.has(inputType))) { 3666 applyRecord(record.current); 3667 return; 3668 } 3669 const currentValue = createRecord(); 3670 const { 3671 start, 3672 activeFormats: oldActiveFormats = [] 3673 } = record.current; 3674 3675 // Update the formats between the last and new caret position. 3676 const change = updateFormats({ 3677 value: currentValue, 3678 start, 3679 end: currentValue.start, 3680 formats: oldActiveFormats 3681 }); 3682 handleChange(change); 3683 } 3684 3685 /** 3686 * Syncs the selection to local state. A callback for the 3687 * `selectionchange` event. 3688 */ 3689 function handleSelectionChange() { 3690 const { 3691 record, 3692 applyRecord, 3693 createRecord, 3694 onSelectionChange 3695 } = propsRef.current; 3696 3697 // Check if the implementor disabled editing. `contentEditable` 3698 // does disable input, but not text selection, so we must ignore 3699 // selection changes. 3700 if (element.contentEditable !== 'true') { 3701 return; 3702 } 3703 3704 // Ensure the active element is the rich text element. 3705 if (ownerDocument.activeElement !== element) { 3706 // If it is not, we can stop listening for selection changes. 3707 // We resume listening when the element is focused. 3708 ownerDocument.removeEventListener('selectionchange', handleSelectionChange); 3709 return; 3710 } 3711 3712 // In case of a keyboard event, ignore selection changes during 3713 // composition. 3714 if (isComposing) { 3715 return; 3716 } 3717 const { 3718 start, 3719 end, 3720 text 3721 } = createRecord(); 3722 const oldRecord = record.current; 3723 3724 // Fallback mechanism for IE11, which doesn't support the input event. 3725 // Any input results in a selection change. 3726 if (text !== oldRecord.text) { 3727 onInput(); 3728 return; 3729 } 3730 if (start === oldRecord.start && end === oldRecord.end) { 3731 // Sometimes the browser may set the selection on the placeholder 3732 // element, in which case the caret is not visible. We need to set 3733 // the caret before the placeholder if that's the case. 3734 if (oldRecord.text.length === 0 && start === 0) { 3735 fixPlaceholderSelection(defaultView); 3736 } 3737 return; 3738 } 3739 const newValue = { 3740 ...oldRecord, 3741 start, 3742 end, 3743 // _newActiveFormats may be set on arrow key navigation to control 3744 // the right boundary position. If undefined, getActiveFormats will 3745 // give the active formats according to the browser. 3746 activeFormats: oldRecord._newActiveFormats, 3747 _newActiveFormats: undefined 3748 }; 3749 const newActiveFormats = getActiveFormats(newValue, use_input_and_selection_EMPTY_ACTIVE_FORMATS); 3750 3751 // Update the value with the new active formats. 3752 newValue.activeFormats = newActiveFormats; 3753 3754 // It is important that the internal value is updated first, 3755 // otherwise the value will be wrong on render! 3756 record.current = newValue; 3757 applyRecord(newValue, { 3758 domOnly: true 3759 }); 3760 onSelectionChange(start, end); 3761 } 3762 function onCompositionStart() { 3763 isComposing = true; 3764 // Do not update the selection when characters are being composed as 3765 // this rerenders the component and might destroy internal browser 3766 // editing state. 3767 ownerDocument.removeEventListener('selectionchange', handleSelectionChange); 3768 // Remove the placeholder. Since the rich text value doesn't update 3769 // during composition, the placeholder doesn't get removed. There's 3770 // no need to re-add it, when the value is updated on compositionend 3771 // it will be re-added when the value is empty. 3772 element.querySelector(`[$PLACEHOLDER_ATTR_NAME}]`)?.remove(); 3773 } 3774 function onCompositionEnd() { 3775 isComposing = false; 3776 // Ensure the value is up-to-date for browsers that don't emit a final 3777 // input event after composition. 3778 onInput({ 3779 inputType: 'insertText' 3780 }); 3781 // Tracking selection changes can be resumed. 3782 ownerDocument.addEventListener('selectionchange', handleSelectionChange); 3783 } 3784 function onFocus() { 3785 const { 3786 record, 3787 isSelected, 3788 onSelectionChange, 3789 applyRecord 3790 } = propsRef.current; 3791 3792 // When the whole editor is editable, let writing flow handle 3793 // selection. 3794 if (element.parentElement.closest('[contenteditable="true"]')) { 3795 return; 3796 } 3797 if (!isSelected) { 3798 // We know for certain that on focus, the old selection is invalid. 3799 // It will be recalculated on the next mouseup, keyup, or touchend 3800 // event. 3801 const index = undefined; 3802 record.current = { 3803 ...record.current, 3804 start: index, 3805 end: index, 3806 activeFormats: use_input_and_selection_EMPTY_ACTIVE_FORMATS 3807 }; 3808 } else { 3809 applyRecord(record.current, { 3810 domOnly: true 3811 }); 3812 } 3813 onSelectionChange(record.current.start, record.current.end); 3814 ownerDocument.addEventListener('selectionchange', handleSelectionChange); 3815 } 3816 element.addEventListener('input', onInput); 3817 element.addEventListener('compositionstart', onCompositionStart); 3818 element.addEventListener('compositionend', onCompositionEnd); 3819 element.addEventListener('focus', onFocus); 3820 return () => { 3821 element.removeEventListener('input', onInput); 3822 element.removeEventListener('compositionstart', onCompositionStart); 3823 element.removeEventListener('compositionend', onCompositionEnd); 3824 element.removeEventListener('focus', onFocus); 3825 }; 3826 }, []); 3827 } 3828 3829 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-selection-change-compat.js 3830 /** 3831 * WordPress dependencies 3832 */ 3833 3834 3835 /** 3836 * Internal dependencies 3837 */ 3838 3839 3840 /** 3841 * Sometimes some browsers are not firing a `selectionchange` event when 3842 * changing the selection by mouse or keyboard. This hook makes sure that, if we 3843 * detect no `selectionchange` or `input` event between the up and down events, 3844 * we fire a `selectionchange` event. 3845 * 3846 * @return {import('@wordpress/compose').RefEffect} A ref effect attaching the 3847 * listeners. 3848 */ 3849 function useSelectionChangeCompat() { 3850 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3851 const { 3852 ownerDocument 3853 } = element; 3854 const { 3855 defaultView 3856 } = ownerDocument; 3857 const selection = defaultView?.getSelection(); 3858 let range; 3859 function getRange() { 3860 return selection.rangeCount ? selection.getRangeAt(0) : null; 3861 } 3862 function onDown(event) { 3863 const type = event.type === 'keydown' ? 'keyup' : 'pointerup'; 3864 function onCancel() { 3865 ownerDocument.removeEventListener(type, onUp); 3866 ownerDocument.removeEventListener('selectionchange', onCancel); 3867 ownerDocument.removeEventListener('input', onCancel); 3868 } 3869 function onUp() { 3870 onCancel(); 3871 if (isRangeEqual(range, getRange())) return; 3872 ownerDocument.dispatchEvent(new Event('selectionchange')); 3873 } 3874 ownerDocument.addEventListener(type, onUp); 3875 ownerDocument.addEventListener('selectionchange', onCancel); 3876 ownerDocument.addEventListener('input', onCancel); 3877 range = getRange(); 3878 } 3879 element.addEventListener('pointerdown', onDown); 3880 element.addEventListener('keydown', onDown); 3881 return () => { 3882 element.removeEventListener('pointerdown', onDown); 3883 element.removeEventListener('keydown', onDown); 3884 }; 3885 }, []); 3886 } 3887 3888 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-delete.js 3889 /** 3890 * WordPress dependencies 3891 */ 3892 3893 3894 3895 3896 /** 3897 * Internal dependencies 3898 */ 3899 3900 function useDelete(props) { 3901 const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); 3902 propsRef.current = props; 3903 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3904 function onKeyDown(event) { 3905 const { 3906 keyCode 3907 } = event; 3908 const { 3909 createRecord, 3910 handleChange 3911 } = propsRef.current; 3912 if (event.defaultPrevented) { 3913 return; 3914 } 3915 if (keyCode !== external_wp_keycodes_namespaceObject.DELETE && keyCode !== external_wp_keycodes_namespaceObject.BACKSPACE) { 3916 return; 3917 } 3918 const currentValue = createRecord(); 3919 const { 3920 start, 3921 end, 3922 text 3923 } = currentValue; 3924 3925 // Always handle full content deletion ourselves. 3926 if (start === 0 && end !== 0 && end === text.length) { 3927 handleChange(remove_remove(currentValue)); 3928 event.preventDefault(); 3929 } 3930 } 3931 element.addEventListener('keydown', onKeyDown); 3932 return () => { 3933 element.removeEventListener('keydown', onKeyDown); 3934 }; 3935 }, []); 3936 } 3937 3938 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/index.js 3939 /** 3940 * WordPress dependencies 3941 */ 3942 3943 3944 3945 3946 /** 3947 * Internal dependencies 3948 */ 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 function useRichText({ 3961 value = '', 3962 selectionStart, 3963 selectionEnd, 3964 placeholder, 3965 onSelectionChange, 3966 preserveWhiteSpace, 3967 onChange, 3968 __unstableDisableFormats: disableFormats, 3969 __unstableIsSelected: isSelected, 3970 __unstableDependencies = [], 3971 __unstableAfterParse, 3972 __unstableBeforeSerialize, 3973 __unstableAddInvisibleFormats 3974 }) { 3975 const registry = (0,external_wp_data_namespaceObject.useRegistry)(); 3976 const [, forceRender] = (0,external_wp_element_namespaceObject.useReducer)(() => ({})); 3977 const ref = (0,external_wp_element_namespaceObject.useRef)(); 3978 function createRecord() { 3979 const { 3980 ownerDocument: { 3981 defaultView 3982 } 3983 } = ref.current; 3984 const selection = defaultView.getSelection(); 3985 const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null; 3986 return create({ 3987 element: ref.current, 3988 range, 3989 __unstableIsEditableTree: true 3990 }); 3991 } 3992 function applyRecord(newRecord, { 3993 domOnly 3994 } = {}) { 3995 apply({ 3996 value: newRecord, 3997 current: ref.current, 3998 prepareEditableTree: __unstableAddInvisibleFormats, 3999 __unstableDomOnly: domOnly, 4000 placeholder 4001 }); 4002 } 4003 4004 // Internal values are updated synchronously, unlike props and state. 4005 const _value = (0,external_wp_element_namespaceObject.useRef)(value); 4006 const record = (0,external_wp_element_namespaceObject.useRef)(); 4007 function setRecordFromProps() { 4008 _value.current = value; 4009 record.current = value; 4010 if (!(value instanceof RichTextData)) { 4011 record.current = value ? RichTextData.fromHTMLString(value, { 4012 preserveWhiteSpace 4013 }) : RichTextData.empty(); 4014 } 4015 // To do: make rich text internally work with RichTextData. 4016 record.current = { 4017 text: record.current.text, 4018 formats: record.current.formats, 4019 replacements: record.current.replacements 4020 }; 4021 if (disableFormats) { 4022 record.current.formats = Array(value.length); 4023 record.current.replacements = Array(value.length); 4024 } 4025 if (__unstableAfterParse) { 4026 record.current.formats = __unstableAfterParse(record.current); 4027 } 4028 record.current.start = selectionStart; 4029 record.current.end = selectionEnd; 4030 } 4031 const hadSelectionUpdate = (0,external_wp_element_namespaceObject.useRef)(false); 4032 if (!record.current) { 4033 hadSelectionUpdate.current = isSelected; 4034 setRecordFromProps(); 4035 } else if (selectionStart !== record.current.start || selectionEnd !== record.current.end) { 4036 hadSelectionUpdate.current = isSelected; 4037 record.current = { 4038 ...record.current, 4039 start: selectionStart, 4040 end: selectionEnd, 4041 activeFormats: undefined 4042 }; 4043 } 4044 4045 /** 4046 * Sync the value to global state. The node tree and selection will also be 4047 * updated if differences are found. 4048 * 4049 * @param {Object} newRecord The record to sync and apply. 4050 */ 4051 function handleChange(newRecord) { 4052 record.current = newRecord; 4053 applyRecord(newRecord); 4054 if (disableFormats) { 4055 _value.current = newRecord.text; 4056 } else { 4057 const newFormats = __unstableBeforeSerialize ? __unstableBeforeSerialize(newRecord) : newRecord.formats; 4058 newRecord = { 4059 ...newRecord, 4060 formats: newFormats 4061 }; 4062 if (typeof value === 'string') { 4063 _value.current = toHTMLString({ 4064 value: newRecord, 4065 preserveWhiteSpace 4066 }); 4067 } else { 4068 _value.current = new RichTextData(newRecord); 4069 } 4070 } 4071 const { 4072 start, 4073 end, 4074 formats, 4075 text 4076 } = record.current; 4077 4078 // Selection must be updated first, so it is recorded in history when 4079 // the content change happens. 4080 // We batch both calls to only attempt to rerender once. 4081 registry.batch(() => { 4082 onSelectionChange(start, end); 4083 onChange(_value.current, { 4084 __unstableFormats: formats, 4085 __unstableText: text 4086 }); 4087 }); 4088 forceRender(); 4089 } 4090 function applyFromProps() { 4091 setRecordFromProps(); 4092 applyRecord(record.current); 4093 } 4094 const didMount = (0,external_wp_element_namespaceObject.useRef)(false); 4095 4096 // Value updates must happen synchonously to avoid overwriting newer values. 4097 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 4098 if (didMount.current && value !== _value.current) { 4099 applyFromProps(); 4100 forceRender(); 4101 } 4102 }, [value]); 4103 4104 // Value updates must happen synchonously to avoid overwriting newer values. 4105 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 4106 if (!hadSelectionUpdate.current) { 4107 return; 4108 } 4109 if (ref.current.ownerDocument.activeElement !== ref.current) { 4110 ref.current.focus(); 4111 } 4112 applyRecord(record.current); 4113 hadSelectionUpdate.current = false; 4114 }, [hadSelectionUpdate.current]); 4115 const mergedRefs = (0,external_wp_compose_namespaceObject.useMergeRefs)([ref, useDefaultStyle(), useBoundaryStyle({ 4116 record 4117 }), useCopyHandler({ 4118 record 4119 }), useSelectObject(), useFormatBoundaries({ 4120 record, 4121 applyRecord 4122 }), useDelete({ 4123 createRecord, 4124 handleChange 4125 }), useInputAndSelection({ 4126 record, 4127 applyRecord, 4128 createRecord, 4129 handleChange, 4130 isSelected, 4131 onSelectionChange 4132 }), useSelectionChangeCompat(), (0,external_wp_compose_namespaceObject.useRefEffect)(() => { 4133 applyFromProps(); 4134 didMount.current = true; 4135 }, [placeholder, ...__unstableDependencies])]); 4136 return { 4137 value: record.current, 4138 // A function to get the most recent value so event handlers in 4139 // useRichText implementations have access to it. For example when 4140 // listening to input events, we internally update the state, but this 4141 // state is not yet available to the input event handler because React 4142 // may re-render asynchronously. 4143 getValue: () => record.current, 4144 onChange: handleChange, 4145 ref: mergedRefs 4146 }; 4147 } 4148 function __experimentalRichText() {} 4149 4150 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/index.js 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 /** 4180 * An object which represents a formatted string. See main `@wordpress/rich-text` 4181 * documentation for more information. 4182 */ 4183 4184 (window.wp = window.wp || {}).richText = __webpack_exports__; 4185 /******/ })() 4186 ;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Tue Mar 19 08:20:01 2024 | Cross-referenced by PHPXref |