mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2026-01-07 12:51:07 +00:00
fix: supported incremental updates (#6531)
This commit is contained in:
parent
ea61c81cce
commit
b5936cec54
@ -143,6 +143,7 @@ export const CustomEditor = {
|
||||
|
||||
handleMergeBlockBackwardWithTxn(editor, node, point);
|
||||
} else {
|
||||
|
||||
Transforms.collapse(editor, { edge: 'start' });
|
||||
removeRangeWithTxn(editor, sharedRoot, newAt);
|
||||
}
|
||||
|
||||
@ -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<T extends Editor> (
|
||||
apply(op);
|
||||
};
|
||||
|
||||
e.applyRemoteEvents = (_events: Array<YEvent>, _transaction: Transaction) => {
|
||||
e.applyRemoteEvents = (events: Array<YEvent>, 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<T extends Editor> (
|
||||
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<T extends Editor> (
|
||||
};
|
||||
|
||||
const handleYEvents = (events: Array<YEvent>, transaction: Transaction) => {
|
||||
if (transaction.origin !== CollabOrigin.Local) {
|
||||
YjsEditor.applyRemoteEvents(e, events, transaction);
|
||||
}
|
||||
if (transaction.origin === CollabOrigin.Local) return;
|
||||
YjsEditor.applyRemoteEvents(e, events, transaction);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -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<any>
|
||||
|
||||
export function translateYEvents (editor: YjsEditor, events: Array<YEvent>) {
|
||||
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<unknown>);
|
||||
}
|
||||
|
||||
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<unknown>) {
|
||||
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<Element>;
|
||||
const oldData = node.data as Record<string, unknown>;
|
||||
|
||||
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<string, number[]> = {};
|
||||
|
||||
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<string, number[]>) {
|
||||
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,
|
||||
});
|
||||
|
||||
}
|
||||
@ -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[]) {
|
||||
|
||||
@ -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<string, any>) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -84,7 +84,7 @@ function MoreActions () {
|
||||
];
|
||||
|
||||
const importShow = false;
|
||||
|
||||
|
||||
if (importShow) {
|
||||
items.unshift({
|
||||
Icon: ImportIcon,
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -90,5 +90,5 @@ export const LinkPreview = memo(
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
);
|
||||
(prev, next) => prev.node.data.url === next.node.data.url);
|
||||
export default LinkPreview;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
prevPath = Path.previous(prevPath);
|
||||
}
|
||||
|
||||
if (prevPath.length === 1 && prevPath[0] === 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
prevPath = Path.previous(prevPath);
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
if (!topNode) {
|
||||
|
||||
@ -4,11 +4,13 @@ import React, { forwardRef, memo, useMemo } from 'react';
|
||||
export const Quote = memo(
|
||||
forwardRef<HTMLDivElement, EditorElementProps<QuoteNode>>(({ 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 (
|
||||
<div {...attributes} ref={ref} className={className}>
|
||||
<div {...attributes} ref={ref}
|
||||
className={className}
|
||||
>
|
||||
<div className={'border-l-4 border-fill-default pl-2'}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
|
||||
@ -95,8 +95,8 @@ const rules: Rule[] = [
|
||||
const level = match[1].length;
|
||||
const [node] = getBlockEntry(editor);
|
||||
|
||||
deletePrefix(editor, level);
|
||||
CustomEditor.turnToBlock<HeadingBlockData>(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<TodoListBlockData>(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<ToggleListBlockData>(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<NumberedListBlockData>(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);
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user