From ef54736b0563a05971522c909b094528453d7408 Mon Sep 17 00:00:00 2001 From: Kilu Date: Fri, 11 Oct 2024 11:09:24 +0800 Subject: [PATCH] fix: supported incremental updates --- .../application/slate-yjs/command/index.ts | 1 + .../application/slate-yjs/plugins/withYjs.ts | 31 ++- .../slate-yjs/utils/applyToSlate.ts | 208 ++++++++++++++++++ .../application/slate-yjs/utils/applyToYjs.ts | 12 +- .../application/slate-yjs/utils/convert.ts | 3 +- .../application/slate-yjs/utils/positions.ts | 13 +- .../slate-yjs/utils/yjsOperations.ts | 13 +- .../_shared/more-actions/MoreActions.tsx | 2 +- .../__tests__/shortcuts/Markdown.cy.tsx | 2 + .../blocks/link-preview/LinkPreview.tsx | 2 +- .../blocks/numbered-list/NumberListIcon.tsx | 32 +-- .../editor/components/blocks/quote/Quote.tsx | 6 +- .../components/leaf/formula/FormulaLeaf.tsx | 2 +- .../selection-toolbar/actions/Formula.tsx | 15 +- .../src/components/editor/editor.scss | 11 + .../components/editor/plugins/withDelete.ts | 16 +- .../src/components/editor/utils/markdown.ts | 20 +- 17 files changed, 342 insertions(+), 47 deletions(-) create mode 100644 frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToSlate.ts diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts index 006266dcbb84..2c62b80e54e9 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/command/index.ts @@ -143,6 +143,7 @@ export const CustomEditor = { handleMergeBlockBackwardWithTxn(editor, node, point); } else { + Transforms.collapse(editor, { edge: 'start' }); removeRangeWithTxn(editor, sharedRoot, newAt); } diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/plugins/withYjs.ts b/frontend/appflowy_web_app/src/application/slate-yjs/plugins/withYjs.ts index 69d9225d9278..e1c391cb8af1 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/plugins/withYjs.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/plugins/withYjs.ts @@ -1,3 +1,4 @@ +import { translateYEvents } from '@/application/slate-yjs/utils/applyToSlate'; import { CollabOrigin, YjsEditorKey, YSharedRoot } from '@/application/types'; import { applyToYjs } from '@/application/slate-yjs/utils/applyToYjs'; import { Editor, Operation, Descendant, Transforms } from 'slate'; @@ -125,7 +126,7 @@ export function withYjs ( apply(op); }; - e.applyRemoteEvents = (_events: Array, _transaction: Transaction) => { + e.applyRemoteEvents = (events: Array, transaction: Transaction) => { console.time('applyRemoteEvents'); // Flush local changes to ensure all local changes are applied before processing remote events YjsEditor.flushLocalChanges(e); @@ -133,7 +134,28 @@ export function withYjs ( e.interceptLocalChange = true; // Initialize or update the document content to ensure it is in the correct state before applying remote events - initializeDocumentContent(); + if (transaction.origin === CollabOrigin.Remote) { + initializeDocumentContent(); + } else { + const selection = editor.selection; + + Editor.withoutNormalizing(e, () => { + translateYEvents(e, events); + }); + if (selection) { + if (!ReactEditor.hasRange(editor, selection)) { + try { + Transforms.select(e, Editor.start(editor, [0])); + + } catch (e) { + console.error(e); + editor.deselect(); + } + } else { + e.select(selection); + } + } + } // Restore the apply function to store local changes after applying remote changes e.interceptLocalChange = false; @@ -141,9 +163,8 @@ export function withYjs ( }; const handleYEvents = (events: Array, transaction: Transaction) => { - if (transaction.origin !== CollabOrigin.Local) { - YjsEditor.applyRemoteEvents(e, events, transaction); - } + if (transaction.origin === CollabOrigin.Local) return; + YjsEditor.applyRemoteEvents(e, events, transaction); }; diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToSlate.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToSlate.ts new file mode 100644 index 000000000000..5b8ce897aedc --- /dev/null +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToSlate.ts @@ -0,0 +1,208 @@ +import { YjsEditor } from '@/application/slate-yjs'; +import { BlockJson } from '@/application/slate-yjs/types'; +import { blockToSlateNode, deltaInsertToSlateNode } from '@/application/slate-yjs/utils/convert'; +import { + dataStringTOJson, + getBlock, + getChildrenArray, + getPageId, + getText, +} from '@/application/slate-yjs/utils/yjsOperations'; +import { YBlock, YjsEditorKey } from '@/application/types'; +import isEqual from 'lodash-es/isEqual'; +import { Editor, Element, NodeEntry } from 'slate'; +import { YEvent, YMapEvent, YTextEvent } from 'yjs'; +import { YText } from 'yjs/dist/src/types/YText'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type BlockMapEvent = YMapEvent + +export function translateYEvents (editor: YjsEditor, events: Array) { + console.log('=== Translating Yjs events ===', events); + + events.forEach((event) => { + console.log(event.path); + if (isEqual(event.path, ['document', 'blocks'])) { + applyBlocksYEvent(editor, event as BlockMapEvent); + } + + if (isEqual((event.path), ['document', 'blocks', event.path[2]])) { + const blockId = event.path[2] as string; + + applyUpdateBlockYEvent(editor, blockId, event as YMapEvent); + } + + if (isEqual(event.path, ['document', 'meta', 'text_map', event.path[3]])) { + const textId = event.path[3] as string; + + applyTextYEvent(editor, textId, event as YTextEvent); + } + }); + +} + +function applyUpdateBlockYEvent (editor: YjsEditor, blockId: string, event: YMapEvent) { + const { target } = event; + const block = target as YBlock; + const newData = dataStringTOJson(block.get(YjsEditorKey.block_data)); + const [entry] = editor.nodes({ + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === blockId, + mode: 'all', + }); + + if (!entry) { + console.error('Block node not found', blockId); + return []; + } + + const [node, path] = entry as NodeEntry; + const oldData = node.data as Record; + + editor.apply({ + type: 'set_node', + path, + newProperties: { + data: newData, + }, + properties: { + data: oldData, + }, + }); +} + +function applyTextYEvent (editor: YjsEditor, textId: string, event: YTextEvent) { + const { target } = event; + + const yText = target as YText; + const delta = yText.toDelta(); + const slateDelta = delta.flatMap(deltaInsertToSlateNode); + const [entry] = editor.nodes({ + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.textId === textId, + mode: 'all', + }); + + if (!entry) { + console.error('Text node not found', textId); + return []; + } + + editor.apply({ + type: 'remove_node', + path: entry[1], + node: entry[0], + }); + editor.apply({ + type: 'insert_node', + path: entry[1], + node: { + textId, + type: YjsEditorKey.text, + children: slateDelta, + }, + }); + +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function applyBlocksYEvent (editor: YjsEditor, event: BlockMapEvent) { + const { changes, keysChanged } = event; + const { keys } = changes; + + const keyPath: Record = {}; + + keysChanged.forEach((key: string) => { + const value = keys.get(key); + + if (!value) return; + + if (value.action === 'add') { + handleNewBlock(editor, key, keyPath); + + } else if (value.action === 'delete') { + handleDeleteNode(editor, key); + } else if (value.action === 'update') { + console.log('=== Applying block update Yjs event ===', key); + } + }); + +} + +function handleNewBlock (editor: YjsEditor, key: string, keyPath: Record) { + const block = getBlock(key, editor.sharedRoot); + const parentId = block.get(YjsEditorKey.block_parent); + const pageId = getPageId(editor.sharedRoot); + const parent = getBlock(parentId, editor.sharedRoot); + const parentChildren = getChildrenArray(parent.get(YjsEditorKey.block_children), editor.sharedRoot); + const index = parentChildren.toArray().findIndex((child) => child === key); + const slateNode = blockToSlateNode(block.toJSON() as BlockJson); + const textId = block.get(YjsEditorKey.block_external_id); + const yText = getText(textId, editor.sharedRoot); + const delta = yText.toDelta(); + const slateDelta = delta.flatMap(deltaInsertToSlateNode); + + if (slateDelta.length === 0) { + slateDelta.push({ + text: '', + }); + } + + const textNode: Element = { + textId, + type: YjsEditorKey.text, + children: slateDelta, + }; + let path = [index]; + + if (parentId !== pageId) { + const [parentEntry] = editor.nodes({ + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === parentId, + mode: 'all', + }); + + if (!parentEntry) { + if (keyPath[parentId]) { + path = [...keyPath[parentId], index + 1]; + } else { + console.error('Parent block not found', parentId); + return []; + } + } else { + const childrenLength = (parentEntry[0] as Element).children.length; + + path = [...parentEntry[1], Math.min(index + 1, childrenLength)]; + } + } + + editor.apply({ + type: 'insert_node', + path, + node: { + ...slateNode, + children: [textNode], + }, + }); + + keyPath[key] = path; + +} + +function handleDeleteNode (editor: YjsEditor, key: string) { + const [entry] = editor.nodes({ + at: [], + match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId === key, + }); + + if (!entry) { + console.error('Block not found'); + return []; + } + + const [node, path] = entry; + + editor.apply({ + type: 'remove_node', + path, + node, + }); + +} \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToYjs.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToYjs.ts index b475e569d651..6a237bccce37 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToYjs.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/applyToYjs.ts @@ -123,18 +123,26 @@ function applyRemoveText (ydoc: Y.Doc, editor: Editor, op: RemoveTextOperation, const textId = node.textId; - if (!textId) return; + if (!textId) { + console.error('textId not found', node); + return; + } const sharedRoot = ydoc.getMap(YjsEditorKey.data_section) as YSharedRoot; const yText = getText(textId, sharedRoot); - if (!yText) return; + if (!yText) { + console.error('yText not found', textId, sharedRoot.toJSON()); + return; + } const point = { path, offset }; const relativeOffset = Math.min(calculateOffsetRelativeToParent(node, point), yText.toJSON().length); yText.delete(relativeOffset, text.length); + + console.log('applyRemoveText', op, yText.toDelta()); } function applySetNode (ydoc: Y.Doc, editor: Editor, op: SetNodeOperation, slateContent: Descendant[]) { diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts index 3f6da96b8058..b92ac70fd250 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/convert.ts @@ -64,6 +64,7 @@ export function yDataToSlateContent ({ const yText = textId ? textMap.get(textId) : undefined; if (!yText) { + if (children.length === 0) { children.push({ text: '', @@ -185,7 +186,7 @@ function dealWithEmptyAttribute (attributes: Record) { } // Helper function to convert Slate text node to Delta insert -function slateNodeToDeltaInsert (node: Text): YDelta { +export function slateNodeToDeltaInsert (node: Text): YDelta { const { text, ...attributes } = node; return { diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/positions.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/positions.ts index 9dda8afaef96..8b1f2950db8f 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/positions.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/positions.ts @@ -1,4 +1,5 @@ -import { getText } from '@/application/slate-yjs/utils/yjsOperations'; +import { slateNodeToDeltaInsert } from '@/application/slate-yjs/utils/convert'; +import { getText, getTextMap } from '@/application/slate-yjs/utils/yjsOperations'; import { YSharedRoot } from '@/application/types'; import { BasePoint, BaseRange, Node, Element, Editor, NodeEntry, Text } from 'slate'; import { RelativeRange } from '../types'; @@ -55,10 +56,16 @@ export function slatePointToRelativePosition ( } const textId = node.textId as string; - const ytext = getText(textId, sharedRoot); + let ytext = getText(textId, sharedRoot); if (!ytext) { - throw new Error('YText not found'); + const newYText = new Y.Text(); + const textMap = getTextMap(sharedRoot); + const ops = (node.children as Text[]).map(slateNodeToDeltaInsert); + + newYText.applyDelta(ops); + textMap.set(textId, newYText); + ytext = newYText; } const offset = Math.min(calculateOffsetRelativeToParent(node, point), ytext.length); diff --git a/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts b/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts index 31dda29c96b2..903f2a0a67c0 100644 --- a/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts +++ b/frontend/appflowy_web_app/src/application/slate-yjs/utils/yjsOperations.ts @@ -69,11 +69,16 @@ export function createEmptyDocument () { return doc; } -export function getText (textId: string, sharedRoot: YSharedRoot) { - +export function getTextMap (sharedRoot: YSharedRoot) { const document = sharedRoot.get(YjsEditorKey.document); const meta = document.get(YjsEditorKey.meta) as YMeta; - const textMap = meta.get(YjsEditorKey.text_map) as YTextMap; + + return meta.get(YjsEditorKey.text_map) as YTextMap; +} + +export function getText (textId: string, sharedRoot: YSharedRoot) { + + const textMap = getTextMap(sharedRoot); return textMap.get(textId); } @@ -191,6 +196,8 @@ export function handleCollapsedBreakWithTxn (editor: YjsEditor, sharedRoot: YSha } else { Transforms.select(editor, Editor.start(editor, at)); } + + console.log('handleCollapsedBreakWithTxn', editor.selection); } export function removeRangeWithTxn (editor: YjsEditor, sharedRoot: YSharedRoot, range: Range) { diff --git a/frontend/appflowy_web_app/src/components/_shared/more-actions/MoreActions.tsx b/frontend/appflowy_web_app/src/components/_shared/more-actions/MoreActions.tsx index 6abd716137d4..8e504e998181 100644 --- a/frontend/appflowy_web_app/src/components/_shared/more-actions/MoreActions.tsx +++ b/frontend/appflowy_web_app/src/components/_shared/more-actions/MoreActions.tsx @@ -84,7 +84,7 @@ function MoreActions () { ]; const importShow = false; - + if (importShow) { items.unshift({ Icon: ImportIcon, diff --git a/frontend/appflowy_web_app/src/components/editor/__tests__/shortcuts/Markdown.cy.tsx b/frontend/appflowy_web_app/src/components/editor/__tests__/shortcuts/Markdown.cy.tsx index 561972d5ee73..73c1c15e13b4 100644 --- a/frontend/appflowy_web_app/src/components/editor/__tests__/shortcuts/Markdown.cy.tsx +++ b/frontend/appflowy_web_app/src/components/editor/__tests__/shortcuts/Markdown.cy.tsx @@ -40,6 +40,8 @@ describe('Markdown editing', () => { // Test 1: heading cy.get('@editor').type('##'); cy.get('@editor').realPress('Space'); + cy.wait(50); + cy.get('@editor').type('Heading 2'); expectedJson = [...expectedJson, { type: 'heading', diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx index f9a962741eb2..00d3c927a348 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/link-preview/LinkPreview.tsx @@ -90,5 +90,5 @@ export const LinkPreview = memo( ); }), -); + (prev, next) => prev.node.data.url === next.node.data.url); export default LinkPreview; diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/numbered-list/NumberListIcon.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/numbered-list/NumberListIcon.tsx index b27c1db1208b..68f97a2f2ff3 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/numbered-list/NumberListIcon.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/numbered-list/NumberListIcon.tsx @@ -34,25 +34,29 @@ export function NumberListIcon ({ block, className }: { block: NumberedListNode; return index; } - let prevPath = Path.previous(path); + try { + let prevPath = Path.previous(path); - while (prevPath) { - const prev = editor.node(prevPath); + while (prevPath) { + const prev = editor.node(prevPath); - const prevNode = prev[0] as Element; + const prevNode = prev[0] as Element; - if (prevNode.type === block.type) { - index += 1; - topNode = prevNode; - } else { - break; - } + if (prevNode.type === block.type) { + index += 1; + topNode = prevNode; + } else { + break; + } - if (prevPath.length === 1 && prevPath[0] === 0) { - return index; - } + if (prevPath.length === 1 && prevPath[0] === 0) { + return index; + } - prevPath = Path.previous(prevPath); + prevPath = Path.previous(prevPath); + } + } catch (e) { + // do nothing } if (!topNode) { diff --git a/frontend/appflowy_web_app/src/components/editor/components/blocks/quote/Quote.tsx b/frontend/appflowy_web_app/src/components/editor/components/blocks/quote/Quote.tsx index 75e799f7401a..fb8ee542e4f9 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/blocks/quote/Quote.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/blocks/quote/Quote.tsx @@ -4,11 +4,13 @@ import React, { forwardRef, memo, useMemo } from 'react'; export const Quote = memo( forwardRef>(({ node: _, children, ...attributes }, ref) => { const className = useMemo(() => { - return `my-1 ${attributes.className ?? ''} pl-3`; + return `my-1 ${attributes.className ?? ''} pl-3 quote-block`; }, [attributes.className]); return ( -
+
{children}
diff --git a/frontend/appflowy_web_app/src/components/editor/components/leaf/formula/FormulaLeaf.tsx b/frontend/appflowy_web_app/src/components/editor/components/leaf/formula/FormulaLeaf.tsx index 39cdba236b40..2f59702751b0 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/leaf/formula/FormulaLeaf.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/leaf/formula/FormulaLeaf.tsx @@ -65,7 +65,7 @@ function FormulaLeaf ({ formula, text }: { CustomEditor.removeMark(editor, EditorMarkFormat.Formula); - editor.deleteBackward('character'); + editor.delete(); editor.insertText(formula); }, [editor, formula, handleClose, text]); diff --git a/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Formula.tsx b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Formula.tsx index cf7e9046884a..152f54a5bd25 100644 --- a/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Formula.tsx +++ b/frontend/appflowy_web_app/src/components/editor/components/toolbar/selection-toolbar/actions/Formula.tsx @@ -25,9 +25,17 @@ function Formula () { editor.delete(); editor.insertText('$'); + + const newSelection = editor.selection; + + if (!newSelection) { + console.error('newSelection is undefined'); + return; + } + Transforms.select(editor, { anchor: start, - focus: { path: start.path, offset: start.offset + 1 }, + focus: newSelection.focus, }); CustomEditor.addMark(editor, { key: EditorMarkFormat.Formula, @@ -46,10 +54,7 @@ function Formula () { CustomEditor.removeMark(editor, EditorMarkFormat.Formula); - editor.collapse({ - edge: 'end', - }); - editor.deleteBackward('character'); + editor.delete(); editor.insertText(formula); } diff --git a/frontend/appflowy_web_app/src/components/editor/editor.scss b/frontend/appflowy_web_app/src/components/editor/editor.scss index 2d771920225f..b3d391e91823 100644 --- a/frontend/appflowy_web_app/src/components/editor/editor.scss +++ b/frontend/appflowy_web_app/src/components/editor/editor.scss @@ -30,6 +30,10 @@ text-align: left; justify-content: flex-start; } + + .quote-block { + @apply items-start; + } } .block-element.block-align-right { @@ -37,6 +41,10 @@ text-align: right; justify-content: flex-end; } + + .quote-block { + @apply items-end; + } } .block-element.block-align-center { @@ -45,6 +53,9 @@ justify-content: center; } + .quote-block { + @apply items-center; + } } diff --git a/frontend/appflowy_web_app/src/components/editor/plugins/withDelete.ts b/frontend/appflowy_web_app/src/components/editor/plugins/withDelete.ts index 07a1451d64f4..58c359590cf0 100644 --- a/frontend/appflowy_web_app/src/components/editor/plugins/withDelete.ts +++ b/frontend/appflowy_web_app/src/components/editor/plugins/withDelete.ts @@ -1,6 +1,11 @@ import { YjsEditor } from '@/application/slate-yjs'; import { CustomEditor } from '@/application/slate-yjs/command'; -import { isAtBlockStart, isAtBlockEnd, isEntireDocumentSelected } from '@/application/slate-yjs/utils/yjsOperations'; +import { + isAtBlockStart, + isAtBlockEnd, + isEntireDocumentSelected, + getBlockEntry, +} from '@/application/slate-yjs/utils/yjsOperations'; import { TextUnit, Range, EditorFragmentDeletionOptions } from 'slate'; import { ReactEditor } from 'slate-react'; import { TextDeleteOptions } from 'slate/dist/interfaces/transforms/text'; @@ -18,6 +23,15 @@ export function withDelete (editor: ReactEditor) { return; } + const [start, end] = Range.edges(selection); + const startBlock = getBlockEntry(editor as YjsEditor, start)[0]; + const endBlock = getBlockEntry(editor as YjsEditor, end)[0]; + + if (startBlock.blockId === endBlock.blockId) { + deleteText(options); + return; + } + CustomEditor.deleteBlockBackward(editor as YjsEditor, selection); }; diff --git a/frontend/appflowy_web_app/src/components/editor/utils/markdown.ts b/frontend/appflowy_web_app/src/components/editor/utils/markdown.ts index fe004127b477..53905b1f4c4b 100644 --- a/frontend/appflowy_web_app/src/components/editor/utils/markdown.ts +++ b/frontend/appflowy_web_app/src/components/editor/utils/markdown.ts @@ -95,8 +95,8 @@ const rules: Rule[] = [ const level = match[1].length; const [node] = getBlockEntry(editor); - deletePrefix(editor, level); CustomEditor.turnToBlock(editor, node.blockId as string, BlockType.HeadingBlock, { level }); + deletePrefix(editor, level); }, }, { @@ -107,8 +107,8 @@ const rules: Rule[] = [ return getNodeType(editor) === BlockType.QuoteBlock; }, transform: (editor) => { - deletePrefix(editor, 1); CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.QuoteBlock, {}); + deletePrefix(editor, 1); }, }, { @@ -123,10 +123,11 @@ const rules: Rule[] = [ return blockType === BlockType.TodoListBlock && (blockData as TodoListBlockData).checked === checked; }, transform: (editor, match) => { - deletePrefix(editor, match[0].length - 1); + const checked = match[2] === 'x'; CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.TodoListBlock, { checked }); + deletePrefix(editor, match[0].length - 1); }, }, { @@ -137,8 +138,9 @@ const rules: Rule[] = [ return getNodeType(editor) === BlockType.ToggleListBlock; }, transform: (editor) => { - deletePrefix(editor, 1); CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.ToggleListBlock, { collapsed: false }); + deletePrefix(editor, 1); + }, }, { @@ -149,9 +151,9 @@ const rules: Rule[] = [ return !isEmptyLine(editor, 2) || getNodeType(editor) === BlockType.CodeBlock; }, transform: (editor) => { - deletePrefix(editor, 2); CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.CodeBlock, {}); + deletePrefix(editor, 2); }, }, { @@ -162,8 +164,9 @@ const rules: Rule[] = [ return getNodeType(editor) === BlockType.BulletedListBlock; }, transform: (editor) => { - deletePrefix(editor, 1); + CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.BulletedListBlock, {}); + deletePrefix(editor, 1); }, }, { @@ -180,8 +183,8 @@ const rules: Rule[] = [ transform: (editor, match) => { const start = parseInt(match[1]); - deletePrefix(editor, String(start).length + 1); CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.NumberedListBlock, { number: start }); + deletePrefix(editor, String(start).length + 1); }, }, @@ -193,8 +196,9 @@ const rules: Rule[] = [ return !isEmptyLine(editor, 2) || getNodeType(editor) === BlockType.DividerBlock; }, transform: (editor) => { - deletePrefix(editor, 2); + CustomEditor.turnToBlock(editor, getBlockEntry(editor)[0].blockId as string, BlockType.DividerBlock, {}); + deletePrefix(editor, 2); }, },