[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /******/ (() => { // webpackBootstrap 2 /******/ "use strict"; 3 /******/ // The require scope 4 /******/ var __webpack_require__ = {}; 5 /******/ 6 /************************************************************************/ 7 /******/ /* webpack/runtime/compat get default export */ 8 /******/ (() => { 9 /******/ // getDefaultExport function for compatibility with non-harmony modules 10 /******/ __webpack_require__.n = (module) => { 11 /******/ var getter = module && module.__esModule ? 12 /******/ () => (module['default']) : 13 /******/ () => (module); 14 /******/ __webpack_require__.d(getter, { a: getter }); 15 /******/ return getter; 16 /******/ }; 17 /******/ })(); 18 /******/ 19 /******/ /* webpack/runtime/define property getters */ 20 /******/ (() => { 21 /******/ // define getter functions for harmony exports 22 /******/ __webpack_require__.d = (exports, definition) => { 23 /******/ for(var key in definition) { 24 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 25 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 26 /******/ } 27 /******/ } 28 /******/ }; 29 /******/ })(); 30 /******/ 31 /******/ /* webpack/runtime/hasOwnProperty shorthand */ 32 /******/ (() => { 33 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 34 /******/ })(); 35 /******/ 36 /******/ /* webpack/runtime/make namespace object */ 37 /******/ (() => { 38 /******/ // define __esModule on exports 39 /******/ __webpack_require__.r = (exports) => { 40 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 41 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 42 /******/ } 43 /******/ Object.defineProperty(exports, '__esModule', { value: true }); 44 /******/ }; 45 /******/ })(); 46 /******/ 47 /************************************************************************/ 48 var __webpack_exports__ = {}; 49 // ESM COMPAT FLAG 50 __webpack_require__.r(__webpack_exports__); 51 52 // EXPORTS 53 __webpack_require__.d(__webpack_exports__, { 54 RichTextData: () => (/* reexport */ RichTextData), 55 __experimentalRichText: () => (/* reexport */ __experimentalRichText), 56 __unstableCreateElement: () => (/* reexport */ createElement), 57 __unstableToDom: () => (/* reexport */ toDom), 58 __unstableUseRichText: () => (/* reexport */ useRichText), 59 applyFormat: () => (/* reexport */ applyFormat), 60 concat: () => (/* reexport */ concat), 61 create: () => (/* reexport */ create), 62 getActiveFormat: () => (/* reexport */ getActiveFormat), 63 getActiveFormats: () => (/* reexport */ getActiveFormats), 64 getActiveObject: () => (/* reexport */ getActiveObject), 65 getTextContent: () => (/* reexport */ getTextContent), 66 insert: () => (/* reexport */ insert), 67 insertObject: () => (/* reexport */ insertObject), 68 isCollapsed: () => (/* reexport */ isCollapsed), 69 isEmpty: () => (/* reexport */ isEmpty), 70 join: () => (/* reexport */ join), 71 registerFormatType: () => (/* reexport */ registerFormatType), 72 remove: () => (/* reexport */ remove_remove), 73 removeFormat: () => (/* reexport */ removeFormat), 74 replace: () => (/* reexport */ replace_replace), 75 slice: () => (/* reexport */ slice), 76 split: () => (/* reexport */ split), 77 store: () => (/* reexport */ store), 78 toHTMLString: () => (/* reexport */ toHTMLString), 79 toggleFormat: () => (/* reexport */ toggleFormat), 80 unregisterFormatType: () => (/* reexport */ unregisterFormatType), 81 useAnchor: () => (/* reexport */ useAnchor), 82 useAnchorRef: () => (/* reexport */ useAnchorRef) 83 }); 84 85 // NAMESPACE OBJECT: ./node_modules/@wordpress/rich-text/build-module/store/selectors.js 86 var selectors_namespaceObject = {}; 87 __webpack_require__.r(selectors_namespaceObject); 88 __webpack_require__.d(selectors_namespaceObject, { 89 getFormatType: () => (getFormatType), 90 getFormatTypeForBareElement: () => (getFormatTypeForBareElement), 91 getFormatTypeForClassName: () => (getFormatTypeForClassName), 92 getFormatTypes: () => (getFormatTypes) 93 }); 94 95 // NAMESPACE OBJECT: ./node_modules/@wordpress/rich-text/build-module/store/actions.js 96 var actions_namespaceObject = {}; 97 __webpack_require__.r(actions_namespaceObject); 98 __webpack_require__.d(actions_namespaceObject, { 99 addFormatTypes: () => (addFormatTypes), 100 removeFormatTypes: () => (removeFormatTypes) 101 }); 102 103 ;// CONCATENATED MODULE: external ["wp","data"] 104 const external_wp_data_namespaceObject = window["wp"]["data"]; 105 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/store/reducer.js 106 /** 107 * WordPress dependencies 108 */ 109 110 111 /** 112 * Reducer managing the format types 113 * 114 * @param {Object} state Current state. 115 * @param {Object} action Dispatched action. 116 * 117 * @return {Object} Updated state. 118 */ 119 function formatTypes(state = {}, action) { 120 switch (action.type) { 121 case 'ADD_FORMAT_TYPES': 122 return { 123 ...state, 124 // Key format types by their name. 125 ...action.formatTypes.reduce((newFormatTypes, type) => ({ 126 ...newFormatTypes, 127 [type.name]: type 128 }), {}) 129 }; 130 case 'REMOVE_FORMAT_TYPES': 131 return Object.fromEntries(Object.entries(state).filter(([key]) => !action.names.includes(key))); 132 } 133 return state; 134 } 135 /* harmony default export */ const reducer = ((0,external_wp_data_namespaceObject.combineReducers)({ 136 formatTypes 137 })); 138 139 ;// CONCATENATED MODULE: ./node_modules/@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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: external ["wp","escapeHtml"] 585 const external_wp_escapeHtml_namespaceObject = window["wp"]["escapeHtml"]; 586 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 ;// CONCATENATED MODULE: ./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 === 'script') { 900 pointer = append(getParent(pointer), fromFormat({ 901 type: 'script', 902 isEditableTree 903 })); 904 append(pointer, { 905 html: decodeURIComponent(attributes['data-rich-text-script']) 906 }); 907 } else if (formatType?.contentEditable === false) { 908 // For non editable formats, render the stored inner HTML. 909 pointer = append(getParent(pointer), fromFormat({ 910 ...replacement, 911 isEditableTree, 912 boundaryClass: start === i && end === i + 1 913 })); 914 if (innerHTML) { 915 append(pointer, { 916 html: innerHTML 917 }); 918 } 919 } else { 920 pointer = append(getParent(pointer), fromFormat({ 921 ...replacement, 922 object: true, 923 isEditableTree 924 })); 925 } 926 // Ensure pointer is text node. 927 pointer = append(getParent(pointer), ''); 928 } else if (!preserveWhiteSpace && character === '\n') { 929 pointer = append(getParent(pointer), { 930 type: 'br', 931 attributes: isEditableTree ? { 932 'data-rich-text-line-break': 'true' 933 } : undefined, 934 object: true 935 }); 936 // Ensure pointer is text node. 937 pointer = append(getParent(pointer), ''); 938 } else if (!isText(pointer)) { 939 pointer = append(getParent(pointer), character); 940 } else { 941 appendText(pointer, character); 942 } 943 if (onStartIndex && start === i + 1) { 944 onStartIndex(tree, pointer); 945 } 946 if (onEndIndex && end === i + 1) { 947 onEndIndex(tree, pointer); 948 } 949 if (shouldInsertPadding && i === text.length) { 950 append(getParent(pointer), ZWNBSP); 951 952 // We CANNOT use CSS to add a placeholder with pseudo elements on 953 // the main block wrappers because that could clash with theme CSS. 954 if (placeholder && text.length === 0) { 955 append(getParent(pointer), { 956 type: 'span', 957 attributes: { 958 'data-rich-text-placeholder': placeholder, 959 // Necessary to prevent the placeholder from catching 960 // selection and being editable. 961 style: 'pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;' 962 } 963 }); 964 } 965 } 966 lastCharacterFormats = characterFormats; 967 lastCharacter = character; 968 } 969 return tree; 970 } 971 972 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/to-html-string.js 973 /** 974 * WordPress dependencies 975 */ 976 977 978 979 /** 980 * Internal dependencies 981 */ 982 983 984 985 /** @typedef {import('./types').RichTextValue} RichTextValue */ 986 987 /** 988 * Create an HTML string from a Rich Text value. 989 * 990 * @param {Object} $1 Named argements. 991 * @param {RichTextValue} $1.value Rich text value. 992 * @param {boolean} [$1.preserveWhiteSpace] Preserves newlines if true. 993 * 994 * @return {string} HTML string. 995 */ 996 function toHTMLString({ 997 value, 998 preserveWhiteSpace 999 }) { 1000 const tree = toTree({ 1001 value, 1002 preserveWhiteSpace, 1003 createEmpty, 1004 append, 1005 getLastChild, 1006 getParent, 1007 isText, 1008 getText, 1009 remove, 1010 appendText 1011 }); 1012 return createChildrenHTML(tree.children); 1013 } 1014 function createEmpty() { 1015 return {}; 1016 } 1017 function getLastChild({ 1018 children 1019 }) { 1020 return children && children[children.length - 1]; 1021 } 1022 function append(parent, object) { 1023 if (typeof object === 'string') { 1024 object = { 1025 text: object 1026 }; 1027 } 1028 object.parent = parent; 1029 parent.children = parent.children || []; 1030 parent.children.push(object); 1031 return object; 1032 } 1033 function appendText(object, text) { 1034 object.text += text; 1035 } 1036 function getParent({ 1037 parent 1038 }) { 1039 return parent; 1040 } 1041 function isText({ 1042 text 1043 }) { 1044 return typeof text === 'string'; 1045 } 1046 function getText({ 1047 text 1048 }) { 1049 return text; 1050 } 1051 function remove(object) { 1052 const index = object.parent.children.indexOf(object); 1053 if (index !== -1) { 1054 object.parent.children.splice(index, 1); 1055 } 1056 return object; 1057 } 1058 function createElementHTML({ 1059 type, 1060 attributes, 1061 object, 1062 children 1063 }) { 1064 let attributeString = ''; 1065 for (const key in attributes) { 1066 if (!(0,external_wp_escapeHtml_namespaceObject.isValidAttributeName)(key)) { 1067 continue; 1068 } 1069 attributeString += ` $key}="${(0,external_wp_escapeHtml_namespaceObject.escapeAttribute)(attributes[key])}"`; 1070 } 1071 if (object) { 1072 return `<$type}$attributeString}>`; 1073 } 1074 return `<$type}$attributeString}>$createChildrenHTML(children)}</$type}>`; 1075 } 1076 function createChildrenHTML(children = []) { 1077 return children.map(child => { 1078 if (child.html !== undefined) { 1079 return child.html; 1080 } 1081 return child.text === undefined ? createElementHTML(child) : (0,external_wp_escapeHtml_namespaceObject.escapeEditableHTML)(child.text); 1082 }).join(''); 1083 } 1084 1085 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-text-content.js 1086 /** 1087 * Internal dependencies 1088 */ 1089 1090 1091 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1092 1093 /** 1094 * Get the textual content of a Rich Text value. This is similar to 1095 * `Element.textContent`. 1096 * 1097 * @param {RichTextValue} value Value to use. 1098 * 1099 * @return {string} The text content. 1100 */ 1101 function getTextContent({ 1102 text 1103 }) { 1104 return text.replace(OBJECT_REPLACEMENT_CHARACTER, ''); 1105 } 1106 1107 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/create.js 1108 /** 1109 * WordPress dependencies 1110 */ 1111 1112 1113 /** 1114 * Internal dependencies 1115 */ 1116 1117 1118 1119 1120 1121 1122 1123 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1124 1125 function createEmptyValue() { 1126 return { 1127 formats: [], 1128 replacements: [], 1129 text: '' 1130 }; 1131 } 1132 function toFormat({ 1133 tagName, 1134 attributes 1135 }) { 1136 let formatType; 1137 if (attributes && attributes.class) { 1138 formatType = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForClassName(attributes.class); 1139 if (formatType) { 1140 // Preserve any additional classes. 1141 attributes.class = ` $attributes.class} `.replace(` $formatType.className} `, ' ').trim(); 1142 if (!attributes.class) { 1143 delete attributes.class; 1144 } 1145 } 1146 } 1147 if (!formatType) { 1148 formatType = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForBareElement(tagName); 1149 } 1150 if (!formatType) { 1151 return attributes ? { 1152 type: tagName, 1153 attributes 1154 } : { 1155 type: tagName 1156 }; 1157 } 1158 if (formatType.__experimentalCreatePrepareEditableTree && !formatType.__experimentalCreateOnChangeEditableValue) { 1159 return null; 1160 } 1161 if (!attributes) { 1162 return { 1163 formatType, 1164 type: formatType.name, 1165 tagName 1166 }; 1167 } 1168 const registeredAttributes = {}; 1169 const unregisteredAttributes = {}; 1170 const _attributes = { 1171 ...attributes 1172 }; 1173 for (const key in formatType.attributes) { 1174 const name = formatType.attributes[key]; 1175 registeredAttributes[key] = _attributes[name]; 1176 1177 // delete the attribute and what's left is considered 1178 // to be unregistered. 1179 delete _attributes[name]; 1180 if (typeof registeredAttributes[key] === 'undefined') { 1181 delete registeredAttributes[key]; 1182 } 1183 } 1184 for (const name in _attributes) { 1185 unregisteredAttributes[name] = attributes[name]; 1186 } 1187 if (formatType.contentEditable === false) { 1188 delete unregisteredAttributes.contenteditable; 1189 } 1190 return { 1191 formatType, 1192 type: formatType.name, 1193 tagName, 1194 attributes: registeredAttributes, 1195 unregisteredAttributes 1196 }; 1197 } 1198 1199 /** 1200 * The RichTextData class is used to instantiate a wrapper around rich text 1201 * values, with methods that can be used to transform or manipulate the data. 1202 * 1203 * - Create an empty instance: `new RichTextData()`. 1204 * - Create one from an HTML string: `RichTextData.fromHTMLString( 1205 * '<em>hello</em>' )`. 1206 * - Create one from a wrapper HTMLElement: `RichTextData.fromHTMLElement( 1207 * document.querySelector( 'p' ) )`. 1208 * - Create one from plain text: `RichTextData.fromPlainText( '1\n2' )`. 1209 * - Create one from a rich text value: `new RichTextData( { text: '...', 1210 * formats: [ ... ] } )`. 1211 * 1212 * @todo Add methods to manipulate the data, such as applyFormat, slice etc. 1213 */ 1214 class RichTextData { 1215 #value; 1216 static empty() { 1217 return new RichTextData(); 1218 } 1219 static fromPlainText(text) { 1220 return new RichTextData(create({ 1221 text 1222 })); 1223 } 1224 static fromHTMLString(html) { 1225 return new RichTextData(create({ 1226 html 1227 })); 1228 } 1229 static fromHTMLElement(htmlElement, options = {}) { 1230 const { 1231 preserveWhiteSpace = false 1232 } = options; 1233 const element = preserveWhiteSpace ? htmlElement : collapseWhiteSpace(htmlElement); 1234 const richTextData = new RichTextData(create({ 1235 element 1236 })); 1237 Object.defineProperty(richTextData, 'originalHTML', { 1238 value: htmlElement.innerHTML 1239 }); 1240 return richTextData; 1241 } 1242 constructor(init = createEmptyValue()) { 1243 this.#value = init; 1244 } 1245 toPlainText() { 1246 return getTextContent(this.#value); 1247 } 1248 // We could expose `toHTMLElement` at some point as well, but we'd only use 1249 // it internally. 1250 toHTMLString({ 1251 preserveWhiteSpace 1252 } = {}) { 1253 return this.originalHTML || toHTMLString({ 1254 value: this.#value, 1255 preserveWhiteSpace 1256 }); 1257 } 1258 valueOf() { 1259 return this.toHTMLString(); 1260 } 1261 toString() { 1262 return this.toHTMLString(); 1263 } 1264 toJSON() { 1265 return this.toHTMLString(); 1266 } 1267 get length() { 1268 return this.text.length; 1269 } 1270 get formats() { 1271 return this.#value.formats; 1272 } 1273 get replacements() { 1274 return this.#value.replacements; 1275 } 1276 get text() { 1277 return this.#value.text; 1278 } 1279 } 1280 for (const name of Object.getOwnPropertyNames(String.prototype)) { 1281 if (RichTextData.prototype.hasOwnProperty(name)) { 1282 continue; 1283 } 1284 Object.defineProperty(RichTextData.prototype, name, { 1285 value(...args) { 1286 // Should we convert back to RichTextData? 1287 return this.toHTMLString()[name](...args); 1288 } 1289 }); 1290 } 1291 1292 /** 1293 * Create a RichText value from an `Element` tree (DOM), an HTML string or a 1294 * plain text string, with optionally a `Range` object to set the selection. If 1295 * called without any input, an empty value will be created. The optional 1296 * functions can be used to filter out content. 1297 * 1298 * A value will have the following shape, which you are strongly encouraged not 1299 * to modify without the use of helper functions: 1300 * 1301 * ```js 1302 * { 1303 * text: string, 1304 * formats: Array, 1305 * replacements: Array, 1306 * ?start: number, 1307 * ?end: number, 1308 * } 1309 * ``` 1310 * 1311 * As you can see, text and formatting are separated. `text` holds the text, 1312 * including any replacement characters for objects and lines. `formats`, 1313 * `objects` and `lines` are all sparse arrays of the same length as `text`. It 1314 * holds information about the formatting at the relevant text indices. Finally 1315 * `start` and `end` state which text indices are selected. They are only 1316 * provided if a `Range` was given. 1317 * 1318 * @param {Object} [$1] Optional named arguments. 1319 * @param {Element} [$1.element] Element to create value from. 1320 * @param {string} [$1.text] Text to create value from. 1321 * @param {string} [$1.html] HTML to create value from. 1322 * @param {Range} [$1.range] Range to create value from. 1323 * @param {boolean} [$1.__unstableIsEditableTree] 1324 * @return {RichTextValue} A rich text value. 1325 */ 1326 function create({ 1327 element, 1328 text, 1329 html, 1330 range, 1331 __unstableIsEditableTree: isEditableTree 1332 } = {}) { 1333 if (html instanceof RichTextData) { 1334 return { 1335 text: html.text, 1336 formats: html.formats, 1337 replacements: html.replacements 1338 }; 1339 } 1340 if (typeof text === 'string' && text.length > 0) { 1341 return { 1342 formats: Array(text.length), 1343 replacements: Array(text.length), 1344 text 1345 }; 1346 } 1347 if (typeof html === 'string' && html.length > 0) { 1348 // It does not matter which document this is, we're just using it to 1349 // parse. 1350 element = createElement(document, html); 1351 } 1352 if (typeof element !== 'object') { 1353 return createEmptyValue(); 1354 } 1355 return createFromElement({ 1356 element, 1357 range, 1358 isEditableTree 1359 }); 1360 } 1361 1362 /** 1363 * Helper to accumulate the value's selection start and end from the current 1364 * node and range. 1365 * 1366 * @param {Object} accumulator Object to accumulate into. 1367 * @param {Node} node Node to create value with. 1368 * @param {Range} range Range to create value with. 1369 * @param {Object} value Value that is being accumulated. 1370 */ 1371 function accumulateSelection(accumulator, node, range, value) { 1372 if (!range) { 1373 return; 1374 } 1375 const { 1376 parentNode 1377 } = node; 1378 const { 1379 startContainer, 1380 startOffset, 1381 endContainer, 1382 endOffset 1383 } = range; 1384 const currentLength = accumulator.text.length; 1385 1386 // Selection can be extracted from value. 1387 if (value.start !== undefined) { 1388 accumulator.start = currentLength + value.start; 1389 // Range indicates that the current node has selection. 1390 } else if (node === startContainer && node.nodeType === node.TEXT_NODE) { 1391 accumulator.start = currentLength + startOffset; 1392 // Range indicates that the current node is selected. 1393 } else if (parentNode === startContainer && node === startContainer.childNodes[startOffset]) { 1394 accumulator.start = currentLength; 1395 // Range indicates that the selection is after the current node. 1396 } else if (parentNode === startContainer && node === startContainer.childNodes[startOffset - 1]) { 1397 accumulator.start = currentLength + value.text.length; 1398 // Fallback if no child inside handled the selection. 1399 } else if (node === startContainer) { 1400 accumulator.start = currentLength; 1401 } 1402 1403 // Selection can be extracted from value. 1404 if (value.end !== undefined) { 1405 accumulator.end = currentLength + value.end; 1406 // Range indicates that the current node has selection. 1407 } else if (node === endContainer && node.nodeType === node.TEXT_NODE) { 1408 accumulator.end = currentLength + endOffset; 1409 // Range indicates that the current node is selected. 1410 } else if (parentNode === endContainer && node === endContainer.childNodes[endOffset - 1]) { 1411 accumulator.end = currentLength + value.text.length; 1412 // Range indicates that the selection is before the current node. 1413 } else if (parentNode === endContainer && node === endContainer.childNodes[endOffset]) { 1414 accumulator.end = currentLength; 1415 // Fallback if no child inside handled the selection. 1416 } else if (node === endContainer) { 1417 accumulator.end = currentLength + endOffset; 1418 } 1419 } 1420 1421 /** 1422 * Adjusts the start and end offsets from a range based on a text filter. 1423 * 1424 * @param {Node} node Node of which the text should be filtered. 1425 * @param {Range} range The range to filter. 1426 * @param {Function} filter Function to use to filter the text. 1427 * 1428 * @return {Object|void} Object containing range properties. 1429 */ 1430 function filterRange(node, range, filter) { 1431 if (!range) { 1432 return; 1433 } 1434 const { 1435 startContainer, 1436 endContainer 1437 } = range; 1438 let { 1439 startOffset, 1440 endOffset 1441 } = range; 1442 if (node === startContainer) { 1443 startOffset = filter(node.nodeValue.slice(0, startOffset)).length; 1444 } 1445 if (node === endContainer) { 1446 endOffset = filter(node.nodeValue.slice(0, endOffset)).length; 1447 } 1448 return { 1449 startContainer, 1450 startOffset, 1451 endContainer, 1452 endOffset 1453 }; 1454 } 1455 1456 /** 1457 * Collapse any whitespace used for HTML formatting to one space character, 1458 * because it will also be displayed as such by the browser. 1459 * 1460 * We need to strip it from the content because we use white-space: pre-wrap for 1461 * displaying editable rich text. Without using white-space: pre-wrap, the 1462 * browser will litter the content with non breaking spaces, among other issues. 1463 * See packages/rich-text/src/component/use-default-style.js. 1464 * 1465 * @see 1466 * https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space 1467 * 1468 * @param {HTMLElement} element 1469 * @param {boolean} isRoot 1470 * 1471 * @return {HTMLElement} New element with collapsed whitespace. 1472 */ 1473 function collapseWhiteSpace(element, isRoot = true) { 1474 const clone = element.cloneNode(true); 1475 clone.normalize(); 1476 Array.from(clone.childNodes).forEach((node, i, nodes) => { 1477 if (node.nodeType === node.TEXT_NODE) { 1478 let newNodeValue = node.nodeValue; 1479 if (/[\n\t\r\f]/.test(newNodeValue)) { 1480 newNodeValue = newNodeValue.replace(/[\n\t\r\f]+/g, ' '); 1481 } 1482 if (newNodeValue.indexOf(' ') !== -1) { 1483 newNodeValue = newNodeValue.replace(/ {2,}/g, ' '); 1484 } 1485 if (i === 0 && newNodeValue.startsWith(' ')) { 1486 newNodeValue = newNodeValue.slice(1); 1487 } else if (isRoot && i === nodes.length - 1 && newNodeValue.endsWith(' ')) { 1488 newNodeValue = newNodeValue.slice(0, -1); 1489 } 1490 node.nodeValue = newNodeValue; 1491 } else if (node.nodeType === node.ELEMENT_NODE) { 1492 collapseWhiteSpace(node, false); 1493 } 1494 }); 1495 return clone; 1496 } 1497 1498 /** 1499 * We need to normalise line breaks to `\n` so they are consistent across 1500 * platforms and serialised properly. Not removing \r would cause it to 1501 * linger and result in double line breaks when whitespace is preserved. 1502 */ 1503 const CARRIAGE_RETURN = '\r'; 1504 1505 /** 1506 * Removes reserved characters used by rich-text (zero width non breaking spaces 1507 * added by `toTree` and object replacement characters). 1508 * 1509 * @param {string} string 1510 */ 1511 function removeReservedCharacters(string) { 1512 // with the global flag, note that we should create a new regex each time OR 1513 // reset lastIndex state. 1514 return string.replace(new RegExp(`[$ZWNBSP}$OBJECT_REPLACEMENT_CHARACTER}$CARRIAGE_RETURN}]`, 'gu'), ''); 1515 } 1516 1517 /** 1518 * Creates a Rich Text value from a DOM element and range. 1519 * 1520 * @param {Object} $1 Named argements. 1521 * @param {Element} [$1.element] Element to create value from. 1522 * @param {Range} [$1.range] Range to create value from. 1523 * @param {boolean} [$1.isEditableTree] 1524 * 1525 * @return {RichTextValue} A rich text value. 1526 */ 1527 function createFromElement({ 1528 element, 1529 range, 1530 isEditableTree 1531 }) { 1532 const accumulator = createEmptyValue(); 1533 if (!element) { 1534 return accumulator; 1535 } 1536 if (!element.hasChildNodes()) { 1537 accumulateSelection(accumulator, element, range, createEmptyValue()); 1538 return accumulator; 1539 } 1540 const length = element.childNodes.length; 1541 1542 // Optimise for speed. 1543 for (let index = 0; index < length; index++) { 1544 const node = element.childNodes[index]; 1545 const tagName = node.nodeName.toLowerCase(); 1546 if (node.nodeType === node.TEXT_NODE) { 1547 const text = removeReservedCharacters(node.nodeValue); 1548 range = filterRange(node, range, removeReservedCharacters); 1549 accumulateSelection(accumulator, node, range, { 1550 text 1551 }); 1552 // Create a sparse array of the same length as `text`, in which 1553 // formats can be added. 1554 accumulator.formats.length += text.length; 1555 accumulator.replacements.length += text.length; 1556 accumulator.text += text; 1557 continue; 1558 } 1559 if (node.nodeType !== node.ELEMENT_NODE) { 1560 continue; 1561 } 1562 if (isEditableTree && 1563 // Ignore any line breaks that are not inserted by us. 1564 tagName === 'br' && !node.getAttribute('data-rich-text-line-break')) { 1565 accumulateSelection(accumulator, node, range, createEmptyValue()); 1566 continue; 1567 } 1568 if (tagName === 'script') { 1569 const value = { 1570 formats: [,], 1571 replacements: [{ 1572 type: tagName, 1573 attributes: { 1574 'data-rich-text-script': node.getAttribute('data-rich-text-script') || encodeURIComponent(node.innerHTML) 1575 } 1576 }], 1577 text: OBJECT_REPLACEMENT_CHARACTER 1578 }; 1579 accumulateSelection(accumulator, node, range, value); 1580 mergePair(accumulator, value); 1581 continue; 1582 } 1583 if (tagName === 'br') { 1584 accumulateSelection(accumulator, node, range, createEmptyValue()); 1585 mergePair(accumulator, create({ 1586 text: '\n' 1587 })); 1588 continue; 1589 } 1590 const format = toFormat({ 1591 tagName, 1592 attributes: getAttributes({ 1593 element: node 1594 }) 1595 }); 1596 1597 // When a format type is declared as not editable, replace it with an 1598 // object replacement character and preserve the inner HTML. 1599 if (format?.formatType?.contentEditable === false) { 1600 delete format.formatType; 1601 accumulateSelection(accumulator, node, range, createEmptyValue()); 1602 mergePair(accumulator, { 1603 formats: [,], 1604 replacements: [{ 1605 ...format, 1606 innerHTML: node.innerHTML 1607 }], 1608 text: OBJECT_REPLACEMENT_CHARACTER 1609 }); 1610 continue; 1611 } 1612 if (format) { 1613 delete format.formatType; 1614 } 1615 const value = createFromElement({ 1616 element: node, 1617 range, 1618 isEditableTree 1619 }); 1620 accumulateSelection(accumulator, node, range, value); 1621 1622 // Ignore any placeholders, but keep their content since the browser 1623 // might insert text inside them when the editable element is flex. 1624 if (!format || node.getAttribute('data-rich-text-placeholder')) { 1625 mergePair(accumulator, value); 1626 } else if (value.text.length === 0) { 1627 if (format.attributes) { 1628 mergePair(accumulator, { 1629 formats: [,], 1630 replacements: [format], 1631 text: OBJECT_REPLACEMENT_CHARACTER 1632 }); 1633 } 1634 } else { 1635 // Indices should share a reference to the same formats array. 1636 // Only create a new reference if `formats` changes. 1637 function mergeFormats(formats) { 1638 if (mergeFormats.formats === formats) { 1639 return mergeFormats.newFormats; 1640 } 1641 const newFormats = formats ? [format, ...formats] : [format]; 1642 mergeFormats.formats = formats; 1643 mergeFormats.newFormats = newFormats; 1644 return newFormats; 1645 } 1646 1647 // Since the formats parameter can be `undefined`, preset 1648 // `mergeFormats` with a new reference. 1649 mergeFormats.newFormats = [format]; 1650 mergePair(accumulator, { 1651 ...value, 1652 formats: Array.from(value.formats, mergeFormats) 1653 }); 1654 } 1655 } 1656 return accumulator; 1657 } 1658 1659 /** 1660 * Gets the attributes of an element in object shape. 1661 * 1662 * @param {Object} $1 Named argements. 1663 * @param {Element} $1.element Element to get attributes from. 1664 * 1665 * @return {Object|void} Attribute object or `undefined` if the element has no 1666 * attributes. 1667 */ 1668 function getAttributes({ 1669 element 1670 }) { 1671 if (!element.hasAttributes()) { 1672 return; 1673 } 1674 const length = element.attributes.length; 1675 let accumulator; 1676 1677 // Optimise for speed. 1678 for (let i = 0; i < length; i++) { 1679 const { 1680 name, 1681 value 1682 } = element.attributes[i]; 1683 if (name.indexOf('data-rich-text-') === 0) { 1684 continue; 1685 } 1686 const safeName = /^on/i.test(name) ? 'data-disable-rich-text-' + name : name; 1687 accumulator = accumulator || {}; 1688 accumulator[safeName] = value; 1689 } 1690 return accumulator; 1691 } 1692 1693 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/concat.js 1694 /** 1695 * Internal dependencies 1696 */ 1697 1698 1699 1700 1701 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1702 1703 /** 1704 * Concats a pair of rich text values. Not that this mutates `a` and does NOT 1705 * normalise formats! 1706 * 1707 * @param {Object} a Value to mutate. 1708 * @param {Object} b Value to add read from. 1709 * 1710 * @return {Object} `a`, mutated. 1711 */ 1712 function mergePair(a, b) { 1713 a.formats = a.formats.concat(b.formats); 1714 a.replacements = a.replacements.concat(b.replacements); 1715 a.text += b.text; 1716 return a; 1717 } 1718 1719 /** 1720 * Combine all Rich Text values into one. This is similar to 1721 * `String.prototype.concat`. 1722 * 1723 * @param {...RichTextValue} values Objects to combine. 1724 * 1725 * @return {RichTextValue} A new value combining all given records. 1726 */ 1727 function concat(...values) { 1728 return normaliseFormats(values.reduce(mergePair, create())); 1729 } 1730 1731 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-active-format.js 1732 /** 1733 * Internal dependencies 1734 */ 1735 1736 1737 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1738 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 1739 1740 /** 1741 * Gets the format object by type at the start of the selection. This can be 1742 * used to get e.g. the URL of a link format at the current selection, but also 1743 * to check if a format is active at the selection. Returns undefined if there 1744 * is no format at the selection. 1745 * 1746 * @param {RichTextValue} value Value to inspect. 1747 * @param {string} formatType Format type to look for. 1748 * 1749 * @return {RichTextFormat|undefined} Active format object of the specified 1750 * type, or undefined. 1751 */ 1752 function getActiveFormat(value, formatType) { 1753 return getActiveFormats(value).find(({ 1754 type 1755 }) => type === formatType); 1756 } 1757 1758 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/get-active-object.js 1759 /** 1760 * Internal dependencies 1761 */ 1762 1763 1764 1765 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1766 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 1767 1768 /** 1769 * Gets the active object, if there is any. 1770 * 1771 * @param {RichTextValue} value Value to inspect. 1772 * 1773 * @return {RichTextFormat|void} Active object, or undefined. 1774 */ 1775 function getActiveObject({ 1776 start, 1777 end, 1778 replacements, 1779 text 1780 }) { 1781 if (start + 1 !== end || text[start] !== OBJECT_REPLACEMENT_CHARACTER) { 1782 return; 1783 } 1784 return replacements[start]; 1785 } 1786 1787 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-collapsed.js 1788 /** 1789 * Internal dependencies 1790 */ 1791 1792 /** 1793 * Check if the selection of a Rich Text value is collapsed or not. Collapsed 1794 * means that no characters are selected, but there is a caret present. If there 1795 * is no selection, `undefined` will be returned. This is similar to 1796 * `window.getSelection().isCollapsed()`. 1797 * 1798 * @param props The rich text value to check. 1799 * @param props.start 1800 * @param props.end 1801 * @return True if the selection is collapsed, false if not, undefined if there is no selection. 1802 */ 1803 function isCollapsed({ 1804 start, 1805 end 1806 }) { 1807 if (start === undefined || end === undefined) { 1808 return; 1809 } 1810 return start === end; 1811 } 1812 1813 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-empty.js 1814 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1815 1816 /** 1817 * Check if a Rich Text value is Empty, meaning it contains no text or any 1818 * objects (such as images). 1819 * 1820 * @param {RichTextValue} value Value to use. 1821 * 1822 * @return {boolean} True if the value is empty, false if not. 1823 */ 1824 function isEmpty({ 1825 text 1826 }) { 1827 return text.length === 0; 1828 } 1829 1830 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/join.js 1831 /** 1832 * Internal dependencies 1833 */ 1834 1835 1836 1837 1838 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1839 1840 /** 1841 * Combine an array of Rich Text values into one, optionally separated by 1842 * `separator`, which can be a Rich Text value, HTML string, or plain text 1843 * string. This is similar to `Array.prototype.join`. 1844 * 1845 * @param {Array<RichTextValue>} values An array of values to join. 1846 * @param {string|RichTextValue} [separator] Separator string or value. 1847 * 1848 * @return {RichTextValue} A new combined value. 1849 */ 1850 function join(values, separator = '') { 1851 if (typeof separator === 'string') { 1852 separator = create({ 1853 text: separator 1854 }); 1855 } 1856 return normaliseFormats(values.reduce((accumlator, { 1857 formats, 1858 replacements, 1859 text 1860 }) => ({ 1861 formats: accumlator.formats.concat(separator.formats, formats), 1862 replacements: accumlator.replacements.concat(separator.replacements, replacements), 1863 text: accumlator.text + separator.text + text 1864 }))); 1865 } 1866 1867 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/register-format-type.js 1868 /** 1869 * WordPress dependencies 1870 */ 1871 1872 /** 1873 * Internal dependencies 1874 */ 1875 1876 /** 1877 * @typedef {Object} WPFormat 1878 * 1879 * @property {string} name A string identifying the format. Must be 1880 * unique across all registered formats. 1881 * @property {string} tagName The HTML tag this format will wrap the 1882 * selection with. 1883 * @property {boolean} interactive Whether format makes content interactive or not. 1884 * @property {string | null} [className] A class to match the format. 1885 * @property {string} title Name of the format. 1886 * @property {Function} edit Should return a component for the user to 1887 * interact with the new registered format. 1888 */ 1889 1890 /** 1891 * Registers a new format provided a unique name and an object defining its 1892 * behavior. 1893 * 1894 * @param {string} name Format name. 1895 * @param {WPFormat} settings Format settings. 1896 * 1897 * @return {WPFormat|undefined} The format, if it has been successfully 1898 * registered; otherwise `undefined`. 1899 */ 1900 function registerFormatType(name, settings) { 1901 settings = { 1902 name, 1903 ...settings 1904 }; 1905 if (typeof settings.name !== 'string') { 1906 window.console.error('Format names must be strings.'); 1907 return; 1908 } 1909 if (!/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/.test(settings.name)) { 1910 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'); 1911 return; 1912 } 1913 if ((0,external_wp_data_namespaceObject.select)(store).getFormatType(settings.name)) { 1914 window.console.error('Format "' + settings.name + '" is already registered.'); 1915 return; 1916 } 1917 if (typeof settings.tagName !== 'string' || settings.tagName === '') { 1918 window.console.error('Format tag names must be a string.'); 1919 return; 1920 } 1921 if ((typeof settings.className !== 'string' || settings.className === '') && settings.className !== null) { 1922 window.console.error('Format class names must be a string, or null to handle bare elements.'); 1923 return; 1924 } 1925 if (!/^[_a-zA-Z]+[a-zA-Z0-9_-]*$/.test(settings.className)) { 1926 window.console.error('A class name must begin with a letter, followed by any number of hyphens, underscores, letters, or numbers.'); 1927 return; 1928 } 1929 if (settings.className === null) { 1930 const formatTypeForBareElement = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForBareElement(settings.tagName); 1931 if (formatTypeForBareElement && formatTypeForBareElement.name !== 'core/unknown') { 1932 window.console.error(`Format "$formatTypeForBareElement.name}" is already registered to handle bare tag name "$settings.tagName}".`); 1933 return; 1934 } 1935 } else { 1936 const formatTypeForClassName = (0,external_wp_data_namespaceObject.select)(store).getFormatTypeForClassName(settings.className); 1937 if (formatTypeForClassName) { 1938 window.console.error(`Format "$formatTypeForClassName.name}" is already registered to handle class name "$settings.className}".`); 1939 return; 1940 } 1941 } 1942 if (!('title' in settings) || settings.title === '') { 1943 window.console.error('The format "' + settings.name + '" must have a title.'); 1944 return; 1945 } 1946 if ('keywords' in settings && settings.keywords.length > 3) { 1947 window.console.error('The format "' + settings.name + '" can have a maximum of 3 keywords.'); 1948 return; 1949 } 1950 if (typeof settings.title !== 'string') { 1951 window.console.error('Format titles must be strings.'); 1952 return; 1953 } 1954 (0,external_wp_data_namespaceObject.dispatch)(store).addFormatTypes(settings); 1955 return settings; 1956 } 1957 1958 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/remove-format.js 1959 /** 1960 * Internal dependencies 1961 */ 1962 1963 1964 1965 /** @typedef {import('./types').RichTextValue} RichTextValue */ 1966 1967 /** 1968 * Remove any format object from a Rich Text value by type from the given 1969 * `startIndex` to the given `endIndex`. Indices are retrieved from the 1970 * selection if none are provided. 1971 * 1972 * @param {RichTextValue} value Value to modify. 1973 * @param {string} formatType Format type to remove. 1974 * @param {number} [startIndex] Start index. 1975 * @param {number} [endIndex] End index. 1976 * 1977 * @return {RichTextValue} A new value with the format applied. 1978 */ 1979 function removeFormat(value, formatType, startIndex = value.start, endIndex = value.end) { 1980 const { 1981 formats, 1982 activeFormats 1983 } = value; 1984 const newFormats = formats.slice(); 1985 1986 // If the selection is collapsed, expand start and end to the edges of the 1987 // format. 1988 if (startIndex === endIndex) { 1989 const format = newFormats[startIndex]?.find(({ 1990 type 1991 }) => type === formatType); 1992 if (format) { 1993 while (newFormats[startIndex]?.find(newFormat => newFormat === format)) { 1994 filterFormats(newFormats, startIndex, formatType); 1995 startIndex--; 1996 } 1997 endIndex++; 1998 while (newFormats[endIndex]?.find(newFormat => newFormat === format)) { 1999 filterFormats(newFormats, endIndex, formatType); 2000 endIndex++; 2001 } 2002 } 2003 } else { 2004 for (let i = startIndex; i < endIndex; i++) { 2005 if (newFormats[i]) { 2006 filterFormats(newFormats, i, formatType); 2007 } 2008 } 2009 } 2010 return normaliseFormats({ 2011 ...value, 2012 formats: newFormats, 2013 activeFormats: activeFormats?.filter(({ 2014 type 2015 }) => type !== formatType) || [] 2016 }); 2017 } 2018 function filterFormats(formats, index, formatType) { 2019 const newFormats = formats[index].filter(({ 2020 type 2021 }) => type !== formatType); 2022 if (newFormats.length) { 2023 formats[index] = newFormats; 2024 } else { 2025 delete formats[index]; 2026 } 2027 } 2028 2029 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/insert.js 2030 /** 2031 * Internal dependencies 2032 */ 2033 2034 2035 2036 2037 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2038 2039 /** 2040 * Insert a Rich Text value, an HTML string, or a plain text string, into a 2041 * Rich Text value at the given `startIndex`. Any content between `startIndex` 2042 * and `endIndex` will be removed. Indices are retrieved from the selection if 2043 * none are provided. 2044 * 2045 * @param {RichTextValue} value Value to modify. 2046 * @param {RichTextValue|string} valueToInsert Value to insert. 2047 * @param {number} [startIndex] Start index. 2048 * @param {number} [endIndex] End index. 2049 * 2050 * @return {RichTextValue} A new value with the value inserted. 2051 */ 2052 function insert(value, valueToInsert, startIndex = value.start, endIndex = value.end) { 2053 const { 2054 formats, 2055 replacements, 2056 text 2057 } = value; 2058 if (typeof valueToInsert === 'string') { 2059 valueToInsert = create({ 2060 text: valueToInsert 2061 }); 2062 } 2063 const index = startIndex + valueToInsert.text.length; 2064 return normaliseFormats({ 2065 formats: formats.slice(0, startIndex).concat(valueToInsert.formats, formats.slice(endIndex)), 2066 replacements: replacements.slice(0, startIndex).concat(valueToInsert.replacements, replacements.slice(endIndex)), 2067 text: text.slice(0, startIndex) + valueToInsert.text + text.slice(endIndex), 2068 start: index, 2069 end: index 2070 }); 2071 } 2072 2073 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/remove.js 2074 /** 2075 * Internal dependencies 2076 */ 2077 2078 2079 2080 2081 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2082 2083 /** 2084 * Remove content from a Rich Text value between the given `startIndex` and 2085 * `endIndex`. Indices are retrieved from the selection if none are provided. 2086 * 2087 * @param {RichTextValue} value Value to modify. 2088 * @param {number} [startIndex] Start index. 2089 * @param {number} [endIndex] End index. 2090 * 2091 * @return {RichTextValue} A new value with the content removed. 2092 */ 2093 function remove_remove(value, startIndex, endIndex) { 2094 return insert(value, create(), startIndex, endIndex); 2095 } 2096 2097 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/replace.js 2098 /** 2099 * Internal dependencies 2100 */ 2101 2102 2103 2104 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2105 2106 /** 2107 * Search a Rich Text value and replace the match(es) with `replacement`. This 2108 * is similar to `String.prototype.replace`. 2109 * 2110 * @param {RichTextValue} value The value to modify. 2111 * @param {RegExp|string} pattern A RegExp object or literal. Can also be 2112 * a string. It is treated as a verbatim 2113 * string and is not interpreted as a 2114 * regular expression. Only the first 2115 * occurrence will be replaced. 2116 * @param {Function|string} replacement The match or matches are replaced with 2117 * the specified or the value returned by 2118 * the specified function. 2119 * 2120 * @return {RichTextValue} A new value with replacements applied. 2121 */ 2122 function replace_replace({ 2123 formats, 2124 replacements, 2125 text, 2126 start, 2127 end 2128 }, pattern, replacement) { 2129 text = text.replace(pattern, (match, ...rest) => { 2130 const offset = rest[rest.length - 2]; 2131 let newText = replacement; 2132 let newFormats; 2133 let newReplacements; 2134 if (typeof newText === 'function') { 2135 newText = replacement(match, ...rest); 2136 } 2137 if (typeof newText === 'object') { 2138 newFormats = newText.formats; 2139 newReplacements = newText.replacements; 2140 newText = newText.text; 2141 } else { 2142 newFormats = Array(newText.length); 2143 newReplacements = Array(newText.length); 2144 if (formats[offset]) { 2145 newFormats = newFormats.fill(formats[offset]); 2146 } 2147 } 2148 formats = formats.slice(0, offset).concat(newFormats, formats.slice(offset + match.length)); 2149 replacements = replacements.slice(0, offset).concat(newReplacements, replacements.slice(offset + match.length)); 2150 if (start) { 2151 start = end = offset + newText.length; 2152 } 2153 return newText; 2154 }); 2155 return normaliseFormats({ 2156 formats, 2157 replacements, 2158 text, 2159 start, 2160 end 2161 }); 2162 } 2163 2164 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/insert-object.js 2165 /** 2166 * Internal dependencies 2167 */ 2168 2169 2170 2171 2172 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2173 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2174 2175 /** 2176 * Insert a format as an object into a Rich Text value at the given 2177 * `startIndex`. Any content between `startIndex` and `endIndex` will be 2178 * removed. Indices are retrieved from the selection if none are provided. 2179 * 2180 * @param {RichTextValue} value Value to modify. 2181 * @param {RichTextFormat} formatToInsert Format to insert as object. 2182 * @param {number} [startIndex] Start index. 2183 * @param {number} [endIndex] End index. 2184 * 2185 * @return {RichTextValue} A new value with the object inserted. 2186 */ 2187 function insertObject(value, formatToInsert, startIndex, endIndex) { 2188 const valueToInsert = { 2189 formats: [,], 2190 replacements: [formatToInsert], 2191 text: OBJECT_REPLACEMENT_CHARACTER 2192 }; 2193 return insert(value, valueToInsert, startIndex, endIndex); 2194 } 2195 2196 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/slice.js 2197 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2198 2199 /** 2200 * Slice a Rich Text value from `startIndex` to `endIndex`. Indices are 2201 * retrieved from the selection if none are provided. This is similar to 2202 * `String.prototype.slice`. 2203 * 2204 * @param {RichTextValue} value Value to modify. 2205 * @param {number} [startIndex] Start index. 2206 * @param {number} [endIndex] End index. 2207 * 2208 * @return {RichTextValue} A new extracted value. 2209 */ 2210 function slice(value, startIndex = value.start, endIndex = value.end) { 2211 const { 2212 formats, 2213 replacements, 2214 text 2215 } = value; 2216 if (startIndex === undefined || endIndex === undefined) { 2217 return { 2218 ...value 2219 }; 2220 } 2221 return { 2222 formats: formats.slice(startIndex, endIndex), 2223 replacements: replacements.slice(startIndex, endIndex), 2224 text: text.slice(startIndex, endIndex) 2225 }; 2226 } 2227 2228 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/split.js 2229 /** 2230 * Internal dependencies 2231 */ 2232 2233 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2234 2235 /** 2236 * Split a Rich Text value in two at the given `startIndex` and `endIndex`, or 2237 * split at the given separator. This is similar to `String.prototype.split`. 2238 * Indices are retrieved from the selection if none are provided. 2239 * 2240 * @param {RichTextValue} value 2241 * @param {number|string} [string] Start index, or string at which to split. 2242 * 2243 * @return {Array<RichTextValue>|undefined} An array of new values. 2244 */ 2245 function split({ 2246 formats, 2247 replacements, 2248 text, 2249 start, 2250 end 2251 }, string) { 2252 if (typeof string !== 'string') { 2253 return splitAtSelection(...arguments); 2254 } 2255 let nextStart = 0; 2256 return text.split(string).map(substring => { 2257 const startIndex = nextStart; 2258 const value = { 2259 formats: formats.slice(startIndex, startIndex + substring.length), 2260 replacements: replacements.slice(startIndex, startIndex + substring.length), 2261 text: substring 2262 }; 2263 nextStart += string.length + substring.length; 2264 if (start !== undefined && end !== undefined) { 2265 if (start >= startIndex && start < nextStart) { 2266 value.start = start - startIndex; 2267 } else if (start < startIndex && end > startIndex) { 2268 value.start = 0; 2269 } 2270 if (end >= startIndex && end < nextStart) { 2271 value.end = end - startIndex; 2272 } else if (start < nextStart && end > nextStart) { 2273 value.end = substring.length; 2274 } 2275 } 2276 return value; 2277 }); 2278 } 2279 function splitAtSelection({ 2280 formats, 2281 replacements, 2282 text, 2283 start, 2284 end 2285 }, startIndex = start, endIndex = end) { 2286 if (start === undefined || end === undefined) { 2287 return; 2288 } 2289 const before = { 2290 formats: formats.slice(0, startIndex), 2291 replacements: replacements.slice(0, startIndex), 2292 text: text.slice(0, startIndex) 2293 }; 2294 const after = { 2295 formats: formats.slice(endIndex), 2296 replacements: replacements.slice(endIndex), 2297 text: text.slice(endIndex), 2298 start: 0, 2299 end: 0 2300 }; 2301 return [before, after]; 2302 } 2303 2304 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/is-range-equal.js 2305 /** 2306 * Returns true if two ranges are equal, or false otherwise. Ranges are 2307 * considered equal if their start and end occur in the same container and 2308 * offset. 2309 * 2310 * @param {Range|null} a First range object to test. 2311 * @param {Range|null} b First range object to test. 2312 * 2313 * @return {boolean} Whether the two ranges are equal. 2314 */ 2315 function isRangeEqual(a, b) { 2316 return a === b || a && b && a.startContainer === b.startContainer && a.startOffset === b.startOffset && a.endContainer === b.endContainer && a.endOffset === b.endOffset; 2317 } 2318 2319 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/to-dom.js 2320 /** 2321 * Internal dependencies 2322 */ 2323 2324 2325 2326 2327 2328 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2329 2330 /** 2331 * Creates a path as an array of indices from the given root node to the given 2332 * node. 2333 * 2334 * @param {Node} node Node to find the path of. 2335 * @param {HTMLElement} rootNode Root node to find the path from. 2336 * @param {Array} path Initial path to build on. 2337 * 2338 * @return {Array} The path from the root node to the node. 2339 */ 2340 function createPathToNode(node, rootNode, path) { 2341 const parentNode = node.parentNode; 2342 let i = 0; 2343 while (node = node.previousSibling) { 2344 i++; 2345 } 2346 path = [i, ...path]; 2347 if (parentNode !== rootNode) { 2348 path = createPathToNode(parentNode, rootNode, path); 2349 } 2350 return path; 2351 } 2352 2353 /** 2354 * Gets a node given a path (array of indices) from the given node. 2355 * 2356 * @param {HTMLElement} node Root node to find the wanted node in. 2357 * @param {Array} path Path (indices) to the wanted node. 2358 * 2359 * @return {Object} Object with the found node and the remaining offset (if any). 2360 */ 2361 function getNodeByPath(node, path) { 2362 path = [...path]; 2363 while (node && path.length > 1) { 2364 node = node.childNodes[path.shift()]; 2365 } 2366 return { 2367 node, 2368 offset: path[0] 2369 }; 2370 } 2371 function to_dom_append(element, child) { 2372 if (child.html !== undefined) { 2373 return element.innerHTML += child.html; 2374 } 2375 if (typeof child === 'string') { 2376 child = element.ownerDocument.createTextNode(child); 2377 } 2378 const { 2379 type, 2380 attributes 2381 } = child; 2382 if (type) { 2383 child = element.ownerDocument.createElement(type); 2384 for (const key in attributes) { 2385 child.setAttribute(key, attributes[key]); 2386 } 2387 } 2388 return element.appendChild(child); 2389 } 2390 function to_dom_appendText(node, text) { 2391 node.appendData(text); 2392 } 2393 function to_dom_getLastChild({ 2394 lastChild 2395 }) { 2396 return lastChild; 2397 } 2398 function to_dom_getParent({ 2399 parentNode 2400 }) { 2401 return parentNode; 2402 } 2403 function to_dom_isText(node) { 2404 return node.nodeType === node.TEXT_NODE; 2405 } 2406 function to_dom_getText({ 2407 nodeValue 2408 }) { 2409 return nodeValue; 2410 } 2411 function to_dom_remove(node) { 2412 return node.parentNode.removeChild(node); 2413 } 2414 function toDom({ 2415 value, 2416 prepareEditableTree, 2417 isEditableTree = true, 2418 placeholder, 2419 doc = document 2420 }) { 2421 let startPath = []; 2422 let endPath = []; 2423 if (prepareEditableTree) { 2424 value = { 2425 ...value, 2426 formats: prepareEditableTree(value) 2427 }; 2428 } 2429 2430 /** 2431 * Returns a new instance of a DOM tree upon which RichText operations can be 2432 * applied. 2433 * 2434 * Note: The current implementation will return a shared reference, reset on 2435 * each call to `createEmpty`. Therefore, you should not hold a reference to 2436 * the value to operate upon asynchronously, as it may have unexpected results. 2437 * 2438 * @return {Object} RichText tree. 2439 */ 2440 const createEmpty = () => createElement(doc, ''); 2441 const tree = toTree({ 2442 value, 2443 createEmpty, 2444 append: to_dom_append, 2445 getLastChild: to_dom_getLastChild, 2446 getParent: to_dom_getParent, 2447 isText: to_dom_isText, 2448 getText: to_dom_getText, 2449 remove: to_dom_remove, 2450 appendText: to_dom_appendText, 2451 onStartIndex(body, pointer) { 2452 startPath = createPathToNode(pointer, body, [pointer.nodeValue.length]); 2453 }, 2454 onEndIndex(body, pointer) { 2455 endPath = createPathToNode(pointer, body, [pointer.nodeValue.length]); 2456 }, 2457 isEditableTree, 2458 placeholder 2459 }); 2460 return { 2461 body: tree, 2462 selection: { 2463 startPath, 2464 endPath 2465 } 2466 }; 2467 } 2468 2469 /** 2470 * Create an `Element` tree from a Rich Text value and applies the difference to 2471 * the `Element` tree contained by `current`. 2472 * 2473 * @param {Object} $1 Named arguments. 2474 * @param {RichTextValue} $1.value Value to apply. 2475 * @param {HTMLElement} $1.current The live root node to apply the element tree to. 2476 * @param {Function} [$1.prepareEditableTree] Function to filter editorable formats. 2477 * @param {boolean} [$1.__unstableDomOnly] Only apply elements, no selection. 2478 * @param {string} [$1.placeholder] Placeholder text. 2479 */ 2480 function apply({ 2481 value, 2482 current, 2483 prepareEditableTree, 2484 __unstableDomOnly, 2485 placeholder 2486 }) { 2487 // Construct a new element tree in memory. 2488 const { 2489 body, 2490 selection 2491 } = toDom({ 2492 value, 2493 prepareEditableTree, 2494 placeholder, 2495 doc: current.ownerDocument 2496 }); 2497 applyValue(body, current); 2498 if (value.start !== undefined && !__unstableDomOnly) { 2499 applySelection(selection, current); 2500 } 2501 } 2502 function applyValue(future, current) { 2503 let i = 0; 2504 let futureChild; 2505 while (futureChild = future.firstChild) { 2506 const currentChild = current.childNodes[i]; 2507 if (!currentChild) { 2508 current.appendChild(futureChild); 2509 } else if (!currentChild.isEqualNode(futureChild)) { 2510 if (currentChild.nodeName !== futureChild.nodeName || currentChild.nodeType === currentChild.TEXT_NODE && currentChild.data !== futureChild.data) { 2511 current.replaceChild(futureChild, currentChild); 2512 } else { 2513 const currentAttributes = currentChild.attributes; 2514 const futureAttributes = futureChild.attributes; 2515 if (currentAttributes) { 2516 let ii = currentAttributes.length; 2517 2518 // Reverse loop because `removeAttribute` on `currentChild` 2519 // changes `currentAttributes`. 2520 while (ii--) { 2521 const { 2522 name 2523 } = currentAttributes[ii]; 2524 if (!futureChild.getAttribute(name)) { 2525 currentChild.removeAttribute(name); 2526 } 2527 } 2528 } 2529 if (futureAttributes) { 2530 for (let ii = 0; ii < futureAttributes.length; ii++) { 2531 const { 2532 name, 2533 value 2534 } = futureAttributes[ii]; 2535 if (currentChild.getAttribute(name) !== value) { 2536 currentChild.setAttribute(name, value); 2537 } 2538 } 2539 } 2540 applyValue(futureChild, currentChild); 2541 future.removeChild(futureChild); 2542 } 2543 } else { 2544 future.removeChild(futureChild); 2545 } 2546 i++; 2547 } 2548 while (current.childNodes[i]) { 2549 current.removeChild(current.childNodes[i]); 2550 } 2551 } 2552 function applySelection({ 2553 startPath, 2554 endPath 2555 }, current) { 2556 const { 2557 node: startContainer, 2558 offset: startOffset 2559 } = getNodeByPath(current, startPath); 2560 const { 2561 node: endContainer, 2562 offset: endOffset 2563 } = getNodeByPath(current, endPath); 2564 const { 2565 ownerDocument 2566 } = current; 2567 const { 2568 defaultView 2569 } = ownerDocument; 2570 const selection = defaultView.getSelection(); 2571 const range = ownerDocument.createRange(); 2572 range.setStart(startContainer, startOffset); 2573 range.setEnd(endContainer, endOffset); 2574 const { 2575 activeElement 2576 } = ownerDocument; 2577 if (selection.rangeCount > 0) { 2578 // If the to be added range and the live range are the same, there's no 2579 // need to remove the live range and add the equivalent range. 2580 if (isRangeEqual(range, selection.getRangeAt(0))) { 2581 return; 2582 } 2583 selection.removeAllRanges(); 2584 } 2585 selection.addRange(range); 2586 2587 // This function is not intended to cause a shift in focus. Since the above 2588 // selection manipulations may shift focus, ensure that focus is restored to 2589 // its previous state. 2590 if (activeElement !== ownerDocument.activeElement) { 2591 // The `instanceof` checks protect against edge cases where the focused 2592 // element is not of the interface HTMLElement (does not have a `focus` 2593 // or `blur` property). 2594 // 2595 // See: https://github.com/Microsoft/TypeScript/issues/5901#issuecomment-431649653 2596 if (activeElement instanceof defaultView.HTMLElement) { 2597 activeElement.focus(); 2598 } 2599 } 2600 } 2601 2602 ;// CONCATENATED MODULE: external ["wp","a11y"] 2603 const external_wp_a11y_namespaceObject = window["wp"]["a11y"]; 2604 ;// CONCATENATED MODULE: external ["wp","i18n"] 2605 const external_wp_i18n_namespaceObject = window["wp"]["i18n"]; 2606 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/toggle-format.js 2607 /** 2608 * WordPress dependencies 2609 */ 2610 2611 2612 2613 2614 /** 2615 * Internal dependencies 2616 */ 2617 2618 2619 2620 2621 2622 /** @typedef {import('./types').RichTextValue} RichTextValue */ 2623 /** @typedef {import('./types').RichTextFormat} RichTextFormat */ 2624 2625 /** 2626 * Toggles a format object to a Rich Text value at the current selection. 2627 * 2628 * @param {RichTextValue} value Value to modify. 2629 * @param {RichTextFormat} format Format to apply or remove. 2630 * 2631 * @return {RichTextValue} A new value with the format applied or removed. 2632 */ 2633 function toggleFormat(value, format) { 2634 if (getActiveFormat(value, format.type)) { 2635 // For screen readers, will announce if formatting control is disabled. 2636 if (format.title) { 2637 // translators: %s: title of the formatting control 2638 (0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.sprintf)((0,external_wp_i18n_namespaceObject.__)('%s removed.'), format.title), 'assertive'); 2639 } 2640 return removeFormat(value, format.type); 2641 } 2642 // For screen readers, will announce if formatting control is enabled. 2643 if (format.title) { 2644 // translators: %s: title of the formatting control 2645 (0,external_wp_a11y_namespaceObject.speak)((0,external_wp_i18n_namespaceObject.sprintf)((0,external_wp_i18n_namespaceObject.__)('%s applied.'), format.title), 'assertive'); 2646 } 2647 return applyFormat(value, format); 2648 } 2649 2650 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/unregister-format-type.js 2651 /** 2652 * WordPress dependencies 2653 */ 2654 2655 2656 /** 2657 * Internal dependencies 2658 */ 2659 2660 2661 /** @typedef {import('./register-format-type').WPFormat} WPFormat */ 2662 2663 /** 2664 * Unregisters a format. 2665 * 2666 * @param {string} name Format name. 2667 * 2668 * @return {WPFormat|undefined} The previous format value, if it has 2669 * been successfully unregistered; 2670 * otherwise `undefined`. 2671 */ 2672 function unregisterFormatType(name) { 2673 const oldFormat = (0,external_wp_data_namespaceObject.select)(store).getFormatType(name); 2674 if (!oldFormat) { 2675 window.console.error(`Format $name} is not registered.`); 2676 return; 2677 } 2678 (0,external_wp_data_namespaceObject.dispatch)(store).removeFormatTypes(name); 2679 return oldFormat; 2680 } 2681 2682 ;// CONCATENATED MODULE: external ["wp","element"] 2683 const external_wp_element_namespaceObject = window["wp"]["element"]; 2684 ;// CONCATENATED MODULE: external ["wp","deprecated"] 2685 const external_wp_deprecated_namespaceObject = window["wp"]["deprecated"]; 2686 var external_wp_deprecated_default = /*#__PURE__*/__webpack_require__.n(external_wp_deprecated_namespaceObject); 2687 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-anchor-ref.js 2688 /** 2689 * WordPress dependencies 2690 */ 2691 2692 2693 2694 /** 2695 * Internal dependencies 2696 */ 2697 2698 2699 /** 2700 * @template T 2701 * @typedef {import('@wordpress/element').RefObject<T>} RefObject<T> 2702 */ 2703 /** @typedef {import('../register-format-type').WPFormat} WPFormat */ 2704 /** @typedef {import('../types').RichTextValue} RichTextValue */ 2705 2706 /** 2707 * This hook, to be used in a format type's Edit component, returns the active 2708 * element that is formatted, or the selection range if no format is active. 2709 * The returned value is meant to be used for positioning UI, e.g. by passing it 2710 * to the `Popover` component. 2711 * 2712 * @param {Object} $1 Named parameters. 2713 * @param {RefObject<HTMLElement>} $1.ref React ref of the element 2714 * containing the editable content. 2715 * @param {RichTextValue} $1.value Value to check for selection. 2716 * @param {WPFormat} $1.settings The format type's settings. 2717 * 2718 * @return {Element|Range} The active element or selection range. 2719 */ 2720 function useAnchorRef({ 2721 ref, 2722 value, 2723 settings = {} 2724 }) { 2725 external_wp_deprecated_default()('`useAnchorRef` hook', { 2726 since: '6.1', 2727 alternative: '`useAnchor` hook' 2728 }); 2729 const { 2730 tagName, 2731 className, 2732 name 2733 } = settings; 2734 const activeFormat = name ? getActiveFormat(value, name) : undefined; 2735 return (0,external_wp_element_namespaceObject.useMemo)(() => { 2736 if (!ref.current) { 2737 return; 2738 } 2739 const { 2740 ownerDocument: { 2741 defaultView 2742 } 2743 } = ref.current; 2744 const selection = defaultView.getSelection(); 2745 if (!selection.rangeCount) { 2746 return; 2747 } 2748 const range = selection.getRangeAt(0); 2749 if (!activeFormat) { 2750 return range; 2751 } 2752 let element = range.startContainer; 2753 2754 // If the caret is right before the element, select the next element. 2755 element = element.nextElementSibling || element; 2756 while (element.nodeType !== element.ELEMENT_NODE) { 2757 element = element.parentNode; 2758 } 2759 return element.closest(tagName + (className ? '.' + className : '')); 2760 }, [activeFormat, value.start, value.end, tagName, className]); 2761 } 2762 2763 ;// CONCATENATED MODULE: external ["wp","compose"] 2764 const external_wp_compose_namespaceObject = window["wp"]["compose"]; 2765 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-anchor.js 2766 /** 2767 * WordPress dependencies 2768 */ 2769 2770 2771 2772 /** @typedef {import('../register-format-type').WPFormat} WPFormat */ 2773 /** @typedef {import('../types').RichTextValue} RichTextValue */ 2774 2775 /** 2776 * Given a range and a format tag name and class name, returns the closest 2777 * format element. 2778 * 2779 * @param {Range} range The Range to check. 2780 * @param {HTMLElement} editableContentElement The editable wrapper. 2781 * @param {string} tagName The tag name of the format element. 2782 * @param {string} className The class name of the format element. 2783 * 2784 * @return {HTMLElement|undefined} The format element, if found. 2785 */ 2786 function getFormatElement(range, editableContentElement, tagName, className) { 2787 let element = range.startContainer; 2788 2789 // Even if the active format is defined, the actualy DOM range's start 2790 // container may be outside of the format's DOM element: 2791 // `a‸<strong>b</strong>` (DOM) while visually it's `a<strong>‸b</strong>`. 2792 // So at a given selection index, start with the deepest format DOM element. 2793 if (element.nodeType === element.TEXT_NODE && range.startOffset === element.length && element.nextSibling) { 2794 element = element.nextSibling; 2795 while (element.firstChild) { 2796 element = element.firstChild; 2797 } 2798 } 2799 if (element.nodeType !== element.ELEMENT_NODE) { 2800 element = element.parentElement; 2801 } 2802 if (!element) { 2803 return; 2804 } 2805 if (element === editableContentElement) { 2806 return; 2807 } 2808 if (!editableContentElement.contains(element)) { 2809 return; 2810 } 2811 const selector = tagName + (className ? '.' + className : ''); 2812 2813 // .closest( selector ), but with a boundary. Check if the element matches 2814 // the selector. If it doesn't match, try the parent element if it's not the 2815 // editable wrapper. We don't want to try to match ancestors of the editable 2816 // wrapper, which is what .closest( selector ) would do. When the element is 2817 // the editable wrapper (which is most likely the case because most text is 2818 // unformatted), this never runs. 2819 while (element !== editableContentElement) { 2820 if (element.matches(selector)) { 2821 return element; 2822 } 2823 element = element.parentElement; 2824 } 2825 } 2826 2827 /** 2828 * @typedef {Object} VirtualAnchorElement 2829 * @property {() => DOMRect} getBoundingClientRect A function returning a DOMRect 2830 * @property {HTMLElement} contextElement The actual DOM element 2831 */ 2832 2833 /** 2834 * Creates a virtual anchor element for a range. 2835 * 2836 * @param {Range} range The range to create a virtual anchor element for. 2837 * @param {HTMLElement} editableContentElement The editable wrapper. 2838 * 2839 * @return {VirtualAnchorElement} The virtual anchor element. 2840 */ 2841 function createVirtualAnchorElement(range, editableContentElement) { 2842 return { 2843 contextElement: editableContentElement, 2844 getBoundingClientRect() { 2845 return editableContentElement.contains(range.startContainer) ? range.getBoundingClientRect() : editableContentElement.getBoundingClientRect(); 2846 } 2847 }; 2848 } 2849 2850 /** 2851 * Get the anchor: a format element if there is a matching one based on the 2852 * tagName and className or a range otherwise. 2853 * 2854 * @param {HTMLElement} editableContentElement The editable wrapper. 2855 * @param {string} tagName The tag name of the format 2856 * element. 2857 * @param {string} className The class name of the format 2858 * element. 2859 * 2860 * @return {HTMLElement|VirtualAnchorElement|undefined} The anchor. 2861 */ 2862 function getAnchor(editableContentElement, tagName, className) { 2863 if (!editableContentElement) { 2864 return; 2865 } 2866 const { 2867 ownerDocument 2868 } = editableContentElement; 2869 const { 2870 defaultView 2871 } = ownerDocument; 2872 const selection = defaultView.getSelection(); 2873 if (!selection) { 2874 return; 2875 } 2876 if (!selection.rangeCount) { 2877 return; 2878 } 2879 const range = selection.getRangeAt(0); 2880 if (!range || !range.startContainer) { 2881 return; 2882 } 2883 const formatElement = getFormatElement(range, editableContentElement, tagName, className); 2884 if (formatElement) { 2885 return formatElement; 2886 } 2887 return createVirtualAnchorElement(range, editableContentElement); 2888 } 2889 2890 /** 2891 * This hook, to be used in a format type's Edit component, returns the active 2892 * element that is formatted, or a virtual element for the selection range if 2893 * no format is active. The returned value is meant to be used for positioning 2894 * UI, e.g. by passing it to the `Popover` component via the `anchor` prop. 2895 * 2896 * @param {Object} $1 Named parameters. 2897 * @param {HTMLElement|null} $1.editableContentElement The element containing 2898 * the editable content. 2899 * @param {WPFormat=} $1.settings The format type's settings. 2900 * @return {Element|VirtualAnchorElement|undefined|null} The active element or selection range. 2901 */ 2902 function useAnchor({ 2903 editableContentElement, 2904 settings = {} 2905 }) { 2906 const { 2907 tagName, 2908 className, 2909 isActive 2910 } = settings; 2911 const [anchor, setAnchor] = (0,external_wp_element_namespaceObject.useState)(() => getAnchor(editableContentElement, tagName, className)); 2912 const wasActive = (0,external_wp_compose_namespaceObject.usePrevious)(isActive); 2913 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 2914 if (!editableContentElement) { 2915 return; 2916 } 2917 function callback() { 2918 setAnchor(getAnchor(editableContentElement, tagName, className)); 2919 } 2920 function attach() { 2921 ownerDocument.addEventListener('selectionchange', callback); 2922 } 2923 function detach() { 2924 ownerDocument.removeEventListener('selectionchange', callback); 2925 } 2926 const { 2927 ownerDocument 2928 } = editableContentElement; 2929 if (editableContentElement === ownerDocument.activeElement || 2930 // When a link is created, we need to attach the popover to the newly created anchor. 2931 !wasActive && isActive || 2932 // Sometimes we're _removing_ an active anchor, such as the inline color popover. 2933 // When we add the color, it switches from a virtual anchor to a `<mark>` element. 2934 // When we _remove_ the color, it switches from a `<mark>` element to a virtual anchor. 2935 wasActive && !isActive) { 2936 setAnchor(getAnchor(editableContentElement, tagName, className)); 2937 attach(); 2938 } 2939 editableContentElement.addEventListener('focusin', attach); 2940 editableContentElement.addEventListener('focusout', detach); 2941 return () => { 2942 detach(); 2943 editableContentElement.removeEventListener('focusin', attach); 2944 editableContentElement.removeEventListener('focusout', detach); 2945 }; 2946 }, [editableContentElement, tagName, className, isActive, wasActive]); 2947 return anchor; 2948 } 2949 2950 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-default-style.js 2951 /** 2952 * WordPress dependencies 2953 */ 2954 2955 2956 /** 2957 * In HTML, leading and trailing spaces are not visible, and multiple spaces 2958 * elsewhere are visually reduced to one space. This rule prevents spaces from 2959 * collapsing so all space is visible in the editor and can be removed. It also 2960 * prevents some browsers from inserting non-breaking spaces at the end of a 2961 * line to prevent the space from visually disappearing. Sometimes these non 2962 * breaking spaces can linger in the editor causing unwanted non breaking spaces 2963 * in between words. If also prevent Firefox from inserting a trailing `br` node 2964 * to visualise any trailing space, causing the element to be saved. 2965 * 2966 * > Authors are encouraged to set the 'white-space' property on editing hosts 2967 * > and on markup that was originally created through these editing mechanisms 2968 * > to the value 'pre-wrap'. Default HTML whitespace handling is not well 2969 * > suited to WYSIWYG editing, and line wrapping will not work correctly in 2970 * > some corner cases if 'white-space' is left at its default value. 2971 * 2972 * https://html.spec.whatwg.org/multipage/interaction.html#best-practices-for-in-page-editors 2973 * 2974 * @type {string} 2975 */ 2976 const whiteSpace = 'pre-wrap'; 2977 2978 /** 2979 * A minimum width of 1px will prevent the rich text container from collapsing 2980 * to 0 width and hiding the caret. This is useful for inline containers. 2981 */ 2982 const minWidth = '1px'; 2983 function useDefaultStyle() { 2984 return (0,external_wp_element_namespaceObject.useCallback)(element => { 2985 if (!element) { 2986 return; 2987 } 2988 element.style.whiteSpace = whiteSpace; 2989 element.style.minWidth = minWidth; 2990 }, []); 2991 } 2992 2993 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/use-boundary-style.js 2994 /** 2995 * WordPress dependencies 2996 */ 2997 2998 2999 /* 3000 * Calculates and renders the format boundary style when the active formats 3001 * change. 3002 */ 3003 function useBoundaryStyle({ 3004 record 3005 }) { 3006 const ref = (0,external_wp_element_namespaceObject.useRef)(); 3007 const { 3008 activeFormats = [], 3009 replacements, 3010 start 3011 } = record.current; 3012 const activeReplacement = replacements[start]; 3013 (0,external_wp_element_namespaceObject.useEffect)(() => { 3014 // There's no need to recalculate the boundary styles if no formats are 3015 // active, because no boundary styles will be visible. 3016 if ((!activeFormats || !activeFormats.length) && !activeReplacement) { 3017 return; 3018 } 3019 const boundarySelector = '*[data-rich-text-format-boundary]'; 3020 const element = ref.current.querySelector(boundarySelector); 3021 if (!element) { 3022 return; 3023 } 3024 const { 3025 ownerDocument 3026 } = element; 3027 const { 3028 defaultView 3029 } = ownerDocument; 3030 const computedStyle = defaultView.getComputedStyle(element); 3031 const newColor = computedStyle.color.replace(')', ', 0.2)').replace('rgb', 'rgba'); 3032 const selector = `.rich-text:focus $boundarySelector}`; 3033 const rule = `background-color: $newColor}`; 3034 const style = `$selector} {$rule}}`; 3035 const globalStyleId = 'rich-text-boundary-style'; 3036 let globalStyle = ownerDocument.getElementById(globalStyleId); 3037 if (!globalStyle) { 3038 globalStyle = ownerDocument.createElement('style'); 3039 globalStyle.id = globalStyleId; 3040 ownerDocument.head.appendChild(globalStyle); 3041 } 3042 if (globalStyle.innerHTML !== style) { 3043 globalStyle.innerHTML = style; 3044 } 3045 }, [activeFormats, activeReplacement]); 3046 return ref; 3047 } 3048 3049 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/copy-handler.js 3050 /** 3051 * Internal dependencies 3052 */ 3053 3054 3055 3056 3057 /* harmony default export */ const copy_handler = (props => element => { 3058 function onCopy(event) { 3059 const { 3060 record 3061 } = props.current; 3062 const { 3063 ownerDocument 3064 } = element; 3065 if (isCollapsed(record.current) || !element.contains(ownerDocument.activeElement)) { 3066 return; 3067 } 3068 const selectedRecord = slice(record.current); 3069 const plainText = getTextContent(selectedRecord); 3070 const html = toHTMLString({ 3071 value: selectedRecord 3072 }); 3073 event.clipboardData.setData('text/plain', plainText); 3074 event.clipboardData.setData('text/html', html); 3075 event.clipboardData.setData('rich-text', 'true'); 3076 event.preventDefault(); 3077 if (event.type === 'cut') { 3078 ownerDocument.execCommand('delete'); 3079 } 3080 } 3081 const { 3082 defaultView 3083 } = element.ownerDocument; 3084 defaultView.addEventListener('copy', onCopy); 3085 defaultView.addEventListener('cut', onCopy); 3086 return () => { 3087 defaultView.removeEventListener('copy', onCopy); 3088 defaultView.removeEventListener('cut', onCopy); 3089 }; 3090 }); 3091 3092 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/select-object.js 3093 /* harmony default export */ const select_object = (() => element => { 3094 function onClick(event) { 3095 const { 3096 target 3097 } = event; 3098 3099 // If the child element has no text content, it must be an object. 3100 if (target === element || target.textContent && target.isContentEditable) { 3101 return; 3102 } 3103 const { 3104 ownerDocument 3105 } = target; 3106 const { 3107 defaultView 3108 } = ownerDocument; 3109 const selection = defaultView.getSelection(); 3110 3111 // If it's already selected, do nothing and let default behavior happen. 3112 // This means it's "click-through". 3113 if (selection.containsNode(target)) { 3114 return; 3115 } 3116 const range = ownerDocument.createRange(); 3117 // If the target is within a non editable element, select the non 3118 // editable element. 3119 const nodeToSelect = target.isContentEditable ? target : target.closest('[contenteditable]'); 3120 range.selectNode(nodeToSelect); 3121 selection.removeAllRanges(); 3122 selection.addRange(range); 3123 event.preventDefault(); 3124 } 3125 function onFocusIn(event) { 3126 // When there is incoming focus from a link, select the object. 3127 if (event.relatedTarget && !element.contains(event.relatedTarget) && event.relatedTarget.tagName === 'A') { 3128 onClick(event); 3129 } 3130 } 3131 element.addEventListener('click', onClick); 3132 element.addEventListener('focusin', onFocusIn); 3133 return () => { 3134 element.removeEventListener('click', onClick); 3135 element.removeEventListener('focusin', onFocusIn); 3136 }; 3137 }); 3138 3139 ;// CONCATENATED MODULE: external ["wp","keycodes"] 3140 const external_wp_keycodes_namespaceObject = window["wp"]["keycodes"]; 3141 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/format-boundaries.js 3142 /** 3143 * WordPress dependencies 3144 */ 3145 3146 3147 /** 3148 * Internal dependencies 3149 */ 3150 3151 const EMPTY_ACTIVE_FORMATS = []; 3152 /* harmony default export */ const format_boundaries = (props => element => { 3153 function onKeyDown(event) { 3154 const { 3155 keyCode, 3156 shiftKey, 3157 altKey, 3158 metaKey, 3159 ctrlKey 3160 } = event; 3161 if ( 3162 // Only override left and right keys without modifiers pressed. 3163 shiftKey || altKey || metaKey || ctrlKey || keyCode !== external_wp_keycodes_namespaceObject.LEFT && keyCode !== external_wp_keycodes_namespaceObject.RIGHT) { 3164 return; 3165 } 3166 const { 3167 record, 3168 applyRecord, 3169 forceRender 3170 } = props.current; 3171 const { 3172 text, 3173 formats, 3174 start, 3175 end, 3176 activeFormats: currentActiveFormats = [] 3177 } = record.current; 3178 const collapsed = isCollapsed(record.current); 3179 const { 3180 ownerDocument 3181 } = element; 3182 const { 3183 defaultView 3184 } = ownerDocument; 3185 // To do: ideally, we should look at visual position instead. 3186 const { 3187 direction 3188 } = defaultView.getComputedStyle(element); 3189 const reverseKey = direction === 'rtl' ? external_wp_keycodes_namespaceObject.RIGHT : external_wp_keycodes_namespaceObject.LEFT; 3190 const isReverse = event.keyCode === reverseKey; 3191 3192 // If the selection is collapsed and at the very start, do nothing if 3193 // navigating backward. 3194 // If the selection is collapsed and at the very end, do nothing if 3195 // navigating forward. 3196 if (collapsed && currentActiveFormats.length === 0) { 3197 if (start === 0 && isReverse) { 3198 return; 3199 } 3200 if (end === text.length && !isReverse) { 3201 return; 3202 } 3203 } 3204 3205 // If the selection is not collapsed, let the browser handle collapsing 3206 // the selection for now. Later we could expand this logic to set 3207 // boundary positions if needed. 3208 if (!collapsed) { 3209 return; 3210 } 3211 const formatsBefore = formats[start - 1] || EMPTY_ACTIVE_FORMATS; 3212 const formatsAfter = formats[start] || EMPTY_ACTIVE_FORMATS; 3213 const destination = isReverse ? formatsBefore : formatsAfter; 3214 const isIncreasing = currentActiveFormats.every((format, index) => format === destination[index]); 3215 let newActiveFormatsLength = currentActiveFormats.length; 3216 if (!isIncreasing) { 3217 newActiveFormatsLength--; 3218 } else if (newActiveFormatsLength < destination.length) { 3219 newActiveFormatsLength++; 3220 } 3221 if (newActiveFormatsLength === currentActiveFormats.length) { 3222 record.current._newActiveFormats = destination; 3223 return; 3224 } 3225 event.preventDefault(); 3226 const origin = isReverse ? formatsAfter : formatsBefore; 3227 const source = isIncreasing ? destination : origin; 3228 const newActiveFormats = source.slice(0, newActiveFormatsLength); 3229 const newValue = { 3230 ...record.current, 3231 activeFormats: newActiveFormats 3232 }; 3233 record.current = newValue; 3234 applyRecord(newValue); 3235 forceRender(); 3236 } 3237 element.addEventListener('keydown', onKeyDown); 3238 return () => { 3239 element.removeEventListener('keydown', onKeyDown); 3240 }; 3241 }); 3242 3243 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/delete.js 3244 /** 3245 * WordPress dependencies 3246 */ 3247 3248 3249 /** 3250 * Internal dependencies 3251 */ 3252 3253 /* harmony default export */ const event_listeners_delete = (props => element => { 3254 function onKeyDown(event) { 3255 const { 3256 keyCode 3257 } = event; 3258 const { 3259 createRecord, 3260 handleChange 3261 } = props.current; 3262 if (event.defaultPrevented) { 3263 return; 3264 } 3265 if (keyCode !== external_wp_keycodes_namespaceObject.DELETE && keyCode !== external_wp_keycodes_namespaceObject.BACKSPACE) { 3266 return; 3267 } 3268 const currentValue = createRecord(); 3269 const { 3270 start, 3271 end, 3272 text 3273 } = currentValue; 3274 3275 // Always handle full content deletion ourselves. 3276 if (start === 0 && end !== 0 && end === text.length) { 3277 handleChange(remove_remove(currentValue)); 3278 event.preventDefault(); 3279 } 3280 } 3281 element.addEventListener('keydown', onKeyDown); 3282 return () => { 3283 element.removeEventListener('keydown', onKeyDown); 3284 }; 3285 }); 3286 3287 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/update-formats.js 3288 /** 3289 * Internal dependencies 3290 */ 3291 3292 3293 3294 /** @typedef {import('./types').RichTextValue} RichTextValue */ 3295 3296 /** 3297 * Efficiently updates all the formats from `start` (including) until `end` 3298 * (excluding) with the active formats. Mutates `value`. 3299 * 3300 * @param {Object} $1 Named paramentes. 3301 * @param {RichTextValue} $1.value Value te update. 3302 * @param {number} $1.start Index to update from. 3303 * @param {number} $1.end Index to update until. 3304 * @param {Array} $1.formats Replacement formats. 3305 * 3306 * @return {RichTextValue} Mutated value. 3307 */ 3308 function updateFormats({ 3309 value, 3310 start, 3311 end, 3312 formats 3313 }) { 3314 // Start and end may be switched in case of delete. 3315 const min = Math.min(start, end); 3316 const max = Math.max(start, end); 3317 const formatsBefore = value.formats[min - 1] || []; 3318 const formatsAfter = value.formats[max] || []; 3319 3320 // First, fix the references. If any format right before or after are 3321 // equal, the replacement format should use the same reference. 3322 value.activeFormats = formats.map((format, index) => { 3323 if (formatsBefore[index]) { 3324 if (isFormatEqual(format, formatsBefore[index])) { 3325 return formatsBefore[index]; 3326 } 3327 } else if (formatsAfter[index]) { 3328 if (isFormatEqual(format, formatsAfter[index])) { 3329 return formatsAfter[index]; 3330 } 3331 } 3332 return format; 3333 }); 3334 while (--end >= start) { 3335 if (value.activeFormats.length > 0) { 3336 value.formats[end] = value.activeFormats; 3337 } else { 3338 delete value.formats[end]; 3339 } 3340 } 3341 return value; 3342 } 3343 3344 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/input-and-selection.js 3345 /** 3346 * Internal dependencies 3347 */ 3348 3349 3350 3351 /** 3352 * All inserting input types that would insert HTML into the DOM. 3353 * 3354 * @see https://www.w3.org/TR/input-events-2/#interface-InputEvent-Attributes 3355 * 3356 * @type {Set} 3357 */ 3358 const INSERTION_INPUT_TYPES_TO_IGNORE = new Set(['insertParagraph', 'insertOrderedList', 'insertUnorderedList', 'insertHorizontalRule', 'insertLink']); 3359 const input_and_selection_EMPTY_ACTIVE_FORMATS = []; 3360 const PLACEHOLDER_ATTR_NAME = 'data-rich-text-placeholder'; 3361 3362 /** 3363 * If the selection is set on the placeholder element, collapse the selection to 3364 * the start (before the placeholder). 3365 * 3366 * @param {Window} defaultView 3367 */ 3368 function fixPlaceholderSelection(defaultView) { 3369 const selection = defaultView.getSelection(); 3370 const { 3371 anchorNode, 3372 anchorOffset 3373 } = selection; 3374 if (anchorNode.nodeType !== anchorNode.ELEMENT_NODE) { 3375 return; 3376 } 3377 const targetNode = anchorNode.childNodes[anchorOffset]; 3378 if (!targetNode || targetNode.nodeType !== targetNode.ELEMENT_NODE || !targetNode.hasAttribute(PLACEHOLDER_ATTR_NAME)) { 3379 return; 3380 } 3381 selection.collapseToStart(); 3382 } 3383 /* harmony default export */ const input_and_selection = (props => element => { 3384 const { 3385 ownerDocument 3386 } = element; 3387 const { 3388 defaultView 3389 } = ownerDocument; 3390 let isComposing = false; 3391 function onInput(event) { 3392 // Do not trigger a change if characters are being composed. Browsers 3393 // will usually emit a final `input` event when the characters are 3394 // composed. As of December 2019, Safari doesn't support 3395 // nativeEvent.isComposing. 3396 if (isComposing) { 3397 return; 3398 } 3399 let inputType; 3400 if (event) { 3401 inputType = event.inputType; 3402 } 3403 const { 3404 record, 3405 applyRecord, 3406 createRecord, 3407 handleChange 3408 } = props.current; 3409 3410 // The browser formatted something or tried to insert HTML. Overwrite 3411 // it. It will be handled later by the format library if needed. 3412 if (inputType && (inputType.indexOf('format') === 0 || INSERTION_INPUT_TYPES_TO_IGNORE.has(inputType))) { 3413 applyRecord(record.current); 3414 return; 3415 } 3416 const currentValue = createRecord(); 3417 const { 3418 start, 3419 activeFormats: oldActiveFormats = [] 3420 } = record.current; 3421 3422 // Update the formats between the last and new caret position. 3423 const change = updateFormats({ 3424 value: currentValue, 3425 start, 3426 end: currentValue.start, 3427 formats: oldActiveFormats 3428 }); 3429 handleChange(change); 3430 } 3431 3432 /** 3433 * Syncs the selection to local state. A callback for the `selectionchange` 3434 * event. 3435 */ 3436 function handleSelectionChange() { 3437 const { 3438 record, 3439 applyRecord, 3440 createRecord, 3441 onSelectionChange 3442 } = props.current; 3443 3444 // Check if the implementor disabled editing. `contentEditable` does 3445 // disable input, but not text selection, so we must ignore selection 3446 // changes. 3447 if (element.contentEditable !== 'true') { 3448 return; 3449 } 3450 3451 // Ensure the active element is the rich text element. 3452 if (ownerDocument.activeElement !== element) { 3453 // If it is not, we can stop listening for selection changes. We 3454 // resume listening when the element is focused. 3455 ownerDocument.removeEventListener('selectionchange', handleSelectionChange); 3456 return; 3457 } 3458 3459 // In case of a keyboard event, ignore selection changes during 3460 // composition. 3461 if (isComposing) { 3462 return; 3463 } 3464 const { 3465 start, 3466 end, 3467 text 3468 } = createRecord(); 3469 const oldRecord = record.current; 3470 3471 // Fallback mechanism for IE11, which doesn't support the input event. 3472 // Any input results in a selection change. 3473 if (text !== oldRecord.text) { 3474 onInput(); 3475 return; 3476 } 3477 if (start === oldRecord.start && end === oldRecord.end) { 3478 // Sometimes the browser may set the selection on the placeholder 3479 // element, in which case the caret is not visible. We need to set 3480 // the caret before the placeholder if that's the case. 3481 if (oldRecord.text.length === 0 && start === 0) { 3482 fixPlaceholderSelection(defaultView); 3483 } 3484 return; 3485 } 3486 const newValue = { 3487 ...oldRecord, 3488 start, 3489 end, 3490 // _newActiveFormats may be set on arrow key navigation to control 3491 // the right boundary position. If undefined, getActiveFormats will 3492 // give the active formats according to the browser. 3493 activeFormats: oldRecord._newActiveFormats, 3494 _newActiveFormats: undefined 3495 }; 3496 const newActiveFormats = getActiveFormats(newValue, input_and_selection_EMPTY_ACTIVE_FORMATS); 3497 3498 // Update the value with the new active formats. 3499 newValue.activeFormats = newActiveFormats; 3500 3501 // It is important that the internal value is updated first, 3502 // otherwise the value will be wrong on render! 3503 record.current = newValue; 3504 applyRecord(newValue, { 3505 domOnly: true 3506 }); 3507 onSelectionChange(start, end); 3508 } 3509 function onCompositionStart() { 3510 isComposing = true; 3511 // Do not update the selection when characters are being composed as 3512 // this rerenders the component and might destroy internal browser 3513 // editing state. 3514 ownerDocument.removeEventListener('selectionchange', handleSelectionChange); 3515 // Remove the placeholder. Since the rich text value doesn't update 3516 // during composition, the placeholder doesn't get removed. There's no 3517 // need to re-add it, when the value is updated on compositionend it 3518 // will be re-added when the value is empty. 3519 element.querySelector(`[$PLACEHOLDER_ATTR_NAME}]`)?.remove(); 3520 } 3521 function onCompositionEnd() { 3522 isComposing = false; 3523 // Ensure the value is up-to-date for browsers that don't emit a final 3524 // input event after composition. 3525 onInput({ 3526 inputType: 'insertText' 3527 }); 3528 // Tracking selection changes can be resumed. 3529 ownerDocument.addEventListener('selectionchange', handleSelectionChange); 3530 } 3531 function onFocus() { 3532 const { 3533 record, 3534 isSelected, 3535 onSelectionChange, 3536 applyRecord 3537 } = props.current; 3538 3539 // When the whole editor is editable, let writing flow handle 3540 // selection. 3541 if (element.parentElement.closest('[contenteditable="true"]')) { 3542 return; 3543 } 3544 if (!isSelected) { 3545 // We know for certain that on focus, the old selection is invalid. 3546 // It will be recalculated on the next mouseup, keyup, or touchend 3547 // event. 3548 const index = undefined; 3549 record.current = { 3550 ...record.current, 3551 start: index, 3552 end: index, 3553 activeFormats: input_and_selection_EMPTY_ACTIVE_FORMATS 3554 }; 3555 } else { 3556 applyRecord(record.current, { 3557 domOnly: true 3558 }); 3559 } 3560 onSelectionChange(record.current.start, record.current.end); 3561 3562 // There is no selection change event when the element is focused, so 3563 // we need to manually trigger it. The selection is also not available 3564 // yet in this call stack. 3565 window.queueMicrotask(handleSelectionChange); 3566 ownerDocument.addEventListener('selectionchange', handleSelectionChange); 3567 } 3568 element.addEventListener('input', onInput); 3569 element.addEventListener('compositionstart', onCompositionStart); 3570 element.addEventListener('compositionend', onCompositionEnd); 3571 element.addEventListener('focus', onFocus); 3572 return () => { 3573 element.removeEventListener('input', onInput); 3574 element.removeEventListener('compositionstart', onCompositionStart); 3575 element.removeEventListener('compositionend', onCompositionEnd); 3576 element.removeEventListener('focus', onFocus); 3577 }; 3578 }); 3579 3580 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/selection-change-compat.js 3581 /** 3582 * Internal dependencies 3583 */ 3584 3585 3586 /** 3587 * Sometimes some browsers are not firing a `selectionchange` event when 3588 * changing the selection by mouse or keyboard. This hook makes sure that, if we 3589 * detect no `selectionchange` or `input` event between the up and down events, 3590 * we fire a `selectionchange` event. 3591 */ 3592 /* harmony default export */ const selection_change_compat = (() => element => { 3593 const { 3594 ownerDocument 3595 } = element; 3596 const { 3597 defaultView 3598 } = ownerDocument; 3599 const selection = defaultView?.getSelection(); 3600 let range; 3601 function getRange() { 3602 return selection.rangeCount ? selection.getRangeAt(0) : null; 3603 } 3604 function onDown(event) { 3605 const type = event.type === 'keydown' ? 'keyup' : 'pointerup'; 3606 function onCancel() { 3607 ownerDocument.removeEventListener(type, onUp); 3608 ownerDocument.removeEventListener('selectionchange', onCancel); 3609 ownerDocument.removeEventListener('input', onCancel); 3610 } 3611 function onUp() { 3612 onCancel(); 3613 if (isRangeEqual(range, getRange())) { 3614 return; 3615 } 3616 ownerDocument.dispatchEvent(new Event('selectionchange')); 3617 } 3618 ownerDocument.addEventListener(type, onUp); 3619 ownerDocument.addEventListener('selectionchange', onCancel); 3620 ownerDocument.addEventListener('input', onCancel); 3621 range = getRange(); 3622 } 3623 element.addEventListener('pointerdown', onDown); 3624 element.addEventListener('keydown', onDown); 3625 return () => { 3626 element.removeEventListener('pointerdown', onDown); 3627 element.removeEventListener('keydown', onDown); 3628 }; 3629 }); 3630 3631 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/event-listeners/index.js 3632 /** 3633 * WordPress dependencies 3634 */ 3635 3636 3637 3638 /** 3639 * Internal dependencies 3640 */ 3641 3642 3643 3644 3645 3646 3647 const allEventListeners = [copy_handler, select_object, format_boundaries, event_listeners_delete, input_and_selection, selection_change_compat]; 3648 function useEventListeners(props) { 3649 const propsRef = (0,external_wp_element_namespaceObject.useRef)(props); 3650 propsRef.current = props; 3651 const refEffects = (0,external_wp_element_namespaceObject.useMemo)(() => allEventListeners.map(refEffect => refEffect(propsRef)), [propsRef]); 3652 return (0,external_wp_compose_namespaceObject.useRefEffect)(element => { 3653 const cleanups = refEffects.map(effect => effect(element)); 3654 return () => { 3655 cleanups.forEach(cleanup => cleanup()); 3656 }; 3657 }, [refEffects]); 3658 } 3659 3660 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/component/index.js 3661 /** 3662 * WordPress dependencies 3663 */ 3664 3665 3666 3667 3668 /** 3669 * Internal dependencies 3670 */ 3671 3672 3673 3674 3675 3676 3677 function useRichText({ 3678 value = '', 3679 selectionStart, 3680 selectionEnd, 3681 placeholder, 3682 onSelectionChange, 3683 preserveWhiteSpace, 3684 onChange, 3685 __unstableDisableFormats: disableFormats, 3686 __unstableIsSelected: isSelected, 3687 __unstableDependencies = [], 3688 __unstableAfterParse, 3689 __unstableBeforeSerialize, 3690 __unstableAddInvisibleFormats 3691 }) { 3692 const registry = (0,external_wp_data_namespaceObject.useRegistry)(); 3693 const [, forceRender] = (0,external_wp_element_namespaceObject.useReducer)(() => ({})); 3694 const ref = (0,external_wp_element_namespaceObject.useRef)(); 3695 function createRecord() { 3696 const { 3697 ownerDocument: { 3698 defaultView 3699 } 3700 } = ref.current; 3701 const selection = defaultView.getSelection(); 3702 const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null; 3703 return create({ 3704 element: ref.current, 3705 range, 3706 __unstableIsEditableTree: true 3707 }); 3708 } 3709 function applyRecord(newRecord, { 3710 domOnly 3711 } = {}) { 3712 apply({ 3713 value: newRecord, 3714 current: ref.current, 3715 prepareEditableTree: __unstableAddInvisibleFormats, 3716 __unstableDomOnly: domOnly, 3717 placeholder 3718 }); 3719 } 3720 3721 // Internal values are updated synchronously, unlike props and state. 3722 const _valueRef = (0,external_wp_element_namespaceObject.useRef)(value); 3723 const recordRef = (0,external_wp_element_namespaceObject.useRef)(); 3724 function setRecordFromProps() { 3725 _valueRef.current = value; 3726 recordRef.current = value; 3727 if (!(value instanceof RichTextData)) { 3728 recordRef.current = value ? RichTextData.fromHTMLString(value, { 3729 preserveWhiteSpace 3730 }) : RichTextData.empty(); 3731 } 3732 // To do: make rich text internally work with RichTextData. 3733 recordRef.current = { 3734 text: recordRef.current.text, 3735 formats: recordRef.current.formats, 3736 replacements: recordRef.current.replacements 3737 }; 3738 if (disableFormats) { 3739 recordRef.current.formats = Array(value.length); 3740 recordRef.current.replacements = Array(value.length); 3741 } 3742 if (__unstableAfterParse) { 3743 recordRef.current.formats = __unstableAfterParse(recordRef.current); 3744 } 3745 recordRef.current.start = selectionStart; 3746 recordRef.current.end = selectionEnd; 3747 } 3748 const hadSelectionUpdateRef = (0,external_wp_element_namespaceObject.useRef)(false); 3749 if (!recordRef.current) { 3750 hadSelectionUpdateRef.current = isSelected; 3751 setRecordFromProps(); 3752 } else if (selectionStart !== recordRef.current.start || selectionEnd !== recordRef.current.end) { 3753 hadSelectionUpdateRef.current = isSelected; 3754 recordRef.current = { 3755 ...recordRef.current, 3756 start: selectionStart, 3757 end: selectionEnd, 3758 activeFormats: undefined 3759 }; 3760 } 3761 3762 /** 3763 * Sync the value to global state. The node tree and selection will also be 3764 * updated if differences are found. 3765 * 3766 * @param {Object} newRecord The record to sync and apply. 3767 */ 3768 function handleChange(newRecord) { 3769 recordRef.current = newRecord; 3770 applyRecord(newRecord); 3771 if (disableFormats) { 3772 _valueRef.current = newRecord.text; 3773 } else { 3774 const newFormats = __unstableBeforeSerialize ? __unstableBeforeSerialize(newRecord) : newRecord.formats; 3775 newRecord = { 3776 ...newRecord, 3777 formats: newFormats 3778 }; 3779 if (typeof value === 'string') { 3780 _valueRef.current = toHTMLString({ 3781 value: newRecord, 3782 preserveWhiteSpace 3783 }); 3784 } else { 3785 _valueRef.current = new RichTextData(newRecord); 3786 } 3787 } 3788 const { 3789 start, 3790 end, 3791 formats, 3792 text 3793 } = recordRef.current; 3794 3795 // Selection must be updated first, so it is recorded in history when 3796 // the content change happens. 3797 // We batch both calls to only attempt to rerender once. 3798 registry.batch(() => { 3799 onSelectionChange(start, end); 3800 onChange(_valueRef.current, { 3801 __unstableFormats: formats, 3802 __unstableText: text 3803 }); 3804 }); 3805 forceRender(); 3806 } 3807 function applyFromProps() { 3808 setRecordFromProps(); 3809 applyRecord(recordRef.current); 3810 } 3811 const didMountRef = (0,external_wp_element_namespaceObject.useRef)(false); 3812 3813 // Value updates must happen synchonously to avoid overwriting newer values. 3814 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 3815 if (didMountRef.current && value !== _valueRef.current) { 3816 applyFromProps(); 3817 forceRender(); 3818 } 3819 }, [value]); 3820 3821 // Value updates must happen synchonously to avoid overwriting newer values. 3822 (0,external_wp_element_namespaceObject.useLayoutEffect)(() => { 3823 if (!hadSelectionUpdateRef.current) { 3824 return; 3825 } 3826 if (ref.current.ownerDocument.activeElement !== ref.current) { 3827 ref.current.focus(); 3828 } 3829 applyRecord(recordRef.current); 3830 hadSelectionUpdateRef.current = false; 3831 }, [hadSelectionUpdateRef.current]); 3832 const mergedRefs = (0,external_wp_compose_namespaceObject.useMergeRefs)([ref, useDefaultStyle(), useBoundaryStyle({ 3833 record: recordRef 3834 }), useEventListeners({ 3835 record: recordRef, 3836 handleChange, 3837 applyRecord, 3838 createRecord, 3839 isSelected, 3840 onSelectionChange, 3841 forceRender 3842 }), (0,external_wp_compose_namespaceObject.useRefEffect)(() => { 3843 applyFromProps(); 3844 didMountRef.current = true; 3845 }, [placeholder, ...__unstableDependencies])]); 3846 return { 3847 value: recordRef.current, 3848 // A function to get the most recent value so event handlers in 3849 // useRichText implementations have access to it. For example when 3850 // listening to input events, we internally update the state, but this 3851 // state is not yet available to the input event handler because React 3852 // may re-render asynchronously. 3853 getValue: () => recordRef.current, 3854 onChange: handleChange, 3855 ref: mergedRefs 3856 }; 3857 } 3858 function __experimentalRichText() {} 3859 3860 ;// CONCATENATED MODULE: ./node_modules/@wordpress/rich-text/build-module/index.js 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 /** 3890 * An object which represents a formatted string. See main `@wordpress/rich-text` 3891 * documentation for more information. 3892 */ 3893 3894 (window.wp = window.wp || {}).richText = __webpack_exports__; 3895 /******/ })() 3896 ;
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Sat Nov 23 08:20:01 2024 | Cross-referenced by PHPXref |