{
  "version": 3,
  "sources": ["../../src/utils/crdt-utils.ts"],
  "sourcesContent": ["import { Y } from '@wordpress/sync';\nimport { create, insert, toHTMLString } from '@wordpress/rich-text';\nimport type { YBlock, YBlocks } from './crdt-blocks';\nimport type { YPostRecord } from './crdt';\nimport { CRDT_RECORD_MAP_KEY } from '../sync';\n\n/**\n * A YMapRecord represents the shape of the data stored in a Y.Map.\n */\nexport type YMapRecord = Record< string, unknown >;\n\n/**\n * A wrapper around Y.Map to provide type safety. The generic type accepted by\n * Y.Map represents the union of possible values of the map, which are varied in\n * many cases. This type is accurate, but its non-specificity requires aggressive\n * type narrowing or type casting / destruction with `as`.\n *\n * This type provides type enhancements so that the correct value type can be\n * inferred based on the provided key. It is just a type wrap / overlay, and\n * does not change the runtime behavior of Y.Map.\n *\n * This interface cannot extend Y.Map directly due to the limitations of\n * TypeScript's structural typing. One negative consequence of this is that\n * `instanceof` checks against Y.Map continue to work at runtime but will blur\n * the type at compile time. To navigate this, use the `isYMap` function below.\n */\nexport interface YMapWrap< T extends YMapRecord > extends Y.AbstractType< T > {\n\tdelete: < K extends keyof T >( key: K ) => void;\n\tforEach: (\n\t\tcallback: (\n\t\t\tvalue: T[ keyof T ],\n\t\t\tkey: keyof T,\n\t\t\tmap: YMapWrap< T >\n\t\t) => void\n\t) => void;\n\thas: < K extends keyof T >( key: K ) => boolean;\n\tget: < K extends keyof T >( key: K ) => T[ K ] | undefined;\n\tset: < K extends keyof T >( key: K, value: T[ K ] ) => void;\n\ttoJSON: () => T;\n\t// add types for other Y.Map methods as needed\n}\n\n/**\n * Get or create a root-level Map for the given Y.Doc. Use this instead of\n * doc.getMap() for additional type safety.\n *\n * @param doc Y.Doc\n * @param key Map key\n */\nexport function getRootMap< T extends YMapRecord >(\n\tdoc: Y.Doc,\n\tkey: string\n): YMapWrap< T > {\n\treturn doc.getMap< T >( key ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Create a new Y.Map (provided with YMapWrap type), optionally initialized with\n * data. Use this instead of `new Y.Map()` for additional type safety.\n *\n * @param partial Partial data to initialize the map with.\n */\nexport function createYMap< T extends YMapRecord >(\n\tpartial: Partial< T > = {}\n): YMapWrap< T > {\n\treturn new Y.Map( Object.entries( partial ) ) as unknown as YMapWrap< T >;\n}\n\n/**\n * Type guard to check if a value is a Y.Map without losing type information.\n *\n * @param value Value to check.\n */\nexport function isYMap< T extends YMapRecord >(\n\tvalue: YMapWrap< T > | undefined\n): value is YMapWrap< T > {\n\treturn value instanceof Y.Map;\n}\n\ndeclare const richTextOffsetBrand: unique symbol;\ndeclare const htmlStringIndexBrand: unique symbol;\n\n/**\n * Branded type to prevent confusion between HTML string indices and RichText offsets.\n *\n * @see asRichTextOffset()\n */\nexport type RichTextOffset = number & {\n\treadonly [ richTextOffsetBrand ]: 'RichTextOffset';\n};\n\n/**\n * Branded type to prevent confusion between HTML string indices and RichText offsets.\n *\n * @see asHtmlStringIndex()\n */\nexport type HtmlStringIndex = number & {\n\treadonly [ htmlStringIndexBrand ]: 'HtmlStringIndex';\n};\n\n/**\n * Brand a number as an offset into a RichText’s text content.\n *\n * @param offset The rich-text offset to brand.\n * @return The branded rich-text offset.\n */\nexport function asRichTextOffset( offset: number ): RichTextOffset {\n\treturn offset as RichTextOffset;\n}\n\n/**\n * Brand a number as a string index into serialized HTML.\n *\n * @param index The HTML string index to brand.\n * @return The branded HTML string index.\n */\nexport function asHtmlStringIndex( index: number ): HtmlStringIndex {\n\treturn index as HtmlStringIndex;\n}\n\n/**\n * Resolve a selection attribute key to a Y.Text value.\n *\n * RichText identifiers are normally top-level block attribute keys, but nested\n * rich-text fields can provide a dot path such as `body.0.cells.0.content`.\n *\n * @param attributes   The block attributes map.\n * @param attributeKey The top-level attribute key or nested attribute path.\n * @return The matching Y.Text, or null if the path is not a rich-text field.\n */\nexport function getYTextByAttributeKey(\n\tattributes: Y.Map< unknown >,\n\tattributeKey: string\n): Y.Text | null {\n\tconst directValue = attributes.get( attributeKey );\n\tif ( directValue instanceof Y.Text ) {\n\t\treturn directValue;\n\t}\n\n\tlet value: unknown = attributes;\n\tfor ( const pathPart of attributeKey.split( '.' ) ) {\n\t\tif ( value instanceof Y.Map ) {\n\t\t\tvalue = value.get( pathPart );\n\t\t} else if ( value instanceof Y.Array ) {\n\t\t\tconst index = Number.parseInt( pathPart, 10 );\n\t\t\tif (\n\t\t\t\t! Number.isSafeInteger( index ) ||\n\t\t\t\tindex < 0 ||\n\t\t\t\tindex.toString() !== pathPart\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tvalue = value.get( index );\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn value instanceof Y.Text ? value : null;\n}\n\n/**\n * Given a block ID and a Y.Doc, find the block in the document.\n *\n * @param blockId The block ID to find\n * @param ydoc    The Y.Doc to find the block in\n * @return The block, or null if the block is not found\n */\nexport function findBlockByClientIdInDoc(\n\tblockId: string,\n\tydoc: Y.Doc\n): YBlock | null {\n\tconst ymap = getRootMap< YPostRecord >( ydoc, CRDT_RECORD_MAP_KEY );\n\tconst blocks = ymap.get( 'blocks' );\n\n\tif ( ! ( blocks instanceof Y.Array ) ) {\n\t\treturn null;\n\t}\n\n\treturn findBlockByClientIdInBlocks( blockId, blocks );\n}\n\n// Marker for insertion.\nconst MARKER_START = 0xe000;\n\n/**\n * Pick a marker character that does not appear in `text`. Returns the marker\n * or `null` if all candidates are present (extremely unlikely in practice).\n *\n * @param text The string to check for existing marker characters.\n */\nfunction pickMarker( text: string ): string | null {\n\tconst tryCount = 0x10;\n\n\t// Scan the unicode private use area for the first code point not present\n\t// in the text.\n\tfor ( let code = MARKER_START; code < MARKER_START + tryCount; code++ ) {\n\t\tconst candidate = String.fromCharCode( code );\n\n\t\tif ( ! text.includes( candidate ) ) {\n\t\t\treturn candidate;\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Convert an HTML character index (counting tag characters) to a rich-text\n * offset (counting only text characters). Used on read paths where Y.Text\n * resolves to an HTML index but the block editor expects a text offset.\n *\n * @param html      The full HTML string from Y.Text.\n * @param htmlIndex The HTML character index.\n * @return The corresponding rich-text offset.\n */\nexport function htmlIndexToRichTextOffset(\n\thtml: string,\n\thtmlIndex: HtmlStringIndex\n): RichTextOffset {\n\tif ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {\n\t\treturn asRichTextOffset( htmlIndex );\n\t}\n\n\tconst marker = pickMarker( html );\n\tif ( ! marker ) {\n\t\treturn asRichTextOffset( htmlIndex );\n\t}\n\n\t// Insert marker and let create() do the parsing.\n\tconst withMarker =\n\t\thtml.slice( 0, htmlIndex ) + marker + html.slice( htmlIndex );\n\tconst value = create( { html: withMarker } );\n\tconst markerPos = value.text.indexOf( marker );\n\n\treturn asRichTextOffset( markerPos === -1 ? htmlIndex : markerPos );\n}\n\n/**\n * Convert a rich-text offset (counting only text characters) to an HTML\n * character index (counting tag characters). Used on write paths where the\n * block editor provides a text offset but Y.Text expects an HTML index.\n *\n * @param html           The full HTML string from Y.Text.\n * @param richTextOffset The rich-text text offset.\n * @return The corresponding HTML character index.\n */\nexport function richTextOffsetToHtmlIndex(\n\thtml: string,\n\trichTextOffset: RichTextOffset\n): HtmlStringIndex {\n\tif ( ! html.includes( '<' ) && ! html.includes( '&' ) ) {\n\t\treturn asHtmlStringIndex( richTextOffset );\n\t}\n\n\tconst marker = pickMarker( html );\n\tif ( ! marker ) {\n\t\treturn asHtmlStringIndex( richTextOffset );\n\t}\n\n\tconst value = create( { html } );\n\tconst markerValue = create( { text: marker } );\n\t// The marker must inherit the formatting at the insertion point so that\n\t// toHTMLString does not split surrounding tags (e.g. <strong>) around it.\n\tif ( value.formats[ richTextOffset ] ) {\n\t\tmarkerValue.formats[ 0 ] = value.formats[ richTextOffset ];\n\t}\n\n\tconst withMarker = insert(\n\t\tvalue,\n\t\tmarkerValue,\n\t\trichTextOffset,\n\t\trichTextOffset\n\t);\n\n\tconst htmlWithMarker = toHTMLString( { value: withMarker } );\n\tconst markerIndex = htmlWithMarker.indexOf( marker );\n\treturn asHtmlStringIndex(\n\t\tmarkerIndex === -1 ? richTextOffset : markerIndex\n\t);\n}\n\nfunction findBlockByClientIdInBlocks(\n\tblockId: string,\n\tblocks: YBlocks\n): YBlock | null {\n\tfor ( const block of blocks ) {\n\t\tif ( block.get( 'clientId' ) === blockId ) {\n\t\t\treturn block;\n\t\t}\n\n\t\tconst innerBlocks = block.get( 'innerBlocks' );\n\n\t\tif ( innerBlocks && innerBlocks.length > 0 ) {\n\t\t\tconst innerBlock = findBlockByClientIdInBlocks(\n\t\t\t\tblockId,\n\t\t\t\tinnerBlocks\n\t\t\t);\n\n\t\t\tif ( innerBlock ) {\n\t\t\t\treturn innerBlock;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n"],
  "mappings": "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",
  "names": ["import_sync"]
}
