mirror of
https://github.com/hcengineering/platform.git
synced 2026-10-01 14:05:05 +02:00
UBERF-5811: rework backlinks (#4887)
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
|
||||
import { getReferencesData } from '../references'
|
||||
|
||||
describe('extractBacklinks', () => {
|
||||
it('should return no references for empty document', () => {
|
||||
const content = '<p></p>'
|
||||
const references = getReferencesData(
|
||||
'srcDocId' as Ref<Doc>,
|
||||
'srcDocClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
'attachedDocClass' as Ref<Class<Doc>>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(references).toEqual([])
|
||||
})
|
||||
|
||||
it('should parse single backlink', () => {
|
||||
const content =
|
||||
'<p>hello <span class="reference" data-type="reference" data-id="id" data-objectclass="contact:class:Person" data-label="Appleseed John">@Appleseed John</span> </p>'
|
||||
|
||||
const references = getReferencesData(
|
||||
'srcDocId' as Ref<Doc>,
|
||||
'srcDocClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
'attachedDocClass' as Ref<Class<Doc>>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(references.length).toBe(1)
|
||||
expect(references).toEqual([
|
||||
{
|
||||
attachedTo: 'id',
|
||||
attachedToClass: 'contact:class:Person',
|
||||
collection: 'references',
|
||||
srcDocId: 'srcDocId',
|
||||
srcDocClass: 'srcDocClass',
|
||||
message:
|
||||
'hello <span data-type="reference" data-id="id" data-objectclass="contact:class:Person" data-label="Appleseed John" class="reference">@Appleseed John</span>',
|
||||
attachedDocId: 'attachedDocId',
|
||||
attachedDocClass: 'attachedDocClass'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('should parse single backlink for multiple references', () => {
|
||||
const content =
|
||||
'<p><span class="reference" data-type="reference" data-id="id" data-label="Appleseed John" data-objectclass="contact:class:Person">@Appleseed John</span> <span data-type="reference" class="reference" data-id="id" data-label="Appleseed John" data-objectclass="contact:class:Person">@Appleseed John</span> </p>'
|
||||
|
||||
const references = getReferencesData(
|
||||
'srcDocId' as Ref<Doc>,
|
||||
'srcDocClass' as Ref<Class<Doc>>,
|
||||
'attachedDocId' as Ref<Doc>,
|
||||
'attachedDocClass' as Ref<Class<Doc>>,
|
||||
content
|
||||
)
|
||||
|
||||
expect(references.length).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
|
||||
import { getDocUpdateAction, getTxAttributesUpdates } from './utils'
|
||||
import { ReferenceTrigger } from './references'
|
||||
|
||||
export async function OnReactionChanged (originTx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const tx = originTx as TxCollectionCUD<ActivityMessage, Reaction>
|
||||
@@ -60,14 +61,26 @@ export async function removeReactionNotifications (
|
||||
control: TriggerControl
|
||||
): Promise<Tx[]> {
|
||||
const message = (
|
||||
await control.findAll(activity.class.ActivityMessage, { objectId: tx.tx.objectId }, { projection: { _id: 1 } })
|
||||
await control.findAll(
|
||||
activity.class.ActivityMessage,
|
||||
{ objectId: tx.tx.objectId },
|
||||
{ projection: { _id: 1, _class: 1, space: 1 } }
|
||||
)
|
||||
)[0]
|
||||
|
||||
if (message === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
return await removeDocInboxNotifications(message._id, control)
|
||||
const res: Tx[] = []
|
||||
const txes = await removeDocInboxNotifications(message._id, control)
|
||||
|
||||
const removeTx = control.txFactory.createTxRemoveDoc(message._class, message.space, message._id)
|
||||
|
||||
res.push(removeTx)
|
||||
res.push(...txes)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export async function createReactionNotifications (
|
||||
@@ -384,6 +397,7 @@ async function OnDocRemoved (originTx: TxCUD<Doc>, control: TriggerControl): Pro
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
trigger: {
|
||||
ReferenceTrigger,
|
||||
ActivityMessagesHandler,
|
||||
OnDocRemoved,
|
||||
OnReactionChanged
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
//
|
||||
// Copyright © 2023 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.
|
||||
//
|
||||
|
||||
import { loadCollaborativeDoc, yDocToBuffer } from '@hcengineering/collaboration'
|
||||
import core, {
|
||||
Account,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
CollaborativeDoc,
|
||||
Data,
|
||||
Doc,
|
||||
Hierarchy,
|
||||
Ref,
|
||||
Space,
|
||||
Tx,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxFactory,
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc,
|
||||
Type
|
||||
} from '@hcengineering/core'
|
||||
import notification, { CommonInboxNotification } from '@hcengineering/notification'
|
||||
import { ServerKit, extractReferences, getHTML, parseHTML, yDocContentToNodes } from '@hcengineering/text'
|
||||
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
|
||||
import activity, { ActivityMessage, ActivityReference } from '@hcengineering/activity'
|
||||
import contact, { Person, PersonAccount } from '@hcengineering/contact'
|
||||
import {
|
||||
getPushCollaboratorTx,
|
||||
getCommonNotificationTxes,
|
||||
isMessageAlreadyNotified,
|
||||
shouldNotifyCommon
|
||||
} from '@hcengineering/server-notification-resources'
|
||||
|
||||
const extensions = [ServerKit]
|
||||
|
||||
export async function getPersonNotificationTxes (
|
||||
reference: Data<ActivityReference>,
|
||||
control: TriggerControl,
|
||||
senderId: Ref<Account>,
|
||||
space: Ref<Space>,
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
if (reference.attachedTo === senderId) {
|
||||
return []
|
||||
}
|
||||
|
||||
const receiver = (
|
||||
await control.modelDb.findAll(
|
||||
contact.class.PersonAccount,
|
||||
{
|
||||
person: reference.attachedTo as Ref<Person>
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
|
||||
if (receiver === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
const isAvailable = await isSpaceAvailable(receiver, space, control)
|
||||
|
||||
if (!isAvailable) {
|
||||
return []
|
||||
}
|
||||
|
||||
const res: Tx[] = []
|
||||
const collaboratorsTx = await getCollaboratorsTxes(reference, control, receiver)
|
||||
|
||||
if (collaboratorsTx !== undefined) {
|
||||
res.push(collaboratorsTx)
|
||||
}
|
||||
|
||||
if (await isReferenceAlreadyNotified(reference, receiver._id, control)) {
|
||||
return res
|
||||
}
|
||||
|
||||
const doc = (await control.findAll(reference.srcDocClass, { _id: reference.srcDocId }))[0]
|
||||
|
||||
if (doc === undefined) {
|
||||
return res
|
||||
}
|
||||
|
||||
const data: Partial<Data<CommonInboxNotification>> = {
|
||||
header: activity.string.MentionedYouIn,
|
||||
messageHtml: reference.message
|
||||
}
|
||||
|
||||
const notifyResult = await shouldNotifyCommon(control, receiver._id, notification.ids.MentionCommonNotificationType)
|
||||
const texes = await getCommonNotificationTxes(
|
||||
control,
|
||||
doc,
|
||||
data,
|
||||
receiver._id,
|
||||
senderId,
|
||||
reference.srcDocId,
|
||||
reference.srcDocClass,
|
||||
space,
|
||||
originTx.modifiedOn,
|
||||
notifyResult
|
||||
)
|
||||
|
||||
res.push(...texes)
|
||||
return res
|
||||
}
|
||||
|
||||
async function isSpaceAvailable (user: PersonAccount, spaceId: Ref<Space>, control: TriggerControl): Promise<boolean> {
|
||||
const space = (await control.findAll<Space>(core.class.Space, { _id: spaceId }))[0]
|
||||
|
||||
if (!space?.private) {
|
||||
return true
|
||||
}
|
||||
|
||||
return space.members.includes(user._id)
|
||||
}
|
||||
|
||||
async function getCollaboratorsTxes (
|
||||
reference: Data<ActivityReference>,
|
||||
control: TriggerControl,
|
||||
receiver: Account
|
||||
): Promise<TxMixin<Doc, Doc> | undefined> {
|
||||
const { hierarchy } = control
|
||||
|
||||
if (reference.attachedDocClass === undefined || reference.attachedDocId === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (!hierarchy.isDerived(reference.attachedDocClass, activity.class.ActivityMessage)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const message = (
|
||||
await control.findAll<ActivityMessage>(
|
||||
reference.attachedDocClass,
|
||||
{
|
||||
_id: reference.attachedDocId as Ref<ActivityMessage>
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
)[0]
|
||||
|
||||
if (message === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getPushCollaboratorTx(control, receiver._id, message)
|
||||
}
|
||||
|
||||
async function isReferenceAlreadyNotified (
|
||||
reference: Data<ActivityReference>,
|
||||
receiver: Ref<Account>,
|
||||
control: TriggerControl
|
||||
): Promise<boolean> {
|
||||
const { hierarchy } = control
|
||||
|
||||
if (reference.attachedDocClass === undefined || reference.attachedDocId === undefined) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!hierarchy.isDerived(reference.attachedDocClass, activity.class.ActivityMessage)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return await isMessageAlreadyNotified(reference.attachedDocId as Ref<ActivityMessage>, receiver, control)
|
||||
}
|
||||
|
||||
function isMarkupType (type: Ref<Class<Type<any>>>): boolean {
|
||||
return type === core.class.TypeMarkup || type === core.class.TypeCollaborativeMarkup
|
||||
}
|
||||
|
||||
function isCollaborativeType (type: Ref<Class<Type<any>>>): boolean {
|
||||
return type === core.class.TypeCollaborativeDoc
|
||||
}
|
||||
|
||||
async function getCreateReferencesTxes (
|
||||
control: TriggerControl,
|
||||
storage: StorageAdapter,
|
||||
txFactory: TxFactory,
|
||||
createdDoc: Doc,
|
||||
srcDocId: Ref<Doc>,
|
||||
srcDocClass: Ref<Class<Doc>>,
|
||||
srcDocSpace: Ref<Space>,
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
const attachedDocId = createdDoc._id
|
||||
const attachedDocClass = createdDoc._class
|
||||
|
||||
const refs: Data<ActivityReference>[] = []
|
||||
const attributes = control.hierarchy.getAllAttributes(createdDoc._class)
|
||||
|
||||
for (const attr of attributes.values()) {
|
||||
if (isMarkupType(attr.type._class)) {
|
||||
const content = (createdDoc as any)[attr.name]?.toString() ?? ''
|
||||
const attrReferences = getReferencesData(srcDocId, srcDocClass, attachedDocId, attachedDocClass, content)
|
||||
|
||||
refs.push(...attrReferences)
|
||||
} else if (attr.type._class === core.class.TypeCollaborativeDoc) {
|
||||
const collaborativeDoc = (createdDoc as any)[attr.name] as CollaborativeDoc
|
||||
try {
|
||||
const ydoc = await loadCollaborativeDoc(storage, control.workspace, collaborativeDoc, control.ctx)
|
||||
if (ydoc !== undefined) {
|
||||
const attrReferences = getReferencesData(
|
||||
srcDocId,
|
||||
srcDocClass,
|
||||
attachedDocId,
|
||||
attachedDocClass,
|
||||
yDocToBuffer(ydoc)
|
||||
)
|
||||
refs.push(...attrReferences)
|
||||
}
|
||||
} catch {
|
||||
// do nothing, the collaborative doc does not sem to exist yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const refSpace: Ref<Space> = control.hierarchy.isDerived(srcDocClass, core.class.Space)
|
||||
? (srcDocId as Ref<Space>)
|
||||
: srcDocSpace
|
||||
|
||||
return await getReferencesTxes(control, txFactory, refs, refSpace, [], originTx)
|
||||
}
|
||||
|
||||
async function getUpdateReferencesTxes (
|
||||
control: TriggerControl,
|
||||
storage: StorageAdapter,
|
||||
txFactory: TxFactory,
|
||||
updatedDoc: Doc,
|
||||
srcDocId: Ref<Doc>,
|
||||
srcDocClass: Ref<Class<Doc>>,
|
||||
srcDocSpace: Ref<Space>,
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
const attachedDocId = updatedDoc._id
|
||||
const attachedDocClass = updatedDoc._class
|
||||
|
||||
// collect attribute references
|
||||
let hasReferenceAttrs = false
|
||||
const references: Data<ActivityReference>[] = []
|
||||
const attributes = control.hierarchy.getAllAttributes(updatedDoc._class)
|
||||
for (const attr of attributes.values()) {
|
||||
if (isMarkupType(attr.type._class)) {
|
||||
hasReferenceAttrs = true
|
||||
const content = (updatedDoc as any)[attr.name]?.toString() ?? ''
|
||||
const attrReferences = getReferencesData(srcDocId, srcDocClass, attachedDocId, attachedDocClass, content)
|
||||
references.push(...attrReferences)
|
||||
} else if (attr.type._class === core.class.TypeCollaborativeDoc) {
|
||||
hasReferenceAttrs = true
|
||||
try {
|
||||
const collaborativeDoc = (updatedDoc as any)[attr.name] as CollaborativeDoc
|
||||
const ydoc = await loadCollaborativeDoc(storage, control.workspace, collaborativeDoc, control.ctx)
|
||||
if (ydoc !== undefined) {
|
||||
const attrReferences = getReferencesData(
|
||||
srcDocId,
|
||||
srcDocClass,
|
||||
attachedDocId,
|
||||
attachedDocClass,
|
||||
yDocToBuffer(ydoc)
|
||||
)
|
||||
references.push(...attrReferences)
|
||||
}
|
||||
} catch {
|
||||
// do nothing, the collaborative doc does not sem to exist yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There is a chance that references are managed manually
|
||||
// do not update references if there are no reference sources in the doc
|
||||
if (hasReferenceAttrs) {
|
||||
const current = await control.findAll(activity.class.ActivityReference, {
|
||||
srcDocId,
|
||||
srcDocClass,
|
||||
attachedDocId,
|
||||
collection: 'references'
|
||||
})
|
||||
|
||||
const refSpace: Ref<Space> = control.hierarchy.isDerived(srcDocClass, core.class.Space)
|
||||
? (srcDocId as Ref<Space>)
|
||||
: srcDocSpace
|
||||
|
||||
return await getReferencesTxes(control, txFactory, references, refSpace, current, originTx)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
export function getReferencesData (
|
||||
srcDocId: Ref<Doc>,
|
||||
srcDocClass: Ref<Class<Doc>>,
|
||||
attachedDocId: Ref<Doc> | undefined,
|
||||
attachedDocClass: Ref<Class<Doc>> | undefined,
|
||||
content: string | Buffer
|
||||
): Array<Data<ActivityReference>> {
|
||||
const result: Array<Data<ActivityReference>> = []
|
||||
const references = []
|
||||
|
||||
if (content instanceof Buffer) {
|
||||
const nodes = yDocContentToNodes(extensions, content)
|
||||
for (const node of nodes) {
|
||||
references.push(...extractReferences(node))
|
||||
}
|
||||
} else {
|
||||
const doc = parseHTML(content, extensions)
|
||||
references.push(...extractReferences(doc))
|
||||
}
|
||||
|
||||
for (const ref of references) {
|
||||
if (ref.objectId !== attachedDocId && ref.objectId !== srcDocId) {
|
||||
result.push({
|
||||
attachedTo: ref.objectId,
|
||||
attachedToClass: ref.objectClass,
|
||||
collection: 'references',
|
||||
srcDocId,
|
||||
srcDocClass,
|
||||
message: ref.parentNode !== null ? getHTML(ref.parentNode, extensions) : '',
|
||||
attachedDocId,
|
||||
attachedDocClass
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function createReferenceTxes (
|
||||
control: TriggerControl,
|
||||
txFactory: TxFactory,
|
||||
ref: Data<ActivityReference>,
|
||||
space: Ref<Space>,
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
if (control.hierarchy.isDerived(ref.attachedToClass, contact.class.Person)) {
|
||||
return await getPersonNotificationTxes(ref, control, txFactory.account, space, originTx)
|
||||
}
|
||||
|
||||
const refTx = control.txFactory.createTxCreateDoc(activity.class.ActivityReference, space, ref)
|
||||
const tx = control.txFactory.createTxCollectionCUD(ref.attachedToClass, ref.attachedTo, space, ref.collection, refTx)
|
||||
|
||||
return [tx]
|
||||
}
|
||||
|
||||
async function getReferencesTxes (
|
||||
control: TriggerControl,
|
||||
txFactory: TxFactory,
|
||||
references: Data<ActivityReference>[],
|
||||
space: Ref<Space>,
|
||||
current: ActivityReference[],
|
||||
originTx: TxCUD<Doc>
|
||||
): Promise<Tx[]> {
|
||||
const txes: Tx[] = []
|
||||
|
||||
for (const c of current) {
|
||||
// Find existing and check if we need to update message
|
||||
const pos = references.findIndex(
|
||||
(b) => b.srcDocId === c.srcDocId && b.srcDocClass === c.srcDocClass && b.attachedTo === c.attachedTo
|
||||
)
|
||||
if (pos !== -1) {
|
||||
// Update existing references when message changed
|
||||
const data = references[pos]
|
||||
if (c.message !== data.message) {
|
||||
const innerTx = txFactory.createTxUpdateDoc(c._class, c.space, c._id, {
|
||||
message: data.message
|
||||
})
|
||||
txes.push(txFactory.createTxCollectionCUD(c.attachedToClass, c.attachedTo, c.space, c.collection, innerTx))
|
||||
}
|
||||
references.splice(pos, 1)
|
||||
} else {
|
||||
// Remove not found references
|
||||
const innerTx = txFactory.createTxRemoveDoc(c._class, c.space, c._id)
|
||||
txes.push(txFactory.createTxCollectionCUD(c.attachedToClass, c.attachedTo, c.space, c.collection, innerTx))
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing references
|
||||
for (const ref of references) {
|
||||
txes.push(...(await createReferenceTxes(control, txFactory, ref, space, originTx)))
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
async function getRemoveActivityReferenceTxes (
|
||||
control: TriggerControl,
|
||||
txFactory: TxFactory,
|
||||
removedDocId: Ref<Doc>
|
||||
): Promise<Tx[]> {
|
||||
const txes: Tx[] = []
|
||||
const refs = await control.findAll(activity.class.ActivityReference, {
|
||||
attachedDocId: removedDocId,
|
||||
collection: 'references'
|
||||
})
|
||||
|
||||
for (const ref of refs) {
|
||||
const removeTx = txFactory.createTxRemoveDoc(ref._class, ref.space, ref._id)
|
||||
txes.push(txFactory.createTxCollectionCUD(ref.attachedToClass, ref.attachedTo, ref.space, ref.collection, removeTx))
|
||||
}
|
||||
|
||||
return txes
|
||||
}
|
||||
|
||||
function guessReferenceTx (hierarchy: Hierarchy, tx: TxCUD<Doc>): TxCUD<Doc> {
|
||||
// Try to guess reference target Tx for TxCollectionCUD txes based on collaborators availability
|
||||
if (hierarchy.isDerived(tx._class, core.class.TxCollectionCUD)) {
|
||||
const cltx = tx as TxCollectionCUD<Doc, AttachedDoc>
|
||||
tx = TxProcessor.extractTx(cltx) as TxCUD<Doc>
|
||||
|
||||
if (hierarchy.isDerived(tx.objectClass, activity.class.ActivityMessage)) {
|
||||
return cltx
|
||||
}
|
||||
|
||||
const mixin = hierarchy.classHierarchyMixin(tx.objectClass, notification.mixin.ClassCollaborators)
|
||||
return mixin !== undefined ? tx : cltx
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
async function ActivityReferenceCreate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Doc>
|
||||
|
||||
if (ctx._class !== core.class.TxCreateDoc) return []
|
||||
if (control.hierarchy.isDerived(ctx.objectClass, activity.class.ActivityReference)) return []
|
||||
|
||||
control.storageFx(async (adapter) => {
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
|
||||
const doc = TxProcessor.createDoc2Doc(ctx)
|
||||
const targetTx = guessReferenceTx(control.hierarchy, tx)
|
||||
|
||||
const txes: Tx[] = await getCreateReferencesTxes(
|
||||
control,
|
||||
adapter,
|
||||
txFactory,
|
||||
doc,
|
||||
targetTx.objectId,
|
||||
targetTx.objectClass,
|
||||
targetTx.objectSpace,
|
||||
tx
|
||||
)
|
||||
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
})
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
async function ActivityReferenceUpdate (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Doc>
|
||||
const attributes = control.hierarchy.getAllAttributes(ctx.objectClass)
|
||||
|
||||
let hasUpdates = false
|
||||
|
||||
for (const attr of attributes.values()) {
|
||||
if (isMarkupType(attr.type._class) || isCollaborativeType(attr.type._class)) {
|
||||
if (TxProcessor.txHasUpdate(ctx, attr.name)) {
|
||||
hasUpdates = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasUpdates) {
|
||||
return []
|
||||
}
|
||||
|
||||
const rawDoc = (await control.findAll(ctx.objectClass, { _id: ctx.objectId }))[0]
|
||||
|
||||
if (rawDoc === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
control.storageFx(async (adapter) => {
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
const doc = TxProcessor.updateDoc2Doc(rawDoc, ctx)
|
||||
const targetTx = guessReferenceTx(control.hierarchy, tx)
|
||||
|
||||
const txes: Tx[] = await getUpdateReferencesTxes(
|
||||
control,
|
||||
adapter,
|
||||
txFactory,
|
||||
doc,
|
||||
targetTx.objectId,
|
||||
targetTx.objectClass,
|
||||
targetTx.objectSpace,
|
||||
tx
|
||||
)
|
||||
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
})
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
async function ActivityReferenceRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
|
||||
const ctx = TxProcessor.extractTx(tx) as TxRemoveDoc<Doc>
|
||||
const attributes = control.hierarchy.getAllAttributes(ctx.objectClass)
|
||||
|
||||
let hasMarkdown = false
|
||||
|
||||
for (const attr of attributes.values()) {
|
||||
if (isMarkupType(attr.type._class)) {
|
||||
hasMarkdown = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMarkdown) {
|
||||
const txFactory = new TxFactory(control.txFactory.account)
|
||||
|
||||
const txes: Tx[] = await getRemoveActivityReferenceTxes(control, txFactory, ctx.objectId)
|
||||
if (txes.length !== 0) {
|
||||
await control.apply(txes, true)
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function ReferenceTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
|
||||
const result: Tx[] = []
|
||||
|
||||
const etx = TxProcessor.extractTx(tx) as TxCreateDoc<Doc>
|
||||
if (control.hierarchy.isDerived(etx.objectClass, activity.class.ActivityReference)) return []
|
||||
|
||||
if (etx._class === core.class.TxCreateDoc) {
|
||||
result.push(...(await ActivityReferenceCreate(tx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
result.push(...(await ActivityReferenceUpdate(tx, control)))
|
||||
}
|
||||
if (etx._class === core.class.TxRemoveDoc) {
|
||||
result.push(...(await ActivityReferenceRemove(tx, control)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user