Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
christianhg committed Oct 2, 2024
1 parent a5f3937 commit 90eaf8c
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 94 deletions.
53 changes: 46 additions & 7 deletions packages/editor/src/editor/PortableTextEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import type {
} from '@sanity/types'
import {Component, type MutableRefObject, type PropsWithChildren} from 'react'
import {Subject} from 'rxjs'
import {createActor} from 'xstate'
import {Editor} from 'slate'
import {assertEvent, createActor, raise} from 'xstate'
import type {
EditableAPI,
EditableAPIDeleteOptions,
Expand All @@ -26,7 +27,14 @@ import {getPortableTextMemberSchemaTypes} from '../utils/getPortableTextMemberSc
import {compileType} from '../utils/schema'
import {SlateContainer} from './components/SlateContainer'
import {Synchronizer} from './components/Synchronizer'
import {editorMachine, type EditorActor} from './editor-machine'
import {
atTheEndOfSpan,
editorHasAnnotationMarks,
editorMachine,
selectingSpan,
selectionCollapsed,
type EditorActor,
} from './editor-machine'
import {PortableTextEditorContext} from './hooks/usePortableTextEditor'
import {defaultKeyGenerator} from './hooks/usePortableTextEditorKeyGenerator'
import {PortableTextEditorSelectionProvider} from './hooks/usePortableTextEditorSelection'
Expand Down Expand Up @@ -128,12 +136,43 @@ export class PortableTextEditor extends Component<PortableTextEditorProps> {
: compileType(props.schemaType),
)

this.editorActor = createActor(editorMachine, {
input: {
keyGenerator: this.props.keyGenerator ?? defaultKeyGenerator,
schemaTypes: this.schemaTypes,
this.editorActor = createActor(
editorMachine.provide({
actions: {
'on insert text': raise(({context, event}) => {
assertEvent(event, 'insert text')

if (
selectionCollapsed({event}) &&
selectingSpan({event}) &&
atTheEndOfSpan({event}) &&
editorHasAnnotationMarks({context, event})
) {
return {
type: 'insert span' as const,
editor: event.editor,
text: event.text,
marks: (Editor.marks(event.editor)?.marks ?? []).filter(
(mark) => context.schema.decorators.includes(mark),
),
}
}

return {
type: 'default insert text' as const,
text: event.text,
editor: event.editor,
}
}),
},
}),
{
input: {
keyGenerator: this.props.keyGenerator ?? defaultKeyGenerator,
schemaTypes: this.schemaTypes,
},
},
})
)
this.editorActor.start()
}

Expand Down
194 changes: 107 additions & 87 deletions packages/editor/src/editor/editor-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {PortableTextBlock} from '@sanity/types'
import type {FocusEvent} from 'react'
import {Editor, Range, Transforms} from 'slate'
import {
and,
assertEvent,
assign,
emit,
Expand Down Expand Up @@ -49,6 +48,17 @@ const networkLogic = fromCallback(({sendBack}) => {
*/
export type PatchEvent = {type: 'patch'; patch: Patch}

/**
* @internal
*/
export type EditorContext = {
keyGenerator: () => string
pendingEvents: Array<PatchEvent | MutationEvent>
schema: {
decorators: Array<string>
}
}

/**
* @internal
*/
Expand All @@ -58,6 +68,24 @@ export type MutationEvent = {
snapshot: Array<PortableTextBlock> | undefined
}

/**
* @internal
*/
export type InsertTextEvent = {
type: 'insert text'
text: string
editor: PortableTextSlateEditor
}

/**
* @internal
*/
export type DefaultInsertTextEvent = {
type: 'default insert text'
text: string
editor: PortableTextSlateEditor
}

type EditorEvent =
| {type: 'normalizing'}
| {type: 'done normalizing'}
Expand All @@ -66,11 +94,8 @@ type EditorEvent =
type: 'update schema'
schemaTypes: PortableTextMemberSchemaTypes
}
| {
type: 'insert text'
text: string
editor: PortableTextSlateEditor
}
| InsertTextEvent
| DefaultInsertTextEvent
| {
type: 'insert span'
editor: PortableTextSlateEditor
Expand Down Expand Up @@ -114,13 +139,7 @@ type EditorEmittedEvent =
*/
export const editorMachine = setup({
types: {
context: {} as {
keyGenerator: () => string
pendingEvents: Array<PatchEvent | MutationEvent>
schema: {
decorators: Array<string>
}
},
context: {} as EditorContext,
events: {} as EditorEvent,
emitted: {} as EditorEmittedEvent,
input: {} as {
Expand Down Expand Up @@ -161,59 +180,10 @@ export const editorMachine = setup({
'clear pending events': assign({
pendingEvents: [],
}),
},
guards: {
'selection collapsed': ({event}) => {
assertEvent(event, 'insert text')

return (
event.editor.selection != null &&
Range.isCollapsed(event.editor.selection)
)
},
'selecting span': ({event}) => {
assertEvent(event, 'insert text')

if (!event.editor.selection) {
return false
}

const [node] = Array.from(
Editor.nodes(event.editor, {
mode: 'lowest',
at: event.editor.selection.focus,
match: (n) => event.editor.isTextSpan(n),
voids: false,
}),
)[0]

return node !== undefined
},
'at the end of span': ({event}) => {
assertEvent(event, 'insert text')

if (!event.editor.selection) {
return false
}

const [node] = Array.from(
Editor.nodes(event.editor, {
mode: 'lowest',
at: event.editor.selection.focus,
match: (n) => event.editor.isTextSpan(n),
voids: false,
}),
)[0]

return node.text.length === event.editor.selection.focus.offset
},
'editor has annotation marks': ({context, event}) => {
'on insert text': raise(({event}) => {
assertEvent(event, 'insert text')

return (Editor.marks(event.editor)?.marks ?? []).some(
(mark) => !context.schema.decorators.includes(mark),
)
},
return {...event, type: 'default insert text' as const}
}),
},
actors: {
networkLogic,
Expand Down Expand Up @@ -247,29 +217,15 @@ export const editorMachine = setup({
'loading': {actions: emit({type: 'loading'})},
'done loading': {actions: emit({type: 'done loading'})},
'update schema': {actions: 'assign schema'},
'insert text': [
{
guard: and([
'selecting span',
'selection collapsed',
'at the end of span',
'editor has annotation marks',
]),
actions: raise(({context, event}) => ({
type: 'insert span',
text: event.text,
editor: event.editor,
marks: (Editor.marks(event.editor)?.marks ?? []).filter((mark) =>
context.schema.decorators.includes(mark),
),
})),
},
{
actions: ({event}) => {
Editor.insertText(event.editor, event.text)
},
'insert text': {
actions: 'on insert text',
},
'default insert text': {
actions: ({event}) => {
assertEvent(event, 'default insert text')
Editor.insertText(event.editor, event.text)
},
],
},
'insert span': {
actions: ({context, event}) => {
Transforms.insertNodes(event.editor, {
Expand Down Expand Up @@ -329,3 +285,67 @@ export const editorMachine = setup({
},
},
})

/**
* @internal
*/
export function editorHasAnnotationMarks({
context,
event,
}: {
context: EditorContext
event: InsertTextEvent
}) {
return (Editor.marks(event.editor)?.marks ?? []).some(
(mark) => !context.schema.decorators.includes(mark),
)
}

/**
* @internal
*/
export function selectionCollapsed({event}: {event: InsertTextEvent}) {
return (
event.editor.selection != null && Range.isCollapsed(event.editor.selection)
)
}

/**
* @internal
*/
export function selectingSpan({event}: {event: InsertTextEvent}) {
if (!event.editor.selection) {
return false
}

const [node] = Array.from(
Editor.nodes(event.editor, {
mode: 'lowest',
at: event.editor.selection.focus,
match: (n) => event.editor.isTextSpan(n),
voids: false,
}),
)[0]

return node !== undefined
}

/**
* @internal
*/
export function atTheEndOfSpan({event}: {event: InsertTextEvent}) {
if (!event.editor.selection) {
return false
}

const [node] = Array.from(
Editor.nodes(event.editor, {
mode: 'lowest',
at: event.editor.selection.focus,
match: (n) => event.editor.isTextSpan(n),
voids: false,
}),
)[0]

return node.text.length === event.editor.selection.focus.offset
}
3 changes: 3 additions & 0 deletions packages/editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ export {PortableTextEditable} from './editor/Editable'
export type {PortableTextEditableProps} from './editor/Editable'
export {
editorMachine,
type DefaultInsertTextEvent,
type EditorActor,
type EditorContext,
type InsertTextEvent,
type MutationEvent,
type PatchEvent,
} from './editor/editor-machine'
Expand Down

0 comments on commit 90eaf8c

Please sign in to comment.