UBERF-7844 Collaborative markup activity diff (#6320)

Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2024-08-12 16:27:23 +07:00 committed by GitHub
parent 319ce9cafc
commit 27717058cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 73 additions and 43 deletions

View File

@ -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
}
}

View File

@ -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, {

View File

@ -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,

View File

@ -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<string, any>) => Record<string, any>,
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)

View File

@ -1,21 +0,0 @@
<!--
// Copyright © 2024 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { CollaborativeDoc } from '@hcengineering/core'
export let value: CollaborativeDoc
</script>
<span>{value}</span>

View File

@ -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<Resources> => ({
HTMLEditor,
CollaborativeDocEditor,
CollaborativeHTMLEditor,
CollaborativeDocActivityPresenter,
ListView,
GrowPresenter,
DividerPresenter,

View File

@ -197,7 +197,7 @@ function getDocUpdateMessageTx (
}
export async function pushDocUpdateMessages (
ctx: MeasureContext | undefined,
ctx: MeasureContext,
control: ActivityControl,
res: TxCollectionCUD<Doc, DocUpdateMessage>[],
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(

View File

@ -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<Doc>, 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<Doc>,
tx: TxCUD<Doc>,
@ -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<Markup> {
if (value === undefined) return EmptyMarkup
value = collaborativeDocFromLastVersion(value)
const ydoc = await loadCollaborativeDoc(storage, workspace, value, ctx)
return ydoc !== undefined ? yDocToMarkup(ydoc, field) : EmptyMarkup
}

View File

@ -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<Ref<Doc>, Doc | null>
@ -10,4 +11,6 @@ export interface ActivityControl {
hierarchy: Hierarchy
txFactory: TxFactory
modelDb: ModelDb
storageAdapter: StorageAdapter
workspace: WorkspaceId
}

View File

@ -131,7 +131,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, 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 []

View File

@ -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) {