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