UBERF-8969 Rewrite extractReferences to use JSON only (#7506)

This commit is contained in:
Alexander Onnikov 2024-12-19 22:25:03 +07:00 committed by GitHub
parent 5bd3424c9b
commit 63464cbc54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 77 additions and 55 deletions

View File

@ -16,6 +16,7 @@
export * from './extensions' export * from './extensions'
export * from './markup/dsl' export * from './markup/dsl'
export * from './markup/model' export * from './markup/model'
export * from './markup/reference'
export * from './markup/traverse' export * from './markup/traverse'
export * from './markup/utils' export * from './markup/utils'
export * from './nodes' export * from './nodes'

View File

@ -17,8 +17,8 @@ describe('traverseNode', () => {
traverseNode(node as MarkupNode, callback) traverseNode(node as MarkupNode, callback)
expect(callback).toHaveBeenCalledTimes(2) expect(callback).toHaveBeenCalledTimes(2)
expect(callback).toHaveBeenCalledWith(node) expect(callback).toHaveBeenCalledWith(node, undefined)
expect(callback).toHaveBeenCalledWith(node.content[0]) expect(callback).toHaveBeenCalledWith(node.content[0], node)
}) })
it('should stop traversing if the callback returns false', () => { it('should stop traversing if the callback returns false', () => {
@ -40,7 +40,7 @@ describe('traverseNode', () => {
traverseNode(node, callback) traverseNode(node, callback)
expect(callback).toHaveBeenCalledTimes(1) expect(callback).toHaveBeenCalledTimes(1)
expect(callback).toHaveBeenCalledWith(node) expect(callback).toHaveBeenCalledWith(node, undefined)
}) })
}) })

View File

@ -84,6 +84,7 @@ export interface LinkMark extends MarkupMark {
} }
/** @public */ /** @public */
export interface ReferenceMark extends MarkupMark { export interface ReferenceMarkupNode extends MarkupNode {
type: MarkupNodeType.reference
attrs: { id: string, label: string, objectclass: string } attrs: { id: string, label: string, objectclass: string }
} }

View File

@ -0,0 +1,49 @@
//
// 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 { MarkupNode, MarkupNodeType, ReferenceMarkupNode } from '../markup/model'
import { traverseNode } from '../markup/traverse'
/**
* @public
*/
export interface Reference {
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
parentNode: MarkupNode | null
}
/**
* @public
*/
export function extractReferences (content: MarkupNode): Array<Reference> {
const result: Array<Reference> = []
traverseNode(content, (node, parent) => {
if (node.type === MarkupNodeType.reference) {
const reference = node as ReferenceMarkupNode
const objectId = reference.attrs.id as Ref<Doc>
const objectClass = reference.attrs.objectclass as Ref<Class<Doc>>
const e = result.find((e) => e.objectId === objectId && e.objectClass === objectClass)
if (e === undefined) {
result.push({ objectId, objectClass, parentNode: parent ?? node })
}
}
return true
})
return result
}

View File

@ -15,11 +15,22 @@
import { MarkupMark, MarkupNode } from './model' import { MarkupMark, MarkupNode } from './model'
export function traverseNode (node: MarkupNode, f: (el: MarkupNode) => boolean | undefined): void { export function traverseNode (
const result = f(node) node: MarkupNode,
fn: (el: MarkupNode, parent: MarkupNode | undefined) => boolean | undefined
): void {
_traverseNode(node, undefined, fn)
}
function _traverseNode (
node: MarkupNode,
parent: MarkupNode | undefined,
fn: (el: MarkupNode, parent: MarkupNode | undefined) => boolean | undefined
): void {
const result = fn(node, parent)
if (result !== false) { if (result !== false) {
node.content?.forEach((p) => { node.content?.forEach((p) => {
traverseNode(p, f) _traverseNode(p, node, fn)
}) })
} }
} }

View File

