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