Skip to content

Commit

Permalink
feat()
Browse files Browse the repository at this point in the history
- 优化卡片异步渲染流程
- 优化 setValue setHtml setJsonValue 方法,移除 enableAsync triggerOT 配置选项,默认始终以异步渲染卡片
- 优化协同流程,协同数据将持久化到 MongoDB
- 增加 getJsonValue api
- 优化粘贴markdown检测流程
- 表格在下方添加行的位置不正确
- link 插件增加 onConfirm 选项
  • Loading branch information
yanmao committed Sep 12, 2021
1 parent f4384ac commit d54423a
Show file tree
Hide file tree
Showing 63 changed files with 1,260 additions and 851 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ There is no backend API configured in the `Vue` case. For details, please refer
## Contribution
Thanks [pleasedmi](https://github.com/pleasedmi) for donation
Thanks [pleasedmi](https://github.com/pleasedmi)、[Elena211314](https://github.com/Elena211314) for donation
### Alipay
Expand Down
4 changes: 3 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,9 @@ yarn serve
## 贡献
感谢 [pleasedmi](https://github.com/pleasedmi) 的捐赠
感谢 [pleasedmi](https://github.com/pleasedmi)、[Elena211314](https://github.com/Elena211314) 的捐赠
如果您愿意,可以在这里留下你的名字。
### 支付宝
Expand Down
51 changes: 27 additions & 24 deletions examples/react/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,22 @@ const EditorComponent: React.FC<EditorProps> = ({
...pluginConfig,
'mark-range': getConfig(engine, comment),
},
// 编辑器值改变事件
onChange: useCallback(
(value: string, trigger: 'remote' | 'local' | 'both') => {
if (loading) return;
setValue(value);
//自动保存,非远程更改,触发保存
if (trigger !== 'remote') autoSave();
if (props.onChange) props.onChange(value, trigger);
// 获取编辑器的值
console.log(`value ${trigger} update:`, value);
// 获取当前所有at插件中的名单
console.log(
'mention:',
engine.current?.command.executeMethod('mention', 'getList'),
);
// 获取编辑器的html
console.log('html:', engine.current?.getHtml());
},
[loading, autoSave, props.onChange],
Expand Down Expand Up @@ -156,30 +160,29 @@ const EditorComponent: React.FC<EditorProps> = ({
defaultValue.value,
)
: defaultValue.value;
//设置编辑器值,并异步渲染卡片
engine.current.setValue(value, {
enableAsync: true,
triggerOT: props.ot ? false : true, //对于异步渲染后的卡片节点不提交到协同服务端,否则会冲突
callback: () => {
//卡片异步渲染完成后
if (!props.ot) {
if (onLoad) onLoad(engine.current!);
return setLoading(false);
}
//实例化协作编辑客户端
const ot = new OTClient(engine.current!);
//连接到协作服务端,demo文档
const { url, docId, onReady } = props.ot;
ot.connect(url, docId);
ot.on('ready', (member) => {
if (onLoad) onLoad(engine.current!);
if (onReady) onReady(member);
setMember(member);
setLoading(false);
});
otClient.current = ot;
},
});

//连接到协作服务端,demo文档
if (props.ot) {
//实例化协作编辑客户端
const ot = new OTClient(engine.current);
const { url, docId, onReady } = props.ot;
// 连接协同服务端,如果服务端没有对应docId的文档,将使用 defaultValue 初始化
ot.connect(url, docId, value);
ot.on('ready', (member) => {
if (onLoad) onLoad(engine.current!);
if (onReady) onReady(member);
setMember(member);
setLoading(false);
});
otClient.current = ot;
} else {
// 非协同编辑,设置编辑器值,异步渲染后回调
engine.current.setValue(value, (count) => {
console.log(count);
if (onLoad) onLoad(engine.current!);
return setLoading(false);
});
}
setValue(value);
}

Expand Down
129 changes: 91 additions & 38 deletions examples/react/components/editor/ot/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,25 @@ export const ERROR_LEVEL = {
WARNING: 'WARNING',
NOTICE: 'NOTICE',
};

/**
* 协同客户端
*/
class OTClient extends EventEmitter {
// 编辑器引擎
protected engine: EngineInterface;
// ws 连接实例
protected socket?: WebSocket;
// 当前协同的所有用户
protected members: Array<Member> = [];
// 当前用户
protected current?: Member;
// 当前状态
protected status?: string;
// 协作的文档对象
protected doc?: Doc;
// 当前 ws 是否关闭
protected isClosed: boolean = true;
// 心跳检测对象
protected heartbeat?: {
timeout: NodeJS.Timeout;
datetime: Date;
Expand All @@ -81,61 +92,95 @@ class OTClient extends EventEmitter {
if (this.heartbeat?.timeout) clearTimeout(this.heartbeat.timeout);
const timeout = setTimeout(() => {
const now = new Date();

if (
!this.heartbeat ||
now.getTime() - this.heartbeat.datetime.getTime() >= millisecond
!this.isClosed &&
(!this.heartbeat ||
now.getTime() - this.heartbeat.datetime.getTime() >=
millisecond)
) {
this.sendMessage('heartbeat', { time: now.getTime() });
this.heartbeat = {
timeout,
datetime: now,
};
} else {
} else if (this.heartbeat) {
this.heartbeat.timeout = timeout;
}
this.checkHeartbeat(millisecond);
}, 1000);
}

/**
* 连接到协作文档
* @param url 协同服务地址
* @param docID 文档唯一ID
* @param defautlValue 如果协作服务端没有创建的文档,将作为协同文档的初始值
* @param collectionName 协作服务名称,与协同服务端相对应
*/
connect(
url: string,
documentID: string,
docID: string,
defautlValue?: string,
collectionName: string = 'yanmao',
) {
const socket = new ReconnectingWebSocket(url, [], {
maxReconnectionDelay: 30000,
minReconnectionDelay: 10000,
reconnectionDelayGrowFactor: 10000,
maxRetries: 10,
});
if (this.socket) this.socket.close();
// 实例化一个可以自动重连的 ws
const socket = new ReconnectingWebSocket(
async () => {
const token = await new Promise((resolve) => {
// 这里可以异步获取一个Token,如果有的话
resolve('');
});
// 组合ws链接
return `${url}&id=${docID}&token=${token}`;
},
[],
{
maxReconnectionDelay: 30000,
minReconnectionDelay: 10000,
reconnectionDelayGrowFactor: 10000,
maxRetries: 10,
},
);
// ws 已链接
socket.addEventListener('open', () => {
console.log('collab server connected');
this.socket = socket as WebSocket;
// 标记关闭状态为false
this.isClosed = false;
// 监听协同服务端自定义消息
this.socket.addEventListener('message', (event) => {
const { data, action } = JSON.parse(event.data);
// 当前所有的协作用户
if ('members' === action) {
this.addMembers(data);
this.engine.ot.setMembers(data);
return;
}
// 有新的协作者加入了
if ('join' === action) {
this.addMembers([data]);
this.engine.ot.addMember(data);
return;
}
// 有协作者离开了
if ('leave' === action) {
this.engine.ot.removeMember(data);
this.removeMember(data);
return;
}
// 协作服务端准备好了,可以实例化编辑器内部的协同服务了
if ('ready' === action) {
this.current = data;
console.log('current member->', data);
// 当前协作者用户
this.current = data as Member;
this.engine.ot.setCurrentMember(data);
this.load(documentID, collectionName);
// 加载编辑器内部的协同服务
this.load(docID, collectionName, defautlValue);
}
// 广播信息,一个协作用户发送给全部协作者的广播
if ('broadcast' === action) {
const { uuid, body, type } = data;
// 如果接收者和发送者不是同一人就触发一个message事件,外部可以监听这个事件并作出响应
if (uuid !== this.current?.uuid) {
this.emit(EVENT.message, {
type,
Expand All @@ -144,43 +189,54 @@ class OTClient extends EventEmitter {
}
}
});
// 开始检测心跳
this.checkHeartbeat();
});
// 监听ws关闭事件
socket.addEventListener('close', () => {
console.log(
'collab server connection close, current status: ',
this.status,
);
// 如果不是主动退出的关闭,就显示错误信息
if (this.status !== STATUS.exit) {
console.log('connect closed');
this.onError({
code: ERROR_CODE.DISCONNECTED,
level: ERROR_LEVEL.FATAL,
message: '网络连接异常,无法继续编辑',
});
}
});
// 监听ws错误消息
socket.addEventListener('error', (error) => {
console.log('collab server connection error');
this.onError({
code: ERROR_CODE.CONNECTION_ERROR,
level: ERROR_LEVEL.FATAL,
message: '网络连接异常,无法继续编辑',
message: '协作服务异常,无法继续编辑',
error,
});
});
}

load(documentID: string, collectionName: string) {
/**
* 加载编辑器内部协同服务
* @param docId 文档唯一ID
* @param collectionName 协作服务名称
* @param defaultValue 如果服务端没有对应docId的文档,就用这个值初始化
*/
load(docId: string, collectionName: string, defaultValue?: string) {
// 实例化一个协同客户端的连接
const connection = new sharedb.Connection(this.socket as Socket);
const doc = connection.get(collectionName, documentID);
// 获取文档对象
const doc = connection.get(collectionName, docId);
this.doc = doc;
// 订阅
doc.subscribe((error) => {
if (error) {
console.log('collab doc subscribe error', error);
} else {
try {
this.initOt(doc);
// 实例化编辑器内部协同服务
this.engine.ot.init(doc, defaultValue);
// 聚焦到编辑器
this.engine.focus();
this.emit('ready', this.engine.ot.getCurrentMember());
this.emit(EVENT.membersChange, this.normalizeMembers());
this.transmit(STATUS.active);
} catch (err) {
Expand Down Expand Up @@ -210,23 +266,20 @@ class OTClient extends EventEmitter {
});
}

initOt(doc: Doc) {
this.engine.ot.init(doc);
this.engine.focus();
this.emit('ready', this.engine.ot.getCurrentMember());
}

reset() {
this.members = [];
this.current = undefined;
this.disconnect();
this.transmit(STATUS.init);
}

/**
* 广播一个消息
* @param type 消息类型
* @param body 消息内容
*/
broadcast(type: string, body: any = {}) {
this.sendMessage('broadcast', { type, body });
}

/**
* 给服务端发送一个消息
* @param action 消息类型
* @param data 消息数据
*/
sendMessage(action: string, data?: any) {
this.socket?.send(
JSON.stringify({
Expand Down
4 changes: 0 additions & 4 deletions examples/react/components/engine/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ const EngineComponent: React.FC<EngineProps> = forwardRef<
});
});
};
//初始化本地协作,用作记录历史
engine.ot.initLockMode();

engine.setValue(defaultValue || '');

engineRef.current = engine;
return engine;
Expand Down
Loading

0 comments on commit d54423a

Please sign in to comment.