@ -14,14 +14,6 @@
// //
import { Node } from '@tiptap/core' import { Node } from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
/**
* @public
*/
export interface Comment {
parentNode: ProseMirrorNode | null
}
/** /**
* @public * @public

View File

@ -13,42 +13,9 @@
// limitations under the License. // limitations under the License.
// //
import { Class, Doc, Ref } from '@hcengineering/core'
import { Node, mergeAttributes } from '@tiptap/core' import { Node, mergeAttributes } from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { getDataAttribute } from './utils' import { getDataAttribute } from './utils'
/**
* @public
*/
export interface Reference {
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
parentNode: ProseMirrorNode | null
}
/**
* @public
*/
export function extractReferences (content: ProseMirrorNode): Array<Reference> {
const result: Array<Reference> = []
content.descendants((node, _pos, parent): boolean => {
if (node.type.name === ReferenceNode.name) {
const objectId = node.attrs.id as Ref<Doc>
const objectClass = node.attrs.objectclass as Ref<Class<Doc>>
const e = result.find((e) => e.objectId === objectId && e.objectClass === objectClass)
if (e === undefined) {
result.push({ objectId, objectClass, parentNode: parent })
}
}
return true
})
return result
}
export interface ReferenceOptions { export interface ReferenceOptions {
renderLabel: (props: { options: ReferenceOptions, node: any }) => string renderLabel: (props: { options: ReferenceOptions, node: any }) => string
suggestion: { char: string } suggestion: { char: string }

View File

@ -73,7 +73,7 @@ describe('extractBacklinks', () => {
srcDocId: 'srcDocId', srcDocId: 'srcDocId',
srcDocClass: 'srcDocClass', srcDocClass: 'srcDocClass',
message: message:
'{"type":"paragraph","content":[{"type":"reference","attrs":{"id":"id","objectclass":"contact:class:Person","label":"Appleseed John","class":null}},{"type":"text","text":" hello"}]}', '{"type":"paragraph","content":[{"type":"reference","attrs":{"id":"id","objectclass":"contact:class:Person","label":"Appleseed John"}},{"type":"text","text":" hello"}]}',
attachedDocId: 'attachedDocId', attachedDocId: 'attachedDocId',
attachedDocClass: 'attachedDocClass' attachedDocClass: 'attachedDocClass'
} }

View File

@ -23,6 +23,7 @@ import core, {
Doc, Doc,
generateId, generateId,
Hierarchy, Hierarchy,
Markup,
Ref, Ref,
Space, Space,
Tx, Tx,
@ -50,12 +51,12 @@ import {
toReceiverInfo, toReceiverInfo,
type NotificationProviderControl type NotificationProviderControl
} from '@hcengineering/server-notification-resources' } from '@hcengineering/server-notification-resources'
import { areEqualJson, extractReferences, markupToPmNode, pmNodeToMarkup } from '@hcengineering/text' import { areEqualJson, extractReferences, jsonToMarkup, markupToJSON } from '@hcengineering/text'
export function isDocMentioned (doc: Ref<Doc>, content: string): boolean { export function isDocMentioned (doc: Ref<Doc>, content: string): boolean {
const references = [] const references = []
const node = markupToPmNode(content) const node = markupToJSON(content)
references.push(...extractReferences(node)) references.push(...extractReferences(node))
for (const ref of references) { for (const ref of references) {
@ -491,13 +492,13 @@ export function getReferencesData (
srcDocClass: Ref<Class<Doc>>, srcDocClass: Ref<Class<Doc>>,
attachedDocId: Ref<Doc> | undefined, attachedDocId: Ref<Doc> | undefined,
attachedDocClass: Ref<Class<Doc>> | undefined, attachedDocClass: Ref<Class<Doc>> | undefined,
content: string content: Markup
): Array<Data<ActivityReference>> { ): Array<Data<ActivityReference>> {
const result: Array<Data<ActivityReference>> = [] const result: Array<Data<ActivityReference>> = []
const references = [] const references = []
const doc = markupToPmNode(content) const node = markupToJSON(content)
references.push(...extractReferences(doc)) references.push(...extractReferences(node))
for (const ref of references) { for (const ref of references) {
if (ref.objectId !== attachedDocId && ref.objectId !== srcDocId) { if (ref.objectId !== attachedDocId && ref.objectId !== srcDocId) {
@ -507,7 +508,7 @@ export function getReferencesData (
collection: 'references', collection: 'references',
srcDocId, srcDocId,
srcDocClass, srcDocClass,
message: ref.parentNode !== null ? pmNodeToMarkup(ref.parentNode) : '', message: ref.parentNode !== null ? jsonToMarkup(ref.parentNode) : '',
attachedDocId, attachedDocId,
attachedDocClass attachedDocClass
}) })