sync-ydoc.ts 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { Origin, YDocStatus } from '@growi/core';
  2. import type { Document } from 'y-socket.io/dist/server';
  3. import loggerFactory from '~/utils/logger';
  4. import { Revision } from '../../models/revision';
  5. import type { MongodbPersistence } from './extended/mongodb-persistence';
  6. const logger = loggerFactory('growi:service:yjs:sync-ydoc');
  7. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  8. type Delta = Array<{insert?:Array<any>|string, delete?:number, retain?:number}>;
  9. type Context = {
  10. ydocStatus: YDocStatus,
  11. }
  12. /**
  13. * Sync the text and the meta data with the latest revision body
  14. * @param mdb
  15. * @param doc
  16. * @param context true to force sync
  17. */
  18. export const syncYDoc = async(mdb: MongodbPersistence, doc: Document, context: true | Context): Promise<void> => {
  19. const pageId = doc.name;
  20. const revision = await Revision
  21. .findOne(
  22. // filter
  23. { pageId },
  24. // projection
  25. { body: 1, createdAt: 1, origin: 1 },
  26. // options
  27. { sort: { createdAt: -1 } },
  28. )
  29. .lean();
  30. if (revision == null) {
  31. logger.warn(`Synchronization has been canceled since the revision of the page ('${pageId}') could not be found`);
  32. return;
  33. }
  34. const shouldSync = context === true
  35. || (() => {
  36. switch (context.ydocStatus) {
  37. case YDocStatus.NEW:
  38. return true;
  39. case YDocStatus.OUTDATED:
  40. // should skip when the YDoc is outdated and the latest revision is created by the editor
  41. return revision.origin !== Origin.Editor;
  42. default:
  43. return false;
  44. }
  45. })();
  46. if (shouldSync) {
  47. logger.debug(`YDoc for the page ('${pageId}') is synced with the latest revision body`);
  48. const ytext = doc.getText('codemirror');
  49. const delta: Delta = [];
  50. if (ytext.length > 0) {
  51. delta.push({ delete: ytext.length });
  52. }
  53. if (revision.body != null) {
  54. delta.push({ insert: revision.body });
  55. }
  56. ytext.applyDelta(delta, { sanitize: false });
  57. }
  58. const shouldSyncMeta = context === true
  59. || context.ydocStatus === YDocStatus.NEW
  60. || context.ydocStatus === YDocStatus.OUTDATED;
  61. if (shouldSyncMeta) {
  62. mdb.setMeta(doc.name, 'updatedAt', revision.createdAt.getTime() ?? Date.now());
  63. }
  64. };