@textbus/collaborate
Version:
Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.
1,273 lines (1,258 loc) • 68.4 kB
JavaScript
'use strict';
var core$1 = require('@viewfly/core');
var core = require('@textbus/core');
var stream = require('@tanbo/stream');
var yjs = require('yjs');
var provider = require('@hocuspocus/provider');
var yWebsocket = require('y-websocket');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __param(paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const subModelLoaderErrorFn = core.makeError('subModelLoaderError');
/**
* 子文档加载器
*/
class SubModelLoader {
}
exports.NonSubModelLoader = class NonSubModelLoader extends SubModelLoader {
createSubModelBySlot() {
throw subModelLoaderErrorFn('single document does not support async slot.');
}
createSubModelByComponent() {
throw subModelLoaderErrorFn('single document does not support async component.');
}
loadSubModelByComponent() {
throw subModelLoaderErrorFn('single document does not support async component.');
}
loadSubModelBySlot() {
throw subModelLoaderErrorFn('single document does not support async slot.');
}
getLoadedModelBySlot() {
throw subModelLoaderErrorFn('single document does not support async slot.');
}
getLoadedModelByComponent() {
throw subModelLoaderErrorFn('single document does not support async component.');
}
};
exports.NonSubModelLoader = __decorate([
core$1.Injectable()
], exports.NonSubModelLoader);
const collaborateErrorFn = core.makeError('Collaborate');
class SlotMap {
constructor() {
Object.defineProperty(this, "slotAndYTextMap", {
enumerable: true,
configurable: true,
writable: true,
value: new WeakMap()
});
Object.defineProperty(this, "yTextAndSlotMap", {
enumerable: true,
configurable: true,
writable: true,
value: new WeakMap()
});
}
set(key, value) {
if (key instanceof core.Slot) {
this.slotAndYTextMap.set(key, value);
this.yTextAndSlotMap.set(value, key);
}
else {
this.slotAndYTextMap.set(value, key);
this.yTextAndSlotMap.set(key, value);
}
}
get(key) {
if (key instanceof core.Slot) {
return this.slotAndYTextMap.get(key) || null;
}
return this.yTextAndSlotMap.get(key) || null;
}
delete(key) {
if (key instanceof core.Slot) {
const v = this.slotAndYTextMap.get(key);
this.slotAndYTextMap.delete(key);
if (v) {
this.yTextAndSlotMap.delete(v);
}
}
else {
const v = this.yTextAndSlotMap.get(key);
this.yTextAndSlotMap.delete(key);
if (v) {
this.slotAndYTextMap.delete(v);
}
}
}
}
exports.Collaborate = class Collaborate {
constructor(scheduler, registry, selection, subModelLoader) {
Object.defineProperty(this, "scheduler", {
enumerable: true,
configurable: true,
writable: true,
value: scheduler
});
Object.defineProperty(this, "registry", {
enumerable: true,
configurable: true,
writable: true,
value: registry
});
Object.defineProperty(this, "selection", {
enumerable: true,
configurable: true,
writable: true,
value: selection
});
Object.defineProperty(this, "subModelLoader", {
enumerable: true,
configurable: true,
writable: true,
value: subModelLoader
});
Object.defineProperty(this, "yDoc", {
enumerable: true,
configurable: true,
writable: true,
value: new yjs.Doc()
});
Object.defineProperty(this, "slotMap", {
enumerable: true,
configurable: true,
writable: true,
value: new SlotMap()
});
Object.defineProperty(this, "onAddSubModel", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "subscriptions", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "updateFromRemote", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "addSubModelEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
Object.defineProperty(this, "updateRemoteActions", {
enumerable: true,
configurable: true,
writable: true,
value: new WeakMap()
});
Object.defineProperty(this, "noRecord", {
enumerable: true,
configurable: true,
writable: true,
value: {}
});
this.onAddSubModel = this.addSubModelEvent.asObservable();
}
syncRootComponent(yDoc, sharedComponent, localComponent) {
this.initSyncEvent(yDoc);
this.syncComponent(yDoc, sharedComponent, localComponent);
}
syncRootSlot(yDoc, sharedSlot, localSlot) {
if (sharedSlot.length) {
localSlot.retain(0);
localSlot.delete(localSlot.length);
localSlot.cleanAttributes();
localSlot.cleanFormats();
this.initLocalSlotBySharedSlot(sharedSlot, localSlot);
}
else {
yDoc.transact(() => {
this.initSharedSlotByLocalSlot(sharedSlot, localSlot);
});
}
this.initSyncEvent(yDoc);
this.syncSlot(sharedSlot, localSlot);
}
getAbstractSelection(position) {
const anchorPosition = yjs.createAbsolutePositionFromRelativePosition(position.anchor.position, position.anchor.doc);
const focusPosition = yjs.createAbsolutePositionFromRelativePosition(position.focus.position, position.focus.doc);
if (anchorPosition && focusPosition) {
const focusSlot = this.slotMap.get(focusPosition.type);
const anchorSlot = this.slotMap.get(anchorPosition.type);
if (focusSlot && anchorSlot) {
return {
anchorSlot,
anchorOffset: anchorPosition.index,
focusSlot,
focusOffset: focusPosition.index
};
}
}
return null;
}
getRelativeCursorLocation() {
const { anchorSlot, anchorOffset, focusSlot, focusOffset } = this.selection;
if (anchorSlot) {
const anchorYText = this.slotMap.get(anchorSlot);
if (anchorYText) {
const anchorPosition = yjs.createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
if (focusSlot) {
const focusYText = this.slotMap.get(focusSlot);
if (focusYText) {
const focusPosition = yjs.createRelativePositionFromTypeIndex(focusYText, focusOffset);
return {
focus: {
doc: focusYText.doc,
position: focusPosition
},
anchor: {
doc: anchorYText.doc,
position: anchorPosition
}
};
}
}
}
}
return null;
}
restoreCursorPosition(position) {
if (!position) {
this.selection.unSelect();
return;
}
const selection = this.getAbstractSelection(position);
if (selection) {
this.selection.setBaseAndExtent(selection.anchorSlot, selection.anchorOffset, selection.focusSlot, selection.focusOffset);
}
}
initSyncEvent(yDoc) {
this.subscriptions.push(this.scheduler.onDocChanged.pipe(stream.map(item => {
return item.filter(i => {
return i.from !== core.ChangeOrigin.Remote;
});
}), stream.filter(item => {
return item.length;
})).subscribe(() => {
const updates = [];
let update = null;
const updateRemoteActions = this.updateRemoteActions.get(yDoc) || [];
for (const item of updateRemoteActions) {
if (!update) {
update = {
record: item.record,
actions: []
};
updates.push(update);
}
if (update.record === item.record) {
update.actions.push(item.action);
}
else {
update = {
record: item.record,
actions: [item.action]
};
updates.push(update);
}
}
this.updateRemoteActions.delete(yDoc);
for (const item of updates) {
yDoc.transact(() => {
item.actions.forEach(fn => {
fn();
});
}, item.record ? yDoc : this.noRecord);
}
}));
}
syncComponent(yDoc, sharedComponent, localComponent) {
let state = sharedComponent.get('state');
if (!state) {
state = new yjs.Map();
this.syncLocalMapToSharedMap(localComponent.state, state);
yDoc.transact(() => {
sharedComponent.set('state', state);
});
}
else {
Object.keys(localComponent.state).forEach(key => {
Reflect.deleteProperty(localComponent.state, key);
});
this.syncSharedMapToLocalMap(state, localComponent.state);
}
}
syncSlot(sharedSlot, localSlot) {
const syncRemote = (ev, tr) => {
this.runRemoteUpdate(tr, () => {
localSlot.retain(0);
ev.keysChanged.forEach(key => {
const change = ev.keys.get(key);
if (!change) {
return;
}
const updateType = change.action;
if (updateType === 'update' || updateType === 'add') {
const attribute = this.registry.getAttribute(key);
if (attribute) {
localSlot.setAttribute(attribute, sharedSlot.getAttribute(key));
}
}
else if (updateType === 'delete') {
const attribute = this.registry.getAttribute(key);
if (attribute) {
localSlot.removeAttribute(attribute);
}
}
});
ev.delta.forEach(action => {
if (Reflect.has(action, 'retain')) {
if (action.attributes) {
const formats = remoteFormatsToLocal(this.registry, action.attributes);
if (formats.length) {
localSlot.retain(action.retain, formats);
}
}
localSlot.retain(localSlot.index + action.retain);
}
else if (action.insert) {
const index = localSlot.index;
let length = 1;
if (typeof action.insert === 'string') {
length = action.insert.length;
localSlot.insert(action.insert, remoteFormatsToLocal(this.registry, action.attributes));
}
else {
const sharedComponent = action.insert;
const component = this.createLocalComponentBySharedComponent(sharedComponent);
localSlot.insert(component);
}
if (this.selection.isSelected && !(tr.origin instanceof yjs.UndoManager)) {
if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset > index) {
this.selection.setAnchor(localSlot, this.selection.anchorOffset + length);
}
if (localSlot === this.selection.focusSlot && this.selection.focusOffset > index) {
this.selection.setFocus(localSlot, this.selection.focusOffset + length);
}
}
}
else if (action.delete) {
const index = localSlot.index;
localSlot.delete(action.delete);
if (this.selection.isSelected && !(tr.origin instanceof yjs.UndoManager)) {
if (localSlot === this.selection.anchorSlot && this.selection.anchorOffset >= index) {
this.selection.setAnchor(localSlot, this.selection.startOffset - action.delete);
}
if (localSlot === this.selection.focusSlot && this.selection.focusOffset >= index) {
this.selection.setFocus(localSlot, this.selection.focusOffset - action.delete);
}
}
}
});
});
};
sharedSlot.observe(syncRemote);
const sub = localSlot.onContentChange.subscribe(actions => {
this.runLocalUpdate(sharedSlot.doc, true, () => {
var _a;
let offset = 0;
let length = 0;
for (const action of actions) {
if (action.type === 'retain') {
const formats = action.formats;
if (formats) {
const keys = Object.keys(formats);
let length = keys.length;
keys.forEach(key => {
const formatter = this.registry.getFormatter(key);
if (!formatter) {
length--;
Reflect.deleteProperty(formats, key);
}
});
if (length) {
sharedSlot.format(offset, action.offset, formats);
}
}
else {
offset = action.offset;
}
}
else if (action.type === 'contentInsert') {
const delta = sharedSlot.toDelta();
const isEmpty = delta.length === 1 && delta[0].insert === core.Slot.emptyPlaceholder;
if (typeof action.content === 'string') {
length = action.content.length;
sharedSlot.insert(offset, action.content, action.formats || {});
}
else {
length = 1;
const sharedComponent = this.createSharedComponentByLocalComponent(action.ref);
sharedSlot.insertEmbed(offset, sharedComponent, action.formats || {});
}
if (isEmpty && offset === 0) {
sharedSlot.delete(sharedSlot.length - 1, 1);
}
offset += length;
}
else if (action.type === 'delete') {
const delta = sharedSlot.toDelta();
if (sharedSlot.length) {
sharedSlot.delete(offset, action.count);
}
if (sharedSlot.length === 0) {
sharedSlot.insert(0, '\n', (_a = delta[0]) === null || _a === void 0 ? void 0 : _a.attributes);
}
}
else if (action.type === 'attrSet') {
sharedSlot.setAttribute(action.name, action.value);
}
else if (action.type === 'attrDelete') {
sharedSlot.removeAttribute(action.name);
}
}
});
});
this.slotMap.set(localSlot, sharedSlot);
localSlot.__changeMarker__.addDetachCallback(() => {
this.slotMap.delete(localSlot);
sharedSlot.unobserve(syncRemote);
sub.unsubscribe();
});
}
destroy() {
this.subscriptions.forEach(i => i.unsubscribe());
this.subscriptions = [];
}
syncSharedMapToLocalMap(sharedMap, localMap) {
sharedMap.forEach((value, key) => {
localMap[key] = this.createLocalModelBySharedByModel(value);
});
this.syncObject(sharedMap, localMap);
}
createLocalMapBySharedMap(sharedMap) {
const localMap = core.observe({});
this.syncSharedMapToLocalMap(sharedMap, localMap);
return localMap;
}
createLocalArrayBySharedArray(sharedArray) {
const localArray = core.observe([]);
localArray.push(...sharedArray.map(item => this.createLocalModelBySharedByModel(item)));
this.syncArray(sharedArray, localArray);
return localArray;
}
syncLocalMapToSharedMap(localMap, sharedMap) {
Object.entries(localMap).forEach(([key, value]) => {
sharedMap.set(key, this.createSharedModelByLocalModel(value));
});
this.syncObject(sharedMap, localMap);
}
createSharedMapByLocalMap(localMap) {
const sharedMap = new yjs.Map();
this.syncLocalMapToSharedMap(localMap, sharedMap);
return sharedMap;
}
createSharedArrayByLocalArray(localArray) {
const sharedArray = new yjs.Array();
localArray.forEach(value => {
sharedArray.push([this.createSharedModelByLocalModel(value)]);
});
this.syncArray(sharedArray, localArray);
return sharedArray;
}
createSharedSlotByLocalSlot(localSlot) {
const sharedSlot = new yjs.Text();
const isAsyncSlot = localSlot instanceof core.AsyncSlot;
sharedSlot.setAttribute('schema', [...localSlot.schema]);
sharedSlot.setAttribute('type', isAsyncSlot ? 'async' : 'sync');
if (isAsyncSlot) {
let isDestroyed = false;
const sharedMetadata = this.createSharedMapByLocalMap(localSlot.metadata);
sharedSlot.setAttribute('metadata', sharedMetadata);
this.subModelLoader.createSubModelBySlot(localSlot).then(subDocument => {
if (isDestroyed) {
return;
}
const content = subDocument.getText('content');
const state = subDocument.getMap('state');
this.syncLocalMapToSharedMap(localSlot.state, state);
this.initSharedSlotByLocalSlot(content, localSlot);
this.syncSlot(content, localSlot);
this.addSubModelEvent.next({
yDoc: subDocument,
yType: content
});
this.initSyncEvent(subDocument);
localSlot.loader.markAsLoaded();
});
localSlot.__changeMarker__.addDetachCallback(() => {
isDestroyed = true;
});
return sharedSlot;
}
const sharedSlotState = new yjs.Map();
this.syncLocalMapToSharedMap(localSlot.state, sharedSlotState);
sharedSlot.setAttribute('state', sharedSlotState);
const sharedContent = new yjs.Text();
this.initSharedSlotByLocalSlot(sharedContent, localSlot);
sharedSlot.insertEmbed(0, sharedContent);
this.syncSlot(sharedContent, localSlot);
return sharedSlot;
}
initSharedSlotByLocalSlot(sharedContent, localSlot) {
let offset = 0;
localSlot.toDelta().forEach(i => {
let formats = {};
if (i.formats) {
i.formats.forEach(item => {
formats[item[0].name] = item[1];
});
}
else {
formats = null;
}
if (typeof i.insert === 'string') {
sharedContent.insert(offset, i.insert, formats);
}
else {
const sharedComponent = this.createSharedComponentByLocalComponent(i.insert);
sharedContent.insertEmbed(offset, sharedComponent, formats);
}
offset += i.insert.length;
});
localSlot.getAttributes().forEach(item => {
sharedContent.setAttribute(item[0].name, item[1]);
});
}
createLocalSlotBySharedSlot(sharedSlot) {
var _a;
const type = sharedSlot.getAttribute('type');
const schema = sharedSlot.getAttribute('schema');
if (type === 'async') {
const metadata = sharedSlot.getAttribute('metadata');
const slot = new core.AsyncSlot(schema || [], {}, {});
this.syncSharedMapToLocalMap(metadata, slot.metadata);
const loadedSubDocument = this.subModelLoader.getLoadedModelBySlot(slot);
if (loadedSubDocument) {
const subContent = loadedSubDocument.getText('content');
const data = loadedSubDocument.getMap('state');
this.syncSharedMapToLocalMap(data, slot.state);
this.syncRootSlot(loadedSubDocument, subContent, slot);
this.addSubModelEvent.next({
yDoc: loadedSubDocument,
yType: subContent
});
slot.loader.markAsLoaded();
return slot;
}
let isDestroyed = false;
slot.loader.onRequestLoad.toPromise().then(() => {
return this.subModelLoader.loadSubModelBySlot(slot);
}).then(subDocument => {
if (isDestroyed) {
return;
}
const subContent = subDocument.getText('content');
const state = subDocument.getMap('state');
this.syncSharedMapToLocalMap(state, slot.state);
this.syncRootSlot(subDocument, subContent, slot);
this.addSubModelEvent.next({
yDoc: subDocument,
yType: subContent
});
slot.loader.markAsLoaded();
});
slot.__changeMarker__.addDetachCallback(() => {
isDestroyed = true;
});
return slot;
}
const contentDelta = sharedSlot.toDelta();
const content = (_a = contentDelta[0]) === null || _a === void 0 ? void 0 : _a.insert;
if (!(content instanceof yjs.Text)) {
throw collaborateErrorFn('shared slot content type is not `YText`.');
}
const localSlot = new core.Slot(schema || [], {});
const sharedSlotState = sharedSlot.getAttribute('state');
this.syncSharedMapToLocalMap(sharedSlotState, localSlot.state);
this.initLocalSlotBySharedSlot(content, localSlot);
this.syncSlot(content, localSlot);
return localSlot;
}
initLocalSlotBySharedSlot(content, localSlot) {
const delta = content.toDelta();
const attrs = content.getAttributes();
Object.keys(attrs).forEach(key => {
const attribute = this.registry.getAttribute(key);
if (attribute) {
localSlot.setAttribute(attribute, attrs[key]);
}
});
for (const action of delta) {
if (action.insert) {
if (typeof action.insert === 'string') {
const formats = remoteFormatsToLocal(this.registry, action.attributes);
localSlot.insert(action.insert, formats);
}
else {
const sharedComponent = action.insert;
const component = this.createLocalComponentBySharedComponent(sharedComponent);
localSlot.insert(component, remoteFormatsToLocal(this.registry, action.attributes));
}
}
else {
throw collaborateErrorFn('unexpected delta action.');
}
}
}
createSharedModelByLocalModel(localModel) {
if (localModel instanceof core.Slot) {
return this.createSharedSlotByLocalSlot(localModel);
}
if (Array.isArray(localModel)) {
return this.createSharedArrayByLocalArray(localModel);
}
if (typeof localModel === 'object' && localModel !== null) {
return this.createSharedMapByLocalMap(localModel);
}
return localModel;
}
createLocalModelBySharedByModel(sharedModel) {
if (sharedModel instanceof yjs.Map) {
return this.createLocalMapBySharedMap(sharedModel);
}
if (sharedModel instanceof yjs.Array) {
return this.createLocalArrayBySharedArray(sharedModel);
}
if (sharedModel instanceof yjs.Text) {
return this.createLocalSlotBySharedSlot(sharedModel);
}
return sharedModel;
}
createSharedComponentByLocalComponent(component) {
const sharedComponent = new yjs.Map();
sharedComponent.set('name', component.name);
if (component instanceof core.AsyncComponent) {
sharedComponent.set('type', 'async');
const sharedMetadata = this.createSharedMapByLocalMap(component.metadata);
sharedComponent.set('metadata', sharedMetadata);
const state = component.state;
let isDestroyed = false;
state.__changeMarker__.addDetachCallback(() => {
isDestroyed = true;
});
this.subModelLoader.createSubModelByComponent(component).then(subDocument => {
if (isDestroyed) {
return;
}
const state = subDocument.getMap('state');
this.syncComponent(subDocument, state, component);
this.addSubModelEvent.next({
yType: state,
yDoc: subDocument
});
this.initSyncEvent(subDocument);
component.loader.markAsLoaded();
});
return sharedComponent;
}
const sharedState = this.createSharedMapByLocalMap(component.state);
sharedComponent.set('state', sharedState);
sharedComponent.set('type', 'sync');
return sharedComponent;
}
createLocalComponentBySharedComponent(yMap) {
const componentName = yMap.get('name');
const type = yMap.get('type');
let instance;
if (type === 'async') {
instance = this.registry.createComponentByData(componentName, {}, {});
if (instance instanceof core.AsyncComponent) {
const sharedMetadata = yMap.get('metadata');
this.syncSharedMapToLocalMap(sharedMetadata, instance.metadata);
const loadedSubDocument = this.subModelLoader.getLoadedModelByComponent(instance);
if (loadedSubDocument) {
const state = loadedSubDocument.getMap('state');
this.syncComponent(loadedSubDocument, state, instance);
this.addSubModelEvent.next({
yType: state,
yDoc: loadedSubDocument
});
instance.loader.markAsLoaded();
return instance;
}
const state = instance.state;
let isDestroyed = false;
instance.loader.onRequestLoad.toPromise().then(() => {
return this.subModelLoader.loadSubModelByComponent(instance);
})
.then(subDocument => {
if (isDestroyed) {
return;
}
const state = subDocument.getMap('state');
this.syncComponent(subDocument, state, instance);
this.addSubModelEvent.next({
yType: state,
yDoc: subDocument
});
instance.loader.markAsLoaded();
});
state.__changeMarker__.addDetachCallback(() => {
isDestroyed = true;
});
}
else if (instance instanceof core.Component) {
throw collaborateErrorFn(`component name \`${componentName}\` is not a async component.`);
}
}
else {
const sharedState = yMap.get('state');
const state = this.createLocalMapBySharedMap(sharedState);
instance = this.registry.createComponentByData(componentName, state);
}
if (instance) {
return instance;
}
throw collaborateErrorFn(`cannot find component factory \`${componentName}\`.`);
}
/**
* 双向同步数组
* @param sharedArray
* @param localArray
* @private
*/
syncArray(sharedArray, localArray) {
function logError(type) {
console.error(collaborateErrorFn(`${type} error, length exceeded, path in ${localArray.__changeMarker__.getPaths().join('/')}`));
}
const sub = localArray.__changeMarker__.onSelfChange.subscribe((actions) => {
this.runLocalUpdate(sharedArray.doc, !localArray.__changeMarker__.irrevocableUpdate, () => {
let index = 0;
for (const action of actions) {
switch (action.type) {
case 'retain':
index = action.offset;
break;
case 'insert':
{
const ref = action.ref;
if (!Array.isArray(ref)) {
throw collaborateErrorFn('The insertion action must have a reference value.');
}
const data = ref.map(item => {
return this.createSharedModelByLocalModel(item);
});
if (index <= sharedArray.length) {
sharedArray.insert(index, data);
}
else {
sharedArray.insert(sharedArray.length, data);
logError('insert');
}
}
break;
case 'delete':
if (action.count <= 0) {
break;
}
if (index < sharedArray.length) {
sharedArray.delete(index, action.count);
}
else {
logError('delete');
}
break;
case 'setIndex':
if (action.index < sharedArray.length) {
sharedArray.delete(action.index, 1);
sharedArray.insert(action.index, [this.createSharedModelByLocalModel(action.ref)]);
}
else {
sharedArray.insert(sharedArray.length, [this.createSharedModelByLocalModel(action.ref)]);
logError('setIndex');
}
break;
}
}
});
});
const syncRemote = (ev, tr) => {
this.runRemoteUpdate(tr, () => {
let index = 0;
ev.delta.forEach((action) => {
if (Reflect.has(action, 'retain')) {
index += action.retain;
}
else if (action.insert) {
const data = action.insert.map((item) => {
return this.createLocalModelBySharedByModel(item);
});
localArray.splice(index, 0, ...data);
index += data.length;
}
else if (action.delete) {
localArray.splice(index, action.delete);
}
});
});
};
sharedArray.observe(syncRemote);
localArray.__changeMarker__.addDetachCallback(() => {
sub.unsubscribe();
sharedArray.unobserve(syncRemote);
});
}
/**
* 双向同步对象
* @param sharedObject
* @param localObject
* @private
*/
syncObject(sharedObject, localObject) {
const syncRemote = (ev, tr) => {
this.runRemoteUpdate(tr, () => {
ev.changes.keys.forEach((item, key) => {
if (item.action === 'add' || item.action === 'update') {
const value = sharedObject.get(key);
localObject[key] = this.createLocalModelBySharedByModel(value);
}
else {
Reflect.deleteProperty(localObject, key);
}
});
});
};
sharedObject.observe(syncRemote);
const sub = localObject.__changeMarker__.onSelfChange.subscribe((actions) => {
this.runLocalUpdate(sharedObject.doc, !localObject.__changeMarker__.irrevocableUpdate, () => {
for (const action of actions) {
switch (action.type) {
case 'propSet':
{
const subModel = this.createSharedModelByLocalModel(action.ref);
sharedObject.set(action.key, subModel);
if (sharedObject.size === 0) {
// 奇怪的 bug,设置了子模型,但子模型会标记为 deleted,导致设置后无效
console.error(collaborateErrorFn(`prop set error, key is ${action.key}`));
}
}
break;
case 'propDelete':
sharedObject.delete(action.key);
break;
}
}
});
});
localObject.__changeMarker__.addDetachCallback(function () {
sharedObject.unobserve(syncRemote);
sub.unsubscribe();
});
}
runLocalUpdate(yDoc, record, fn) {
if (this.updateFromRemote || !yDoc) {
return;
}
let changeList = this.updateRemoteActions.get(yDoc);
if (!changeList) {
changeList = [];
this.updateRemoteActions.set(yDoc, changeList);
}
changeList.push({
record,
action: fn
});
}
runRemoteUpdate(tr, fn) {
if (tr.origin === tr.doc) {
return;
}
this.updateFromRemote = true;
if (tr.origin instanceof yjs.UndoManager) {
this.scheduler.historyApplyTransact(fn);
}
else {
this.scheduler.remoteUpdateTransact(fn);
}
this.updateFromRemote = false;
}
};
exports.Collaborate = __decorate([
core$1.Injectable(),
__metadata("design:paramtypes", [core.Scheduler,
core.Registry,
core.Selection,
SubModelLoader])
], exports.Collaborate);
function remoteFormatsToLocal(registry, attrs) {
const formats = [];
if (attrs) {
Object.keys(attrs).forEach(key => {
const formatter = registry.getFormatter(key);
if (formatter) {
formats.push([formatter, attrs[key]]);
}
});
}
return formats;
}
class CustomUndoManagerConfig {
}
const collabHistoryErrorFn = core.makeError('CollabHistory');
exports.CollabHistory = class CollabHistory {
get canBack() {
var _a;
return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canUndo()) || false;
}
get canForward() {
var _a;
return ((_a = this.manager) === null || _a === void 0 ? void 0 : _a.canRedo()) || false;
}
constructor(rootComponentRef, collaborate, scheduler, stackSize, undoManagerConfig) {
Object.defineProperty(this, "rootComponentRef", {
enumerable: true,
configurable: true,
writable: true,
value: rootComponentRef
});
Object.defineProperty(this, "collaborate", {
enumerable: true,
configurable: true,
writable: true,
value: collaborate
});
Object.defineProperty(this, "scheduler", {
enumerable: true,
configurable: true,
writable: true,
value: scheduler
});
Object.defineProperty(this, "stackSize", {
enumerable: true,
configurable: true,
writable: true,
value: stackSize
});
Object.defineProperty(this, "undoManagerConfig", {
enumerable: true,
configurable: true,
writable: true,
value: undoManagerConfig
});
Object.defineProperty(this, "onBack", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onForward", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onChange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onPush", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "manager", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
Object.defineProperty(this, "historyItems", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "index", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
Object.defineProperty(this, "subscriptions", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "backEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
Object.defineProperty(this, "forwardEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
Object.defineProperty(this, "changeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
Object.defineProperty(this, "pushEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
this.onBack = this.backEvent.asObservable();
this.onForward = this.forwardEvent.asObservable();
this.onChange = this.changeEvent.asObservable();
this.onPush = this.pushEvent.asObservable();
}
listen() {
const root = this.collaborate.yDoc.getMap('RootComponent');
const rootComponent = this.rootComponentRef.component;
this.collaborate.syncRootComponent(this.collaborate.yDoc, root, rootComponent);
const undoManagerConfig = this.undoManagerConfig || {};
const manager = new yjs.UndoManager(root, {
trackedOrigins: new Set([this.collaborate.yDoc]),
captureTransaction(arg) {
if (undoManagerConfig.captureTransaction) {
return undoManagerConfig.captureTransaction(arg);
}
return true;
},
deleteFilter(item) {
if (undoManagerConfig.deleteFilter) {
return undoManagerConfig.deleteFilter(item);
}
return true;
}
});
this.manager = manager;
let beforePosition = null;
this.subscriptions.push(this.scheduler.onLocalChangeBefore.subscribe(() => {
beforePosition = this.collaborate.getRelativeCursorLocation();
}), this.collaborate.onAddSubModel.subscribe(() => {
throw collabHistoryErrorFn('single document does not support submodels.');
}));
manager.on('stack-item-added', (event) => {
if (event.type === 'undo') {
if (event.origin === manager) {
this.index++;
}
else {
this.historyItems.length = this.index;
this.historyItems.push({
before: beforePosition,
after: this.collaborate.getRelativeCursorLocation()
});
this.index++;
}
}
else {
this.index--;
}
if (manager.undoStack.length > this.stackSize) {
this.historyItems.shift();
manager.undoStack.shift();
}
if (event.origin === this.collaborate.yDoc) {
this.pushEvent.next();
}
this.changeEvent.next();
});
manager.on('stack-item-popped', (ev) => {
const index = ev.type === 'undo' ? this.index : this.index - 1;
const position = this.historyItems[index] || null;
const p = ev.type === 'undo' ? position === null || position === void 0 ? void 0 : position.before : position === null || position === void 0 ? void 0 : position.after;
this.collaborate.restoreCursorPosition(p);
});
}
back() {
var _a;
if (this.canBack) {
(_a = this.manager) === null || _a === void 0 ? void 0 : _a.undo();
this.backEvent.next();
}
}
forward() {
var _a;
if (this.canForward) {
(_a = this.manager) === null || _a === void 0 ? void 0 : _a.redo();
this.forwardEvent.next();
}
}
clear() {
var _a;
const last = this.historyItems.pop();
this.historyItems = last ? [last] : [];
this.index = last ? 1 : 0;
(_a = this.manager) === null || _a === void 0 ? void 0 : _a.clear();
this.changeEvent.next();
}
destroy() {
this.index = 0;
this.historyItems = [];
this.subscriptions.forEach(i => i.unsubscribe());
if (this.manager) {
this.manager.destroy();
this.manager.captureTransaction = () => true;
this.manager.deleteFilter = () => true;
this.manager.trackedOrigins = new Set([null]);
}
this.manager = null;
}
};
exports.CollabHistory = __decorate([
core$1.Injectable(),
__param(3, core$1.Inject(core.HISTORY_STACK_SIZE)),
__param(4, core$1.Optional()),
__metadata("design:paramtypes", [core.RootComponentRef,
exports.Collaborate,
core.Scheduler, Number, CustomUndoManagerConfig])
], exports.CollabHistory);
/**
* 协作消息总线,用于同步各终端消息
*/
class MessageBus {
constructor() {
Object.defineProperty(this, "onSync", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "syncEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
this.onSync = this.syncEvent.asObservable();
}
/**
* 立即同步消息
*/
sync() {
this.syncEvent.next();
}
}
exports.MultipleDocCollabHistory = class MultipleDocCollabHistory {
get canBack() {
return this.actionStack.length > 0 && this.index > 0;
}
get canForward() {
return this.actionStack.length > 0 && this.index < this.actionStack.length;
}
constructor(collaborate, scheduler, rootComponentRef, stackSize, undoManagerConfig) {
Object.defineProperty(this, "collaborate", {
enumerable: true,
configurable: true,
writable: true,
value: collaborate
});
Object.defineProperty(this, "scheduler", {
enumerable: true,
configurable: true,
writable: true,
value: scheduler
});
Object.defineProperty(this, "rootComponentRef", {
enumerable: true,
configurable: true,
writable: true,
value: rootComponentRef
});
Object.defineProperty(this, "stackSize", {
enumerable: true,
configurable: true,
writable: true,
value: stackSize
});
Object.defineProperty(this, "undoManagerConfig", {
enumerable: true,
configurable: true,
writable: true,
value: undoManagerConfig
});
Object.defineProperty(this, "onChange", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onBack", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onForward", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "onPush", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "isListen", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "changeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new stream.Subject()
});
Object.defineProperty(this, "backEvent", {