From 98c3324624515f07f5dcf6f30015d8bf14ffb253 Mon Sep 17 00:00:00 2001 From: Amp Date: Sat, 14 Nov 2026 17:38:17 +0000 Subject: [PATCH] feat(rust): @lexical/mark's model, with JS-recorded scripts/mark and serialization/mark vectors ## Description The Rust port had no `@lexical/mark`, so it couldn't read or write the comment marks Swift and JS anchor in a document. This adds it behind a new `mark` feature (needs `utils`, as upstream): - `MarkNode` with its generated JSON in both forms, its insertion hooks (`canInsertTextBefore`/`After`, `canBeEmpty`, `isInline`, `excludeFromCopy`, `canRemoveText`, `extractWithChild`) and its id methods (`hasID`, `getIDs`, `setIDs`, `addID`, `deleteID`, which removes the mark with its last id); - `$createMarkNode`, `$isMarkNode`, `$wrapSelectionInMarkNode`, `$unwrapMarkNode` and `$getMarkIDs`; - the `MarkStateExtension.ts` functions that work without the extension, for the default flavor: `$addMarkID`, `$removeMarkID`, `$getNodeMarkIDs`, `$getMarkIDsAtSelection` and `$migrateMarkNodesToMarkState`. `packages/lexical-conformance` gains a `mark: true` script option, actions for those functions and methods, a `scripts/mark` vector (30 cases: wrapping across text nodes, partial text, several blocks, backward selections, overlapping marks, line breaks, decorators and inline elements in the range, typing and deleting at mark edges, unwrapping, ids, MarkState) and a `serialization/mark` vector (4 cases). Swift replays both and matched JS on every case, so no Swift source changes; `LexicalMark` joins the `ConformanceTests` dependencies. Not ported, each listed in PARITY.md: `MarkExtension`, `MarkStateExtension` and the flavor forms (the extension API has just landed; they come next), `registerMarkIDsListener`, `registerMarkState`'s inserted-text rule, and the DOM parts (`createDOM`, rendering, the HTML import rule), which come with `html`. ## Test plan ### Before ``` $ cargo run -q --locked --release -p lexical-conformance | **Total** | 657 | 657 | 0 | 0 | 0 | | ``` (No `scripts/mark` or `serialization/mark` row.) ### After ``` $ cargo run -q --locked --release -p lexical-conformance | `scripts/mark` | 30 | 30 | 0 | 0 | 0 | pass | | `serialization/mark` | 4 | 4 | 0 | 0 | 0 | pass | | **Total** | 691 | 691 | 0 | 0 | 0 | | ``` Mutation checks (each reverted): `deleteID` doing nothing fails 2 cases; a line break wrapped like text fails 1; ids sorted by code point instead of UTF-16 fails 1; `canBeEmpty` true fails 2; `excludeFromCopy` inverted fails 1. `canInsertTextBefore` true fails nothing: headless `$insertText` puts text typed at an inline parent's edge outside it whatever the hook says, as PARITY.md notes for `LinkNode`. `cargo fmt --all --check`, `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings`, `cargo test --workspace --all-features --locked`, `cargo doc` with `-D warnings`, `cargo +1.88 check -p lexical-editor --all-features`, `--no-default-features` alone and with `mark` and `mark,extension`, and the Rune app's clippy all pass. `UPDATE_VECTORS=1 pnpm exec vitest run src/scripts.test.ts src/serialization.test.ts`: 27 passed, and no vector changed (md5). Swift: `swift build --build-tests` and `swift test --skip-build --filter ConformanceTests`, 34 tests in 13 suites passed. Amp-Thread-ID: https://ampcode.com/threads/T-16d45229-bfa8-417a-a091-d3e3888d830d --- packages/lexical-conformance/package.json | 1 + packages/lexical-conformance/src/ops.ts | 146 ++++- .../lexical-conformance/src/scripts.test.ts | 510 ++++++++++++++++++ .../src/serialization.test.ts | 73 +++ .../vectors/scripts/mark.json | 32 ++ .../vectors/serialization/mark.json | 6 + pnpm-lock.yaml | 3 + rust/DESIGN.md | 4 +- rust/PARITY.md | 31 ++ rust/conformance/Cargo.toml | 2 +- rust/conformance/src/scripts.rs | 170 +++++- rust/conformance/src/suites/mod.rs | 13 +- rust/conformance/src/suites/serialization.rs | 5 +- rust/lexical-editor/Cargo.toml | 7 + rust/lexical-editor/src/lib.rs | 2 + .../lexical-editor/src/mark/generated_json.rs | 50 ++ rust/lexical-editor/src/mark/mark_node.rs | 194 +++++++ .../src/mark/mark_state_extension.rs | 447 +++++++++++++++ rust/lexical-editor/src/mark/mod.rs | 171 ++++++ swift/Package.swift | 2 +- swift/Tests/ConformanceTests/Ops.swift | 86 ++- .../Tests/ConformanceTests/ScriptTests.swift | 2 +- .../ConformanceTests/SerializationTests.swift | 7 + 23 files changed, 1948 insertions(+), 16 deletions(-) create mode 100644 packages/lexical-conformance/vectors/scripts/mark.json create mode 100644 packages/lexical-conformance/vectors/serialization/mark.json create mode 100644 rust/lexical-editor/src/mark/generated_json.rs create mode 100644 rust/lexical-editor/src/mark/mark_node.rs create mode 100644 rust/lexical-editor/src/mark/mark_state_extension.rs create mode 100644 rust/lexical-editor/src/mark/mod.rs diff --git a/packages/lexical-conformance/package.json b/packages/lexical-conformance/package.json index 5667ddcf7bf..abf91c7a444 100644 --- a/packages/lexical-conformance/package.json +++ b/packages/lexical-conformance/package.json @@ -27,6 +27,7 @@ "@lexical/html": "workspace:*", "@lexical/link": "workspace:*", "@lexical/list": "workspace:*", + "@lexical/mark": "workspace:*", "@lexical/markdown": "workspace:*", "@lexical/overflow": "workspace:*", "@lexical/plain-text": "workspace:*", diff --git a/packages/lexical-conformance/src/ops.ts b/packages/lexical-conformance/src/ops.ts index 69687e8c451..41fa40b9483 100644 --- a/packages/lexical-conformance/src/ops.ts +++ b/packages/lexical-conformance/src/ops.ts @@ -53,6 +53,18 @@ import { REMOVE_LIST_COMMAND, UPDATE_LIST_START_COMMAND, } from '@lexical/list'; +import { + $addMarkID, + $getMarkIDs, + $getMarkIDsAtSelection, + $getNodeMarkIDs, + $isMarkNode, + $migrateMarkNodesToMarkState, + $removeMarkID, + $unwrapMarkNode, + $wrapSelectionInMarkNode, + MarkNode, +} from '@lexical/mark'; import {OverflowNode} from '@lexical/overflow'; import {registerPlainText} from '@lexical/plain-text'; import { @@ -237,6 +249,37 @@ export type Action = * (@lexical/text). */ | {do: 'logTextIntersection'; characters: number} + /** + * `$wrapSelectionInMarkNode($getSelection(), isBackward, id)` + * (@lexical/mark), with the default `$createMarkNode`. + */ + | {do: 'wrapSelectionInMarkNode'; isBackward: boolean; id: string} + /** `$unwrapMarkNode(node)`. */ + | {do: 'unwrapMarkNode'; key: string} + /** Logs `['markIDs', key, offset, $getMarkIDs(node, offset)]`. */ + | {do: 'logMarkIDs'; key: string; offset: number} + /** + * `$addMarkID(id, $getSelection())` (MarkStateExtension.ts, without the + * extension), logging `['addMarkID', id, keys]` of the nodes it marked. + */ + | {do: 'addMarkID'; id: string} + /** + * `$removeMarkID(id, $getSelection())` with `inSelection`, otherwise + * `$removeMarkID(id)`, which walks the document. + */ + | {do: 'removeMarkID'; id: string; inSelection?: boolean} + /** Logs `['nodeMarkIDs', key, $getNodeMarkIDs(node)]`. */ + | {do: 'logNodeMarkIDs'; key: string} + /** + * Logs `['markIDsAtSelection', mode, ids]`: `$getMarkIDsAtSelection()` of + * the current selection, with `mode` when it is given (`null` otherwise). + */ + | {do: 'logMarkIDsAtSelection'; mode?: 'any' | 'all'} + /** + * `$migrateMarkNodesToMarkState(node)` of the node `key`, or of the root + * when there is none, logging `['migrateMarkNodesToMarkState', count]`. + */ + | {do: 'migrateMarkNodesToMarkState'; key?: string} | {do: 'dispatch'; command: string; payload?: Json}; /** @@ -464,6 +507,12 @@ export type Script = { extensions?: ExtensionArg[]; /** The test extensions `extensions` names, by key. */ extensionDefs?: {[key: string]: ExtensionSpec}; + /** + * Register `@lexical/mark`'s `MarkNode`, as `MarkExtension` does. The + * MarkState functions (`$addMarkID`...) run without `MarkStateExtension`, + * with its default `markableNodes`. + */ + mark?: boolean; steps: Step[]; /** Record `state` after every step (default), or after the last only. */ snapshot?: 'every' | 'final'; @@ -643,6 +692,13 @@ function element(node: LexicalNode): ElementNode { return node; } +function markNode(node: LexicalNode): MarkNode { + if (!$isMarkNode(node)) { + throw new Error(`${node.getKey()} is not a mark node`); + } + return node; +} + function listItem(node: LexicalNode): ListItemNode { if (!$isListItemNode(node)) { throw new Error(`${node.getKey()} is not a list item`); @@ -653,15 +709,48 @@ function listItem(node: LexicalNode): ListItemNode { /** * The node methods a script may call, by name. A method that exists on both * text and element nodes (`select`, `setFormat`, `setStyle`) calls the one of - * the node's kind. The return value is logged only for `splitText`. + * the node's kind. The return value is logged only for `splitText` and the + * methods that read (`hasID`, `getIDs`, `extractWithChild`, + * `excludeFromCopy`). */ const NODE_METHODS: Record< string, (node: LexicalNode, args: Json[] | undefined, ctx: Context) => void > = { + addID: (n, args) => markNode(n).addID(arg(args, 0) as string), append: (n, args) => element(n).append(...$parse(arg(args, 0) as SerializedLexicalNode[])), clear: n => element(n).clear(), + deleteID: (n, args) => markNode(n).deleteID(arg(args, 0) as string), + // `excludeFromCopy(destination)`, logged. + excludeFromCopy: (n, args, ctx) => { + const destination = arg(args, 0) as 'clone' | 'html'; + ctx.log.push([ + 'excludeFromCopy', + n.getKey(), + destination, + element(n).excludeFromCopy(destination), + ]); + }, + // `extractWithChild(child, $getSelection(), destination)`, logged. + extractWithChild: (n, args, ctx) => { + const child = arg(args, 0) as string; + const destination = arg(args, 1) as 'clone' | 'html'; + ctx.log.push([ + 'extractWithChild', + n.getKey(), + child, + destination, + element(n).extractWithChild($node(child), $anySel(), destination), + ]); + }, + getIDs: (n, _args, ctx) => { + ctx.log.push(['getIDs', n.getKey(), markNode(n).getIDs()]); + }, + hasID: (n, args, ctx) => { + const id = arg(args, 0) as string; + ctx.log.push(['hasID', n.getKey(), id, markNode(n).hasID(id)]); + }, insertAfter: (n, args) => n.insertAfter( $parse([arg(args, 0) as SerializedLexicalNode])[0], @@ -711,6 +800,7 @@ const NODE_METHODS: Record< element(n).setFormat(arg(args, 0) as never); } }, + setIDs: (n, args) => markNode(n).setIDs(arg(args, 0) as string[]), setIndent: (n, args) => element(n).setIndent(arg(args, 0) as number), setMode: (n, args) => text(n).setMode(arg(args, 0) as never), setStyle: (n, args) => { @@ -982,6 +1072,58 @@ function $runAction(action: Action, ctx: Context): void { ]); return; } + case 'wrapSelectionInMarkNode': + $wrapSelectionInMarkNode($sel(), action.isBackward, action.id); + return; + case 'unwrapMarkNode': + $unwrapMarkNode(markNode($node(action.key))); + return; + case 'logMarkIDs': + ctx.log.push([ + 'markIDs', + action.key, + action.offset, + $getMarkIDs(text($node(action.key)), action.offset), + ]); + return; + case 'addMarkID': + ctx.log.push([ + 'addMarkID', + action.id, + $addMarkID(action.id, $anySel()).map(n => n.getKey()), + ]); + return; + case 'removeMarkID': + if (action.inSelection) { + $removeMarkID(action.id, $anySel()); + } else { + $removeMarkID(action.id); + } + return; + case 'logNodeMarkIDs': + ctx.log.push([ + 'nodeMarkIDs', + action.key, + $getNodeMarkIDs($node(action.key)), + ]); + return; + case 'logMarkIDsAtSelection': + ctx.log.push([ + 'markIDsAtSelection', + action.mode ?? null, + action.mode === undefined + ? $getMarkIDsAtSelection() + : $getMarkIDsAtSelection(undefined, action.mode), + ]); + return; + case 'migrateMarkNodesToMarkState': + ctx.log.push([ + 'migrateMarkNodesToMarkState', + $migrateMarkNodesToMarkState( + action.key === undefined ? undefined : $node(action.key), + ), + ]); + return; case 'dispatch': ctx.log.push([ 'dispatched', @@ -1400,6 +1542,7 @@ export function startSession(script: Omit): Session { 'overflow', 'hashtag', 'extension', + 'mark', ] as const) { if (script[flag]) { throw new Error(`extensions and ${flag} don't mix`); @@ -1433,6 +1576,7 @@ export function startSession(script: Omit): Session { ...(script.overflow ? [OverflowNode] : []), ...(script.hashtag ? [HashtagNode] : []), ...(script.extension ? [HorizontalRuleNode, DecoratorTextNode] : []), + ...(script.mark ? [MarkNode] : []), ], onError: e => { throw e; diff --git a/packages/lexical-conformance/src/scripts.test.ts b/packages/lexical-conformance/src/scripts.test.ts index 8730d7cac20..6975e580de3 100644 --- a/packages/lexical-conformance/src/scripts.test.ts +++ b/packages/lexical-conformance/src/scripts.test.ts @@ -3798,3 +3798,513 @@ test('scripts/extensions', () => { ], ); }); + +function mark(ids: unknown, ...children: Node[]): Node { + return { + children, + direction: null, + format: '', + ids, + indent: 0, + type: 'mark', + version: 1, + } as Node; +} + +test('scripts/mark', () => { + // @lexical/mark: MarkNode (its ids, its insertion behavior through typing, + // Enter and deletion at its edges, extractWithChild and excludeFromCopy), + // $wrapSelectionInMarkNode, $unwrapMarkNode and $getMarkIDs (index.ts), + // and the MarkState functions of MarkStateExtension.ts that need no + // extension ($addMarkID, $removeMarkID, $getNodeMarkIDs, + // $getMarkIDsAtSelection, $migrateMarkNodesToMarkState), which store ids as + // the `markIds` NodeState of the leaves. Commands are dispatched outside an + // update, as an input layer does. + const hr = (): Node => ({type: 'horizontalrule', version: 1}) as Node; + const dt = (): Node => ({type: 'decorator-text', version: 1}) as Node; + const run = (dispatch: string, payload?: Json): Step => + payload === undefined ? {dispatch} : {dispatch, payload}; + const type = (text: string): Step => + run('CONTROLLED_TEXT_INSERTION_COMMAND', text); + const enter = run('INSERT_PARAGRAPH_COMMAND'); + const backspace = run('DELETE_CHARACTER_COMMAND', true); + const del = run('DELETE_CHARACTER_COMMAND', false); + const wrap = (id: string, isBackward = false): Action => ({ + do: 'wrapSelectionInMarkNode', + id, + isBackward, + }); + const unwrap = (key: string): Action => ({do: 'unwrapMarkNode', key}); + const markIDs = (key: string, offset: number): Action => ({ + do: 'logMarkIDs', + key, + offset, + }); + const addMarkID = (id: string): Action => ({do: 'addMarkID', id}); + const removeMarkID = (id: string, inSelection?: boolean): Action => + inSelection === undefined + ? {do: 'removeMarkID', id} + : {do: 'removeMarkID', id, inSelection}; + const nodeMarkIDs = (key: string): Action => ({do: 'logNodeMarkIDs', key}); + const atSelection = (mode?: 'any' | 'all'): Action => + mode === undefined + ? {do: 'logMarkIDsAtSelection'} + : {do: 'logMarkIDsAtSelection', mode}; + const migrate = (key?: string): Action => + key === undefined + ? {do: 'migrateMarkNodesToMarkState'} + : {do: 'migrateMarkNodesToMarkState', key}; + const ids = (...markIds: unknown[]) => ({$: {markIds}}); + const extension = true; + const link = true; + const richText = true; + // Keys: the paragraph 1, "ab" 2, the mark 3, "cd" 4, "ef" 5. + const edges = doc(p(t('ab'), mark(['a'], t('cd')), t('ef'))); + emit( + 'mark', + "@lexical/mark: $wrapSelectionInMarkNode across text nodes, partial text, blocks, backward selections, overlapping marks, line breaks, decorators and inline elements; $getMarkIDs; $unwrapMarkNode; MarkNode's ids (addID, deleteID down to none, setIDs, hasID, getIDs), its JSON, and its insertion behavior (canInsertTextBefore/After, canBeEmpty, isInline, insertNewAfter, extractWithChild, excludeFromCopy) through typing, Enter and deletion at its edges; and the MarkState functions that need no extension ($addMarkID, $removeMarkID, $getNodeMarkIDs, $getMarkIDsAtSelection, $migrateMarkNodesToMarkState).", + [ + { + mark: true, + name: 'wrapping text nodes and partial text', + steps: [ + doc(p(t('Hello '), t('bold', {format: 1}), t(' world')), p(t('one'))), + update(select(['2', 2, 'text'], ['4', 3, 'text']), wrap('a')), + update(select(['6', 1, 'text'], ['6', 2, 'text']), wrap('b')), + update(select(['6', 0, 'text'], ['6', 0, 'text']), wrap('c')), + ], + }, + { + mark: true, + name: 'wrapping backward selections', + steps: [ + doc( + p(t('Hello '), t('bold', {format: 1}), t(' world')), + p(t('abcdef')), + ), + update(select(['4', 3, 'text'], ['2', 2, 'text']), wrap('a', true)), + update(select(['6', 5, 'text'], ['6', 1, 'text']), wrap('b')), + ], + }, + { + mark: true, + name: 'wrapping across blocks', + steps: [ + doc(p(t('one')), p(t('two'), t('2', {format: 1})), p(t('three'))), + update(select(['2', 1, 'text'], ['7', 2, 'text']), wrap('a')), + update( + select(['root', 0, 'element'], ['root', 3, 'element']), + wrap('b'), + ), + ], + }, + { + mark: true, + name: 'overlapping marks', + steps: [ + doc(p(t('abcdef'))), + update(select(['2', 1, 'text'], ['2', 4, 'text']), wrap('a')), + update(select(['3', 1, 'text'], ['4', 1, 'text']), wrap('b')), + ], + }, + { + extension, + link, + mark: true, + name: 'line breaks, decorators and inline elements in the range', + steps: [ + // Keys: the paragraph 1, "a" 2, the line break 3, "b" 4, the link + // 5, "c" 6, "d" 7, the inline decorator 8, "e" 9, the rule 10, the + // paragraph 11, "f" 12. + doc( + p( + t('a'), + br(), + t('b'), + a('https://x', t('c')), + t('d'), + dt(), + t('e'), + ), + hr(), + p(t('f')), + ), + update(select(['2', 0, 'text'], ['12', 1, 'text']), wrap('a')), + ], + }, + { + extension, + link, + mark: true, + name: 'wrapping from inside a link', + steps: [ + doc(p(t('a'), a('https://x', t('bc')), t('d'))), + update(select(['4', 1, 'text'], ['5', 1, 'text']), wrap('a')), + ], + }, + { + mark: true, + name: 'getMarkIDs', + steps: [ + // Keys: the paragraph 1, "ab" 2, the mark 3, "cd" 4, the mark 5, + // "ef" 6, "gh" 7. + doc( + p( + t('ab'), + mark(['a', 'b'], t('cd'), mark(['c'], t('ef'))), + t('gh'), + ), + ), + update( + markIDs('2', 0), + markIDs('2', 1), + markIDs('2', 2), + markIDs('4', 0), + markIDs('4', 2), + markIDs('6', 1), + markIDs('7', 0), + markIDs('7', 2), + ), + ], + }, + { + mark: true, + name: 'typing at the start of a mark', + richText, + steps: [edges, update(select(['4', 0, 'text'])), type('x')], + }, + { + mark: true, + name: 'typing at the end of a mark', + richText, + steps: [edges, update(select(['4', 2, 'text'])), type('x')], + }, + { + mark: true, + name: 'typing inside a mark', + richText, + steps: [edges, update(select(['4', 1, 'text'])), type('x')], + }, + { + mark: true, + name: 'typing at element points around a mark', + richText, + steps: [ + edges, + update(select(['3', 0, 'element'])), + type('x'), + update(select(['3', 1, 'element'])), + type('y'), + update(select(['1', 1, 'element'])), + type('z'), + ], + }, + { + mark: true, + name: 'typing over a range that ends in a mark', + richText, + steps: [ + edges, + update(select(['2', 1, 'text'], ['4', 1, 'text'])), + type('x'), + ], + }, + { + mark: true, + name: 'Enter in a mark', + richText, + steps: [ + edges, + update(select(['4', 1, 'text'])), + enter, + update(select(['4', 1, 'text'])), + enter, + ], + }, + { + mark: true, + name: 'removing all of a mark', + richText, + steps: [ + edges, + update(select(['4', 0, 'text'], ['4', 2, 'text'])), + run('REMOVE_TEXT_COMMAND'), + ], + }, + { + mark: true, + name: 'removing text across a mark edge', + richText, + steps: [ + edges, + update(select(['2', 1, 'text'], ['4', 1, 'text'])), + del, + update(select(['4', 1, 'text'], ['5', 1, 'text'])), + backspace, + ], + }, + { + mark: true, + name: 'Backspace and Delete at mark edges', + richText, + steps: [ + edges, + update(select(['4', 0, 'text'])), + backspace, + update(select(['4', 2, 'text'])), + del, + update(select(['2', 2, 'text'])), + del, + ], + }, + { + mark: true, + name: 'removing the last child of a mark', + steps: [edges, update(node('4', 'remove'))], + }, + { + mark: true, + name: 'unwrapping', + steps: [ + doc(p(t('a'), mark(['x'], t('b'), t('c', {format: 1})), t('d'))), + update(select(['4', 1, 'text']), unwrap('3')), + ], + }, + { + mark: true, + name: 'unwrapping nested marks', + steps: [ + // Keys: the paragraph 1, the mark 2, "a" 3, the mark 4, "b" 5. + doc(p(mark(['x'], t('a'), mark(['y'], t('b'))))), + update(select(['5', 1, 'text']), unwrap('2')), + update(unwrap('4')), + ], + }, + { + mark: true, + name: 'mark ids', + steps: [ + doc(p(mark(['a'], t('x')))), + update(node('2', 'addID', 'b'), node('2', 'getIDs')), + update(node('2', 'addID', 'a'), node('2', 'getIDs')), + update( + node('2', 'hasID', 'a'), + node('2', 'hasID', 'z'), + node('2', 'deleteID', 'z'), + ), + update(node('2', 'deleteID', 'a'), node('2', 'getIDs')), + update(node('2', 'setIDs', ['c', 'b', 'c']), node('2', 'getIDs')), + update(node('2', 'deleteID', 'c'), node('2', 'getIDs')), + update(node('2', 'addID', 'c'), node('2', 'getIDs')), + ], + }, + { + mark: true, + name: 'deleting the last id', + steps: [ + doc(p(t('a'), mark(['x'], t('b')), t('c'))), + // The mark stays, with no ids. + update(node('3', 'deleteID', 'x'), node('3', 'getIDs')), + // As the playground's CommentPlugin does next. + update(unwrap('3')), + ], + }, + { + mark: true, + name: 'MarkNode JSON with ids that are not strings', + steps: [ + doc( + p( + mark(['a', 1, null, 'b'], t('x')), + mark('a', t('y')), + mark(undefined, t('z')), + ), + ), + update(node('2', 'getIDs'), node('4', 'getIDs'), node('6', 'getIDs')), + ], + }, + { + mark: true, + name: 'extractWithChild and excludeFromCopy', + steps: [ + // Keys: the paragraph 1, the mark 2, "cd" 3, " " 4, the mark 5, + // "ef" 6, "gh" 7. + doc( + p( + mark(['a'], t('cd')), + t(' '), + mark(['b'], t('ef'), t('gh', {format: 1})), + ), + ), + update( + select(['3', 0, 'text'], ['3', 2, 'text']), + node('2', 'extractWithChild', '3', 'clone'), + node('2', 'extractWithChild', '3', 'html'), + ), + update( + select(['3', 2, 'text'], ['3', 0, 'text']), + node('2', 'extractWithChild', '3', 'clone'), + ), + update( + select(['3', 0, 'text'], ['3', 1, 'text']), + node('2', 'extractWithChild', '3', 'clone'), + ), + update( + select(['3', 0, 'text'], ['4', 1, 'text']), + node('2', 'extractWithChild', '3', 'clone'), + ), + update( + select(['6', 0, 'text'], ['7', 2, 'text']), + node('5', 'extractWithChild', '6', 'clone'), + ), + update( + select(['6', 2, 'text'], ['7', 2, 'text']), + node('5', 'extractWithChild', '7', 'clone'), + ), + update( + {do: 'selectNodes', keys: ['3']}, + node('2', 'extractWithChild', '3', 'clone'), + node('2', 'excludeFromCopy', 'clone'), + node('2', 'excludeFromCopy', 'html'), + ), + ], + }, + { + extension, + link, + mark: true, + name: 'addMarkID', + steps: [ + // Keys: the paragraph 1, "Hello world" 2, the line break 3, "next" + // 4, the link 5, "link" 6, the inline decorator 7, "end" 8, the + // rule 9, the paragraph 10, "second" 11. + doc( + p( + t('Hello world'), + br(), + t('next'), + a('https://x', t('link')), + dt(), + t('end'), + ), + hr(), + p(t('second')), + ), + update(select(['2', 6, 'text'], ['11', 3, 'text']), addMarkID('c1')), + update(select(['2', 0, 'text'], ['4', 2, 'text']), addMarkID('c2')), + // Collapsed: nothing is marked. + update(select(['2', 1, 'text']), addMarkID('c3')), + // A node selection of the inline decorator. + update({do: 'selectNodes', keys: ['7']}, addMarkID('c4')), + // Invalid ids throw. + update(select(['2', 0, 'text'], ['2', 1, 'text']), addMarkID('a b')), + update(select(['2', 0, 'text'], ['2', 1, 'text']), addMarkID('')), + ], + }, + { + mark: true, + name: 'getNodeMarkIDs and getMarkIDsAtSelection', + steps: [ + // Keys: the paragraph 1, "ab" 2, "cd" 3, "ef" 4, "gh" 5. + doc(p(t('ab'), t('cd', ids('b', 'a')), t('ef', ids('a')), t('gh'))), + update( + nodeMarkIDs('1'), + nodeMarkIDs('2'), + nodeMarkIDs('3'), + nodeMarkIDs('4'), + ), + update(select(['2', 1, 'text']), atSelection()), + update(select(['2', 2, 'text']), atSelection()), + update(select(['3', 1, 'text']), atSelection()), + update(select(['4', 2, 'text']), atSelection()), + update(select(['1', 1, 'element']), atSelection()), + update(select(['1', 0, 'element']), atSelection()), + update( + select(['2', 1, 'text'], ['4', 1, 'text']), + atSelection(), + atSelection('any'), + atSelection('all'), + ), + update( + select(['2', 2, 'text'], ['3', 2, 'text']), + atSelection('all'), + ), + update( + select(['3', 1, 'text'], ['5', 0, 'text']), + atSelection('all'), + ), + update(select(['5', 0, 'text'], ['5', 2, 'text']), atSelection()), + update({do: 'clearSelection'}, atSelection()), + ], + }, + { + mark: true, + name: 'mark ids from JSON', + steps: [ + doc( + p( + t('x', ids('b', 'a', 'a', ' bad', 3, '', null)), + t(' '), + t('y', ids('\uff01', '\u{1f600}', 'b')), + t(' '), + t('z', {$: {markIds: 'a'}}), + ), + ), + update(nodeMarkIDs('2'), nodeMarkIDs('4'), nodeMarkIDs('6')), + update(select(['2', 0, 'text'], ['4', 1, 'text']), addMarkID('c')), + ], + }, + { + mark: true, + name: 'removeMarkID', + steps: [ + doc( + p(t('one', ids('a', 'b')), t('two', ids('a')), t('three')), + p(t('four', ids('a'))), + ), + update( + select(['3', 1, 'text'], ['6', 2, 'text']), + removeMarkID('a', true), + ), + update(removeMarkID('a')), + update(removeMarkID('b')), + update(removeMarkID('missing')), + ], + }, + { + extension, + link, + mark: true, + name: 'migrateMarkNodesToMarkState', + steps: [ + doc( + p( + t('a'), + mark(['x'], t('b'), mark(['y', 'x'], t('c')), br(), dt()), + t('d'), + ), + p(mark(['z'], a('https://x', t('e'))), t('f')), + ), + update(migrate()), + update(migrate()), + ], + }, + { + mark: true, + name: 'migrateMarkNodesToMarkState of a subtree', + steps: [ + doc(p(mark(['x'], t('a'))), p(mark(['y'], t('b')))), + // Keys: the paragraph 1, the mark 2, "a" 3, the paragraph 4, the + // mark 5, "b" 6. + update(migrate('1')), + update(migrate('4')), + ], + }, + { + mark: true, + name: 'migrateMarkNodesToMarkState with an invalid id', + steps: [doc(p(mark(['a b'], t('a')))), update(migrate())], + }, + ], + ); +}); diff --git a/packages/lexical-conformance/src/serialization.test.ts b/packages/lexical-conformance/src/serialization.test.ts index d1b716310cc..3c3e481104d 100644 --- a/packages/lexical-conformance/src/serialization.test.ts +++ b/packages/lexical-conformance/src/serialization.test.ts @@ -24,10 +24,12 @@ import { ListItemNode, ListNode, } from '@lexical/list'; +import {$addMarkID, $createMarkNode, MarkNode} from '@lexical/mark'; import {$createOverflowNode, OverflowNode} from '@lexical/overflow'; import { $createLineBreakNode, $createParagraphNode, + $createRangeSelection, $createTabNode, $createTextNode, $getRoot, @@ -339,3 +341,74 @@ test('serialization/overflow', () => { cases, ); }); + +// Documents with @lexical/mark's MarkNode, and with the `markIds` NodeState +// that MarkStateExtension's $addMarkID stores on the leaves instead. +test('serialization/mark', () => { + const documents: Record void> = { + 'a mark with element properties': () => { + $getRoot().append( + $createParagraphNode().append( + $createMarkNode(['x']) + .setFormat('center') + .setIndent(1) + .setDirection('rtl') + .setTextFormat(1) + .setTextStyle('color: red') + .append($createTextNode('b')), + ), + ); + }, + 'a mark with no ids': () => { + $getRoot().append( + $createParagraphNode().append( + $createMarkNode().append($createTextNode('none')), + ), + ); + }, + 'mark ids as NodeState': () => { + const text = $createTextNode('one two'); + $getRoot().append( + $createParagraphNode().append(text, $createLineBreakNode()), + ); + const selection = $createRangeSelection(); + selection.anchor.set(text.getKey(), 0, 'text'); + selection.focus.set(text.getKey(), 3, 'text'); + $addMarkID('c2', selection); + $addMarkID('c1', selection); + }, + 'nested and overlapping marks': () => { + $getRoot().append( + $createParagraphNode().append( + $createTextNode('a'), + $createMarkNode(['x', 'y']).append( + $createTextNode('b').toggleFormat('bold'), + $createMarkNode(['z']).append($createTextNode('c')), + ), + $createTextNode('d'), + ), + ); + }, + }; + const cases: Case[] = Object.entries(documents).map(([name, build]) => { + const editor = createHeadlessEditor({ + nodes: [MarkNode], + onError: e => { + throw e; + }, + }); + editor.update(build, {discrete: true}); + const state = editor.getEditorState(); + return { + compact: JSON.stringify(state.toJSON(true)), + legacy: JSON.stringify(state.toJSON()), + name, + }; + }); + emitVectors( + 'serialization/mark', + 'serialization', + 'Documents with @lexical/mark nodes, and mark ids as NodeState, and the exact JSON text JS writes for them.', + cases, + ); +}); diff --git a/packages/lexical-conformance/vectors/scripts/mark.json b/packages/lexical-conformance/vectors/scripts/mark.json new file mode 100644 index 00000000000..1a28ca9b8cb --- /dev/null +++ b/packages/lexical-conformance/vectors/scripts/mark.json @@ -0,0 +1,32 @@ +{"description":"@lexical/mark: $wrapSelectionInMarkNode across text nodes, partial text, blocks, backward selections, overlapping marks, line breaks, decorators and inline elements; $getMarkIDs; $unwrapMarkNode; MarkNode's ids (addID, deleteID down to none, setIDs, hasID, getIDs), its JSON, and its insertion behavior (canInsertTextBefore/After, canBeEmpty, isInline, insertNewAfter, extractWithChild, excludeFromCopy) through typing, Enter and deletion at its edges; and the MarkState functions that need no extension ($addMarkID, $removeMarkID, $getNodeMarkIDs, $getMarkIDsAtSelection, $migrateMarkNodesToMarkState).","formatVersion":1,"kind":"script","cases":[ +{"mark":true,"name":"wrapping text nodes and partial text","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"bold","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" world","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"one","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",2,"text"],"do":"select","focus":["4",3,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":false}]},{"update":[{"anchor":["6",1,"text"],"do":"select","focus":["6",2,"text"]},{"do":"wrapSelectionInMarkNode","id":"b","isBackward":false}]},{"update":[{"anchor":["6",0,"text"],"do":"select","focus":["6",0,"text"]},{"do":"wrapSelectionInMarkNode","id":"c","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"Hello bold world\n\none","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" world\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" world\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\"}]}]}}"},{"log":[],"selection":["range",["4",3,"text"],["4",3,"text"],0,""],"text":"Hello bold world\n\none","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"He\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"llo \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" wo\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"rld\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"He\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"llo \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" wo\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"rld\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\"}]}]}}"},{"log":[],"selection":["range",["10",1,"text"],["10",1,"text"],0,""],"text":"Hello bold world\n\none","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"He\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"llo \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" wo\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"rld\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"o\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"n\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"He\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"llo \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" wo\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"rld\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"o\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"n\"}],\"ids\":[\"b\"]},{\"type\":\"text\",\"text\":\"e\"}]}]}}"},{"log":[],"selection":["range",["6",1,"text"],["6",1,"text"],0,""],"text":"Hello bold world\n\none","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"He\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"llo \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" wo\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"rld\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"o\",\"type\":\"text\",\"version\":1}],\"ids\":[\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"n\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"He\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"llo \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" wo\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"rld\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"o\"}],\"ids\":[\"c\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"n\"}],\"ids\":[\"b\"]},{\"type\":\"text\",\"text\":\"e\"}]}]}}"}]}, +{"mark":true,"name":"wrapping backward selections","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello ","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"bold","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" world","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"abcdef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",3,"text"],"do":"select","focus":["2",2,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":true}]},{"update":[{"anchor":["6",5,"text"],"do":"select","focus":["6",1,"text"]},{"do":"wrapSelectionInMarkNode","id":"b","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"Hello bold world\n\nabcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" world\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abcdef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" world\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abcdef\"}]}]}}"},{"log":[],"selection":["range",["7",0,"text"],["7",0,"text"],0,""],"text":"Hello bold world\n\nabcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"He\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"llo \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" wo\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"rld\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abcdef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"He\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"llo \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" wo\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"rld\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abcdef\"}]}]}}"},{"log":[],"selection":["range",["10",4,"text"],["10",4,"text"],0,""],"text":"Hello bold world\n\nabcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"He\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"llo \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bold\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" wo\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"rld\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bcde\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"He\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"llo \"},{\"type\":\"text\",\"format\":1,\"text\":\"bold\"},{\"type\":\"text\",\"text\":\" wo\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"rld\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"bcde\"}],\"ids\":[\"b\"]},{\"type\":\"text\",\"text\":\"f\"}]}]}}"}]}, +{"mark":true,"name":"wrapping across blocks","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"one","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"two","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"2","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"three","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",1,"text"],"do":"select","focus":["7",2,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":false}]},{"update":[{"anchor":["root",0,"element"],"do":"select","focus":["root",3,"element"]},{"do":"wrapSelectionInMarkNode","id":"b","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"one\n\ntwo2\n\nthree","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"two\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"2\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"three\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"two\"},{\"type\":\"text\",\"format\":1,\"text\":\"2\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"three\"}]}]}}"},{"log":[],"selection":["range",["7",2,"text"],["7",2,"text"],0,""],"text":"one\n\ntwo2\n\nthree","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"o\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"two\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"2\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"th\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ree\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"o\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ne\"}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"two\"},{\"type\":\"text\",\"format\":1,\"text\":\"2\"}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"th\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ree\"}]}]}}"},{"log":[],"selection":["range",["9",3,"text"],["9",3,"text"],0,""],"text":"one\n\ntwo2\n\nthree","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"o\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"two\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"2\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"th\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ree\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"o\"}],\"ids\":[\"b\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ne\"}],\"ids\":[\"b\"]}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"two\"},{\"type\":\"text\",\"format\":1,\"text\":\"2\"}],\"ids\":[\"b\"]}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"th\"}],\"ids\":[\"b\"]}],\"ids\":[\"a\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ree\"}],\"ids\":[\"b\"]}]}]}}"}]}, +{"mark":true,"name":"overlapping marks","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"abcdef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",1,"text"],"do":"select","focus":["2",4,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":false}]},{"update":[{"anchor":["3",1,"text"],"do":"select","focus":["4",1,"text"]},{"do":"wrapSelectionInMarkNode","id":"b","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abcdef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abcdef\"}]}]}}"},{"log":[],"selection":["range",["3",3,"text"],["3",3,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bcd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"bcd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",1,"text"],["4",1,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"b\"]}],\"ids\":[\"a\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"e\"}],\"ids\":[\"b\"]},{\"type\":\"text\",\"text\":\"f\"}]}]}}"}]}, +{"extension":true,"link":true,"mark":true,"name":"line breaks, decorators and inline elements in the range","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"type":"linebreak","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"c","type":"text","version":1}],"direction":null,"format":"","indent":0,"rel":null,"target":null,"title":null,"type":"link","url":"https://x","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"d","type":"text","version":1},{"type":"decorator-text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"e","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"type":"horizontalrule","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"f","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",0,"text"],"do":"select","focus":["12",1,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"a\nbcde\n\n\nf","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"type\":\"linebreak\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"linebreak\"},{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"url\":\"https://x\"},{\"type\":\"text\",\"text\":\"d\"},{\"type\":\"decorator-text\"},{\"type\":\"text\",\"text\":\"e\"}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"f\"}]}]}}"},{"log":[],"selection":["range",["12",1,"text"],["12",1,"text"],0,""],"text":"a\nbcde\n\n\nf","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"type\":\"linebreak\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"a\"}],\"ids\":[\"a\"]},{\"type\":\"linebreak\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"url\":\"https://x\"},{\"type\":\"text\",\"text\":\"d\"},{\"type\":\"decorator-text\"},{\"type\":\"text\",\"text\":\"e\"}],\"ids\":[\"a\"]}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"f\"}],\"ids\":[\"a\"]}]}]}}"}]}, +{"extension":true,"link":true,"mark":true,"name":"wrapping from inside a link","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"bc","type":"text","version":1}],"direction":null,"format":"","indent":0,"rel":null,"target":null,"title":null,"type":"link","url":"https://x","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"d","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",1,"text"],"do":"select","focus":["5",1,"text"]},{"do":"wrapSelectionInMarkNode","id":"a","isBackward":false}]}],"results":[{"log":[],"selection":null,"text":"abcd","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"bc\",\"type\":\"text\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"bc\"}],\"url\":\"https://x\"},{\"type\":\"text\",\"text\":\"d\"}]}]}}"},{"log":[],"selection":["range",["5",1,"text"],["5",1,"text"],0,""],"text":"abcd","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"a\"]}],\"url\":\"https://x\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]}]}]}}"}]}, +{"mark":true,"name":"getMarkIDs","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","ids":["c"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","ids":["a","b"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"gh","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"logMarkIDs","key":"2","offset":0},{"do":"logMarkIDs","key":"2","offset":1},{"do":"logMarkIDs","key":"2","offset":2},{"do":"logMarkIDs","key":"4","offset":0},{"do":"logMarkIDs","key":"4","offset":2},{"do":"logMarkIDs","key":"6","offset":1},{"do":"logMarkIDs","key":"7","offset":0},{"do":"logMarkIDs","key":"7","offset":2}]}],"results":[{"log":[],"selection":null,"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"ids\":[\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"}],\"ids\":[\"c\"]}],\"ids\":[\"a\",\"b\"]},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDs","2",0,null],["markIDs","2",1,null],["markIDs","2",2,["a","b"]],["markIDs","4",0,["a","b"]],["markIDs","4",2,["c"]],["markIDs","6",1,["c"]],["markIDs","7",0,null],["markIDs","7",2,null]],"selection":null,"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"ids\":[\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"a\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"}],\"ids\":[\"c\"]}],\"ids\":[\"a\",\"b\"]},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"}]}, +{"mark":true,"name":"typing at the start of a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",0,"text"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"x"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",0,"text"],["4",0,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["2",3,"text"],["2",3,"text"],0,""],"text":"abxcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abx\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abx\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"}]}, +{"mark":true,"name":"typing at the end of a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",2,"text"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"x"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",2,"text"],["4",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["6",1,"text"],["6",1,"text"],0,""],"text":"abcdxef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"xef\"}]}]}}"}]}, +{"mark":true,"name":"typing inside a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",1,"text"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"x"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",1,"text"],["4",1,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["4",2,"text"],["4",2,"text"],0,""],"text":"abcxdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cxd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cxd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"}]}, +{"mark":true,"name":"typing at element points around a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["3",0,"element"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"x"},{"update":[{"anchor":["3",1,"element"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"y"},{"update":[{"anchor":["1",1,"element"],"do":"select"}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"z"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["3",0,"element"],["3",0,"element"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["2",3,"text"],["2",3,"text"],0,""],"text":"abxcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abx\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abx\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["3",1,"element"],["3",1,"element"],0,""],"text":"abxcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abx\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abx\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["4",3,"text"],["4",3,"text"],0,""],"text":"abxcdyef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abx\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cdy\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abx\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cdy\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["1",1,"element"],["1",1,"element"],0,""],"text":"abxcdyef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abx\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cdy\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abx\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cdy\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["2",4,"text"],["2",4,"text"],0,""],"text":"abxzcdyef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abxz\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cdy\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abxz\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cdy\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"}]}, +{"mark":true,"name":"typing over a range that ends in a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",1,"text"],"do":"select","focus":["4",1,"text"]}]},{"dispatch":"CONTROLLED_TEXT_INSERTION_COMMAND","payload":"x"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["2",1,"text"],["4",1,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","CONTROLLED_TEXT_INSERTION_COMMAND",true]],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"axdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ax\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ax\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"}]}, +{"mark":true,"name":"Enter in a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",1,"text"],"do":"select"}]},{"dispatch":"INSERT_PARAGRAPH_COMMAND"},{"update":[{"anchor":["4",1,"text"],"do":"select"}]},{"dispatch":"INSERT_PARAGRAPH_COMMAND"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",1,"text"],["4",1,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","INSERT_PARAGRAPH_COMMAND",true]],"selection":["range",["6",0,"text"],["6",0,"text"],0,""],"text":"abc\n\ndef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",1,"text"],["4",1,"text"],0,""],"text":"abc\n\ndef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","INSERT_PARAGRAPH_COMMAND",true]],"selection":["range",["9",0,"element"],["9",0,"element"],0,""],"text":"abc\n\n\n\ndef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"a\"]}]},{\"type\":\"paragraph\",\"children\":[]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"}]}, +{"mark":true,"name":"removing all of a mark","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",0,"text"],"do":"select","focus":["4",2,"text"]}]},{"dispatch":"REMOVE_TEXT_COMMAND"}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",0,"text"],["4",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","REMOVE_TEXT_COMMAND",true]],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"abef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abef\"}]}]}}"}]}, +{"mark":true,"name":"removing text across a mark edge","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",1,"text"],"do":"select","focus":["4",1,"text"]}]},{"dispatch":"DELETE_CHARACTER_COMMAND","payload":false},{"update":[{"anchor":["4",1,"text"],"do":"select","focus":["5",1,"text"]}]},{"dispatch":"DELETE_CHARACTER_COMMAND","payload":true}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["2",1,"text"],["4",1,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","DELETE_CHARACTER_COMMAND",true]],"selection":["range",["2",1,"text"],["2",1,"text"],0,""],"text":"adef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",1,"text"],["5",1,"text"],0,""],"text":"adef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[["dispatched","DELETE_CHARACTER_COMMAND",true]],"selection":["range",["4",1,"text"],["4",1,"text"],0,""],"text":"adf","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"d\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"f\"}]}]}}"}]}, +{"mark":true,"name":"Backspace and Delete at mark edges","richText":true,"steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",0,"text"],"do":"select"}]},{"dispatch":"DELETE_CHARACTER_COMMAND","payload":true},{"update":[{"anchor":["4",2,"text"],"do":"select"}]},{"dispatch":"DELETE_CHARACTER_COMMAND","payload":false},{"update":[{"anchor":["2",2,"text"],"do":"select"}]},{"dispatch":"DELETE_CHARACTER_COMMAND","payload":false}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",0,"text"],["4",0,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",0,"text"],["4",0,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}","error":true},{"log":[],"selection":["range",["4",2,"text"],["4",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["4",2,"text"],["4",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}","error":true},{"log":[],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}","error":true}]}, +{"mark":true,"name":"removing the last child of a mark","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"args":[],"do":"node","key":"4","method":"remove"}]}],"results":[{"log":[],"selection":null,"text":"abcdef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\"ef\"}]}]}}"},{"log":[],"selection":null,"text":"abef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abef\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abef\"}]}]}}"}]}, +{"mark":true,"name":"unwrapping","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"c","type":"text","version":1}],"direction":null,"format":"","ids":["x"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"d","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["4",1,"text"],"do":"select"},{"do":"unwrapMarkNode","key":"3"}]}],"results":[{"log":[],"selection":null,"text":"abcd","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"text\",\"format\":1,\"text\":\"c\"}],\"ids\":[\"x\"]},{\"type\":\"text\",\"text\":\"d\"}]}]}}"},{"log":[],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"abcd","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"format\":1,\"text\":\"c\"},{\"type\":\"text\",\"text\":\"d\"}]}]}}"}]}, +{"mark":true,"name":"unwrapping nested marks","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1}],"direction":null,"format":"","ids":["y"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","ids":["x"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["5",1,"text"],"do":"select"},{"do":"unwrapMarkNode","key":"2"}]},{"update":[{"do":"unwrapMarkNode","key":"4"}]}],"results":[{"log":[],"selection":null,"text":"ab","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"y\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"y\"]}],\"ids\":[\"x\"]}]}]}}"},{"log":[],"selection":["range",["5",1,"text"],["5",1,"text"],0,""],"text":"ab","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"y\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"y\"]}]}]}}"},{"log":[],"selection":["range",["3",2,"text"],["3",2,"text"],0,""],"text":"ab","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"}]}]}}"}]}, +{"mark":true,"name":"mark ids","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"x","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"args":["b"],"do":"node","key":"2","method":"addID"},{"args":[],"do":"node","key":"2","method":"getIDs"}]},{"update":[{"args":["a"],"do":"node","key":"2","method":"addID"},{"args":[],"do":"node","key":"2","method":"getIDs"}]},{"update":[{"args":["a"],"do":"node","key":"2","method":"hasID"},{"args":["z"],"do":"node","key":"2","method":"hasID"},{"args":["z"],"do":"node","key":"2","method":"deleteID"}]},{"update":[{"args":["a"],"do":"node","key":"2","method":"deleteID"},{"args":[],"do":"node","key":"2","method":"getIDs"}]},{"update":[{"args":[["c","b","c"]],"do":"node","key":"2","method":"setIDs"},{"args":[],"do":"node","key":"2","method":"getIDs"}]},{"update":[{"args":["c"],"do":"node","key":"2","method":"deleteID"},{"args":[],"do":"node","key":"2","method":"getIDs"}]},{"update":[{"args":["c"],"do":"node","key":"2","method":"addID"},{"args":[],"do":"node","key":"2","method":"getIDs"}]}],"results":[{"log":[],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\"]}]}]}}"},{"log":[["getIDs","2",["a","b"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\",\"b\"]}]}]}}"},{"log":[["getIDs","2",["a","b"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\",\"b\"]}]}]}}"},{"log":[["hasID","2","a",true],["hasID","2","z",false]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\",\"b\"]}]}]}}"},{"log":[["getIDs","2",["b"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["getIDs","2",["c","b","c"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"c\",\"b\",\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"c\",\"b\",\"c\"]}]}]}}"},{"log":[["getIDs","2",["b","c"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\",\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"b\",\"c\"]}]}]}}"},{"log":[["getIDs","2",["b","c"]]],"selection":null,"text":"x","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\",\"c\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"b\",\"c\"]}]}]}}"}]}, +{"mark":true,"name":"deleting the last id","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1}],"direction":null,"format":"","ids":["x"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"c","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"args":["x"],"do":"node","key":"3","method":"deleteID"},{"args":[],"do":"node","key":"3","method":"getIDs"}]},{"update":[{"do":"unwrapMarkNode","key":"3"}]}],"results":[{"log":[],"selection":null,"text":"abc","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"x\"]},{\"type\":\"text\",\"text\":\"c\"}]}]}}"},{"log":[["getIDs","3",[]]],"selection":null,"text":"abc","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}]},{\"type\":\"text\",\"text\":\"c\"}]}]}}"},{"log":[],"selection":null,"text":"abc","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"abc\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"abc\"}]}]}}"}]}, +{"mark":true,"name":"MarkNode JSON with ids that are not strings","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"x","type":"text","version":1}],"direction":null,"format":"","ids":["a",1,null,"b"],"indent":0,"type":"mark","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"y","type":"text","version":1}],"direction":null,"format":"","ids":"a","indent":0,"type":"mark","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"z","type":"text","version":1}],"direction":null,"format":"","indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"args":[],"do":"node","key":"2","method":"getIDs"},{"args":[],"do":"node","key":"4","method":"getIDs"},{"args":[],"do":"node","key":"6","method":"getIDs"}]}],"results":[{"log":[],"selection":null,"text":"xyz","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\",\"\",\"\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"y\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"z\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\",\"\",\"\",\"b\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"y\"}]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"z\"}]}]}]}}"},{"log":[["getIDs","2",["a","","","b"]],["getIDs","4",[]],["getIDs","6",[]]],"selection":null,"text":"xyz","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\",\"\",\"\",\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"y\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"z\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"x\"}],\"ids\":[\"a\",\"\",\"\",\"b\"]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"y\"}]},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"z\"}]}]}]}}"}]}, +{"mark":true,"name":"extractWithChild and excludeFromCopy","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1}],"direction":null,"format":"","ids":["a"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":" ","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1},{"detail":0,"format":1,"mode":"normal","style":"","text":"gh","type":"text","version":1}],"direction":null,"format":"","ids":["b"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["3",0,"text"],"do":"select","focus":["3",2,"text"]},{"args":["3","clone"],"do":"node","key":"2","method":"extractWithChild"},{"args":["3","html"],"do":"node","key":"2","method":"extractWithChild"}]},{"update":[{"anchor":["3",2,"text"],"do":"select","focus":["3",0,"text"]},{"args":["3","clone"],"do":"node","key":"2","method":"extractWithChild"}]},{"update":[{"anchor":["3",0,"text"],"do":"select","focus":["3",1,"text"]},{"args":["3","clone"],"do":"node","key":"2","method":"extractWithChild"}]},{"update":[{"anchor":["3",0,"text"],"do":"select","focus":["4",1,"text"]},{"args":["3","clone"],"do":"node","key":"2","method":"extractWithChild"}]},{"update":[{"anchor":["6",0,"text"],"do":"select","focus":["7",2,"text"]},{"args":["6","clone"],"do":"node","key":"5","method":"extractWithChild"}]},{"update":[{"anchor":["6",2,"text"],"do":"select","focus":["7",2,"text"]},{"args":["7","clone"],"do":"node","key":"5","method":"extractWithChild"}]},{"update":[{"do":"selectNodes","keys":["3"]},{"args":["3","clone"],"do":"node","key":"2","method":"extractWithChild"},{"args":["clone"],"do":"node","key":"2","method":"excludeFromCopy"},{"args":["html"],"do":"node","key":"2","method":"excludeFromCopy"}]}],"results":[{"log":[],"selection":null,"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","2","3","clone",true],["extractWithChild","2","3","html",false]],"selection":["range",["3",0,"text"],["3",2,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","2","3","clone",true]],"selection":["range",["3",2,"text"],["3",0,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","2","3","clone",false]],"selection":["range",["3",0,"text"],["3",1,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","2","3","clone",false]],"selection":["range",["3",0,"text"],["4",1,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","5","6","clone",false]],"selection":["range",["6",0,"text"],["7",2,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","5","7","clone",false]],"selection":["range",["6",2,"text"],["7",2,"text"],0,""],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"},{"log":[["extractWithChild","2","3","clone",false],["excludeFromCopy","2","clone",false],["excludeFromCopy","2","html",true]],"selection":["node",["3"]],"text":"cd efgh","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"ids\":[\"b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"cd\"}],\"ids\":[\"a\"]},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"ef\"},{\"type\":\"text\",\"format\":1,\"text\":\"gh\"}],\"ids\":[\"b\"]}]}]}}"}]}, +{"extension":true,"link":true,"mark":true,"name":"addMarkID","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Hello world","type":"text","version":1},{"type":"linebreak","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"next","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"link","type":"text","version":1}],"direction":null,"format":"","indent":0,"rel":null,"target":null,"title":null,"type":"link","url":"https://x","version":1},{"type":"decorator-text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"end","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"type":"horizontalrule","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"second","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["2",6,"text"],"do":"select","focus":["11",3,"text"]},{"do":"addMarkID","id":"c1"}]},{"update":[{"anchor":["2",0,"text"],"do":"select","focus":["4",2,"text"]},{"do":"addMarkID","id":"c2"}]},{"update":[{"anchor":["2",1,"text"],"do":"select"},{"do":"addMarkID","id":"c3"}]},{"update":[{"do":"selectNodes","keys":["7"]},{"do":"addMarkID","id":"c4"}]},{"update":[{"anchor":["2",0,"text"],"do":"select","focus":["2",1,"text"]},{"do":"addMarkID","id":"a b"}]},{"update":[{"anchor":["2",0,"text"],"do":"select","focus":["2",1,"text"]},{"do":"addMarkID","id":""}]}],"results":[{"log":[],"selection":null,"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello world\",\"type\":\"text\",\"version\":1},{\"type\":\"linebreak\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"next\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"second\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello world\"},{\"type\":\"linebreak\"},{\"type\":\"text\",\"text\":\"next\"},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\"}],\"url\":\"https://x\"},{\"type\":\"decorator-text\"},{\"type\":\"text\",\"text\":\"end\"}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"second\"}]}]}}"},{"log":[["addMarkID","c1",["12","3","4","6","7","8","11"]]],"selection":["range",["12",0,"text"],["11",3,"text"],0,""],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"next\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \"},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"next\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}"},{"log":[["addMarkID","c2",["2","12","3","4"]]],"selection":["range",["2",0,"text"],["4",2,"text"],0,""],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xt\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \",\"$\":{\"markIds\":[\"c2\"]}},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"ne\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"xt\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}"},{"log":[["addMarkID","c3",[]]],"selection":["range",["2",1,"text"],["2",1,"text"],0,""],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xt\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \",\"$\":{\"markIds\":[\"c2\"]}},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"ne\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"xt\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}"},{"log":[["addMarkID","c4",["7"]]],"selection":["node",["7"]],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xt\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \",\"$\":{\"markIds\":[\"c2\"]}},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"ne\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"xt\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}"},{"log":[],"selection":["node",["7"]],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xt\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \",\"$\":{\"markIds\":[\"c2\"]}},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"ne\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"xt\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}","error":true},{"log":[],"selection":["node",["7"]],"text":"Hello world\nnextlinkend\n\n\nsecond","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"Hello \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"world\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ne\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"xt\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"link\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"end\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"type\":\"horizontalrule\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"sec\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ond\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"Hello \",\"$\":{\"markIds\":[\"c2\"]}},{\"type\":\"text\",\"text\":\"world\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"ne\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\"xt\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"link\",\"$\":{\"markIds\":[\"c1\"]}}],\"url\":\"https://x\"},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"c1\",\"c4\"]}},{\"type\":\"text\",\"text\":\"end\",\"$\":{\"markIds\":[\"c1\"]}}]},{\"type\":\"horizontalrule\"},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"sec\",\"$\":{\"markIds\":[\"c1\"]}},{\"type\":\"text\",\"text\":\"ond\"}]}]}}","error":true}]}, +{"mark":true,"name":"getNodeMarkIDs and getMarkIDsAtSelection","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"ab","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"cd","type":"text","version":1,"$":{"markIds":["b","a"]}},{"detail":0,"format":0,"mode":"normal","style":"","text":"ef","type":"text","version":1,"$":{"markIds":["a"]}},{"detail":0,"format":0,"mode":"normal","style":"","text":"gh","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"logNodeMarkIDs","key":"1"},{"do":"logNodeMarkIDs","key":"2"},{"do":"logNodeMarkIDs","key":"3"},{"do":"logNodeMarkIDs","key":"4"}]},{"update":[{"anchor":["2",1,"text"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["2",2,"text"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["3",1,"text"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["4",2,"text"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["1",1,"element"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["1",0,"element"],"do":"select"},{"do":"logMarkIDsAtSelection"}]},{"update":[{"anchor":["2",1,"text"],"do":"select","focus":["4",1,"text"]},{"do":"logMarkIDsAtSelection"},{"do":"logMarkIDsAtSelection","mode":"any"},{"do":"logMarkIDsAtSelection","mode":"all"}]},{"update":[{"anchor":["2",2,"text"],"do":"select","focus":["3",2,"text"]},{"do":"logMarkIDsAtSelection","mode":"all"}]},{"update":[{"anchor":["3",1,"text"],"do":"select","focus":["5",0,"text"]},{"do":"logMarkIDsAtSelection","mode":"all"}]},{"update":[{"anchor":["5",0,"text"],"do":"select","focus":["5",2,"text"]},{"do":"logMarkIDsAtSelection"}]},{"update":[{"do":"clearSelection"},{"do":"logMarkIDsAtSelection"}]}],"results":[{"log":[],"selection":null,"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"b\",\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"b\",\"a\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["nodeMarkIDs","1",[]],["nodeMarkIDs","2",[]],["nodeMarkIDs","3",["a","b"]],["nodeMarkIDs","4",["a"]]],"selection":null,"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,[]]],"selection":["range",["2",1,"text"],["2",1,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,["a","b"]]],"selection":["range",["2",2,"text"],["2",2,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,["a","b"]]],"selection":["range",["3",1,"text"],["3",1,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,["a"]]],"selection":["range",["4",2,"text"],["4",2,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,["a","b"]]],"selection":["range",["1",1,"element"],["1",1,"element"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,[]]],"selection":["range",["1",0,"element"],["1",0,"element"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,["a","b"]],["markIDsAtSelection","any",["a","b"]],["markIDsAtSelection","all",[]]],"selection":["range",["2",1,"text"],["4",1,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection","all",["a","b"]]],"selection":["range",["2",2,"text"],["3",2,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection","all",["a"]]],"selection":["range",["3",1,"text"],["5",0,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,[]]],"selection":["range",["5",0,"text"],["5",2,"text"],0,""],"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"},{"log":[["markIDsAtSelection",null,[]]],"selection":null,"text":"abcdefgh","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ab\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"cd\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ef\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"gh\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"ab\"},{\"type\":\"text\",\"text\":\"cd\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"ef\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"gh\"}]}]}}"}]}, +{"mark":true,"name":"mark ids from JSON","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"x","type":"text","version":1,"$":{"markIds":["b","a","a"," bad",3,"",null]}},{"detail":0,"format":0,"mode":"normal","style":"","text":" ","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"y","type":"text","version":1,"$":{"markIds":["!","😀","b"]}},{"detail":0,"format":0,"mode":"normal","style":"","text":" ","type":"text","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"z","type":"text","version":1,"$":{"markIds":"a"}}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"logNodeMarkIDs","key":"2"},{"do":"logNodeMarkIDs","key":"4"},{"do":"logNodeMarkIDs","key":"6"}]},{"update":[{"anchor":["2",0,"text"],"do":"select","focus":["4",1,"text"]},{"do":"addMarkID","id":"c"}]}],"results":[{"log":[],"selection":null,"text":"x y z","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"b\",\"a\",\"a\",\" bad\",3,\"\",null]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"y\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"!\",\"😀\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"z\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":\"a\"}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"x\",\"$\":{\"markIds\":[\"b\",\"a\",\"a\",\" bad\",3,\"\",null]}},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"text\",\"text\":\"y\",\"$\":{\"markIds\":[\"!\",\"😀\",\"b\"]}},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"text\",\"text\":\"z\",\"$\":{\"markIds\":\"a\"}}]}]}}"},{"log":[["nodeMarkIDs","2",["a","b"]],["nodeMarkIDs","4",["b","😀","!"]],["nodeMarkIDs","6",[]]],"selection":null,"text":"x y z","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"y\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"b\",\"😀\",\"!\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"z\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"x\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"text\",\"text\":\"y\",\"$\":{\"markIds\":[\"b\",\"😀\",\"!\"]}},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"text\",\"text\":\"z\"}]}]}}"},{"log":[["addMarkID","c",["2","3","4"]]],"selection":["range",["2",0,"text"],["4",1,"text"],0,""],"text":"x y z","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"x\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\",\"c\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"y\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"b\",\"c\",\"😀\",\"!\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" \",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"z\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"x\",\"$\":{\"markIds\":[\"a\",\"b\",\"c\"]}},{\"type\":\"text\",\"text\":\" \",\"$\":{\"markIds\":[\"c\"]}},{\"type\":\"text\",\"text\":\"y\",\"$\":{\"markIds\":[\"b\",\"c\",\"😀\",\"!\"]}},{\"type\":\"text\",\"text\":\" \"},{\"type\":\"text\",\"text\":\"z\"}]}]}}"}]}, +{"mark":true,"name":"removeMarkID","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"one","type":"text","version":1,"$":{"markIds":["a","b"]}},{"detail":0,"format":0,"mode":"normal","style":"","text":"two","type":"text","version":1,"$":{"markIds":["a"]}},{"detail":0,"format":0,"mode":"normal","style":"","text":"three","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"four","type":"text","version":1,"$":{"markIds":["a"]}}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"anchor":["3",1,"text"],"do":"select","focus":["6",2,"text"]},{"do":"removeMarkID","id":"a","inSelection":true}]},{"update":[{"do":"removeMarkID","id":"a"}]},{"update":[{"do":"removeMarkID","id":"b"}]},{"update":[{"do":"removeMarkID","id":"missing"}]}],"results":[{"log":[],"selection":null,"text":"onetwothree\n\nfour","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"two\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"three\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"four\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"two\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"three\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"four\",\"$\":{\"markIds\":[\"a\"]}}]}]}}"},{"log":[],"selection":["range",["7",0,"text"],["6",2,"text"],0,""],"text":"onetwothree\n\nfour","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"t\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"wothree\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"fo\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"ur\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"a\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\",\"$\":{\"markIds\":[\"a\",\"b\"]}},{\"type\":\"text\",\"text\":\"t\",\"$\":{\"markIds\":[\"a\"]}},{\"type\":\"text\",\"text\":\"wothree\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"fo\"},{\"type\":\"text\",\"text\":\"ur\",\"$\":{\"markIds\":[\"a\"]}}]}]}}"},{"log":[],"selection":["range",["3",1,"text"],["6",2,"text"],0,""],"text":"onetwothree\n\nfour","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"b\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"twothree\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"four\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\",\"$\":{\"markIds\":[\"b\"]}},{\"type\":\"text\",\"text\":\"twothree\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"four\"}]}]}}"},{"log":[],"selection":["range",["2",4,"text"],["6",2,"text"],0,""],"text":"onetwothree\n\nfour","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"onetwothree\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"four\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"onetwothree\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"four\"}]}]}}"},{"log":[],"selection":["range",["2",4,"text"],["6",2,"text"],0,""],"text":"onetwothree\n\nfour","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"onetwothree\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"four\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"onetwothree\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"four\"}]}]}}"}]}, +{"extension":true,"link":true,"mark":true,"name":"migrateMarkNodesToMarkState","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1},{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"c","type":"text","version":1}],"direction":null,"format":"","ids":["y","x"],"indent":0,"type":"mark","version":1},{"type":"linebreak","version":1},{"type":"decorator-text","version":1}],"direction":null,"format":"","ids":["x"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"d","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"e","type":"text","version":1}],"direction":null,"format":"","indent":0,"rel":null,"target":null,"title":null,"type":"link","url":"https://x","version":1}],"direction":null,"format":"","ids":["z"],"indent":0,"type":"mark","version":1},{"detail":0,"format":0,"mode":"normal","style":"","text":"f","type":"text","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"migrateMarkNodesToMarkState"}]},{"update":[{"do":"migrateMarkNodesToMarkState"}]}],"results":[{"log":[],"selection":null,"text":"abc\nd\n\nef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"y\",\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"type\":\"linebreak\",\"version\":1},{\"type\":\"decorator-text\",\"version\":1}],\"ids\":[\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1}],\"ids\":[\"z\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"y\",\"x\"]},{\"type\":\"linebreak\"},{\"type\":\"decorator-text\"}],\"ids\":[\"x\"]},{\"type\":\"text\",\"text\":\"d\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"e\"}],\"url\":\"https://x\"}],\"ids\":[\"z\"]},{\"type\":\"text\",\"text\":\"f\"}]}]}}"},{"log":[["migrateMarkNodesToMarkState",3]],"selection":null,"text":"abc\nd\n\nef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\",\"y\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"z\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"text\",\"text\":\"b\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"text\",\"text\":\"c\",\"$\":{\"markIds\":[\"x\",\"y\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"text\",\"text\":\"d\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"e\",\"$\":{\"markIds\":[\"z\"]}}],\"url\":\"https://x\"},{\"type\":\"text\",\"text\":\"f\"}]}]}}"},{"log":[["migrateMarkNodesToMarkState",0]],"selection":null,"text":"abc\nd\n\nef","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\",\"y\"]}},{\"type\":\"linebreak\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"decorator-text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"e\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"z\"]}}],\"rel\":null,\"target\":null,\"title\":null,\"url\":\"https://x\",\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"link\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"f\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"text\",\"text\":\"b\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"text\",\"text\":\"c\",\"$\":{\"markIds\":[\"x\",\"y\"]}},{\"type\":\"linebreak\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"decorator-text\",\"$\":{\"markIds\":[\"x\"]}},{\"type\":\"text\",\"text\":\"d\"}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"link\",\"children\":[{\"type\":\"text\",\"text\":\"e\",\"$\":{\"markIds\":[\"z\"]}}],\"url\":\"https://x\"},{\"type\":\"text\",\"text\":\"f\"}]}]}}"}]}, +{"mark":true,"name":"migrateMarkNodesToMarkState of a subtree","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1}],"direction":null,"format":"","ids":["x"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1},{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"b","type":"text","version":1}],"direction":null,"format":"","ids":["y"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"migrateMarkNodesToMarkState","key":"1"}]},{"update":[{"do":"migrateMarkNodesToMarkState","key":"4"}]}],"results":[{"log":[],"selection":null,"text":"a\n\nb","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1}],\"ids\":[\"x\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"y\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"a\"}],\"ids\":[\"x\"]}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"y\"]}]}]}}"},{"log":[["migrateMarkNodesToMarkState",1]],"selection":null,"text":"a\n\nb","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"y\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\",\"$\":{\"markIds\":[\"x\"]}}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"y\"]}]}]}}"},{"log":[["migrateMarkNodesToMarkState",1]],"selection":null,"text":"a\n\nb","state":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"x\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"y\"]}}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\",\"$\":{\"markIds\":[\"x\"]}}]},{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"b\",\"$\":{\"markIds\":[\"y\"]}}]}]}}"}]}, +{"mark":true,"name":"migrateMarkNodesToMarkState with an invalid id","steps":[{"update":[{"do":"append","key":"root","nodes":[{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"a","type":"text","version":1}],"direction":null,"format":"","ids":["a b"],"indent":0,"type":"mark","version":1}],"direction":null,"format":"","indent":0,"textFormat":0,"textStyle":"","type":"paragraph","version":1}]}]},{"update":[{"do":"migrateMarkNodesToMarkState"}]}],"results":[{"log":[],"selection":null,"text":"a","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"a\"}],\"ids\":[\"a b\"]}]}]}}"},{"log":[],"selection":null,"text":"a","state":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1}],\"ids\":[\"a b\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"a\"}],\"ids\":[\"a b\"]}]}]}}","error":true}]} +]} diff --git a/packages/lexical-conformance/vectors/serialization/mark.json b/packages/lexical-conformance/vectors/serialization/mark.json new file mode 100644 index 00000000000..ab7b7cc88f4 --- /dev/null +++ b/packages/lexical-conformance/vectors/serialization/mark.json @@ -0,0 +1,6 @@ +{"description":"Documents with @lexical/mark nodes, and mark ids as NodeState, and the exact JSON text JS writes for them.","formatVersion":1,"kind":"serialization","cases":[ +{"compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"b\"}],\"ids\":[\"x\"],\"direction\":\"rtl\",\"format\":\"center\",\"indent\":1}]}]}}","legacy":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1}],\"ids\":[\"x\"],\"direction\":\"rtl\",\"format\":\"center\",\"indent\":1,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","name":"a mark with element properties"}, +{"compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"none\"}]}]}]}}","legacy":"{\"root\":{\"children\":[{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"none\",\"type\":\"text\",\"version\":1}],\"ids\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","name":"a mark with no ids"}, +{"compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"one\",\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"type\":\"text\",\"text\":\" two\"},{\"type\":\"linebreak\"}]}]}}","legacy":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"one\",\"type\":\"text\",\"version\":1,\"$\":{\"markIds\":[\"c1\",\"c2\"]}},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\" two\",\"type\":\"text\",\"version\":1},{\"type\":\"linebreak\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","name":"mark ids as NodeState"}, +{"compact":"{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"children\":[{\"type\":\"text\",\"text\":\"a\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"format\":1,\"text\":\"b\"},{\"type\":\"mark\",\"children\":[{\"type\":\"text\",\"text\":\"c\"}],\"ids\":[\"z\"]}],\"ids\":[\"x\",\"y\"]},{\"type\":\"text\",\"text\":\"d\"}]}]}}","legacy":"{\"root\":{\"children\":[{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"a\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":1,\"mode\":\"normal\",\"style\":\"\",\"text\":\"b\",\"type\":\"text\",\"version\":1},{\"children\":[{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"c\",\"type\":\"text\",\"version\":1}],\"ids\":[\"z\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1}],\"ids\":[\"x\",\"y\"],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"mark\",\"version\":1},{\"detail\":0,\"format\":0,\"mode\":\"normal\",\"style\":\"\",\"text\":\"d\",\"type\":\"text\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"textFormat\":0,\"textStyle\":\"\",\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}","name":"nested and overlapping marks"} +]} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 426409a191d..dd15b2cdec3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -734,6 +734,9 @@ importers: '@lexical/list': specifier: workspace:* version: link:../lexical-list + '@lexical/mark': + specifier: workspace:* + version: link:../lexical-mark '@lexical/markdown': specifier: workspace:* version: link:../lexical-markdown diff --git a/rust/DESIGN.md b/rust/DESIGN.md index 854d6a60db5..8030835b739 100644 --- a/rust/DESIGN.md +++ b/rust/DESIGN.md @@ -66,6 +66,7 @@ depends on as the upstream package depends on them: | `link` | `@lexical/link` | `utils` (upstream also lists `extension` and `html`, for `LinkExtension` and the DOM import, which aren't ported) | | `autolink` | `@lexical/link`'s `registerAutoLink` | `link`, `regex` | | `code` | `@lexical/code-core` | (upstream lists `extension`, `html` and `utils`, for the extensions, the DOM import and the arrow-key handlers, which aren't ported) | +| `mark` | `@lexical/mark` | `utils` (upstream also lists `extension` and `html`, for `MarkExtension`, `MarkStateExtension` and the DOM, which aren't ported) | | `overflow` | `@lexical/overflow` | | | `text` | `@lexical/text` | | | `hashtag` | `@lexical/hashtag` | `text` (upstream also lists `extension` and `utils`, for `HashtagExtension` and `mergeRegister`, which aren't ported) | @@ -74,7 +75,6 @@ depends on as the upstream package depends on them: | `markdown` | `@lexical/markdown` | `rich-text`, `list`, `link`, `code` | | `file` | `@lexical/file` | | | `clipboard` | `@lexical/clipboard` | `selection`, `utils` (`html` for HTML copy and paste) | -| `mark` | `@lexical/mark` | `utils` | | `devtools` | `@lexical/devtools-core`'s `generateContent` | (upstream lists `html`, `link`, `mark` and `table`; its link branch is built with `link`, and its HTML form, mark and table branches aren't ported) | Each module is `lexical_editor::` (`lexical_editor::rich_text`). @@ -201,7 +201,7 @@ thread. | M3 | The commands `property/sequences` dispatches | `property/sequences` | done (all 81 cases match JS): the suite builds `sequenceEditor`'s editor without `buildEditorFromExtensions`, from what each of its extensions registers, in the builder's order, with `InitialStateExtension`'s paragraph; the commands M2 ported needed two fixes, `NormalizeInlineElementsExtension`'s transform (`register_normalize_inline_elements`, removing empty links) and `$extendSelectionForDeletion` and `modify` stopping where the window gives no DOM selection (`SelectionMovement::has_dom_selection`) | | M4 | `html`: a DOM abstraction over an HTML parser, CSS declarations, export and import | `html/*`, `css/*`, `property/documents` (HTML) | in progress ([HTML thread](https://ampcode.com/threads/T-506c8fa1-4309-40f1-9810-732b311eaeab)) | | M5 | `markdown`, and Markdown shortcuts in both playgrounds | `markdown/import-export`, `property/documents` (Markdown) | in progress ([Markdown thread](https://ampcode.com/threads/T-01a1daf1-150e-709b-aaed-996443a64459)) | -| M6 | The rest of Swift's feature set: the extension API's bundled extensions (`ClearEditor`, `InitialState`, `EditorState`, `TabIndentation`, `NodeStructure`, `SelectBlock` and the others Swift has), clipboard (JSON copy and paste first, HTML once M4 lands), the rest of `selection` and `utils` (style helpers, `$wrapNodes`, `$splitNode`, `$dfs`...), `mark`, `file`, the devtools tree view, `text`'s root helpers, `a11y`'s announcements, code highlighting (Swift uses tree-sitter where JS uses Prism), the character limit, the menu, auto-embed, decorator block and table of contents logic, the remaining built-in commands, slots, node schemas and editable mode | `file/*`, `devtools/tree`, and new JS vectors for the rest | in progress (this thread's owner: the [Rust port thread](https://ampcode.com/threads/T-16d45229-bfa8-417a-a091-d3e3888d830d), which also owns M1's extension API and the merges): `file`, the devtools tree view and `text`'s root helpers are done (`file/*`, `devtools/tree` and `scripts/text` pass) | +| M6 | The rest of Swift's feature set: the extension API's bundled extensions (`ClearEditor`, `InitialState`, `EditorState`, `TabIndentation`, `NodeStructure`, `SelectBlock` and the others Swift has), clipboard (JSON copy and paste first, HTML once M4 lands), the rest of `selection` and `utils` (style helpers, `$wrapNodes`, `$splitNode`, `$dfs`...), `mark`, `file`, the devtools tree view, `text`'s root helpers, `a11y`'s announcements, code highlighting (Swift uses tree-sitter where JS uses Prism), the character limit, the menu, auto-embed, decorator block and table of contents logic, the remaining built-in commands, slots, node schemas and editable mode | `file/*`, `devtools/tree`, `scripts/text`, `scripts/extensions`, `scripts/mark`, `serialization/mark`, and new JS vectors for the rest | in progress (this thread's owner: the [Rust port thread](https://ampcode.com/threads/T-16d45229-bfa8-417a-a091-d3e3888d830d), which also owns M1's extension API and the merges): `file`, the devtools tree view and `text`'s root helpers are done (`file/*`, `devtools/tree` and `scripts/text` pass); `mark`'s model is done (`MarkNode` and its generated JSON, `$wrapSelectionInMarkNode`, `$unwrapMarkNode`, `$getMarkIDs`, and the `MarkStateExtension.ts` functions that need no extension; `scripts/mark` and `serialization/mark` pass), except `MarkExtension` and `MarkStateExtension` themselves | | M7 | Releases: the mirror, changesets, a name tmm settles | | | The `scenarios/*` vectors, recorded from a real browser, need a text diff --git a/rust/PARITY.md b/rust/PARITY.md index d2a1a791235..92a008b9147 100644 --- a/rust/PARITY.md +++ b/rust/PARITY.md @@ -41,6 +41,8 @@ updated with each milestone. | Code nodes (the `code` feature) | `packages/lexical-code-core/src/CodeNode.ts` (`CodeNode`: `getLanguage`, `setLanguage`, `getTheme`, `setTheme`, `getIsSyntaxHighlightSupported`, `setIsSyntaxHighlightSupported`, `insertNewAfter` (the path of an editor without `CodeExtension`), `canIndent`, `collapseAtStart`; `$createCodeNode`, `$isCodeNode`, `DEFAULT_CODE_LANGUAGE`, `getDefaultCodeLanguage`, `$exitCodeNodeOnEnter`), `CodeHighlightNode.ts` (`CodeHighlightNode`: `getHighlightType`, `setHighlightType`, `setFormat` as a no-op, `isParentRequired`, `createParentElementNode`; `$createCodeHighlightNode`, `$isCodeHighlightNode`); their generated JSON (`LexicalCodeCoreGeneratedJSON.ts`, with `afterCloneCodeNode` and `afterCloneCodeHighlightNode` as `Clone`); `TextNode.setFormat` as a node hook; from the core `getTextDirection` and `RTL_REGEX`/`LTR_REGEX` (`LexicalUtils.ts`, `LexicalConstants.ts`) | `code`, `node`, `nodes::text`, `lexical_utils`, `constants` | `serialization/code`, the code cases of `property/documents`, `scripts/code` | | `FlatStructureUtils.ts` (the `code` feature) | `$getFirstCodeNodeOfLine`, `$getLastCodeNodeOfLine`, `$getCodeLineDirection`, `$getStartOfCodeInLine` (with `findNextNonBlankInLine`), `$getEndOfCodeInLine`, `$plainifyCodeContent`, `$outdentLeadingSpaces` | `code::flat_structure_utils` | `scripts/code`; a unit test against JS's output for each function | | `CodeExtension`'s register and `registerCodeIndentation` (the `code` feature) | `CodeExtension.ts` `register` (the `TabNode` transform), as `register_code`; `CodeIndentation.ts` `registerCodeIndentation`'s handlers of `INSERT_TAB_COMMAND`, `INDENT_CONTENT_COMMAND` and `OUTDENT_CONTENT_COMMAND` (`$isSelectionInCode`, `$getCodeLines`, `$outdentLineAtCaret`, `$handleMultilineIndent`), with `tabSize` | `code::register_code`, `code::register_code_indentation` | `scripts/code` | +| Mark node (the `mark` feature) | `packages/lexical-mark/src/MarkNode.ts` (`MarkNode`: `hasID`, `getIDs`, `setIDs`, `addID`, `deleteID`, `insertNewAfter`, `canInsertTextBefore`, `canInsertTextAfter`, `canBeEmpty`, `isInline`, `extractWithChild`, `excludeFromCopy`; `$createMarkNode`, `$isMarkNode`), its generated JSON (`LexicalMarkGeneratedJSON.ts`, with `afterCloneMarkNode` as `Clone`); `index.ts` (`$unwrapMarkNode`, `$wrapSelectionInMarkNode` with `createNode`, `$getMarkIDs`) | `mark` | `serialization/mark`, `scripts/mark` | +| Mark ids as NodeState (the `mark` feature) | `packages/lexical-mark/src/MarkStateExtension.ts`, the parts that need no extension, for `MarkStateExtension`'s ids with the default `markableNodes`: `MARK_STATE_KEY`, `isValidMarkID`, the `markIds` state (`createMarkIDsState`, `arraySetValue(markIDValue())`'s parse, and `$setState`'s `canonicalValue` for it), `$isInlineDecoratorNode`, `$isMarkableNode`, `$getNodeMarkIDs`, `$addMarkID`, `$removeMarkID` (by selection, or walking the document), `$getMarkIDsAtSelection` (with `$getPointMarkIDs` and `$getAdjacentLeaf`), `$migrateMarkNodesToMarkState`, and their helpers (`$readNodeMarkIDs`, `$getMarkableIDs`, `$addNodeMarkIDs`, `$removeNodeMarkIDs`, `assertValidMarkID`, `$markableNodesInSelection`, `$collectMarkableNodes`, `$markableNodesIn`) | `mark::mark_state_extension` | `scripts/mark`, `serialization/mark` | | Overflow node (the `overflow` feature) | `packages/lexical-overflow/src/index.ts` (`OverflowNode`: `insertNewAfter` (its parent's), `excludeFromCopy`, the `$transform` of its `$config` that removes it once empty; `$createOverflowNode`, `$isOverflowNode`), with `ElementNode`'s generated JSON; `ElementNode.excludeFromCopy` as a node hook | `overflow`, `node` | `serialization/overflow`, the overflow cases of `property/documents`, `scripts/overflow` | | Hashtag node and matcher (the `hashtag` feature) | `packages/lexical-hashtag/src/LexicalHashtagNode.ts` (`HashtagNode`: `canInsertTextBefore`, `isTextEntity`; `$createHashtagNode`, `$isHashtagNode`), with `TextNode`'s generated JSON; `LexicalHashtagExtension.ts` (`getHashtagRegexStringChars`, `getHashtagRegexString` and `getHashtagMatch` as the default matcher, `HashtagConfig`, `registerLexicalHashtag`), the character classes as range tables generated from the JS source by `rust/scripts/generate-hashtag-tables.mjs`; `TextNode.isTextEntity` as a node hook | `hashtag`, `node` | `entities/hashtag-match`, `entities/hashtag-documents`, `entities/hashtag-typing`, the hashtag cases of `property/documents` | | `@lexical/extension`'s decorators (the `extension` feature) | `packages/lexical-extension/src/HorizontalRuleExtension.ts` (`HorizontalRuleNode`: a block decorator whose text is `\n`; `$createHorizontalRuleNode`, `$isHorizontalRuleNode`), `DecoratorTextExtension.ts` (`DecoratorTextNode`: inline, `__isInlineFormattable`, `getFormat`, `hasFormat`, `setFormat`, `toggleFormat`, its `format` as a flat NodeState; `$isDecoratorTextNode`), with `LexicalNode`'s generated JSON | `extension` | `scripts/decorator` | @@ -201,6 +203,35 @@ touches: highlighter. Of the 25 `property/documents` cases with code nodes, 4 also hold hashtag or overflow nodes; they waited on those packages, which are now ported. +- `@lexical/mark`'s `createDOM` and `updateDOM` (the `mark` and + `markOverlap` theme classes), which come with `html`, and + `MarkExtension`, which needs the extension API (`mark_nodes()` is its + `nodes`). Of `MarkStateExtension.ts`, everything that needs the + extension API waits for it: `MarkStateExtension`, `MarkBaseExtension`, + `defineMarkExtension` and mark flavors (the flavor-first forms of the `$` + functions, `$getMarkIDsByFlavor`, `AnyMarkFlavorExtension`), the + `markableNodes` config (the functions use `DEFAULT_MARKABLE_NODES`, which + JS uses too when the extension is not installed), `disabled`, the + `markIDNodeKeys` output and `$getMarkIDCandidates` (so `$removeMarkID` + without a selection always walks the document, as JS does without the + extension), `registerMarkIDsListener` and `reportMarkIDs`, and + `registerMarkState`'s inserted-text rule (`$applyInsertedTextMarkIDs` and + the segment matching, composition, the `CONTROLLED_TEXT_INSERTION_COMMAND` + and `PASTE_COMMAND` recorders). Rendering (`$decorateMarkIDs`, + `isMarkIDShown` and its rerender, `exportIDsToHTML`) and the HTML import + rule (`data-lexical-mark-ids`) come with `html` and the DOM. Slots aren't + ported, so `$forEachSlot` is not: the functions do not look into slots. + JS's `markIds` state is an `arraySetValue`; the port has no schema + builders yet, so its config is a `StateValueConfig` with the same parse, + JSON and equality, and the canonical form `$setState` gives a set-valued + state is applied where the module writes it. Ids sort by UTF-16 code + units, as JS's `sort()` does. In a headless editor, + `MarkNode.canInsertTextBefore` and `canInsertTextAfter` change nothing a + vector can see, as for `LinkNode`: `$insertText` puts text typed at the + edge of an inline parent's text outside it either way. `extractWithChild` + and `excludeFromCopy` are node hooks that nothing calls yet (their + callers are `@lexical/clipboard`'s and `@lexical/html`'s copies); + `scripts/mark` calls them directly. - `@lexical/overflow`'s `createDOM` and `updateDOM`, which come with `html`, and `OverflowExtension`, which isn't ported yet (`overflow_nodes()` is its `nodes`). `excludeFromCopy` is a node hook diff --git a/rust/conformance/Cargo.toml b/rust/conformance/Cargo.toml index 0a5fd8e1553..e4798ebad9e 100644 --- a/rust/conformance/Cargo.toml +++ b/rust/conformance/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true repository.workspace = true [dependencies] -lexical = { workspace = true, features = ["autolink", "code", "devtools", "extension", "file", "hashtag", "history", "link", "list", "overflow", "plain-text", "rich-text", "text"] } +lexical = { workspace = true, features = ["autolink", "code", "devtools", "extension", "file", "hashtag", "history", "link", "list", "mark", "overflow", "plain-text", "rich-text", "text"] } [lints] workspace = true diff --git a/rust/conformance/src/scripts.rs b/rust/conformance/src/scripts.rs index ee43cf4e1af..e3e37960eda 100644 --- a/rust/conformance/src/scripts.rs +++ b/rust/conformance/src/scripts.rs @@ -27,9 +27,11 @@ //! `CodeHighlightNode`), `CodeExtension`'s transform and //! `registerCodeIndentation`, one with `overflow: true` //! `@lexical/overflow`'s `OverflowNode`, and one with `hashtag: true` -//! `@lexical/hashtag`'s `HashtagNode` and `registerLexicalHashtag`, and one +//! `@lexical/hashtag`'s `HashtagNode` and `registerLexicalHashtag`, one //! with `extension: true` `@lexical/extension`'s decorators -//! (`HorizontalRuleNode`, `DecoratorTextNode`). +//! (`HorizontalRuleNode`, `DecoratorTextNode`), and one with `mark: true` +//! `@lexical/mark`'s `MarkNode`; the MarkState actions (`addMarkID`...) run +//! without `MarkStateExtension`, as in `ops.ts`. //! //! `setState`, `updateState`, `logState` and `logStateChange` name the //! NodeState configs of `ops.ts`'s `STATES` and its `UPDATERS`, made here @@ -43,6 +45,7 @@ use lexical::history::{create_empty_history_state, register_history}; use lexical::json::{Object, Value}; use lexical::link; use lexical::list; +use lexical::mark; use lexical::rich_text::{HeadingTagType, create_heading_node, create_quote_node}; use lexical::selection::set_blocks_type; use lexical::{ @@ -421,7 +424,15 @@ fn unported_action(action: &Value) -> Option<&'static str> { | "logRootTextContent" | "logRootTextContentEmpty" | "logCanShowPlaceholder" - | "logTextIntersection", + | "logTextIntersection" + | "wrapSelectionInMarkNode" + | "unwrapMarkNode" + | "logMarkIDs" + | "addMarkID" + | "removeMarkID" + | "logNodeMarkIDs" + | "logMarkIDsAtSelection" + | "migrateMarkNodesToMarkState", ) => None, Some("selection" | "selectAll" | "selectNodes" | "insertNodes") => None, _ => Some("unknown"), @@ -466,6 +477,19 @@ fn element(node: NodeRef) -> Result { } } +fn mark_node(node: NodeRef) -> Result { + if mark::is_mark_node(&node) { + Ok(node) + } else { + Err(err(format!("{} is not a mark node", node.get_key()))) + } +} + +/// An array of strings as JSON. +fn strings(values: &[String]) -> Value { + Value::Array(values.iter().map(|v| v.as_str().into()).collect()) +} + fn list_item(node: NodeRef) -> Result { if list::is_list_item_node(&node) { Ok(node) @@ -582,6 +606,13 @@ fn sel() -> Result { .ok_or_else(|| err("not a range selection")) } +/// `$sel()` as the selection itself. +fn range_sel() -> Result { + get_selection() + .filter(SelectionRef::is_range) + .ok_or_else(|| err("not a range selection")) +} + fn direction(v: &Value) -> Result { match v.as_str() { Some("next") => Ok(CaretDirection::Next), @@ -643,8 +674,51 @@ fn run_node_method( ) -> Result<(), LexicalError> { let a = |i| arg(args, i); match method { + "addID" => mark_node(n)?.add_id(&string(&a(0))?), "append" => element(n)?.append(&parse(&a(0))?)?, "clear" => element(n)?.clear()?, + "deleteID" => mark_node(n)?.delete_id(&string(&a(0))?), + "excludeFromCopy" => { + let destination = string(&a(0))?; + let excluded = element(n.clone())?.exclude_from_copy(&destination); + ctx.log(vec![ + "excludeFromCopy".into(), + n.get_key().into(), + destination.into(), + excluded.into(), + ]); + } + "extractWithChild" => { + let child = string(&a(0))?; + let destination = string(&a(1))?; + let selection = get_selection().ok_or_else(|| err("no selection"))?; + let extracted = element(n.clone())?.extract_with_child( + &node(&child)?, + Some(&selection), + &destination, + )?; + ctx.log(vec![ + "extractWithChild".into(), + n.get_key().into(), + child.into(), + destination.into(), + extracted.into(), + ]); + } + "getIDs" => { + let ids = mark_node(n.clone())?.get_ids().unwrap_or_default(); + ctx.log(vec!["getIDs".into(), n.get_key().into(), strings(&ids)]); + } + "hasID" => { + let id = string(&a(0))?; + let has = mark_node(n.clone())?.has_id(&id); + ctx.log(vec![ + "hasID".into(), + n.get_key().into(), + id.into(), + has.into(), + ]); + } "insertAfter" => { n.insert_after(&parse_serialized_node(&a(0))?, flag(&a(1), true))?; } @@ -692,6 +766,15 @@ fn run_node_method( v => n.set_format(number(&v)?), }, "setFormat" => element(n)?.set_element_format(a(0).as_str().unwrap_or("")), + "setIDs" => { + let ids = a(0) + .as_array() + .ok_or_else(|| err("expected an array of ids"))? + .iter() + .map(string) + .collect::, _>>()?; + mark_node(n)?.set_ids(&ids); + } "setIndent" => element(n)?.set_indent(number(&a(0))?)?, "setMode" => text(n)?.set_mode(&string(&a(0))?)?, "setStyle" if n.is_text() => n.set_style(&string(&a(0))?), @@ -968,6 +1051,80 @@ fn run_action(action: &Value, ctx: &Ctx, editor: &Editor) -> Result<(), LexicalE }; set_blocks_type(get_selection().as_ref(), create, None) } + Some("wrapSelectionInMarkNode") => { + let is_backward = flag(action.get("isBackward").unwrap_or(&Value::Null), false); + let id = action.str("id").map_err(err)?; + mark::wrap_selection_in_mark_node(&range_sel()?, is_backward, id, None) + } + Some("unwrapMarkNode") => { + mark::unwrap_mark_node(&mark_node(node(action.str("key").map_err(err)?)?)?) + } + Some("logMarkIDs") => { + let key = action.str("key").map_err(err)?; + let offset = number(action.get("offset").unwrap_or(&Value::Null))?; + let ids = mark::get_mark_ids(&text(node(key)?)?, offset as usize); + ctx.log(vec![ + "markIDs".into(), + key.into(), + offset.into(), + ids.as_deref().map_or(Value::Null, strings), + ]); + Ok(()) + } + Some("addMarkID") => { + let id = action.str("id").map_err(err)?; + let selection = get_selection().ok_or_else(|| err("no selection"))?; + let nodes = mark::add_mark_id(id, &selection)?; + ctx.log(vec![ + "addMarkID".into(), + id.into(), + Value::Array(nodes.iter().map(|n| n.get_key().into()).collect()), + ]); + Ok(()) + } + Some("removeMarkID") => { + let id = action.str("id").map_err(err)?; + if action.get("inSelection").and_then(Value::as_bool) == Some(true) { + let selection = get_selection().ok_or_else(|| err("no selection"))?; + mark::remove_mark_id(id, Some(&selection)) + } else { + mark::remove_mark_id(id, None) + } + } + Some("logNodeMarkIDs") => { + let key = action.str("key").map_err(err)?; + let ids = mark::get_node_mark_ids(&node(key)?)?; + ctx.log(vec!["nodeMarkIDs".into(), key.into(), strings(&ids)]); + Ok(()) + } + Some("logMarkIDsAtSelection") => { + let mode = action.get("mode").and_then(Value::as_str); + let ids = mark::get_mark_ids_at_selection( + get_selection().as_ref(), + match mode { + Some("all") => mark::MarkIDsMode::All, + _ => mark::MarkIDsMode::Any, + }, + )?; + ctx.log(vec![ + "markIDsAtSelection".into(), + mode.map_or(Value::Null, Value::from), + strings(&ids), + ]); + Ok(()) + } + Some("migrateMarkNodesToMarkState") => { + let target = match action.get("key").and_then(Value::as_str) { + Some(key) => Some(node(key)?), + None => None, + }; + let count = mark::migrate_mark_nodes_to_mark_state(target.as_ref())?; + ctx.log(vec![ + "migrateMarkNodesToMarkState".into(), + (count as f64).into(), + ]); + Ok(()) + } Some("dispatch") => { let name = action.get("command").and_then(Value::as_str).unwrap_or(""); let payload = action.get("payload").cloned().unwrap_or(Value::Null); @@ -1138,7 +1295,7 @@ fn snapshot_of(editor: &Editor, ctx: &Ctx, snapshot: bool) -> Result Vec { variant: None, check: serialization::check, }, + Suite { + file: "serialization/mark", + variant: None, + check: serialization::check, + }, Suite { file: "serialization/overflow", variant: None, @@ -132,6 +137,11 @@ pub fn all() -> Vec { variant: None, check: crate::scripts::check, }, + Suite { + file: "scripts/mark", + variant: None, + check: crate::scripts::check, + }, Suite { file: "scripts/node-state", variant: None, @@ -200,7 +210,8 @@ pub fn all() -> Vec { pub fn package_of(node_type: &str) -> Option<&'static str> { Some(match node_type { "root" | "paragraph" | "text" | "linebreak" | "tab" | "heading" | "quote" | "list" - | "listitem" | "link" | "autolink" | "code" | "code-highlight" | "overflow" | "hashtag" => { + | "listitem" | "link" | "autolink" | "code" | "code-highlight" | "overflow" | "hashtag" + | "mark" => { return None; } _ => "unknown", diff --git a/rust/conformance/src/suites/serialization.rs b/rust/conformance/src/suites/serialization.rs index ebf25d5351f..9a4de0ca819 100644 --- a/rust/conformance/src/suites/serialization.rs +++ b/rust/conformance/src/suites/serialization.rs @@ -3,8 +3,8 @@ use lexical::json::Value; use lexical::{CreateEditorArgs, create_editor}; /// `serialization/*.json`: the document parses from both forms and writes -/// both back byte for byte. The editor has the list, link and code nodes, -/// for `serialization/list`, `serialization/link` and `serialization/code`. +/// both back byte for byte. The editor has the list, link, code, overflow, +/// hashtag and mark nodes, for the `serialization/*` file of each package. pub fn check(case: &Value, _file: &Value) -> Result { let legacy = case.str("legacy")?; let compact = case.str("compact")?; @@ -15,6 +15,7 @@ pub fn check(case: &Value, _file: &Value) -> Result { lexical::code::code_nodes(), lexical::overflow::overflow_nodes(), lexical::hashtag::hashtag_nodes(), + lexical::mark::mark_nodes(), ] .concat(), ..CreateEditorArgs::default() diff --git a/rust/lexical-editor/Cargo.toml b/rust/lexical-editor/Cargo.toml index 08d82b9ac85..2b82006513e 100644 --- a/rust/lexical-editor/Cargo.toml +++ b/rust/lexical-editor/Cargo.toml @@ -44,6 +44,13 @@ autolink = ["link", "dep:regex"] # depends on @lexical/extension, @lexical/html and @lexical/utils, for the # extensions, the DOM import and the arrow-key handlers, which aren't ported. code = [] +# @lexical/mark: MarkNode, $wrapSelectionInMarkNode, $unwrapMarkNode, +# $getMarkIDs, and the functions of MarkStateExtension.ts that need no +# extension. Upstream it depends on @lexical/utils, which it enables as +# upstream does (nothing ported uses it yet), and on @lexical/extension and +# @lexical/html, for MarkExtension, MarkStateExtension and the DOM, which +# aren't ported. +mark = ["utils"] # @lexical/overflow: OverflowNode. overflow = [] # @lexical/text: registerLexicalTextEntity, the transforms that make text diff --git a/rust/lexical-editor/src/lib.rs b/rust/lexical-editor/src/lib.rs index afdf449748b..f917a283f3d 100644 --- a/rust/lexical-editor/src/lib.rs +++ b/rust/lexical-editor/src/lib.rs @@ -75,6 +75,8 @@ mod lexical_utils; pub mod link; #[cfg(feature = "list")] pub mod list; +#[cfg(feature = "mark")] +pub mod mark; #[cfg(feature = "extension")] mod merge_register; mod node; diff --git a/rust/lexical-editor/src/mark/generated_json.rs b/rust/lexical-editor/src/mark/generated_json.rs new file mode 100644 index 00000000000..cf0193938fe --- /dev/null +++ b/rust/lexical-editor/src/mark/generated_json.rs @@ -0,0 +1,50 @@ +//! `LexicalMarkGeneratedJSON.ts`: the JSON export and import JS generates +//! from `MarkNode`'s serialization schema, written out by hand in the same +//! order. `afterCloneMarkNode` is the node's `Clone`. + +use super::mark_node::MarkNode; +use crate::generated_json::{ + export_compact_element_node, export_element_node, update_element_node, +}; +use crate::json::{Object, Value}; + +/// `json` with `ids` after `children`, where the generated export writes it +/// (the compact form only when there are ids). +fn with_ids(json: Object, ids: &[String], compact: bool) -> Object { + let mut out = Object::new(); + for (key, value) in json.iter() { + out.insert(key, value.clone()); + if key == "children" && !(compact && ids.is_empty()) { + out.insert( + "ids", + Value::Array(ids.iter().map(|id| id.as_str().into()).collect()), + ); + } + } + out +} + +/// `exportMarkNode`. +pub(super) fn export_mark_node(node: &MarkNode) -> Object { + with_ids(export_element_node(node), &node.ids, false) +} + +/// `exportCompactMarkNode`. +pub(super) fn export_compact_mark_node(node: &MarkNode) -> Object { + with_ids(export_compact_element_node(node), &node.ids, true) +} + +/// `updateMarkNode`: an `ids` that is not an array is `[]`, and an entry +/// that is not a string is `''` (`arrayValue(stringValue())`). +pub(super) fn update_mark_node(node: &mut MarkNode, json: &Object) { + update_element_node(&mut node.element, json); + node.ids = json + .get("ids") + .and_then(Value::as_array) + .map(|ids| { + ids.iter() + .map(|id| id.as_str().unwrap_or("").to_owned()) + .collect() + }) + .unwrap_or_default(); +} diff --git a/rust/lexical-editor/src/mark/mark_node.rs b/rust/lexical-editor/src/mark/mark_node.rs new file mode 100644 index 00000000000..e2b19b595af --- /dev/null +++ b/rust/lexical-editor/src/mark/mark_node.rs @@ -0,0 +1,194 @@ +//! `MarkNode.ts`: `MarkNode`, `$createMarkNode` and `$isMarkNode`. + +use super::generated_json::{export_compact_mark_node, export_mark_node, update_mark_node}; +use crate::json::Object; +use crate::lexical_selection::SelectionRef; +use crate::lexical_utils::{set_node_key, with_writable}; +use crate::node::{LexicalNode, NodeBase, NodeRef}; +use crate::nodes::element::ElementData; +use crate::{LexicalError, utf16}; + +/// `MarkNode`: an inline element over the content that one or more ids +/// (comment threads, for example) annotate. +#[derive(Clone, Debug, Default)] +pub struct MarkNode { + base: NodeBase, + pub(super) element: ElementData, + pub(super) ids: Vec, +} + +impl MarkNode { + /// `__ids`. + pub fn ids(&self) -> &[String] { + &self.ids + } +} + +/// `$createMarkNode(ids)`. +pub fn create_mark_node(ids: &[String]) -> NodeRef { + set_node_key(Box::new(MarkNode { + ids: ids.to_vec(), + ..MarkNode::default() + })) +} + +/// `$isMarkNode(node)`. +pub fn is_mark_node(node: &NodeRef) -> bool { + node.get_latest().is::() +} + +/// Changes a mark's ids (`getWritable()`); does nothing to any other node. +fn with_ids(node: &NodeRef, f: impl FnOnce(&mut Vec)) { + if is_mark_node(node) { + with_writable(node.get_key(), |n| { + f(&mut n.downcast_mut::().expect("a mark").ids) + }); + } +} + +impl NodeRef { + /// `MarkNode.hasID(id)`; false for any other node. + pub fn has_id(&self, id: &str) -> bool { + self.get_ids() + .is_some_and(|ids| ids.iter().any(|i| i == id)) + } + + /// `MarkNode.getIDs()`: a copy of the ids; `None` for any other node. + pub fn get_ids(&self) -> Option> { + self.get_latest() + .downcast_ref::() + .map(|m| m.ids.clone()) + } + + /// `MarkNode.setIDs(ids)`, as given (duplicates too). Does nothing to + /// any other node. + pub fn set_ids(&self, ids: &[String]) { + with_ids(self, |i| *i = ids.to_vec()); + } + + /// `MarkNode.addID(id)`: appends `id` unless the mark has it. The mark + /// is written either way. Does nothing to any other node. + pub fn add_id(&self, id: &str) { + with_ids(self, |ids| { + if !ids.iter().any(|i| i == id) { + ids.push(id.to_owned()); + } + }); + } + + /// `MarkNode.deleteID(id)`: removes the first `id`, if any. The mark is + /// written either way, and stays when it has no ids left. Does nothing + /// to any other node. + pub fn delete_id(&self, id: &str) { + with_ids(self, |ids| { + if let Some(idx) = ids.iter().position(|i| i == id) { + ids.remove(idx); + } + }); + } +} + +impl LexicalNode for MarkNode { + fn get_type(&self) -> &'static str { + "mark" + } + fn base(&self) -> &NodeBase { + &self.base + } + fn base_mut(&mut self) -> &mut NodeBase { + &mut self.base + } + fn element(&self) -> Option<&ElementData> { + Some(&self.element) + } + fn element_mut(&mut self) -> Option<&mut ElementData> { + Some(&mut self.element) + } + + /// `MarkNode.isInline()`. + fn is_inline(&self) -> bool { + true + } + + /// `MarkNode.canBeEmpty()`: a mark left with no children is removed. + fn can_be_empty(&self) -> bool { + false + } + + /// `MarkNode.canInsertTextBefore()`: text typed at the mark's start + /// goes before it. + fn can_insert_text_before(&self) -> bool { + false + } + + /// `MarkNode.canInsertTextAfter()`: text typed at the mark's end goes + /// after it. + fn can_insert_text_after(&self) -> bool { + false + } + + /// `MarkNode.insertNewAfter(selection, restoreSelection)`: a new mark + /// with the same ids, after it. + fn insert_new_after( + &self, + this: &NodeRef, + _: &SelectionRef, + restore_selection: bool, + ) -> Result, LexicalError> { + let mark_node = create_mark_node(&self.ids); + this.insert_after(&mark_node, restore_selection)?; + Ok(Some(mark_node)) + } + + /// `MarkNode.extractWithChild(child, selection, destination)`: a copy + /// (not to HTML) of a range selection whose points are both in the mark + /// takes the mark with it when the difference of their offsets is the + /// mark's text length (offsets in different nodes are compared as they + /// are, as in JS). + fn extract_with_child( + &self, + this: &NodeRef, + _: &NodeRef, + selection: Option<&SelectionRef>, + destination: &str, + ) -> Result { + let Some(selection) = selection.filter(|s| s.is_range()) else { + return Ok(false); + }; + if destination == "html" { + return Ok(false); + } + let range = selection.range().expect("a range selection"); + let anchor_node = range.anchor.get_node()?; + let focus_node = range.focus.get_node()?; + let (anchor, focus) = (range.anchor.offset as i64, range.focus.offset as i64); + let selection_length = if selection.is_backward()? { + anchor - focus + } else { + focus - anchor + }; + Ok(this.is_parent_of(&anchor_node) + && this.is_parent_of(&focus_node) + && utf16::len(&this.get_text_content()) as i64 == selection_length) + } + + /// `MarkNode.excludeFromCopy(destination)`: left out of every copy but + /// a clone. + fn exclude_from_copy(&self, destination: &str) -> bool { + destination != "clone" + } + + /// `exportMarkNode` and `exportCompactMarkNode`. + fn export_json(&self, compact: bool) -> Object { + if compact { + export_compact_mark_node(self) + } else { + export_mark_node(self) + } + } + + /// `updateMarkNode`. + fn update_from_json(&mut self, json: &Object) { + update_mark_node(self, json); + } +} diff --git a/rust/lexical-editor/src/mark/mark_state_extension.rs b/rust/lexical-editor/src/mark/mark_state_extension.rs new file mode 100644 index 00000000000..6d2c782ae25 --- /dev/null +++ b/rust/lexical-editor/src/mark/mark_state_extension.rs @@ -0,0 +1,447 @@ +//! The functions of `MarkStateExtension.ts` that need no extension: mark ids +//! kept as the `markIds` NodeState of the leaves they cover (a sorted array +//! without duplicates, `arraySetValue(markIDValue())`), with the default +//! `markableNodes` (`TextNode`, `LineBreakNode` and inline decorators). +//! These are `MarkStateExtension`'s ids; JS's `$` functions read and write +//! them the same way when the extension is not installed, as here. +//! +//! Not ported: the extension itself and what needs it or a DOM (rust/PARITY.md): +//! the `markableNodes` config, mark flavors (`defineMarkExtension`, +//! `MarkBaseExtension`, the flavor-first forms, `$getMarkIDsByFlavor`), the +//! `markIDNodeKeys` output and `registerMarkIDsListener`, the inserted-text +//! rule, rendering and HTML import and export. Slots aren't ported, so the +//! slots of a node are not looked through. + +use crate::error::invariant; +use crate::json::Value; +use crate::lexical_selection::{PointType, SelectionRef, Which}; +use crate::lexical_utils::get_root; +use crate::node::NodeRef; +use crate::node_state::{ + StateConfig, StateValueConfig, create_state_with, get_state, set_state_with, +}; +use crate::nodes::line_break::LineBreakNode; +use crate::{LexicalError, NodeKey}; +use std::cmp::Ordering; +use std::collections::HashSet; +use std::rc::Rc; + +use super::is_mark_node; + +/// `MARK_STATE_KEY`: the NodeState key that the mark ids of a leaf are +/// stored under, in the `$` record of its JSON. +pub const MARK_STATE_KEY: &str = "markIds"; + +/// JS's `\s` without the `u` flag: ECMAScript's WhiteSpace and +/// LineTerminator. +fn is_js_whitespace(c: char) -> bool { + matches!( + c, + '\t' | '\n' | '\u{0B}' | '\u{0C}' | '\r' | ' ' | '\u{A0}' | '\u{1680}' | '\u{2000}' + ..='\u{200A}' + | '\u{2028}' + | '\u{2029}' + | '\u{202F}' + | '\u{205F}' + | '\u{3000}' + | '\u{FEFF}' + ) +} + +/// `isValidMarkID(id)`: a non-empty string without whitespace. +fn is_valid_mark_id(id: &str) -> bool { + !id.is_empty() && !id.chars().any(is_js_whitespace) +} + +/// JS's default sort order of strings: by UTF-16 code units. +fn js_cmp(a: &str, b: &str) -> Ordering { + a.encode_utf16().cmp(b.encode_utf16()) +} + +/// `arraySetValue(markIDValue())`'s parse of strings: the valid ids, +/// sorted as JS sorts them, without duplicates. +fn canonical_ids(ids: impl IntoIterator) -> Vec { + let mut members: Vec = ids.into_iter().filter(|id| is_valid_mark_id(id)).collect(); + members.sort_by(|a, b| js_cmp(a, b)); + members.dedup(); + members +} + +/// `arraySetValue(markIDValue())`'s parse: anything but an array is `[]`, +/// and a member that is not a valid id is dropped. +fn parse_ids(value: &Value) -> Vec { + value.as_array().map_or_else(Vec::new, |items| { + canonical_ids(items.iter().filter_map(|v| v.as_str().map(str::to_owned))) + }) +} + +thread_local! { + /// `createMarkIDsState(MARK_STATE_KEY)`: `MarkStateExtension`'s state. + static MARK_IDS_STATE: StateConfig> = create_state_with( + MARK_STATE_KEY, + StateValueConfig { + parse: Rc::new(parse_ids), + unparse: Rc::new(|ids: &Vec| { + Value::Array(ids.iter().map(|id| id.as_str().into()).collect()) + }), + // `isArraySetEqual`. + is_equal: Rc::new(|a: &Vec, b: &Vec| a == b), + reset_on_copy_node: false, + }, + ); +} + +fn mark_ids_state() -> StateConfig> { + MARK_IDS_STATE.with(Clone::clone) +} + +/// `$isInlineDecoratorNode(node)`: an inline `DecoratorNode`, one of the +/// default `markableNodes`. +pub fn is_inline_decorator_node(node: &NodeRef) -> bool { + node.is_decorator() && node.is_inline() +} + +/// `$isMarkableNode(flavor, node)` with the default `markableNodes` +/// (`DEFAULT_MARKABLE_NODES`): `TextNode` and its subclasses, +/// `LineBreakNode` and inline decorators. +fn is_markable_node(node: &NodeRef) -> bool { + node.is_text() || node.get_latest().is::() || is_inline_decorator_node(node) +} + +/// `$readNodeMarkIDs(flavor, node)`. +fn read_node_mark_ids(node: &NodeRef) -> Result, LexicalError> { + get_state(node, &mark_ids_state()) +} + +/// `$getMarkableIDs(flavor, node)`: the ids of a markable node, otherwise +/// none. +fn get_markable_ids(node: Option<&NodeRef>) -> Result, LexicalError> { + match node { + Some(node) if is_markable_node(node) => read_node_mark_ids(node), + _ => Ok(Vec::new()), + } +} + +/// `$getNodeMarkIDs(node)`: the mark ids stored on a node, sorted; none for +/// a node that carries none. +pub fn get_node_mark_ids(node: &NodeRef) -> Result, LexicalError> { + read_node_mark_ids(node) +} + +/// `$setState(node, state, updater)` for the set-valued state: the value +/// the updater returns is made canonical (`canonicalValue`) before it is +/// compared and stored. +fn update_node_mark_ids( + node: &NodeRef, + updater: impl FnOnce(&Vec) -> Vec, +) -> Result<(), LexicalError> { + set_state_with(node, &mark_ids_state(), |prev| canonical_ids(updater(prev))).map(|_| ()) +} + +/// `$addNodeMarkIDs(flavor, node, ids)`. +fn add_node_mark_ids(node: &NodeRef, ids: &[String]) -> Result<(), LexicalError> { + update_node_mark_ids(node, |prev| { + if ids.iter().all(|id| prev.contains(id)) { + prev.clone() + } else { + prev.iter().chain(ids).cloned().collect() + } + }) +} + +/// `$removeNodeMarkIDs(flavor, node, ids)`. +fn remove_node_mark_ids(node: &NodeRef, ids: &[String]) -> Result<(), LexicalError> { + update_node_mark_ids(node, |prev| { + prev.iter() + .filter(|id| !ids.contains(id)) + .cloned() + .collect() + }) +} + +/// `assertValidMarkID(id)`. +fn assert_valid_mark_id(id: &str) -> Result<(), LexicalError> { + invariant!( + is_valid_mark_id(id), + "MarkStateExtension: invalid mark id {}, ids must be non-empty strings without whitespace", + Value::from(id) + ); + Ok(()) +} + +/// `$markableNodesInSelection(flavor, selection)`: the markable nodes +/// `selection.extract()` returns, which splits the text nodes at the +/// boundaries of a range selection. None for a collapsed range selection. +fn markable_nodes_in_selection(selection: &SelectionRef) -> Result, LexicalError> { + if selection.range().is_some_and(|r| r.is_collapsed()) { + return Ok(Vec::new()); + } + let mut nodes = Vec::new(); + let mut seen = HashSet::new(); + for node in selection.extract()? { + if is_markable_node(&node) && seen.insert(node.get_key().to_owned()) { + nodes.push(node); + } + } + Ok(nodes) +} + +/// `$addMarkID(id, selection)`: adds the mark `id` to every markable node +/// in `selection`, splitting the text nodes at its boundaries, and returns +/// the nodes that now carry it. Ids already on those nodes are kept, so +/// marks overlap freely; a collapsed selection marks nothing. Fails on an +/// id that is empty or holds whitespace. +pub fn add_mark_id(id: &str, selection: &SelectionRef) -> Result, LexicalError> { + assert_valid_mark_id(id)?; + let nodes = markable_nodes_in_selection(selection)?; + let ids = [id.to_owned()]; + for node in &nodes { + add_node_mark_ids(node, &ids)?; + } + Ok(nodes) +} + +/// `$collectMarkableNodes(flavor, node, out, seen)`: the markable nodes in +/// the subtree of `node`, in document order. +fn collect_markable_nodes(node: &NodeRef, out: &mut Vec, seen: &mut HashSet) { + if is_markable_node(node) && seen.insert(node.get_key().to_owned()) { + out.push(node.clone()); + } + if node.is_element() { + let mut child = node.get_first_child(); + while let Some(c) = child { + collect_markable_nodes(&c, out, seen); + child = c.get_next_sibling(); + } + } +} + +/// `$markableNodesIn(flavor, node)`. +fn markable_nodes_in(node: &NodeRef) -> Vec { + let mut out = Vec::new(); + collect_markable_nodes(node, &mut out, &mut HashSet::new()); + out +} + +/// `$removeMarkID(id, selection)`: removes the mark `id` from the nodes in +/// `selection` (splitting the text nodes at its boundaries), or with `None` +/// from the whole document, which is walked, as JS does when the extension +/// is not active. Text left like its neighbors merges back in +/// normalization. +pub fn remove_mark_id(id: &str, selection: Option<&SelectionRef>) -> Result<(), LexicalError> { + let nodes = match selection { + Some(selection) => markable_nodes_in_selection(selection)?, + None => markable_nodes_in(&get_root()), + }; + let ids = [id.to_owned()]; + for node in &nodes { + remove_node_mark_ids(node, &ids)?; + } + Ok(()) +} + +/// `$getAdjacentLeaf(node, 'next')`: the leaf after `node` within its +/// block, looking through the boundaries of inline elements such as links. +/// An empty inline element is returned as it is. +fn get_next_adjacent_leaf(node: &NodeRef) -> Option { + let mut current = node.clone(); + loop { + let Some(sibling) = current.get_next_sibling() else { + let parent = current.get_parent()?; + if parent.is_element() && parent.is_inline() { + current = parent; + continue; + } + return None; + }; + let mut leaf = sibling; + while leaf.is_element() && leaf.is_inline() { + match leaf.get_first_child() { + Some(child) => leaf = child, + None => break, + } + } + return Some(leaf); + } +} + +/// `$getPointMarkIDs(flavor, point)`: the ids of the leaf a caret is in, +/// or of the next leaf when it is at the end of a text node without ids; +/// at an element point, those of the leaf before it, or else after it. +fn get_point_mark_ids(selection: &SelectionRef, which: Which) -> Result, LexicalError> { + let point = selection.point(which).expect("a range selection"); + let node = point.get_node()?; + if point.point_type == PointType::Text { + let ids = read_node_mark_ids(&node)?; + if ids.is_empty() && node.is_text() && point.offset == node.get_text_content_size() { + // Like $getMarkIDs: a caret right before a marked run is in it + return get_markable_ids(get_next_adjacent_leaf(&node).as_ref()); + } + return Ok(ids); + } + if node.is_element() { + let before = get_markable_ids( + point + .offset + .checked_sub(1) + .and_then(|i| node.get_child_at_index(i)) + .as_ref(), + )?; + return if before.is_empty() { + get_markable_ids(node.get_child_at_index(point.offset).as_ref()) + } else { + Ok(before) + }; + } + Ok(Vec::new()) +} + +/// The `mode` of [`get_mark_ids_at_selection`]. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum MarkIDsMode { + /// `'any'`: the ids on any selected leaf. + #[default] + Any, + /// `'all'`: the ids every selected leaf shares. + All, +} + +/// `$getMarkIDsAtSelection(selection, mode)`: the mark ids at a selection, +/// sorted. For a collapsed range selection, the ids of the leaf holding the +/// caret (or of the next leaf, at the end of a text node without ids); +/// otherwise the ids on any selected leaf, or with [`MarkIDsMode::All`] +/// those shared by every one. Text nodes a range only touches at an edge +/// are left out. `None` is JS's `null`; JS's default, the current +/// selection, is `get_selection().as_ref()`. +pub fn get_mark_ids_at_selection( + selection: Option<&SelectionRef>, + mode: MarkIDsMode, +) -> Result, LexicalError> { + let Some(selection) = selection else { + return Ok(Vec::new()); + }; + let range = selection.range(); + if range.as_ref().is_some_and(|r| r.is_collapsed()) { + return get_point_mark_ids(selection, Which::Anchor); + } + let mut leaves: Vec = selection + .get_nodes()? + .into_iter() + .filter(is_markable_node) + .collect(); + if let Some(range) = range { + let (start, end) = if selection.is_backward()? { + (&range.focus, &range.anchor) + } else { + (&range.anchor, &range.focus) + }; + leaves.retain(|node| { + if !node.is_text() { + return true; + } + let key = node.get_key(); + let from = if start.point_type == PointType::Text && start.key == key { + start.offset + } else { + 0 + }; + let to = if end.point_type == PointType::Text && end.key == key { + end.offset + } else { + node.get_text_content_size() + }; + to > from + }); + } + if leaves.is_empty() { + return Ok(Vec::new()); + } + let id_sets = leaves + .iter() + .map(read_node_mark_ids) + .collect::, _>>()?; + Ok(match mode { + MarkIDsMode::Any => { + let mut ids: Vec = id_sets.into_iter().flatten().collect(); + ids.sort_by(|a, b| js_cmp(a, b)); + ids.dedup(); + ids + } + MarkIDsMode::All => id_sets[0] + .iter() + .filter(|id| id_sets.iter().all(|ids| ids.contains(id))) + .cloned() + .collect(), + }) +} + +/// `$migrateMarkNodesToMarkState(node)`: converts a document that uses +/// `MarkNode` to mark ids on its leaves. The ids of every mark (with those +/// of the marks it is in) are added to the markable nodes inside it, and +/// the mark is unwrapped. `None` is the root. Returns the number of marks +/// unwrapped; fails on an id that is empty or holds whitespace. +pub fn migrate_mark_nodes_to_mark_state(node: Option<&NodeRef>) -> Result { + fn visit( + current: &NodeRef, + inherited: &[String], + count: &mut usize, + ) -> Result<(), LexicalError> { + if is_mark_node(current) { + let own_ids = current.get_ids().unwrap_or_default(); + for id in &own_ids { + assert_valid_mark_id(id)?; + } + let mut ids: Vec = Vec::new(); + for id in inherited.iter().chain(&own_ids) { + if !ids.contains(id) { + ids.push(id.clone()); + } + } + for child in current.get_children() { + visit(&child, &ids, count)?; + } + // Re-read: unwrapping a nested MarkNode replaced it with its + // children + for child in current.get_children() { + current.insert_before(&child, true)?; + } + current.remove(false)?; + *count += 1; + } else { + if is_markable_node(current) && !inherited.is_empty() { + add_node_mark_ids(current, inherited)?; + } + if current.is_element() { + for child in current.get_children() { + visit(&child, inherited, count)?; + } + } + } + Ok(()) + } + let node = node.cloned().unwrap_or_else(get_root); + let mut count = 0; + visit(&node, &[], &mut count)?; + Ok(count) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ids_sort_by_utf16_code_units() { + // U+FF01 comes before U+1F600 by code point, after it by code unit. + assert_eq!( + canonical_ids([ + "\u{ff01}".into(), + "\u{1f600}".into(), + "b".into(), + "b".into() + ]), + ["b", "\u{1f600}", "\u{ff01}"] + ); + assert!(!is_valid_mark_id("a\u{feff}b")); + assert!(is_valid_mark_id("a\u{85}b")); + } +} diff --git a/rust/lexical-editor/src/mark/mod.rs b/rust/lexical-editor/src/mark/mod.rs new file mode 100644 index 00000000000..b26c3ad8054 --- /dev/null +++ b/rust/lexical-editor/src/mark/mod.rs @@ -0,0 +1,171 @@ +//! `@lexical/mark` (the `mark` feature), from `packages/lexical-mark/src`: +//! `MarkNode` with its generated JSON (`MarkNode.ts`, +//! `LexicalMarkGeneratedJSON.ts`), `$wrapSelectionInMarkNode`, +//! `$unwrapMarkNode` and `$getMarkIDs` (`index.ts`), and the functions of +//! `MarkStateExtension.ts` that need no extension, which keep mark ids as +//! the `markIds` NodeState of the leaves they cover instead of wrapping +//! them (`$addMarkID`, `$removeMarkID`, `$getNodeMarkIDs`, +//! `$getMarkIDsAtSelection`, `$migrateMarkNodesToMarkState`), with the +//! default `markableNodes`. +//! +//! Not ported: `createDOM` and `updateDOM`, which come with `html`; +//! `MarkExtension` (its `nodes` are [`mark_nodes`]); and the parts of +//! `MarkStateExtension.ts` that need the extension API or a DOM (rust/PARITY.md). + +mod generated_json; +mod mark_node; +mod mark_state_extension; + +pub use mark_node::{MarkNode, create_mark_node, is_mark_node}; +pub use mark_state_extension::{ + MARK_STATE_KEY, MarkIDsMode, add_mark_id, get_mark_ids_at_selection, get_node_mark_ids, + is_inline_decorator_node, migrate_mark_nodes_to_mark_state, remove_mark_id, +}; + +use crate::lexical_selection::{SelectionRef, Which, create_range_selection}; +use crate::node::NodeRef; +use crate::{LexicalError, NodeClass}; + +/// The node classes of this package (`MarkExtension`'s `nodes`): +/// `MarkNode`. +pub fn mark_nodes() -> Vec { + vec![NodeClass::of::()] +} + +/// `$unwrapMarkNode(node)`: puts the mark's children in its place. +pub fn unwrap_mark_node(node: &NodeRef) -> Result<(), LexicalError> { + let mut target: Option = None; + for child in node.get_children() { + match &target { + None => node.insert_before(&child, true)?, + Some(target) => target.insert_after(&child, true)?, + }; + target = Some(child); + } + node.remove(false) +} + +/// `createNode` of `$wrapSelectionInMarkNode`: a mark with the given ids. +type CreateNode = dyn Fn(&[String]) -> NodeRef; + +/// `$wrapSelectionInMarkNode(selection, isBackward, id, createNode)`: wraps +/// the text and the inline elements and decorators `selection` covers in +/// marks with `id` (made by `create_node`, or [`create_mark_node`]), one per +/// run of siblings. A line break or a block ends a run; a mark in +/// the range is not wrapped itself, its text is (in a mark of its own, +/// inside it). The selection is collapsed at the end of the last mark (its +/// start when `is_backward`). +pub fn wrap_selection_in_mark_node( + selection: &SelectionRef, + is_backward: bool, + id: &str, + create_node: Option<&CreateNode>, +) -> Result<(), LexicalError> { + // Force a forwards selection since append is used, ignore the argument. + // A new selection is used to avoid side-effects of flipping the given + // selection + let forward_selection = create_range_selection(); + let (start, end) = if selection.is_backward()? { + (Which::Focus, Which::Anchor) + } else { + (Which::Anchor, Which::Focus) + }; + for (which, from) in [(Which::Anchor, start), (Which::Focus, end)] { + let point = selection.point(from).expect("a range selection"); + forward_selection.point_set(which, &point.key, point.offset, point.point_type)?; + } + + let mut current_node_parent: Option = None; + let mut last_created_mark_node: Option = None; + + // Note that extract will split text nodes at the boundaries + for node in forward_selection.extract()? { + if last_created_mark_node + .as_ref() + .is_some_and(|mark| mark.is_parent_of(&node)) + { + // If the current node is a child of the last created mark node, + // there is nothing to do here + continue; + } + let target_node = if node.is_text() { + // Case 1: The node is a text node and we can include it + Some(node) + } else if is_mark_node(&node) { + // Case 2: the node is a mark node and we can ignore it as a + // target, moving on to its children + continue; + } else if (node.is_element() || node.is_decorator()) && node.is_inline() { + // Case 3: inline element/decorator nodes can be added in their + // entirety to the new mark + Some(node) + } else { + None + }; + + let Some(target_node) = target_node else { + // If we don't have a target node to wrap we can clear our state + // and continue on with the next node + current_node_parent = None; + last_created_mark_node = None; + continue; + }; + if current_node_parent.as_ref() == Some(&target_node) { + // The current node is a child of the target node to be wrapped, + // there is nothing to do here. + continue; + } + let parent_node = target_node.get_parent(); + if parent_node.is_none() || parent_node != current_node_parent { + // If the parent node is not the current node's parent node, we + // can clear the last created mark node. + last_created_mark_node = None; + } + current_node_parent = parent_node; + + let mark_node = match &last_created_mark_node { + Some(mark_node) => mark_node.clone(), + None => { + // If we don't have a created mark node, we can make one + let ids = [id.to_owned()]; + let mark_node = match create_node { + Some(create_node) => create_node(&ids), + None => create_mark_node(&ids), + }; + target_node.insert_before(&mark_node, true)?; + last_created_mark_node = Some(mark_node.clone()); + mark_node + } + }; + // Add the target node to be wrapped in the latest created mark node + mark_node.append(&[target_node])?; + } + // Make selection collapsed at the end + if let Some(mark_node) = last_created_mark_node { + if is_backward { + mark_node.select_start()?; + } else { + mark_node.select_end()?; + } + } + Ok(()) +} + +/// `$getMarkIDs(node, offset)`: the ids of the nearest mark `node` is in, +/// or of the mark right after a text node on the way up when `offset` is +/// that text node's size (in UTF-16 code units); `None` when there is none. +pub fn get_mark_ids(node: &NodeRef, offset: usize) -> Option> { + let mut current_node = Some(node.clone()); + while let Some(current) = current_node { + if is_mark_node(¤t) { + return current.get_ids(); + } else if current.is_text() + && offset == current.get_text_content_size() + && let Some(next_sibling) = current.get_next_sibling().filter(is_mark_node) + { + return next_sibling.get_ids(); + } + current_node = current.get_parent(); + } + None +} diff --git a/swift/Package.swift b/swift/Package.swift index 46182a0816a..26a72adf419 100644 --- a/swift/Package.swift +++ b/swift/Package.swift @@ -327,7 +327,7 @@ let package = Package( dependencies: [ "Lexical", "LexicalCodeCore", "LexicalDevtoolsCore", "LexicalExtension", "LexicalFile", "LexicalHashtag", "LexicalHistory", "LexicalHTML", "LexicalLink", "LexicalList", - "LexicalMarkdown", "LexicalOverflow", "LexicalPlainText", "LexicalRichText", + "LexicalMark", "LexicalMarkdown", "LexicalOverflow", "LexicalPlainText", "LexicalRichText", "LexicalSelection", "LexicalTesting", "LexicalText", ]), // Ports of the upstream unit tests, each marked with `.upstream(...)`. diff --git a/swift/Tests/ConformanceTests/Ops.swift b/swift/Tests/ConformanceTests/Ops.swift index da8a0dd57ae..75822820ba0 100644 --- a/swift/Tests/ConformanceTests/Ops.swift +++ b/swift/Tests/ConformanceTests/Ops.swift @@ -6,6 +6,7 @@ import LexicalHashtag import LexicalHistory import LexicalLink import LexicalList +import LexicalMark import LexicalOverflow import LexicalPlainText import LexicalRichText @@ -322,6 +323,15 @@ private func element(_ node: LexicalNode) throws -> ElementNode { return element } +private func markNode(_ node: LexicalNode) throws -> MarkNode { + guard let mark = node as? MarkNode else { + throw ScriptError("\(node.getKey()) is not a mark node") + } + return mark +} + +private func strings(_ values: [String]) -> JSONValue { .array(values.map { .string($0) }) } + private func parse(_ nodes: JSONValue) throws -> [LexicalNode] { try (nodes.arrayValue ?? []).map { value in guard let object = value.objectValue else { throw ScriptError("not a serialized node") } @@ -439,8 +449,34 @@ private func runNodeMethod( try parse(.array([a0])).first.orThrow() } switch method { + case "addID": try markNode(n).addID(a0.stringValue.orThrow()) case "append": try element(n).append(contentsOf: parse(a0)) case "clear": try element(n).clear() + case "deleteID": try markNode(n).deleteID(a0.stringValue.orThrow()) + case "excludeFromCopy": + let destination = try a0.stringValue.orThrow() + ctx.log.append( + .array([ + .string("excludeFromCopy"), .string(n.getKey()), .string(destination), + .bool(try element(n).excludeFromCopy(destination)), + ])) + case "extractWithChild": + let child = try a0.stringValue.orThrow() + let destination = try a1.stringValue.orThrow() + let extracted = try element(n).extractWithChild(node(child), anySelection(), destination) + ctx.log.append( + .array([ + .string("extractWithChild"), .string(n.getKey()), .string(child), .string(destination), + .bool(extracted), + ])) + case "getIDs": + ctx.log.append( + .array([.string("getIDs"), .string(n.getKey()), strings(try markNode(n).getIDs())])) + case "hasID": + let id = try a0.stringValue.orThrow() + ctx.log.append( + .array([.string("hasID"), .string(n.getKey()), .string(id), .bool(try markNode(n).hasID(id))]) + ) case "insertAfter": _ = try n.insertAfter(one(), restoreSelection: a1.boolValue ?? true) case "insertBefore": _ = try n.insertBefore(one(), restoreSelection: a1.boolValue ?? true) case "markDirty": try n.markDirty() @@ -470,6 +506,8 @@ private func runNodeMethod( } else { try element(n).setFormat(a0.stringValue.orThrow()) } + case "setIDs": + try markNode(n).setIDs((a0.arrayValue.orThrow()).map { try $0.stringValue.orThrow() }) case "setIndent": try element(n).setIndent(a0.int.orThrow()) case "setMode": try text(n).setMode(a0.stringValue.orThrow()) case "setStyle": @@ -626,6 +664,44 @@ private func runAction(_ action: JSONValue, _ ctx: ScriptContext) throws { .string("textIntersection"), .number(Double(characters)), found.map { .array([.string($0.node.getKey()), .number(Double($0.offset))]) } ?? .null, ])) + case "wrapSelectionInMarkNode": + try wrapSelectionInMarkNode( + rangeSelection(), action["isBackward"]?.boolValue ?? false, + action["id"]?.stringValue.orThrow() ?? "") + case "unwrapMarkNode": + try unwrapMarkNode(markNode(node(action["key"]?.stringValue.orThrow() ?? ""))) + case "logMarkIDs": + let key = try action["key"]?.stringValue.orThrow() ?? "" + let offset = try action["offset"]?.int.orThrow() ?? 0 + let ids = try getMarkIDs(text(node(key)), offset) + ctx.log.append( + .array([.string("markIDs"), .string(key), .int(offset), ids.map(strings) ?? .null])) + case "addMarkID": + let id = try action["id"]?.stringValue.orThrow() ?? "" + let nodes = try addMarkID(id, anySelection()) + ctx.log.append( + .array([.string("addMarkID"), .string(id), .array(nodes.map { .string($0.getKey()) })])) + case "removeMarkID": + let id = try action["id"]?.stringValue.orThrow() ?? "" + if action["inSelection"]?.boolValue == true { + try removeMarkID(id, anySelection()) + } else { + try removeMarkID(id) + } + case "logNodeMarkIDs": + let key = try action["key"]?.stringValue.orThrow() ?? "" + ctx.log.append( + .array([.string("nodeMarkIDs"), .string(key), strings(try getNodeMarkIDs(node(key)))])) + case "logMarkIDsAtSelection": + let mode = action["mode"]?.stringValue + let selection: (any BaseSelection)? = try getSelection() + let ids = try getMarkIDsAtSelection(selection, mode == "all" ? .all : .any) + ctx.log.append( + .array([.string("markIDsAtSelection"), mode.map { .string($0) } ?? .null, strings(ids)])) + case "migrateMarkNodesToMarkState": + let target = try action["key"]?.stringValue.map { try node($0) } + let count = try migrateMarkNodesToMarkState(target) + ctx.log.append(.array([.string("migrateMarkNodesToMarkState"), .int(count)])) case "dispatch": let name = try action["command"]?.stringValue.orThrow() ?? "" let handled = try ctx.dispatch(name, action["payload"] ?? .null) @@ -699,7 +775,7 @@ func runScriptWithBuild(_ script: JSONValue) throws -> (build: JSONValue?, steps var build: JSONValue? if let extensions = script["extensions"]?.arrayValue { for flag in [ - "richText", "plainText", "list", "link", "code", "overflow", "hashtag", "extension", + "richText", "plainText", "list", "link", "code", "overflow", "hashtag", "extension", "mark", ] where script[flag]?.boolValue == true { throw ScriptError("extensions and \(flag) don't mix") @@ -739,8 +815,9 @@ private func makeEditor(_ script: JSONValue) throws -> Editor { // @lexical/code-core's nodes, `CodeExtension`'s TabNode transform and // `registerCodeIndentation`, `overflow: true` @lexical/overflow's // `OverflowNode`, `hashtag: true` @lexical/hashtag's `HashtagNode` and - // `registerLexicalHashtag`, and `extension: true` @lexical/extension's - // `HorizontalRuleNode` and `DecoratorTextNode`, as ops.ts does. + // `registerLexicalHashtag`, `extension: true` @lexical/extension's + // `HorizontalRuleNode` and `DecoratorTextNode`, and `mark: true` + // @lexical/mark's `MarkNode`, as ops.ts does. var nodes: [LexicalNodeConfig] = script["richText"]?.boolValue == true ? [.klass(HeadingNode.self), .klass(QuoteNode.self)] : [] @@ -762,6 +839,9 @@ private func makeEditor(_ script: JSONValue) throws -> Editor { if script["extension"]?.boolValue == true { nodes += [.klass(HorizontalRuleNode.self), .klass(DecoratorTextNode.self)] } + if script["mark"]?.boolValue == true { + nodes += [.klass(MarkNode.self)] + } return try createEditor( CreateEditorArgs(namespace: "", nodes: nodes, onError: { throw $0 })) } diff --git a/swift/Tests/ConformanceTests/ScriptTests.swift b/swift/Tests/ConformanceTests/ScriptTests.swift index a5527951085..a149c83f531 100644 --- a/swift/Tests/ConformanceTests/ScriptTests.swift +++ b/swift/Tests/ConformanceTests/ScriptTests.swift @@ -20,7 +20,7 @@ struct ScriptTests { "scripts/plain-text", "scripts/history", "scripts/list", "scripts/link", "scripts/autolink", "scripts/code", "scripts/overflow", "scripts/node-state", "scripts/set-blocks-type", "scripts/decorator", "scripts/text", "scripts/extensions", - "fuzz/core", + "scripts/mark", "fuzz/core", ] static let cases: [Case] = files.flatMap { file in diff --git a/swift/Tests/ConformanceTests/SerializationTests.swift b/swift/Tests/ConformanceTests/SerializationTests.swift index 142fee6e170..f1508f62823 100644 --- a/swift/Tests/ConformanceTests/SerializationTests.swift +++ b/swift/Tests/ConformanceTests/SerializationTests.swift @@ -2,6 +2,7 @@ import Lexical import LexicalCodeCore import LexicalLink import LexicalList +import LexicalMark import LexicalOverflow import Testing @@ -21,6 +22,7 @@ struct SerializationTests { static let linkCases: [Case] = (try? Vectors.load("serialization/link")) ?? [] static let codeCases: [Case] = (try? Vectors.load("serialization/code")) ?? [] static let overflowCases: [Case] = (try? Vectors.load("serialization/overflow")) ?? [] + static let markCases: [Case] = (try? Vectors.load("serialization/mark")) ?? [] @Test(arguments: cases) func roundTrip(_ vector: Case) throws { @@ -54,6 +56,11 @@ struct SerializationTests { try check(vector, createEditor(CreateEditorArgs(nodes: [.klass(OverflowNode.self)]))) } + @Test(arguments: markCases) + func markRoundTrip(_ vector: Case) throws { + try check(vector, createEditor(CreateEditorArgs(nodes: [.klass(MarkNode.self)]))) + } + func check(_ vector: Case, _ editor: Editor) throws { for input in [vector.legacy, vector.compact] { let editorState = try editor.parseEditorState(input)