mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: refactor match change code (#2352)
This commit is contained in:
parent
eb78f9d36a
commit
9717dfa3c4
@ -1,6 +1,5 @@
|
||||
import TextBlock from '../TextBlock';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { HeadingBlockData } from '@/appflowy_app/interfaces/document';
|
||||
import { HeadingBlockData, Node } from '@/appflowy_app/interfaces/document';
|
||||
|
||||
const fontSize: Record<string, string> = {
|
||||
1: 'mt-8 text-3xl',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { Circle } from '@mui/icons-material';
|
||||
import NodeComponent from '../Node';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function BulletedListBlock({
|
||||
title,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import ColumnBlock from '../ColumnBlock';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function ColumnListBlock({
|
||||
node,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import NodeComponent from '../Node';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function NumberedListBlock({
|
||||
title,
|
||||
|
@ -3,8 +3,7 @@ import TextBlock from '../TextBlock';
|
||||
import NumberedListBlock from './NumberedListBlock';
|
||||
import BulletedListBlock from './BulletedListBlock';
|
||||
import ColumnListBlock from './ColumnListBlock';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { TextDelta } from '@/appflowy_app/interfaces/document';
|
||||
import { Node, TextDelta } from '@/appflowy_app/interfaces/document';
|
||||
|
||||
export default function ListBlock({ node }: { node: Node }) {
|
||||
const title = useMemo(() => {
|
||||
|
@ -2,9 +2,9 @@ import React, { useCallback } from 'react';
|
||||
import { useNode } from './Node.hooks';
|
||||
import { withErrorBoundary } from 'react-error-boundary';
|
||||
import { ErrorBoundaryFallbackComponent } from '../_shared/ErrorBoundaryFallbackComponent';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import TextBlock from '../TextBlock';
|
||||
import { NodeContext } from '../_shared/SubscribeNode.hooks';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes<HTMLDivElement>) {
|
||||
const { node, childIds, isSelected, ref } = useNode(id);
|
||||
|
@ -5,15 +5,9 @@ import { documentActions } from '$app/stores/reducers/document/slice';
|
||||
|
||||
export function useParseTree(documentData: DocumentData) {
|
||||
const dispatch = useAppDispatch();
|
||||
const { blocks, meta } = documentData;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
documentActions.create({
|
||||
nodes: blocks,
|
||||
children: meta.childrenMap,
|
||||
})
|
||||
);
|
||||
dispatch(documentActions.create(documentData));
|
||||
|
||||
return () => {
|
||||
dispatch(documentActions.clear());
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { triggerHotkey } from '@/appflowy_app/utils/slate/hotkey';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import { Range, Editor, Element, Text, Location } from 'slate';
|
||||
import { TextDelta } from '$app/interfaces/document';
|
||||
import { TextDelta, TextSelection } from '$app/interfaces/document';
|
||||
import { useTextInput } from '../_shared/TextInput.hooks';
|
||||
import { useAppDispatch } from '@/appflowy_app/stores/store';
|
||||
import { DocumentControllerContext } from '@/appflowy_app/stores/effects/document/document_controller';
|
||||
@ -10,7 +10,7 @@ import {
|
||||
indentNodeThunk,
|
||||
splitNodeThunk,
|
||||
} from '@/appflowy_app/stores/reducers/document/async_actions';
|
||||
import { documentActions, TextSelection } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { documentActions } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
|
||||
export function useTextBlock(id: string) {
|
||||
const { editor, onChange, value } = useTextInput(id);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Slate, Editable } from 'slate-react';
|
||||
import Leaf from './Leaf';
|
||||
import { useTextBlock } from './TextBlock.hooks';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import NodeComponent from '../Node';
|
||||
import HoveringToolbar from '../_shared/HoveringToolbar';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
function TextBlock({
|
||||
node,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useVirtualizedList } from './VirtualizedList.hooks';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import DocumentTitle from '../DocumentTitle';
|
||||
import Overlay from '../Overlay';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
|
||||
export default function VirtualizedList({
|
||||
childIds,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { useAppSelector } from '@/appflowy_app/stores/store';
|
||||
import { useMemo, createContext } from 'react';
|
||||
import { Node } from '$app/interfaces/document';
|
||||
export const NodeContext = createContext<Node | null>(null);
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useContext, useMemo, useRef, useEffect, useState } from 'react';
|
||||
import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
|
||||
import { TextDelta } from '$app/interfaces/document';
|
||||
import { TextDelta, TextSelection } from '$app/interfaces/document';
|
||||
import { NodeContext } from './SubscribeNode.hooks';
|
||||
import { useAppDispatch, useAppSelector } from '@/appflowy_app/stores/store';
|
||||
|
||||
@ -10,7 +10,7 @@ import { withReact, ReactEditor } from 'slate-react';
|
||||
import * as Y from 'yjs';
|
||||
import { withYjs, YjsEditor, slateNodesToInsertDelta } from '@slate-yjs/core';
|
||||
import { updateNodeDeltaThunk } from '@/appflowy_app/stores/reducers/document/async_actions/update';
|
||||
import { documentActions, TextSelection } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { documentActions } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import { deltaToSlateValue, getDeltaFromSlateNodes } from '@/appflowy_app/utils/block';
|
||||
|
||||
export function useTextInput(id: string) {
|
||||
|
@ -0,0 +1,3 @@
|
||||
export const BLOCK_MAP_NAME = 'blocks';
|
||||
export const META_NAME = 'meta';
|
||||
export const CHILDREN_MAP_NAME = 'children_map';
|
@ -37,13 +37,6 @@ export interface TextDelta {
|
||||
insert: string;
|
||||
attributes?: Record<string, string | boolean>;
|
||||
}
|
||||
export interface DocumentData {
|
||||
rootId: string;
|
||||
blocks: Record<string, NestedBlock>;
|
||||
meta: {
|
||||
childrenMap: Record<string, string[]>;
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum BlockActionType {
|
||||
@ -60,3 +53,51 @@ export interface DeltaItem {
|
||||
value?: NestedBlock | string[];
|
||||
};
|
||||
}
|
||||
|
||||
export type Node = NestedBlock;
|
||||
|
||||
export interface SelectionPoint {
|
||||
path: [number, number];
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface TextSelection {
|
||||
anchor: SelectionPoint;
|
||||
focus: SelectionPoint;
|
||||
}
|
||||
|
||||
export interface DocumentData {
|
||||
rootId: string;
|
||||
// map of block id to block
|
||||
nodes: Record<string, Node>;
|
||||
// map of block id to children block ids
|
||||
children: Record<string, string[]>;
|
||||
}
|
||||
export interface DocumentState {
|
||||
// map of block id to block
|
||||
nodes: Record<string, Node>;
|
||||
// map of block id to children block ids
|
||||
children: Record<string, string[]>;
|
||||
// selected block ids
|
||||
selections: string[];
|
||||
// map of block id to text selection
|
||||
textSelections: Record<string, TextSelection>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum ChangeType {
|
||||
BlockInsert,
|
||||
BlockUpdate,
|
||||
BlockDelete,
|
||||
ChildrenMapInsert,
|
||||
ChildrenMapUpdate,
|
||||
ChildrenMapDelete,
|
||||
}
|
||||
|
||||
export interface BlockPBValue {
|
||||
id: string;
|
||||
ty: string;
|
||||
parent: string;
|
||||
children: string;
|
||||
data: string;
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
import { DocumentData, BlockType } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentData, Node } from '@/appflowy_app/interfaces/document';
|
||||
import { createContext } from 'react';
|
||||
import { DocumentBackendService } from './document_bd_svc';
|
||||
import { FlowyError, BlockActionPB, DocEventPB, BlockActionTypePB, BlockEventPayloadPB } from '@/services/backend';
|
||||
import {
|
||||
FlowyError,
|
||||
BlockActionPB,
|
||||
DocEventPB,
|
||||
BlockActionTypePB,
|
||||
BlockEventPayloadPB,
|
||||
BlockPB,
|
||||
ChildrenPB,
|
||||
} from '@/services/backend';
|
||||
import { DocumentObserver } from './document_observer';
|
||||
import { Node } from '@/appflowy_app/stores/reducers/document/slice';
|
||||
import * as Y from 'yjs';
|
||||
import { blockPB2Node } from '@/appflowy_app/utils/block';
|
||||
import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '@/appflowy_app/constants/block';
|
||||
import { get } from '@/appflowy_app/utils/tool';
|
||||
|
||||
export const DocumentControllerContext = createContext<DocumentController | null>(null);
|
||||
|
||||
@ -34,33 +44,18 @@ export class DocumentController {
|
||||
|
||||
const document = await this.backendService.open();
|
||||
if (document.ok) {
|
||||
const blocks: DocumentData['blocks'] = {};
|
||||
document.val.blocks.forEach((block) => {
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(block.data);
|
||||
} catch {
|
||||
console.log('json parse error', block.data);
|
||||
}
|
||||
|
||||
blocks[block.id] = {
|
||||
id: block.id,
|
||||
type: block.ty as BlockType,
|
||||
parent: block.parent_id,
|
||||
children: block.children_id,
|
||||
data,
|
||||
};
|
||||
const nodes: DocumentData['nodes'] = {};
|
||||
get<Map<string, BlockPB>>(document.val, [BLOCK_MAP_NAME]).forEach((block) => {
|
||||
nodes[block.id] = blockPB2Node(block);
|
||||
});
|
||||
const childrenMap: Record<string, string[]> = {};
|
||||
document.val.meta.children_map.forEach((child, key) => {
|
||||
childrenMap[key] = child.children;
|
||||
const children: Record<string, string[]> = {};
|
||||
get<Map<string, ChildrenPB>>(document.val, [META_NAME, CHILDREN_MAP_NAME]).forEach((child, key) => {
|
||||
children[key] = child.children;
|
||||
});
|
||||
return {
|
||||
rootId: document.val.page_id,
|
||||
blocks,
|
||||
meta: {
|
||||
childrenMap,
|
||||
},
|
||||
nodes,
|
||||
children,
|
||||
};
|
||||
}
|
||||
|
||||
@ -150,8 +145,8 @@ export class DocumentController {
|
||||
if (!this.onDocChange) return;
|
||||
const { events, is_remote } = DocEventPB.deserializeBinary(payload);
|
||||
|
||||
events.forEach((event) => {
|
||||
event.event.forEach((_payload) => {
|
||||
events.forEach((blockEvent) => {
|
||||
blockEvent.event.forEach((_payload) => {
|
||||
this.onDocChange?.({
|
||||
isRemote: is_remote,
|
||||
data: _payload,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BlockType } from '@/appflowy_app/interfaces/document';
|
||||
import { BlockType, DocumentState } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentController } from '$app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { documentActions, DocumentState } from '../slice';
|
||||
import { documentActions } from '../slice';
|
||||
import { outdentNodeThunk } from './outdent';
|
||||
import { setCursorAfterThunk } from './set_cursor';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DocumentController } from '$app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { DocumentState } from '../slice';
|
||||
|
||||
import { DocumentState } from '$app/interfaces/document';
|
||||
|
||||
export const deleteNodeThunk = createAsyncThunk(
|
||||
'document/deleteNode',
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { BlockType } from '@/appflowy_app/interfaces/document';
|
||||
import { BlockType, DocumentState } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentController } from '$app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { DocumentState } from '../slice';
|
||||
|
||||
export const indentNodeThunk = createAsyncThunk(
|
||||
'document/indentNode',
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { BlockType, NestedBlock } from '@/appflowy_app/interfaces/document';
|
||||
import { BlockType, DocumentState, NestedBlock } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentController } from '$app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { DocumentState } from '../slice';
|
||||
import { generateId } from '@/appflowy_app/utils/block';
|
||||
|
||||
export const insertAfterNodeThunk = createAsyncThunk(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { DocumentController } from '$app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { DocumentState } from '../slice';
|
||||
|
||||
import { DocumentState } from '$app/interfaces/document';
|
||||
|
||||
export const outdentNodeThunk = createAsyncThunk(
|
||||
'document/outdentNode',
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { documentActions, DocumentState, SelectionPoint, TextSelection } from '../slice';
|
||||
import { documentActions } from '../slice';
|
||||
import { DocumentState, SelectionPoint, TextSelection } from '$app/interfaces/document';
|
||||
|
||||
export const setCursorBeforeThunk = createAsyncThunk(
|
||||
'document/setCursorBefore',
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BlockType, TextDelta } from '@/appflowy_app/interfaces/document';
|
||||
import { BlockType, DocumentState, TextDelta } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentController } from '@/appflowy_app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { generateId } from '@/appflowy_app/utils/block';
|
||||
import { documentActions, DocumentState } from '../slice';
|
||||
import { documentActions } from '../slice';
|
||||
import { setCursorBeforeThunk } from './set_cursor';
|
||||
|
||||
export const splitNodeThunk = createAsyncThunk(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TextDelta, NestedBlock } from '@/appflowy_app/interfaces/document';
|
||||
import { TextDelta, NestedBlock, DocumentState } from '@/appflowy_app/interfaces/document';
|
||||
import { DocumentController } from '@/appflowy_app/stores/effects/document/document_controller';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { documentActions, DocumentState } from '../slice';
|
||||
import { documentActions } from '../slice';
|
||||
import { debounce } from '$app/utils/tool';
|
||||
export const updateNodeDeltaThunk = createAsyncThunk(
|
||||
'document/updateNodeDelta',
|
||||
|
@ -1,32 +1,8 @@
|
||||
import { NestedBlock } from '@/appflowy_app/interfaces/document';
|
||||
import { blockChangeValue2Node } from '@/appflowy_app/utils/block';
|
||||
import { Log } from '@/appflowy_app/utils/log';
|
||||
import { BlockEventPayloadPB, DeltaTypePB } from '@/services/backend';
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { RegionGrid } from './region_grid';
|
||||
|
||||
export type Node = NestedBlock;
|
||||
|
||||
export interface SelectionPoint {
|
||||
path: [number, number];
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export interface TextSelection {
|
||||
anchor: SelectionPoint;
|
||||
focus: SelectionPoint;
|
||||
}
|
||||
|
||||
export interface DocumentState {
|
||||
// map of block id to block
|
||||
nodes: Record<string, Node>;
|
||||
// map of block id to children block ids
|
||||
children: Record<string, string[]>;
|
||||
// selected block ids
|
||||
selections: string[];
|
||||
// map of block id to text selection
|
||||
textSelections: Record<string, TextSelection>;
|
||||
}
|
||||
import { DocumentState, Node, TextSelection } from '@/appflowy_app/interfaces/document';
|
||||
import { BlockEventPayloadPB } from '@/services/backend';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { RegionGrid } from '@/appflowy_app/utils/region_grid';
|
||||
import { parseValue, matchChange } from '@/appflowy_app/utils/block_change';
|
||||
|
||||
const regionGrid = new RegionGrid(50);
|
||||
|
||||
@ -158,44 +134,11 @@ export const documentSlice = createSlice({
|
||||
const { path, id, value, command } = action.payload.data;
|
||||
const isRemote = action.payload.isRemote;
|
||||
|
||||
let valueJson;
|
||||
try {
|
||||
valueJson = JSON.parse(value);
|
||||
} catch {
|
||||
Log.error('[onDataChange] json parse error', value);
|
||||
return;
|
||||
}
|
||||
const valueJson = parseValue(value);
|
||||
if (!valueJson) return;
|
||||
|
||||
if (command === DeltaTypePB.Inserted || command === DeltaTypePB.Updated) {
|
||||
// set map key and value ( block map or children map)
|
||||
if (path[0] === 'blocks') {
|
||||
const block = blockChangeValue2Node(valueJson);
|
||||
if (command === DeltaTypePB.Updated && !isRemote) {
|
||||
// the `data` from local is already updated in local, so we just need to update other fields
|
||||
const node = state.nodes[block.id];
|
||||
if (!node || node.parent !== block.parent || node.type !== block.type || node.children !== block.children) {
|
||||
state.nodes[block.id] = block;
|
||||
}
|
||||
} else {
|
||||
state.nodes[block.id] = block;
|
||||
}
|
||||
} else {
|
||||
state.children[id] = valueJson;
|
||||
}
|
||||
} else {
|
||||
// remove map key ( block map or children map)
|
||||
if (path[0] === 'blocks') {
|
||||
if (state.selections.indexOf(id)) {
|
||||
state.selections.splice(state.selections.indexOf(id), 1);
|
||||
}
|
||||
regionGrid.removeBlock(id);
|
||||
delete state.textSelections[id];
|
||||
delete state.nodes[id];
|
||||
} else {
|
||||
delete state.children[id];
|
||||
}
|
||||
}
|
||||
// match change
|
||||
matchChange(state, { path, id, value: valueJson, command }, isRemote);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BlockPB } from '@/services/backend/models/flowy-document2';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Descendant, Element, Text } from 'slate';
|
||||
import { TextDelta, BlockType, NestedBlock } from '../interfaces/document';
|
||||
import { BlockType, TextDelta } from '../interfaces/document';
|
||||
import { Log } from './log';
|
||||
|
||||
export function generateId() {
|
||||
return nanoid(10);
|
||||
}
|
||||
@ -36,29 +36,19 @@ export function getDeltaFromSlateNodes(slateNodes: Descendant[]) {
|
||||
});
|
||||
}
|
||||
|
||||
export function blockChangeValue2Node(value: {
|
||||
id: string;
|
||||
ty: string;
|
||||
parent: string;
|
||||
children: string;
|
||||
data: string;
|
||||
}): NestedBlock {
|
||||
const block = {
|
||||
id: value.id,
|
||||
type: value.ty as BlockType,
|
||||
parent: value.parent,
|
||||
children: value.children,
|
||||
data: {},
|
||||
};
|
||||
if ('data' in value && typeof value.data === 'string') {
|
||||
try {
|
||||
Object.assign(block, {
|
||||
data: JSON.parse(value.data),
|
||||
});
|
||||
} catch {
|
||||
Log.error('valueJson data parse error', block.data);
|
||||
}
|
||||
export function blockPB2Node(block: BlockPB) {
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(block.data);
|
||||
} catch {
|
||||
Log.error('[Document Open] json parse error', block.data);
|
||||
}
|
||||
|
||||
return block;
|
||||
const node = {
|
||||
id: block.id,
|
||||
type: block.ty as BlockType,
|
||||
parent: block.parent_id,
|
||||
children: block.children_id,
|
||||
data,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
185
frontend/appflowy_tauri/src/appflowy_app/utils/block_change.ts
Normal file
185
frontend/appflowy_tauri/src/appflowy_app/utils/block_change.ts
Normal file
@ -0,0 +1,185 @@
|
||||
import { DeltaTypePB } from '@/services/backend/models/flowy-document2';
|
||||
import { BlockType, NestedBlock, DocumentState, ChangeType, BlockPBValue } from '../interfaces/document';
|
||||
import { Log } from './log';
|
||||
import { BLOCK_MAP_NAME, CHILDREN_MAP_NAME, META_NAME } from '../constants/block';
|
||||
|
||||
// This is a list of all the possible changes that can happen to document data
|
||||
const matchCases = [
|
||||
{ match: matchBlockInsert, type: ChangeType.BlockInsert, onMatch: onMatchBlockInsert },
|
||||
{ match: matchBlockUpdate, type: ChangeType.BlockUpdate, onMatch: onMatchBlockUpdate },
|
||||
{ match: matchBlockDelete, type: ChangeType.BlockDelete, onMatch: onMatchBlockDelete },
|
||||
{ match: matchChildrenMapInsert, type: ChangeType.ChildrenMapInsert, onMatch: onMatchChildrenInsert },
|
||||
{ match: matchChildrenMapUpdate, type: ChangeType.ChildrenMapUpdate, onMatch: onMatchChildrenUpdate },
|
||||
{ match: matchChildrenMapDelete, type: ChangeType.ChildrenMapDelete, onMatch: onMatchChildrenDelete },
|
||||
];
|
||||
|
||||
export function matchChange(
|
||||
state: DocumentState,
|
||||
{
|
||||
command,
|
||||
path,
|
||||
id,
|
||||
value,
|
||||
}: {
|
||||
command: DeltaTypePB;
|
||||
path: string[];
|
||||
id: string;
|
||||
value: BlockPBValue & string[];
|
||||
},
|
||||
isRemote?: boolean
|
||||
) {
|
||||
const matchCase = matchCases.find((item) => item.match(command, path));
|
||||
|
||||
if (matchCase) {
|
||||
matchCase.onMatch(state, id, value, isRemote);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Inserted
|
||||
* @param path [BLOCK_MAP_NAME]
|
||||
*/
|
||||
function matchBlockInsert(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 1) return false;
|
||||
return command === DeltaTypePB.Inserted && path[0] === BLOCK_MAP_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Updated
|
||||
* @param path [BLOCK_MAP_NAME, blockId]
|
||||
*/
|
||||
function matchBlockUpdate(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 2) return false;
|
||||
return command === DeltaTypePB.Updated && path[0] === BLOCK_MAP_NAME && typeof path[1] === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Removed
|
||||
* @param path [BLOCK_MAP_NAME, blockId]
|
||||
*/
|
||||
function matchBlockDelete(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 2) return false;
|
||||
return command === DeltaTypePB.Removed && path[0] === BLOCK_MAP_NAME && typeof path[1] === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Inserted
|
||||
* @param path [META_NAME, CHILDREN_MAP_NAME]
|
||||
*/
|
||||
function matchChildrenMapInsert(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 2) return false;
|
||||
return command === DeltaTypePB.Inserted && path[0] === META_NAME && path[1] === CHILDREN_MAP_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Updated
|
||||
* @param path [META_NAME, CHILDREN_MAP_NAME, id]
|
||||
*/
|
||||
function matchChildrenMapUpdate(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 3) return false;
|
||||
return (
|
||||
command === DeltaTypePB.Updated &&
|
||||
path[0] === META_NAME &&
|
||||
path[1] === CHILDREN_MAP_NAME &&
|
||||
typeof path[2] === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param command DeltaTypePB.Removed
|
||||
* @param path [META_NAME, CHILDREN_MAP_NAME, id]
|
||||
*/
|
||||
function matchChildrenMapDelete(command: DeltaTypePB, path: string[]) {
|
||||
if (path.length !== 3) return false;
|
||||
return (
|
||||
command === DeltaTypePB.Removed &&
|
||||
path[0] === META_NAME &&
|
||||
path[1] === CHILDREN_MAP_NAME &&
|
||||
typeof path[2] === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
function onMatchBlockInsert(state: DocumentState, blockId: string, blockValue: BlockPBValue, _isRemote?: boolean) {
|
||||
const block = blockChangeValue2Node(blockValue);
|
||||
state.nodes[blockId] = block;
|
||||
}
|
||||
|
||||
function onMatchBlockUpdate(state: DocumentState, blockId: string, blockValue: BlockPBValue, isRemote?: boolean) {
|
||||
const block = blockChangeValue2Node(blockValue);
|
||||
const node = state.nodes[blockId];
|
||||
if (!node) return;
|
||||
// if the change is from remote, we should update all fields
|
||||
if (isRemote) {
|
||||
state.nodes[blockId] = block;
|
||||
return;
|
||||
}
|
||||
// if the change is from local, we should update all fields except `data`,
|
||||
// because we will update `data` field in `updateNodeData` action
|
||||
const shouldUpdate = node.parent !== block.parent || node.type !== block.type || node.children !== block.children;
|
||||
if (shouldUpdate) {
|
||||
state.nodes[blockId] = {
|
||||
...block,
|
||||
data: node.data,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function onMatchBlockDelete(state: DocumentState, blockId: string, blockValue: BlockPBValue, _isRemote?: boolean) {
|
||||
const index = state.selections.indexOf(blockId);
|
||||
if (index > -1) {
|
||||
state.selections.splice(index, 1);
|
||||
}
|
||||
delete state.textSelections[blockId];
|
||||
delete state.nodes[blockId];
|
||||
}
|
||||
|
||||
function onMatchChildrenInsert(state: DocumentState, id: string, children: string[], _isRemote?: boolean) {
|
||||
state.children[id] = children;
|
||||
}
|
||||
|
||||
function onMatchChildrenUpdate(state: DocumentState, id: string, newChildren: string[], _isRemote?: boolean) {
|
||||
const children = state.children[id];
|
||||
if (!children) return;
|
||||
state.children[id] = newChildren;
|
||||
}
|
||||
|
||||
function onMatchChildrenDelete(state: DocumentState, id: string, _children: string[], _isRemote?: boolean) {
|
||||
delete state.children[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* convert block change value to node
|
||||
* @param value
|
||||
*/
|
||||
export function blockChangeValue2Node(value: BlockPBValue): NestedBlock {
|
||||
const block = {
|
||||
id: value.id,
|
||||
type: value.ty as BlockType,
|
||||
parent: value.parent,
|
||||
children: value.children,
|
||||
data: {},
|
||||
};
|
||||
if ('data' in value && typeof value.data === 'string') {
|
||||
try {
|
||||
Object.assign(block, {
|
||||
data: JSON.parse(value.data),
|
||||
});
|
||||
} catch {
|
||||
Log.error('[onDataChange] valueJson data parse error', block.data);
|
||||
}
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
export function parseValue(value: string) {
|
||||
let valueJson;
|
||||
try {
|
||||
valueJson = JSON.parse(value);
|
||||
} catch {
|
||||
Log.error('[onDataChange] json parse error', value);
|
||||
return;
|
||||
}
|
||||
return valueJson;
|
||||
}
|
@ -1,30 +1,30 @@
|
||||
export function debounce(fn: (...args: any[]) => void, delay: number) {
|
||||
let timeout: NodeJS.Timeout;
|
||||
return (...args: any[]) => {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(()=>{
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
fn.apply(undefined, args)
|
||||
}, delay)
|
||||
}
|
||||
fn.apply(undefined, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
export function throttle(fn: (...args: any[]) => void, delay: number, immediate = true) {
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
return (...args: any[]) => {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null
|
||||
timeout = null;
|
||||
// eslint-disable-next-line prefer-spread
|
||||
!immediate && fn.apply(undefined, args)
|
||||
}, delay)
|
||||
!immediate && fn.apply(undefined, args);
|
||||
}, delay);
|
||||
// eslint-disable-next-line prefer-spread
|
||||
immediate && fn.apply(undefined, args)
|
||||
immediate && fn.apply(undefined, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function get(obj: any, path: string[], defaultValue?: any) {
|
||||
export function get<T = any>(obj: any, path: string[], defaultValue?: any): T {
|
||||
let value = obj;
|
||||
for (const prop of path) {
|
||||
value = value[prop];
|
||||
@ -55,7 +55,6 @@ export function isEqual<T>(value1: T, value2: T): boolean {
|
||||
return value1 === value2;
|
||||
}
|
||||
|
||||
|
||||
if (Array.isArray(value1)) {
|
||||
if (!Array.isArray(value2) || value1.length !== value2.length) {
|
||||
return false;
|
||||
@ -77,9 +76,9 @@ export function isEqual<T>(value1: T, value2: T): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
for (const key of keys1) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
// @ts-expect-error
|
||||
if (!isEqual(value1[key], value2[key])) {
|
||||
return false;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user