|  | @@ -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;
 | 
	
		
			
				|  |  | +}
 |