diff --git a/blocksuite/framework/store/src/reactive/flat-native-y/index.ts b/blocksuite/framework/store/src/reactive/flat-native-y/index.ts index 8acd33c868..7bee39560c 100644 --- a/blocksuite/framework/store/src/reactive/flat-native-y/index.ts +++ b/blocksuite/framework/store/src/reactive/flat-native-y/index.ts @@ -4,19 +4,13 @@ import { Array as YArray, type Map as YMap, type YMapEvent } from 'yjs'; import { BaseReactiveYData } from '../base-reactive-data'; import { Boxed, type OnBoxedChange } from '../boxed'; -import { y2Native } from '../native-y'; import { ReactiveYArray } from '../proxy'; import { type OnTextChange, Text } from '../text'; import type { ProxyOptions, UnRecord } from '../types'; import { initializeData } from './initialize'; import { createProxy } from './proxy'; import type { OnChange } from './types'; -import { - deleteEmptyObject, - getFirstKey, - isEmptyObject, - keyWithoutPrefix, -} from './utils'; +import { getYEventHandler } from './y-event-handler'; export class ReactiveFlatYMap extends BaseReactiveYData< UnRecord, @@ -32,72 +26,14 @@ export class ReactiveFlatYMap extends BaseReactiveYData< const yMap = this._ySource; const proxy = this._proxy; this._onObserve(event, () => { - event.keysChanged.forEach(key => { - const type = event.changes.keys.get(key); - if (!type) { - return; - } - if (type.action === 'update' || type.action === 'add') { - const value = yMap.get(key); - const keyName: string = keyWithoutPrefix(key); - const firstKey = getFirstKey(keyName); - if (this._stashed.has(firstKey)) { - return; - } - this._updateWithYjsSkip(() => { - const keys = keyName.split('.'); - void keys.reduce((acc, key, index, arr) => { - if (!acc[key] && index !== arr.length - 1) { - acc[key] = {}; - } - if (index === arr.length - 1) { - acc[key] = y2Native(value, { - transform: (value, origin) => { - return this._transform(firstKey, value, origin); - }, - }); - } - return acc[key] as UnRecord; - }, proxy as UnRecord); - }); - this._onChange?.(firstKey, false); - return; - } - if (type.action === 'delete') { - const keyName: string = keyWithoutPrefix(key); - const firstKey = getFirstKey(keyName); - if (this._stashed.has(firstKey)) { - return; - } - this._updateWithYjsSkip(() => { - const keys = keyName.split('.'); - void keys.reduce((acc, key, index) => { - if (index === keys.length - 1) { - delete acc[key]; - let curr = acc; - let parentKey = keys[index - 1]; - let parent = proxy as UnRecord; - let path = keys.slice(0, -2); - - for (let i = keys.length - 2; i > 0; i--) { - for (const pathKey of path) { - parent = parent[pathKey] as UnRecord; - } - if (!isEmptyObject(curr)) { - break; - } - deleteEmptyObject(curr, parentKey, parent); - curr = parent; - parentKey = keys[i - 1]; - path = path.slice(0, -1); - parent = proxy as UnRecord; - } - } - return acc[key] as UnRecord; - }, proxy as UnRecord); - }); - return; - } + getYEventHandler({ + yMap, + proxy, + stashed: this._stashed, + updateWithYjsSkip: this._updateWithYjsSkip, + transform: this._transform, + onChange: this._onChange, + event, }); }); }; diff --git a/blocksuite/framework/store/src/reactive/flat-native-y/y-event-handler.ts b/blocksuite/framework/store/src/reactive/flat-native-y/y-event-handler.ts new file mode 100644 index 0000000000..23a01fa8e1 --- /dev/null +++ b/blocksuite/framework/store/src/reactive/flat-native-y/y-event-handler.ts @@ -0,0 +1,117 @@ +import type { Map as YMap, YMapEvent } from 'yjs'; + +import { y2Native } from '../native-y'; +import type { UnRecord } from '../types'; +import { + deleteEmptyObject, + getFirstKey, + isEmptyObject, + keyWithoutPrefix, +} from './utils'; + +type YEventHandlerOptions = { + event: YMapEvent; + yMap: YMap; + proxy: UnRecord; + stashed: Set; + updateWithYjsSkip: (fn: () => void) => void; + transform: (key: string, value: unknown, origin: unknown) => unknown; + onChange?: (key: string, isAdd: boolean) => void; +}; + +// update proxy when yjs map changes +export const getYEventHandler = (options: YEventHandlerOptions) => { + const { event } = options; + const { keysChanged, changes } = event; + + keysChanged.forEach(key => { + const type = changes.keys.get(key); + if (!type) return; + + if (type.action === 'update' || type.action === 'add') { + return handleUpdateOrAdd(key, options); + } + if (type.action === 'delete') { + return handleDelete(key, options); + } + }); +}; + +function isStashed(key: string, stashed: Set) { + const keyName: string = keyWithoutPrefix(key); + const firstKey = getFirstKey(keyName); + return stashed.has(firstKey); +} + +function handleUpdateOrAdd( + key: string, + { + yMap, + proxy, + stashed, + updateWithYjsSkip, + transform, + onChange, + }: YEventHandlerOptions +) { + if (isStashed(key, stashed)) { + return; + } + const keyName: string = keyWithoutPrefix(key); + const firstKey = getFirstKey(keyName); + updateWithYjsSkip(() => { + const value = yMap.get(key); + const keys = keyName.split('.'); + void keys.reduce((acc, key, index, arr) => { + if (!acc[key] && index !== arr.length - 1) { + acc[key] = {}; + } + if (index === arr.length - 1) { + acc[key] = y2Native(value, { + transform: (value, origin) => transform(firstKey, value, origin), + }); + } + return acc[key] as UnRecord; + }, proxy as UnRecord); + }); + onChange?.(firstKey, false); +} + +function handleDelete( + key: string, + { proxy, stashed, updateWithYjsSkip, onChange }: YEventHandlerOptions +) { + if (isStashed(key, stashed)) { + return; + } + const keyName: string = keyWithoutPrefix(key); + const firstKey = getFirstKey(keyName); + updateWithYjsSkip(() => { + const keys = keyName.split('.'); + void keys.reduce((acc, key, index) => { + if (index === keys.length - 1) { + delete acc[key]; + let curr = acc; + let parentKey = keys[index - 1]; + let parent = proxy as UnRecord; + let path = keys.slice(0, -2); + + for (let i = keys.length - 2; i > 0; i--) { + for (const pathKey of path) { + parent = parent[pathKey] as UnRecord; + } + if (!isEmptyObject(curr)) { + break; + } + deleteEmptyObject(curr, parentKey, parent); + curr = parent; + parentKey = keys[i - 1]; + path = path.slice(0, -1); + parent = proxy as UnRecord; + } + } + return acc[key] as UnRecord; + }, proxy as UnRecord); + }); + onChange?.(firstKey, false); +}