@portabletext/editor
Version:
Portable Text Editor made in React
300 lines (247 loc) • 7.25 kB
text/typescript
import type {
KeyedSegment,
PortableTextBlock,
PortableTextListBlock,
PortableTextObject,
PortableTextSpan,
PortableTextTextBlock,
} from '@sanity/types'
import type {EditorSelector} from '../editor/editor-selector'
import {isListBlock, isSpan, isTextBlock} from '../internal-utils/parse-blocks'
import {
getBlockKeyFromSelectionPoint,
getChildKeyFromSelectionPoint,
} from '../selection/selection-point'
import {getSelectionEndPoint, getSelectionStartPoint} from '../utils'
/**
* @public
*/
export const getFocusBlock: EditorSelector<
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
if (!snapshot.context.selection) {
return undefined
}
const key = getBlockKeyFromSelectionPoint(snapshot.context.selection.focus)
const node = key
? snapshot.context.value.find((block) => block._key === key)
: undefined
return node && key ? {node, path: [{_key: key}]} : undefined
}
/**
* @public
*/
export const getFocusListBlock: EditorSelector<
{node: PortableTextListBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
const focusTextBlock = getFocusTextBlock(snapshot)
return focusTextBlock && isListBlock(snapshot.context, focusTextBlock.node)
? {node: focusTextBlock.node, path: focusTextBlock.path}
: undefined
}
/**
* @public
*/
export const getFocusTextBlock: EditorSelector<
{node: PortableTextTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
const focusBlock = getFocusBlock(snapshot)
return focusBlock && isTextBlock(snapshot.context, focusBlock.node)
? {node: focusBlock.node, path: focusBlock.path}
: undefined
}
/**
* @public
*/
export const getFocusBlockObject: EditorSelector<
{node: PortableTextObject; path: [KeyedSegment]} | undefined
> = (snapshot) => {
const focusBlock = getFocusBlock(snapshot)
return focusBlock && !isTextBlock(snapshot.context, focusBlock.node)
? {node: focusBlock.node, path: focusBlock.path}
: undefined
}
/**
* @public
*/
export const getFocusChild: EditorSelector<
| {
node: PortableTextObject | PortableTextSpan
path: [KeyedSegment, 'children', KeyedSegment]
}
| undefined
> = (snapshot) => {
if (!snapshot.context.selection) {
return undefined
}
const focusBlock = getFocusTextBlock(snapshot)
if (!focusBlock) {
return undefined
}
const key = getChildKeyFromSelectionPoint(snapshot.context.selection.focus)
const node = key
? focusBlock.node.children.find((span) => span._key === key)
: undefined
return node && key
? {node, path: [...focusBlock.path, 'children', {_key: key}]}
: undefined
}
/**
* @public
*/
export const getFocusSpan: EditorSelector<
| {node: PortableTextSpan; path: [KeyedSegment, 'children', KeyedSegment]}
| undefined
> = (snapshot) => {
const focusChild = getFocusChild(snapshot)
return focusChild && isSpan(snapshot.context, focusChild.node)
? {node: focusChild.node, path: focusChild.path}
: undefined
}
/**
* @public
*/
export const getFirstBlock: EditorSelector<
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
const node = snapshot.context.value[0]
return node ? {node, path: [{_key: node._key}]} : undefined
}
/**
* @public
*/
export const getLastBlock: EditorSelector<
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
const node = snapshot.context.value[snapshot.context.value.length - 1]
? snapshot.context.value[snapshot.context.value.length - 1]
: undefined
return node ? {node, path: [{_key: node._key}]} : undefined
}
/**
* @public
*/
export const getSelectedBlocks: EditorSelector<
Array<{node: PortableTextBlock; path: [KeyedSegment]}>
> = (snapshot) => {
if (!snapshot.context.selection) {
return []
}
const selectedBlocks: Array<{node: PortableTextBlock; path: [KeyedSegment]}> =
[]
const startPoint = getSelectionStartPoint(snapshot.context.selection)
const endPoint = getSelectionEndPoint(snapshot.context.selection)
const startKey = getBlockKeyFromSelectionPoint(startPoint)
const endKey = getBlockKeyFromSelectionPoint(endPoint)
if (!startKey || !endKey) {
return selectedBlocks
}
for (const block of snapshot.context.value) {
if (block._key === startKey) {
selectedBlocks.push({node: block, path: [{_key: block._key}]})
if (startKey === endKey) {
break
}
continue
}
if (block._key === endKey) {
selectedBlocks.push({node: block, path: [{_key: block._key}]})
break
}
if (selectedBlocks.length > 0) {
selectedBlocks.push({node: block, path: [{_key: block._key}]})
}
}
return selectedBlocks
}
/**
* @public
*/
export const getSelectionStartBlock: EditorSelector<
| {
node: PortableTextBlock
path: [KeyedSegment]
}
| undefined
> = (snapshot) => {
if (!snapshot.context.selection) {
return undefined
}
const startPoint = getSelectionStartPoint(snapshot.context.selection)
const key = getBlockKeyFromSelectionPoint(startPoint)
const node = key
? snapshot.context.value.find((block) => block._key === key)
: undefined
return node && key ? {node, path: [{_key: key}]} : undefined
}
/**
* @public
*/
export const getSelectionEndBlock: EditorSelector<
| {
node: PortableTextBlock
path: [KeyedSegment]
}
| undefined
> = (snapshot) => {
if (!snapshot.context.selection) {
return undefined
}
const endPoint = getSelectionEndPoint(snapshot.context.selection)
const key = getBlockKeyFromSelectionPoint(endPoint)
const node = key
? snapshot.context.value.find((block) => block._key === key)
: undefined
return node && key ? {node, path: [{_key: key}]} : undefined
}
/**
* @public
*/
export const getPreviousBlock: EditorSelector<
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
let previousBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
const selectionStartBlock = getSelectionStartBlock(snapshot)
if (!selectionStartBlock) {
return undefined
}
let foundSelectionStartBlock = false
for (const block of snapshot.context.value) {
if (block._key === selectionStartBlock.node._key) {
foundSelectionStartBlock = true
break
}
previousBlock = {node: block, path: [{_key: block._key}]}
}
if (foundSelectionStartBlock && previousBlock) {
return previousBlock
}
return undefined
}
/**
* @public
*/
export const getNextBlock: EditorSelector<
{node: PortableTextBlock; path: [KeyedSegment]} | undefined
> = (snapshot) => {
let nextBlock: {node: PortableTextBlock; path: [KeyedSegment]} | undefined
const selectionEndBlock = getSelectionEndBlock(snapshot)
if (!selectionEndBlock) {
return undefined
}
let foundSelectionEndBlock = false
for (const block of snapshot.context.value) {
if (block._key === selectionEndBlock.node._key) {
foundSelectionEndBlock = true
continue
}
if (foundSelectionEndBlock) {
nextBlock = {node: block, path: [{_key: block._key}]}
break
}
}
if (foundSelectionEndBlock && nextBlock) {
return nextBlock
}
return undefined
}