mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 03:22:19 +03:00
UBERF-7844 Collaborative markup activity diff (#6320)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
319ce9cafc
commit
27717058cc
@ -54,7 +54,9 @@ function getActivityControl (client: MigrationClient): ActivityControl {
|
|||||||
modelDb: client.model,
|
modelDb: client.model,
|
||||||
hierarchy: client.hierarchy,
|
hierarchy: client.hierarchy,
|
||||||
findAll: async (_class, query, options) =>
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +513,7 @@ export function createModel (builder: Builder): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
builder.mixin(core.class.TypeCollaborativeDoc, core.class.Class, view.mixin.ActivityAttributePresenter, {
|
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, {
|
builder.mixin(core.class.TypeCollaborativeDocVersion, core.class.Class, view.mixin.InlineAttributEditor, {
|
||||||
|
@ -74,7 +74,6 @@ export default mergeIds(viewId, view, {
|
|||||||
HTMLEditor: '' as AnyComponent,
|
HTMLEditor: '' as AnyComponent,
|
||||||
CollaborativeHTMLEditor: '' as AnyComponent,
|
CollaborativeHTMLEditor: '' as AnyComponent,
|
||||||
CollaborativeDocEditor: '' as AnyComponent,
|
CollaborativeDocEditor: '' as AnyComponent,
|
||||||
CollaborativeDocActivityPresenter: '' as AnyComponent,
|
|
||||||
MarkupEditor: '' as AnyComponent,
|
MarkupEditor: '' as AnyComponent,
|
||||||
MarkupEditorPopup: '' as AnyComponent,
|
MarkupEditorPopup: '' as AnyComponent,
|
||||||
ListView: '' as AnyComponent,
|
ListView: '' as AnyComponent,
|
||||||
|
@ -13,12 +13,30 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import { Markup } from '@hcengineering/core'
|
||||||
import { Extensions, getSchema } from '@tiptap/core'
|
import { Extensions, getSchema } from '@tiptap/core'
|
||||||
import { Node, Schema } from 'prosemirror-model'
|
import { Node, Schema } from 'prosemirror-model'
|
||||||
import { prosemirrorJSONToYDoc, yDocToProsemirrorJSON } from 'y-prosemirror'
|
import { prosemirrorJSONToYDoc, prosemirrorToYDoc, yDocToProsemirrorJSON } from 'y-prosemirror'
|
||||||
import { Doc, applyUpdate, encodeStateAsUpdate } from 'yjs'
|
import { Doc as YDoc, applyUpdate, encodeStateAsUpdate } from 'yjs'
|
||||||
import { defaultExtensions } from './extensions'
|
import { defaultExtensions } from './extensions'
|
||||||
import { MarkupNode } from './markup/model'
|
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
|
* Get ProseMirror node from Y.Doc content
|
||||||
@ -31,7 +49,7 @@ export function yDocContentToNode (
|
|||||||
schema?: Schema,
|
schema?: Schema,
|
||||||
extensions?: Extensions
|
extensions?: Extensions
|
||||||
): Node {
|
): Node {
|
||||||
const ydoc = new Doc()
|
const ydoc = new YDoc()
|
||||||
const uint8arr = new Uint8Array(content)
|
const uint8arr = new Uint8Array(content)
|
||||||
applyUpdate(ydoc, uint8arr)
|
applyUpdate(ydoc, uint8arr)
|
||||||
|
|
||||||
@ -43,7 +61,7 @@ export function yDocContentToNode (
|
|||||||
*
|
*
|
||||||
* @public
|
* @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)
|
schema ??= getSchema(extensions ?? defaultExtensions)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -66,7 +84,7 @@ export function yDocContentToNodes (content: ArrayBuffer, schema?: Schema, exten
|
|||||||
const nodes: Node[] = []
|
const nodes: Node[] = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ydoc = new Doc()
|
const ydoc = new YDoc()
|
||||||
const uint8arr = new Uint8Array(content)
|
const uint8arr = new Uint8Array(content)
|
||||||
applyUpdate(ydoc, uint8arr)
|
applyUpdate(ydoc, uint8arr)
|
||||||
|
|
||||||
@ -93,12 +111,12 @@ export function updateYDocContent (
|
|||||||
updateFn: (body: Record<string, any>) => Record<string, any>,
|
updateFn: (body: Record<string, any>) => Record<string, any>,
|
||||||
schema?: Schema,
|
schema?: Schema,
|
||||||
extensions?: Extensions
|
extensions?: Extensions
|
||||||
): Doc | undefined {
|
): YDoc | undefined {
|
||||||
schema ??= getSchema(extensions ?? defaultExtensions)
|
schema ??= getSchema(extensions ?? defaultExtensions)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ydoc = new Doc()
|
const ydoc = new YDoc()
|
||||||
const res = new Doc({ gc: false })
|
const res = new YDoc({ gc: false })
|
||||||
const uint8arr = new Uint8Array(content)
|
const uint8arr = new Uint8Array(content)
|
||||||
applyUpdate(ydoc, uint8arr)
|
applyUpdate(ydoc, uint8arr)
|
||||||
|
|
||||||
@ -121,10 +139,10 @@ export function updateYDocContent (
|
|||||||
*
|
*
|
||||||
* @public
|
* @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)
|
schema ??= getSchema(extensions ?? defaultExtensions)
|
||||||
|
|
||||||
const res = new Doc({ gc: false })
|
const res = new YDoc({ gc: false })
|
||||||
|
|
||||||
const yDoc = prosemirrorJSONToYDoc(schema, content, field)
|
const yDoc = prosemirrorJSONToYDoc(schema, content, field)
|
||||||
const update = encodeStateAsUpdate(yDoc)
|
const update = encodeStateAsUpdate(yDoc)
|
||||||
|
@ -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>
|
|
@ -28,7 +28,6 @@ import ClassPresenter from './components/ClassPresenter.svelte'
|
|||||||
import ClassRefPresenter from './components/ClassRefPresenter.svelte'
|
import ClassRefPresenter from './components/ClassRefPresenter.svelte'
|
||||||
import CollaborativeDocEditor from './components/CollaborativeDocEditor.svelte'
|
import CollaborativeDocEditor from './components/CollaborativeDocEditor.svelte'
|
||||||
import CollaborativeHTMLEditor from './components/CollaborativeHTMLEditor.svelte'
|
import CollaborativeHTMLEditor from './components/CollaborativeHTMLEditor.svelte'
|
||||||
import CollaborativeDocActivityPresenter from './components/CollaborativeDocActivityPresenter.svelte'
|
|
||||||
import ColorsPopup from './components/ColorsPopup.svelte'
|
import ColorsPopup from './components/ColorsPopup.svelte'
|
||||||
import DateEditor from './components/DateEditor.svelte'
|
import DateEditor from './components/DateEditor.svelte'
|
||||||
import DatePresenter from './components/DatePresenter.svelte'
|
import DatePresenter from './components/DatePresenter.svelte'
|
||||||
@ -277,7 +276,6 @@ export default async (): Promise<Resources> => ({
|
|||||||
HTMLEditor,
|
HTMLEditor,
|
||||||
CollaborativeDocEditor,
|
CollaborativeDocEditor,
|
||||||
CollaborativeHTMLEditor,
|
CollaborativeHTMLEditor,
|
||||||
CollaborativeDocActivityPresenter,
|
|
||||||
ListView,
|
ListView,
|
||||||
GrowPresenter,
|
GrowPresenter,
|
||||||
DividerPresenter,
|
DividerPresenter,
|
||||||
|
@ -197,7 +197,7 @@ function getDocUpdateMessageTx (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function pushDocUpdateMessages (
|
export async function pushDocUpdateMessages (
|
||||||
ctx: MeasureContext | undefined,
|
ctx: MeasureContext,
|
||||||
control: ActivityControl,
|
control: ActivityControl,
|
||||||
res: TxCollectionCUD<Doc, DocUpdateMessage>[],
|
res: TxCollectionCUD<Doc, DocUpdateMessage>[],
|
||||||
object: Doc | undefined,
|
object: Doc | undefined,
|
||||||
@ -231,7 +231,7 @@ export async function pushDocUpdateMessages (
|
|||||||
: undefined
|
: 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) {
|
for (const attributeUpdates of attributesUpdates) {
|
||||||
res.push(
|
res.push(
|
||||||
|
@ -3,9 +3,13 @@ import {
|
|||||||
AttachedDoc,
|
AttachedDoc,
|
||||||
type Attribute,
|
type Attribute,
|
||||||
Class,
|
Class,
|
||||||
|
CollaborativeDoc,
|
||||||
|
collaborativeDocFromLastVersion,
|
||||||
Collection,
|
Collection,
|
||||||
Doc,
|
Doc,
|
||||||
Hierarchy,
|
Hierarchy,
|
||||||
|
Markup,
|
||||||
|
MeasureContext,
|
||||||
Mixin,
|
Mixin,
|
||||||
Ref,
|
Ref,
|
||||||
RefTo,
|
RefTo,
|
||||||
@ -14,15 +18,18 @@ import {
|
|||||||
TxCUD,
|
TxCUD,
|
||||||
TxMixin,
|
TxMixin,
|
||||||
TxProcessor,
|
TxProcessor,
|
||||||
TxUpdateDoc
|
TxUpdateDoc,
|
||||||
|
WorkspaceId
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import core from '@hcengineering/core/src/component'
|
import core from '@hcengineering/core/src/component'
|
||||||
import { ActivityMessageControl, DocAttributeUpdates, DocUpdateAction } from '@hcengineering/activity'
|
import { ActivityMessageControl, DocAttributeUpdates, DocUpdateAction } from '@hcengineering/activity'
|
||||||
import { ActivityControl, DocObjectCache, getAllObjectTransactions } from '@hcengineering/server-activity'
|
import { ActivityControl, DocObjectCache, getAllObjectTransactions } from '@hcengineering/server-activity'
|
||||||
import { getDocCollaborators } from '@hcengineering/server-notification-resources'
|
import { getDocCollaborators } from '@hcengineering/server-notification-resources'
|
||||||
import notification from '@hcengineering/notification'
|
import notification from '@hcengineering/notification'
|
||||||
import { TriggerControl } from '@hcengineering/server-core'
|
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
|
||||||
import { translate } from '@hcengineering/platform'
|
import { translate } from '@hcengineering/platform'
|
||||||
|
import { loadCollaborativeDoc } from '@hcengineering/collaboration'
|
||||||
|
import { EmptyMarkup, yDocToMarkup } from '@hcengineering/text'
|
||||||
|
|
||||||
function getAvailableAttributesKeys (tx: TxCUD<Doc>, hierarchy: Hierarchy): string[] {
|
function getAvailableAttributesKeys (tx: TxCUD<Doc>, hierarchy: Hierarchy): string[] {
|
||||||
if (hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
if (hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
|
||||||
@ -226,6 +233,7 @@ export async function getAttributeDiff (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getTxAttributesUpdates (
|
export async function getTxAttributesUpdates (
|
||||||
|
ctx: MeasureContext,
|
||||||
control: ActivityControl,
|
control: ActivityControl,
|
||||||
originTx: TxCUD<Doc>,
|
originTx: TxCUD<Doc>,
|
||||||
tx: TxCUD<Doc>,
|
tx: TxCUD<Doc>,
|
||||||
@ -296,6 +304,7 @@ export async function getTxAttributesUpdates (
|
|||||||
if (
|
if (
|
||||||
hierarchy.isDerived(attrClass, core.class.TypeMarkup) ||
|
hierarchy.isDerived(attrClass, core.class.TypeMarkup) ||
|
||||||
hierarchy.isDerived(attrClass, core.class.TypeCollaborativeMarkup) ||
|
hierarchy.isDerived(attrClass, core.class.TypeCollaborativeMarkup) ||
|
||||||
|
hierarchy.isDerived(attrClass, core.class.TypeCollaborativeDoc) ||
|
||||||
mixin === notification.mixin.Collaborators
|
mixin === notification.mixin.Collaborators
|
||||||
) {
|
) {
|
||||||
if (docDiff === undefined) {
|
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 = []
|
let setAttr = []
|
||||||
|
|
||||||
if (Array.isArray(attrValue)) {
|
if (Array.isArray(attrValue)) {
|
||||||
@ -406,3 +425,16 @@ export function getCollectionAttribute (
|
|||||||
|
|
||||||
return undefined
|
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
|
||||||
|
}
|
||||||
|
@ -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 {
|
export interface DocObjectCache {
|
||||||
docs: Map<Ref<Doc>, Doc | null>
|
docs: Map<Ref<Doc>, Doc | null>
|
||||||
@ -10,4 +11,6 @@ export interface ActivityControl {
|
|||||||
hierarchy: Hierarchy
|
hierarchy: Hierarchy
|
||||||
txFactory: TxFactory
|
txFactory: TxFactory
|
||||||
modelDb: ModelDb
|
modelDb: ModelDb
|
||||||
|
storageAdapter: StorageAdapter
|
||||||
|
workspace: WorkspaceId
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
|
|||||||
if (doc === undefined) return []
|
if (doc === undefined) return []
|
||||||
|
|
||||||
const res: Tx[] = []
|
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 []
|
if (messagesTxes.length === 0) return []
|
||||||
|
|
||||||
|
@ -84,7 +84,6 @@ export async function loadCollaborativeDoc (
|
|||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
const { documentId, versionId } = collaborativeDocParse(source)
|
const { documentId, versionId } = collaborativeDocParse(source)
|
||||||
|
|
||||||
ctx.info('loading collaborative document', { source })
|
|
||||||
const ydoc = await loadCollaborativeDocVersion(ctx, storageAdapter, workspace, documentId, versionId)
|
const ydoc = await loadCollaborativeDocVersion(ctx, storageAdapter, workspace, documentId, versionId)
|
||||||
|
|
||||||
if (ydoc !== undefined) {
|
if (ydoc !== undefined) {
|
||||||
|
Loading…
Reference in New Issue
Block a user