mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
refactor(editor): separate yjs subscribe logic of flat model (#10863)
This commit is contained in:
@@ -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,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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<unknown>;
|
||||
yMap: YMap<unknown>;
|
||||
proxy: UnRecord;
|
||||
stashed: Set<string | number>;
|
||||
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<string | number>) {
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user