@textbus/collaborate
Version:
Textbus is a rich text editor and framework that is highly customizable and extensible to achieve rich wysiwyg effects.
1,272 lines (1,258 loc) • 68 kB
JavaScript
import { Injectable, Inject, Optional } from '@viewfly/core';
import { makeError, ChangeOrigin, Slot, observe, AsyncSlot, AsyncComponent, Component, Scheduler, Registry, Selection, HISTORY_STACK_SIZE, RootComponentRef, History } from '@textbus/core';
import { Subject, map, filter, Subscription } from '@tanbo/stream';
import { Doc, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, Map, Array as Array$1, Text, UndoManager } from 'yjs';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { WebsocketProvider } from '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 = makeError('subModelLoaderError');
/**
* 子文档加载器
*/
class SubModelLoader {
}
let 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.');
}
};
NonSubModelLoader = __decorate([
Injectable()
], NonSubModelLoader);
const collaborateErrorFn = 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 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 Slot) {
return this.slotAndYTextMap.get(key) || null;
}
return this.yTextAndSlotMap.get(key) || null;
}
delete(key) {
if (key instanceof 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);
}
}
}
}
let 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 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 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 = createAbsolutePositionFromRelativePosition(position.anchor.position, position.anchor.doc);
const focusPosition = 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 = createRelativePositionFromTypeIndex(anchorYText, anchorOffset);
if (focusSlot) {
const focusYText = this.slotMap.get(focusSlot);
if (focusYText) {
const focusPosition = 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(map(item => {
return item.filter(i => {
return i.from !== ChangeOrigin.Remote;
});
}), 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 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 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 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 === 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 = observe({});
this.syncSharedMapToLocalMap(sharedMap, localMap);
return localMap;
}
createLocalArrayBySharedArray(sharedArray) {
const localArray = 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 Map();
this.syncLocalMapToSharedMap(localMap, sharedMap);
return sharedMap;
}
createSharedArrayByLocalArray(localArray) {
const sharedArray = new Array$1();
localArray.forEach(value => {
sharedArray.push([this.createSharedModelByLocalModel(value)]);
});
this.syncArray(sharedArray, localArray);
return sharedArray;
}
createSharedSlotByLocalSlot(localSlot) {
const sharedSlot = new Text();
const isAsyncSlot = localSlot instanceof 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 Map();
this.syncLocalMapToSharedMap(localSlot.state, sharedSlotState);
sharedSlot.setAttribute('state', sharedSlotState);
const sharedContent = new 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 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 Text)) {
throw collaborateErrorFn('shared slot content type is not `YText`.');
}
const localSlot = new 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 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 Map) {
return this.createLocalMapBySharedMap(sharedModel);
}
if (sharedModel instanceof Array$1) {
return this.createLocalArrayBySharedArray(sharedModel);
}
if (sharedModel instanceof Text) {
return this.createLocalSlotBySharedSlot(sharedModel);
}
return sharedModel;
}
createSharedComponentByLocalComponent(component) {
const sharedComponent = new Map();
sharedComponent.set('name', component.name);
if (component instanceof 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 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 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 UndoManager) {
this.scheduler.historyApplyTransact(fn);
}
else {
this.scheduler.remoteUpdateTransact(fn);
}
this.updateFromRemote = false;
}
};
Collaborate = __decorate([
Injectable(),
__metadata("design:paramtypes", [Scheduler,
Registry,
Selection,
SubModelLoader])
], 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 = makeError('CollabHistory');
let 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 Subject()
});
Object.defineProperty(this, "forwardEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "changeEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new Subject()
});
Object.defineProperty(this, "pushEvent", {
enumerable: true,
configurable: true,
writable: true,
value: new 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 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;
}
};
CollabHistory = __decorate([
Injectable(),
__param(3, Inject(HISTORY_STACK_SIZE)),
__param(4, Optional()),
__metadata("design:paramtypes", [RootComponentRef,
Collaborate,
Scheduler, Number, CustomUndoManagerConfig])
], 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 Subject()
});
this.onSync = this.syncEvent.asObservable();
}
/**
* 立即同步消息
*/
sync() {
this.syncEvent.next();
}
}
let 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 Subject()
});
Object.defineProperty(this, "backEvent", {
enumerable: true,