@portabletext/editor
Version:
Portable Text Editor made in React
139 lines (116 loc) • 3.14 kB
text/typescript
import type {KeyedSegment} from '@sanity/types'
import type {EditorContext} from '../editor/editor-snapshot'
import {isSpan, isTextBlock} from '../internal-utils/parse-blocks'
import {
getBlockKeyFromSelectionPoint,
getChildKeyFromSelectionPoint,
} from '../selection/selection-point'
import type {BlockOffset} from '../types/block-offset'
import type {EditorSelectionPoint} from '../types/editor'
/**
* @public
*/
export function blockOffsetToSpanSelectionPoint({
context,
blockOffset,
direction,
}: {
context: Pick<EditorContext, 'schema' | 'value'>
blockOffset: BlockOffset
direction: 'forward' | 'backward'
}) {
let offsetLeft = blockOffset.offset
let selectionPoint:
| {path: [KeyedSegment, 'children', KeyedSegment]; offset: number}
| undefined
let skippedInlineObject = false
for (const block of context.value) {
if (block._key !== blockOffset.path[0]._key) {
continue
}
if (!isTextBlock(context, block)) {
continue
}
for (const child of block.children) {
if (direction === 'forward') {
if (!isSpan(context, child)) {
continue
}
if (offsetLeft <= child.text.length) {
selectionPoint = {
path: [...blockOffset.path, 'children', {_key: child._key}],
offset: offsetLeft,
}
break
}
offsetLeft -= child.text.length
continue
}
if (!isSpan(context, child)) {
skippedInlineObject = true
continue
}
if (offsetLeft === 0 && selectionPoint && !skippedInlineObject) {
if (skippedInlineObject) {
selectionPoint = {
path: [...blockOffset.path, 'children', {_key: child._key}],
offset: 0,
}
}
break
}
if (offsetLeft > child.text.length) {
offsetLeft -= child.text.length
continue
}
if (offsetLeft <= child.text.length) {
selectionPoint = {
path: [...blockOffset.path, 'children', {_key: child._key}],
offset: offsetLeft,
}
offsetLeft -= child.text.length
if (offsetLeft !== 0) {
break
}
}
}
}
return selectionPoint
}
/**
* @public
*/
export function spanSelectionPointToBlockOffset({
context,
selectionPoint,
}: {
context: Pick<EditorContext, 'schema' | 'value'>
selectionPoint: EditorSelectionPoint
}): BlockOffset | undefined {
let offset = 0
const blockKey = getBlockKeyFromSelectionPoint(selectionPoint)
const spanKey = getChildKeyFromSelectionPoint(selectionPoint)
if (!blockKey || !spanKey) {
return undefined
}
for (const block of context.value) {
if (block._key !== blockKey) {
continue
}
if (!isTextBlock(context, block)) {
continue
}
for (const child of block.children) {
if (!isSpan(context, child)) {
continue
}
if (child._key === spanKey) {
return {
path: [{_key: block._key}],
offset: offset + selectionPoint.offset,
}
}
offset += child.text.length
}
}
}