UNPKG

@syncedstore/yjs-reactive-bindings

Version:

A bridge between Reactive programming libraries (reactive, Vue or MobX) and Yjs

109 lines (96 loc) 3.5 kB
import * as Y from "yjs"; import { observeArray } from "./types/array"; import { observeDoc } from "./types/doc"; import { observeMap } from "./types/map"; import { observeText } from "./types/text"; import { observeXml } from "./types/xml"; export function isYType(element: any) { return element instanceof Y.AbstractType || Object.prototype.hasOwnProperty.call(element, "autoLoad"); // detect subdocs. Is there a better way for this? } export function observeYJS(element: Y.AbstractType<any> | Y.Doc) { if (element instanceof Y.XmlText) { return observeText(element); } else if (element instanceof Y.Text) { return observeText(element); } else if (element instanceof Y.Array) { return observeArray(element); } else if (element instanceof Y.Map) { return observeMap(element); } else if (element instanceof Y.Doc || Object.prototype.hasOwnProperty.call(element, "autoLoad")) { // subdoc. Ok way to detect this? return observeDoc(element as any as Y.Doc); } else if (element instanceof Y.XmlFragment) { return observeXml(element); } else if (element instanceof Y.XmlElement) { return observeXml(element); } else { if (element._item === null && element._start === null) { // console.warn("edge case"); } else { // throw new Error("not yet supported"); } } return element; } function makeYDocRootLevelTypesObservable(doc: Y.Doc) { doc.share.forEach((type) => { // the explicit check is necessary because we sometimes initialize "anonymous" types that the user can't (and shouldn't) access. if (type.constructor !== Y.AbstractType) { // console.log("root", type) observeYJS(type); } }); } function makeStructsObservable(structs: (Y.Item | Y.GC)[], startPos: number) { for (let i = structs.length - 1; i >= startPos; i--) { let struct = structs[i]; if (!struct.deleted) { if (struct instanceof Y.GC) { continue; } struct.content?.getContent().forEach((content) => { if (content instanceof Y.AbstractType) { // console.log("struct", content) observeYJS(content); // console.log(content, "is a created type type"); } }); } } } const docsObserved = new WeakSet<Y.Doc>(); export function makeYDocObservable(doc: Y.Doc) { if (docsObserved.has(doc)) { return; } docsObserved.add(doc); // based on https://github.com/yjs/yjs/pull/298#issuecomment-937636849 // hook new root type creations (when calling getMap() or getArray(), etc) observeYJS(doc); // observe all structs already in the document doc.store.clients.forEach((entry) => { if (entry) { makeStructsObservable(entry, 0); } }); // observe all root-types makeYDocRootLevelTypesObservable(doc); // observe newly created types from now on doc.on("beforeObserverCalls", (tr: Y.Transaction) => { // observe new root types makeYDocRootLevelTypesObservable(doc); // observe new structs tr.afterState.forEach((clock, client) => { const beforeClock = tr.beforeState.get(client) || 0; if (beforeClock !== clock) { const structs = tr.doc.store.clients.get(client); if (!structs) { return; } const firstChangePos = Y.findIndexSS(structs, beforeClock); makeStructsObservable(structs, firstChangePos); } }); }); } export { enableMobxBindings, enableReactiveBindings, enableVueBindings } from "./observableProvider";