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 './markup/dsl'
export * from './markup/model'
export * from './markup/reference'
export * from './markup/traverse'
export * from './markup/utils'
export * from './nodes'

View File

@ -17,8 +17,8 @@ describe('traverseNode', () => {
traverseNode(node as MarkupNode, callback)
expect(callback).toHaveBeenCalledTimes(2)
expect(callback).toHaveBeenCalledWith(node)
expect(callback).toHaveBeenCalledWith(node.content[0])
expect(callback).toHaveBeenCalledWith(node, undefined)
expect(callback).toHaveBeenCalledWith(node.content[0], node)
})
it('should stop traversing if the callback returns false', () => {
@ -40,7 +40,7 @@ describe('traverseNode', () => {
traverseNode(node, callback)
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 */
export interface ReferenceMark extends MarkupMark {
export interface ReferenceMarkupNode extends MarkupNode {
type: MarkupNodeType.reference
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'
export function traverseNode (node: MarkupNode, f: (el: MarkupNode) => boolean | undefined): void {
const result = f(node)
export function traverseNode (
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) {
node.content?.forEach((p) => {
traverseNode(p, f)
_traverseNode(p, node, fn)
})
}
}

View File

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

View File

@ -13,42 +13,9 @@
// limitations under the License.
//
import { Class, Doc, Ref } from '@hcengineering/core'
import { Node, mergeAttributes } from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
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 {
renderLabel: (props: { options: ReferenceOptions, node: any }) => string
suggestion: { char: string }

View File

@ -73,7 +73,7 @@ describe('extractBacklinks', () => {
srcDocId: 'srcDocId',
srcDocClass: 'srcDocClass',
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',
attachedDocClass: 'attachedDocClass'
}

View File

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