snapshot of https://github.com/docmost/docmost.git @ 6205bbeb908fe846f87dd6a2cbf562e24777db38 (2026-09-15)
This commit is contained in:
134
apps/server/src/collaboration/collaboration.handler.ts
Normal file
134
apps/server/src/collaboration/collaboration.handler.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Hocuspocus, Document } from '@hocuspocus/server';
|
||||
import { TiptapTransformer } from '@hocuspocus/transformer';
|
||||
import {
|
||||
prosemirrorNodeToYElement,
|
||||
tiptapExtensions,
|
||||
} from './collaboration.util';
|
||||
import { setYjsMark, updateYjsMarkAttribute, YjsSelection } from './yjs.util';
|
||||
import * as Y from 'yjs';
|
||||
import { User } from '@docmost/db/types/entity.types';
|
||||
|
||||
export type CollabEventHandlers = ReturnType<
|
||||
CollaborationHandler['getHandlers']
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class CollaborationHandler {
|
||||
private readonly logger = new Logger(CollaborationHandler.name);
|
||||
|
||||
constructor() {}
|
||||
|
||||
getHandlers(hocuspocus: Hocuspocus) {
|
||||
return {
|
||||
alterState: async (documentName: string, payload: { pageId: string }) => {
|
||||
// dummy
|
||||
// this.logger.log('Processing', documentName, payload);
|
||||
// await this.withYdocConnection(hocuspocus, documentName, {}, (doc) => {
|
||||
// const fragment = doc.getXmlFragment('default');
|
||||
//});
|
||||
},
|
||||
setCommentMark: async (
|
||||
documentName: string,
|
||||
payload: {
|
||||
yjsSelection: YjsSelection;
|
||||
commentId: string;
|
||||
resolved: boolean;
|
||||
user: User;
|
||||
},
|
||||
) => {
|
||||
const { yjsSelection, commentId, resolved, user } = payload;
|
||||
await this.withYdocConnection(
|
||||
hocuspocus,
|
||||
documentName,
|
||||
{ user },
|
||||
(doc) => {
|
||||
const fragment = doc.getXmlFragment('default');
|
||||
setYjsMark(doc, fragment, yjsSelection, 'comment', {
|
||||
commentId,
|
||||
resolved,
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
resolveCommentMark: async (
|
||||
documentName: string,
|
||||
payload: {
|
||||
commentId: string;
|
||||
resolved: boolean;
|
||||
user: User;
|
||||
},
|
||||
) => {
|
||||
const { commentId, resolved, user } = payload;
|
||||
await this.withYdocConnection(
|
||||
hocuspocus,
|
||||
documentName,
|
||||
{ user },
|
||||
(doc) => {
|
||||
const fragment = doc.getXmlFragment('default');
|
||||
updateYjsMarkAttribute(
|
||||
fragment,
|
||||
'comment',
|
||||
{ name: 'commentId', value: commentId },
|
||||
{ resolved },
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
updatePageContent: async (
|
||||
documentName: string,
|
||||
payload: {
|
||||
prosemirrorJson: any;
|
||||
operation: string;
|
||||
user: User;
|
||||
},
|
||||
) => {
|
||||
const { prosemirrorJson, operation, user } = payload;
|
||||
this.logger.debug('Updating page content via yjs', documentName);
|
||||
await this.withYdocConnection(
|
||||
hocuspocus,
|
||||
documentName,
|
||||
{ user },
|
||||
(doc) => {
|
||||
const fragment = doc.getXmlFragment('default');
|
||||
|
||||
if (operation === 'replace') {
|
||||
if (fragment.length > 0) {
|
||||
fragment.delete(0, fragment.length);
|
||||
}
|
||||
|
||||
const newDoc = TiptapTransformer.toYdoc(
|
||||
prosemirrorJson,
|
||||
'default',
|
||||
tiptapExtensions,
|
||||
);
|
||||
Y.applyUpdate(doc, Y.encodeStateAsUpdate(newDoc));
|
||||
} else {
|
||||
const newContent = prosemirrorJson.content || [];
|
||||
const yElements = newContent.map(prosemirrorNodeToYElement);
|
||||
const position = operation === 'prepend' ? 0 : fragment.length;
|
||||
fragment.insert(position, yElements);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async withYdocConnection(
|
||||
hocuspocus: Hocuspocus,
|
||||
documentName: string,
|
||||
context: any = {},
|
||||
fn: (doc: Document) => void,
|
||||
): Promise<void> {
|
||||
const connection = await hocuspocus.openDirectConnection(
|
||||
documentName,
|
||||
context,
|
||||
);
|
||||
try {
|
||||
await connection.transact(fn);
|
||||
} finally {
|
||||
await connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user