Include styleRuns in text edit undo
styleRuns were updated on the graph during editing (via syncText) but commitTextEdit only captured text changes. Now both text and styleRuns are snapshotted at startTextEditing and restored on undo.
This commit is contained in:
parent
f77c42d798
commit
3be01dfdeb
|
|
@ -1,7 +1,9 @@
|
|||
import type { SceneNode } from '../scene-graph'
|
||||
import type { EditorContext } from './types'
|
||||
|
||||
export function createTextActions(ctx: EditorContext) {
|
||||
let textBeforeEdit: string | null = null
|
||||
let styleRunsBeforeEdit: SceneNode['styleRuns'] | null = null
|
||||
|
||||
function startTextEditing(nodeId: string) {
|
||||
const te = ctx.getTextEditor()
|
||||
|
|
@ -9,6 +11,7 @@ export function createTextActions(ctx: EditorContext) {
|
|||
const node = ctx.graph.getNode(nodeId)
|
||||
if (!node) return
|
||||
textBeforeEdit = node.text
|
||||
styleRunsBeforeEdit = structuredClone(node.styleRuns)
|
||||
ctx.state.editingTextId = nodeId
|
||||
if (te) {
|
||||
te.setRenderer(ctx.getRenderer())
|
||||
|
|
@ -22,28 +25,37 @@ export function createTextActions(ctx: EditorContext) {
|
|||
if (!te?.isActive) {
|
||||
ctx.state.editingTextId = null
|
||||
textBeforeEdit = null
|
||||
styleRunsBeforeEdit = null
|
||||
return
|
||||
}
|
||||
const result = te.stop()
|
||||
if (!result) {
|
||||
ctx.state.editingTextId = null
|
||||
textBeforeEdit = null
|
||||
styleRunsBeforeEdit = null
|
||||
ctx.requestRender()
|
||||
return
|
||||
}
|
||||
const prevText = textBeforeEdit ?? ''
|
||||
const prevRuns = styleRunsBeforeEdit ?? []
|
||||
const newText = result.text
|
||||
ctx.graph.updateNode(result.nodeId, { text: newText })
|
||||
const node = ctx.graph.getNode(result.nodeId)
|
||||
const newRuns = node ? structuredClone(node.styleRuns) : []
|
||||
ctx.graph.updateNode(result.nodeId, { text: newText, styleRuns: newRuns })
|
||||
ctx.state.editingTextId = null
|
||||
textBeforeEdit = null
|
||||
if (prevText !== newText) {
|
||||
styleRunsBeforeEdit = null
|
||||
|
||||
const textChanged = prevText !== newText
|
||||
const runsChanged = JSON.stringify(prevRuns) !== JSON.stringify(newRuns)
|
||||
if (textChanged || runsChanged) {
|
||||
ctx.undo.push({
|
||||
label: 'Edit text',
|
||||
forward: () => {
|
||||
ctx.graph.updateNode(result.nodeId, { text: newText })
|
||||
ctx.graph.updateNode(result.nodeId, { text: newText, styleRuns: newRuns })
|
||||
},
|
||||
inverse: () => {
|
||||
ctx.graph.updateNode(result.nodeId, { text: prevText })
|
||||
ctx.graph.updateNode(result.nodeId, { text: prevText, styleRuns: prevRuns })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { describe, test, expect } from 'bun:test'
|
|||
import { SceneGraph, TextEditor, UndoManager } from '@open-pencil/core'
|
||||
import { createTextActions } from '@open-pencil/core/editor'
|
||||
|
||||
import type { StyleRun } from '@open-pencil/core'
|
||||
import type { EditorContext, EditorState } from '@open-pencil/core/editor'
|
||||
|
||||
function setup() {
|
||||
|
|
@ -117,4 +118,58 @@ describe('text edit undo', () => {
|
|||
undo.undo()
|
||||
expect(graph.getNode(textNode.id)!.text).toBe('Hello')
|
||||
})
|
||||
|
||||
test('undo restores styleRuns when they changed during editing', () => {
|
||||
const { graph, undo, textEditor, textNode, actions } = setup()
|
||||
|
||||
const boldRun: StyleRun = { start: 0, length: 5, style: { fontWeight: 700 } }
|
||||
graph.updateNode(textNode.id, { styleRuns: [boldRun] })
|
||||
|
||||
actions.startTextEditing(textNode.id)
|
||||
|
||||
textEditor.insert(' World', textNode)
|
||||
const newRuns: StyleRun[] = [
|
||||
{ start: 0, length: 5, style: { fontWeight: 700 } },
|
||||
{ start: 5, length: 6, style: { fontWeight: 400 } },
|
||||
]
|
||||
graph.updateNode(textNode.id, { text: textEditor.state!.text, styleRuns: newRuns })
|
||||
|
||||
actions.commitTextEdit()
|
||||
|
||||
expect(graph.getNode(textNode.id)!.styleRuns).toEqual(newRuns)
|
||||
expect(undo.canUndo).toBe(true)
|
||||
|
||||
undo.undo()
|
||||
expect(graph.getNode(textNode.id)!.text).toBe('Hello')
|
||||
expect(graph.getNode(textNode.id)!.styleRuns).toEqual([boldRun])
|
||||
|
||||
undo.redo()
|
||||
expect(graph.getNode(textNode.id)!.text).toBe('Hello World')
|
||||
expect(graph.getNode(textNode.id)!.styleRuns).toEqual(newRuns)
|
||||
})
|
||||
|
||||
test('undo entry is pushed when only styleRuns changed', () => {
|
||||
const { graph, undo, textNode, actions } = setup()
|
||||
|
||||
actions.startTextEditing(textNode.id)
|
||||
|
||||
const newRuns: StyleRun[] = [{ start: 0, length: 5, style: { fontWeight: 700 } }]
|
||||
graph.updateNode(textNode.id, { styleRuns: newRuns })
|
||||
|
||||
actions.commitTextEdit()
|
||||
|
||||
expect(undo.canUndo).toBe(true)
|
||||
|
||||
undo.undo()
|
||||
expect(graph.getNode(textNode.id)!.styleRuns).toEqual([])
|
||||
})
|
||||
|
||||
test('no undo entry when neither text nor styleRuns changed', () => {
|
||||
const { undo, actions, textNode } = setup()
|
||||
|
||||
actions.startTextEditing(textNode.id)
|
||||
actions.commitTextEdit()
|
||||
|
||||
expect(undo.canUndo).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue