From 27717058cc8155dd07572587f2d1515b165444f5 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Mon, 12 Aug 2024 16:27:23 +0700 Subject: [PATCH] UBERF-7844 Collaborative markup activity diff (#6320) Signed-off-by: Alexander Onnikov --- models/server-activity/src/migration.ts | 4 +- models/view/src/index.ts | 2 +- models/view/src/plugin.ts | 1 - packages/text/src/ydoc.ts | 38 ++++++++++++++----- .../CollaborativeDocActivityPresenter.svelte | 21 ---------- plugins/view-resources/src/index.ts | 2 - .../activity-resources/src/index.ts | 4 +- .../activity-resources/src/utils.ts | 36 +++++++++++++++++- server-plugins/activity/src/types.ts | 5 ++- server-plugins/request-resources/src/index.ts | 2 +- .../src/utils/collaborative-doc.ts | 1 - 11 files changed, 73 insertions(+), 43 deletions(-) delete mode 100644 plugins/view-resources/src/components/CollaborativeDocActivityPresenter.svelte diff --git a/models/server-activity/src/migration.ts b/models/server-activity/src/migration.ts index 86640bd7c3..bcf1a01e81 100644 --- a/models/server-activity/src/migration.ts +++ b/models/server-activity/src/migration.ts @@ -54,7 +54,9 @@ function getActivityControl (client: MigrationClient): ActivityControl { modelDb: client.model, hierarchy: client.hierarchy, findAll: async (_class, query, options) => - toFindResult(await client.find(client.hierarchy.getDomain(_class), query, options)) + toFindResult(await client.find(client.hierarchy.getDomain(_class), query, options)), + storageAdapter: client.storageAdapter, + workspace: client.workspaceId } } diff --git a/models/view/src/index.ts b/models/view/src/index.ts index e12b9886df..f9aee0018b 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -513,7 +513,7 @@ export function createModel (builder: Builder): void { }) builder.mixin(core.class.TypeCollaborativeDoc, core.class.Class, view.mixin.ActivityAttributePresenter, { - presenter: view.component.CollaborativeDocActivityPresenter + presenter: view.component.MarkupDiffPresenter }) builder.mixin(core.class.TypeCollaborativeDocVersion, core.class.Class, view.mixin.InlineAttributEditor, { diff --git a/models/view/src/plugin.ts b/models/view/src/plugin.ts index bdfdbd4842..c636de7e02 100644 --- a/models/view/src/plugin.ts +++ b/models/view/src/plugin.ts @@ -74,7 +74,6 @@ export default mergeIds(viewId, view, { HTMLEditor: '' as AnyComponent, CollaborativeHTMLEditor: '' as AnyComponent, CollaborativeDocEditor: '' as AnyComponent, - CollaborativeDocActivityPresenter: '' as AnyComponent, MarkupEditor: '' as AnyComponent, MarkupEditorPopup: '' as AnyComponent, ListView: '' as AnyComponent, diff --git a/packages/text/src/ydoc.ts b/packages/text/src/ydoc.ts index 4fb4f9f5c1..ec50666c92 100644 --- a/packages/text/src/ydoc.ts +++ b/packages/text/src/ydoc.ts @@ -13,12 +13,30 @@ // limitations under the License. // +import { Markup } from '@hcengineering/core' import { Extensions, getSchema } from '@tiptap/core' import { Node, Schema } from 'prosemirror-model' -import { prosemirrorJSONToYDoc, yDocToProsemirrorJSON } from 'y-prosemirror' -import { Doc, applyUpdate, encodeStateAsUpdate } from 'yjs' +import { prosemirrorJSONToYDoc, prosemirrorToYDoc, yDocToProsemirrorJSON } from 'y-prosemirror' +import { Doc as YDoc, applyUpdate, encodeStateAsUpdate } from 'yjs' import { defaultExtensions } from './extensions' import { MarkupNode } from './markup/model' +import { jsonToMarkup, markupToPmNode } from './markup/utils' + +/** + * @public + */ +export function markupToYDoc (markup: Markup, field: string): YDoc { + const node = markupToPmNode(markup) + return prosemirrorToYDoc(node, field) +} + +/** + * @public + */ +export function yDocToMarkup (ydoc: YDoc, field: string): Markup { + const json = yDocToProsemirrorJSON(ydoc, field) + return jsonToMarkup(json as MarkupNode) +} /** * Get ProseMirror node from Y.Doc content @@ -31,7 +49,7 @@ export function yDocContentToNode ( schema?: Schema, extensions?: Extensions ): Node { - const ydoc = new Doc() + const ydoc = new YDoc() const uint8arr = new Uint8Array(content) applyUpdate(ydoc, uint8arr) @@ -43,7 +61,7 @@ export function yDocContentToNode ( * * @public */ -export function yDocToNode (ydoc: Doc, field?: string, schema?: Schema, extensions?: Extensions): Node { +export function yDocToNode (ydoc: YDoc, field?: string, schema?: Schema, extensions?: Extensions): Node { schema ??= getSchema(extensions ?? defaultExtensions) try { @@ -66,7 +84,7 @@ export function yDocContentToNodes (content: ArrayBuffer, schema?: Schema, exten const nodes: Node[] = [] try { - const ydoc = new Doc() + const ydoc = new YDoc() const uint8arr = new Uint8Array(content) applyUpdate(ydoc, uint8arr) @@ -93,12 +111,12 @@ export function updateYDocContent ( updateFn: (body: Record) => Record, schema?: Schema, extensions?: Extensions -): Doc | undefined { +): YDoc | undefined { schema ??= getSchema(extensions ?? defaultExtensions) try { - const ydoc = new Doc() - const res = new Doc({ gc: false }) + const ydoc = new YDoc() + const res = new YDoc({ gc: false }) const uint8arr = new Uint8Array(content) applyUpdate(ydoc, uint8arr) @@ -121,10 +139,10 @@ export function updateYDocContent ( * * @public */ -export function YDocFromContent (content: MarkupNode, field: string, schema?: Schema, extensions?: Extensions): Doc { +export function YDocFromContent (content: MarkupNode, field: string, schema?: Schema, extensions?: Extensions): YDoc { schema ??= getSchema(extensions ?? defaultExtensions) - const res = new Doc({ gc: false }) + const res = new YDoc({ gc: false }) const yDoc = prosemirrorJSONToYDoc(schema, content, field) const update = encodeStateAsUpdate(yDoc) diff --git a/plugins/view-resources/src/components/CollaborativeDocActivityPresenter.svelte b/plugins/view-resources/src/components/CollaborativeDocActivityPresenter.svelte deleted file mode 100644 index 151db800eb..0000000000 --- a/plugins/view-resources/src/components/CollaborativeDocActivityPresenter.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - -{value} diff --git a/plugins/view-resources/src/index.ts b/plugins/view-resources/src/index.ts index 259df74c52..ae48431a23 100644 --- a/plugins/view-resources/src/index.ts +++ b/plugins/view-resources/src/index.ts @@ -28,7 +28,6 @@ import ClassPresenter from './components/ClassPresenter.svelte' import ClassRefPresenter from './components/ClassRefPresenter.svelte' import CollaborativeDocEditor from './components/CollaborativeDocEditor.svelte' import CollaborativeHTMLEditor from './components/CollaborativeHTMLEditor.svelte' -import CollaborativeDocActivityPresenter from './components/CollaborativeDocActivityPresenter.svelte' import ColorsPopup from './components/ColorsPopup.svelte' import DateEditor from './components/DateEditor.svelte' import DatePresenter from './components/DatePresenter.svelte' @@ -277,7 +276,6 @@ export default async (): Promise => ({ HTMLEditor, CollaborativeDocEditor, CollaborativeHTMLEditor, - CollaborativeDocActivityPresenter, ListView, GrowPresenter, DividerPresenter, diff --git a/server-plugins/activity-resources/src/index.ts b/server-plugins/activity-resources/src/index.ts index e8f2e096a9..529c3b2844 100644 --- a/server-plugins/activity-resources/src/index.ts +++ b/server-plugins/activity-resources/src/index.ts @@ -197,7 +197,7 @@ function getDocUpdateMessageTx ( } export async function pushDocUpdateMessages ( - ctx: MeasureContext | undefined, + ctx: MeasureContext, control: ActivityControl, res: TxCollectionCUD[], object: Doc | undefined, @@ -231,7 +231,7 @@ export async function pushDocUpdateMessages ( : undefined } - const attributesUpdates = await getTxAttributesUpdates(control, originTx, tx, object, objectCache, controlRules) + const attributesUpdates = await getTxAttributesUpdates(ctx, control, originTx, tx, object, objectCache, controlRules) for (const attributeUpdates of attributesUpdates) { res.push( diff --git a/server-plugins/activity-resources/src/utils.ts b/server-plugins/activity-resources/src/utils.ts index 1617adcd1c..12dc58c4e6 100644 --- a/server-plugins/activity-resources/src/utils.ts +++ b/server-plugins/activity-resources/src/utils.ts @@ -3,9 +3,13 @@ import { AttachedDoc, type Attribute, Class, + CollaborativeDoc, + collaborativeDocFromLastVersion, Collection, Doc, Hierarchy, + Markup, + MeasureContext, Mixin, Ref, RefTo, @@ -14,15 +18,18 @@ import { TxCUD, TxMixin, TxProcessor, - TxUpdateDoc + TxUpdateDoc, + WorkspaceId } from '@hcengineering/core' import core from '@hcengineering/core/src/component' import { ActivityMessageControl, DocAttributeUpdates, DocUpdateAction } from '@hcengineering/activity' import { ActivityControl, DocObjectCache, getAllObjectTransactions } from '@hcengineering/server-activity' import { getDocCollaborators } from '@hcengineering/server-notification-resources' import notification from '@hcengineering/notification' -import { TriggerControl } from '@hcengineering/server-core' +import { StorageAdapter, TriggerControl } from '@hcengineering/server-core' import { translate } from '@hcengineering/platform' +import { loadCollaborativeDoc } from '@hcengineering/collaboration' +import { EmptyMarkup, yDocToMarkup } from '@hcengineering/text' function getAvailableAttributesKeys (tx: TxCUD, hierarchy: Hierarchy): string[] { if (hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) { @@ -226,6 +233,7 @@ export async function getAttributeDiff ( } export async function getTxAttributesUpdates ( + ctx: MeasureContext, control: ActivityControl, originTx: TxCUD, tx: TxCUD, @@ -296,6 +304,7 @@ export async function getTxAttributesUpdates ( if ( hierarchy.isDerived(attrClass, core.class.TypeMarkup) || hierarchy.isDerived(attrClass, core.class.TypeCollaborativeMarkup) || + hierarchy.isDerived(attrClass, core.class.TypeCollaborativeDoc) || mixin === notification.mixin.Collaborators ) { if (docDiff === undefined) { @@ -323,6 +332,16 @@ export async function getTxAttributesUpdates ( } } + // we don't want to show collaborative documents in activity + // instead we show their content as Markup + // TODO this should be generalized via activity extension + const attrType = mixin !== undefined ? hierarchy.findAttribute(mixin, key) : clazz + if (attrType?.type?._class === core.class.TypeCollaborativeDoc) { + attrClass = isMixin ? attrClass : core.class.TypeMarkup + attrValue = await getMarkup(ctx, control.storageAdapter, control.workspace, attrValue, key) + prevValue = await getMarkup(ctx, control.storageAdapter, control.workspace, prevValue, key) + } + let setAttr = [] if (Array.isArray(attrValue)) { @@ -406,3 +425,16 @@ export function getCollectionAttribute ( return undefined } + +async function getMarkup ( + ctx: MeasureContext, + storage: StorageAdapter, + workspace: WorkspaceId, + value: CollaborativeDoc, + field: string +): Promise { + if (value === undefined) return EmptyMarkup + value = collaborativeDocFromLastVersion(value) + const ydoc = await loadCollaborativeDoc(storage, workspace, value, ctx) + return ydoc !== undefined ? yDocToMarkup(ydoc, field) : EmptyMarkup +} diff --git a/server-plugins/activity/src/types.ts b/server-plugins/activity/src/types.ts index 729c3b9410..b651313dbe 100644 --- a/server-plugins/activity/src/types.ts +++ b/server-plugins/activity/src/types.ts @@ -1,4 +1,5 @@ -import { Doc, Hierarchy, ModelDb, Ref, Storage, TxCUD, TxFactory } from '@hcengineering/core' +import { Doc, Hierarchy, ModelDb, Ref, Storage, TxCUD, TxFactory, WorkspaceId } from '@hcengineering/core' +import { StorageAdapter } from '@hcengineering/server-core' export interface DocObjectCache { docs: Map, Doc | null> @@ -10,4 +11,6 @@ export interface ActivityControl { hierarchy: Hierarchy txFactory: TxFactory modelDb: ModelDb + storageAdapter: StorageAdapter + workspace: WorkspaceId } diff --git a/server-plugins/request-resources/src/index.ts b/server-plugins/request-resources/src/index.ts index 03c5b39d7c..6aa6439a23 100644 --- a/server-plugins/request-resources/src/index.ts +++ b/server-plugins/request-resources/src/index.ts @@ -131,7 +131,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD, cont if (doc === undefined) return [] const res: Tx[] = [] - const messagesTxes = await pushDocUpdateMessages(undefined, control, [], doc, tx) + const messagesTxes = await pushDocUpdateMessages(control.ctx, control, [], doc, tx) if (messagesTxes.length === 0) return [] diff --git a/server/collaboration/src/utils/collaborative-doc.ts b/server/collaboration/src/utils/collaborative-doc.ts index 16f57bb669..80520c7cdd 100644 --- a/server/collaboration/src/utils/collaborative-doc.ts +++ b/server/collaboration/src/utils/collaborative-doc.ts @@ -84,7 +84,6 @@ export async function loadCollaborativeDoc ( for (const source of sources) { const { documentId, versionId } = collaborativeDocParse(source) - ctx.info('loading collaborative document', { source }) const ydoc = await loadCollaborativeDocVersion(ctx, storageAdapter, workspace, documentId, versionId) if (ydoc !== undefined) {