UBER-1120 Fix backlinks creation for wiki documents (#3896)

Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2023-10-26 22:41:44 +07:00 committed by GitHub
parent 16d3ef147a
commit d7c1790d8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 162 additions and 91 deletions

View File

@ -475,6 +475,5 @@
.text-input {
font-size: 0.9375rem;
padding-bottom: 30vh;
}
</style>

View File

@ -14,12 +14,12 @@
//
import { getMetadata } from '@hcengineering/platform'
import presentation, { PDFViewer, getFileUrl } from '@hcengineering/presentation'
import { ImageNode, ImageOptions as ImageNodeOptions } from '@hcengineering/text'
import { IconSize, getIconSize2x, showPopup } from '@hcengineering/ui'
import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core'
import { mergeAttributes, nodeInputRule } from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'
import { EditorView } from '@tiptap/pm/view'
import { getDataAttribute } from '../../utils'
/**
* @public
@ -34,12 +34,8 @@ export type ImageAlignment = 'center' | 'left' | 'right'
/**
* @public
*/
export interface ImageOptions {
inline: boolean
HTMLAttributes: Record<string, any>
export interface ImageOptions extends ImageNodeOptions {
attachFile?: FileAttachFunction
reportNode?: (id: string, node: ProseMirrorNode) => void
}
@ -87,9 +83,7 @@ function getType (type: string): 'image' | 'other' {
/**
* @public
*/
export const ImageExtension = Node.create<ImageOptions>({
name: 'image',
export const ImageExtension = ImageNode.extend<ImageOptions>({
addOptions () {
return {
inline: true,
@ -97,42 +91,6 @@ export const ImageExtension = Node.create<ImageOptions>({
}
},
inline () {
return this.options.inline
},
group () {
return this.options.inline ? 'inline' : 'block'
},
draggable: true,
selectable: true,
addAttributes () {
return {
'file-id': {
default: null
},
width: {
default: null
},
height: {
default: null
},
src: {
default: null
},
alt: {
default: null
},
title: {
default: null
},
align: getDataAttribute('align')
}
},
parseHTML () {
return [
{

View File

@ -25,6 +25,7 @@ import TaskItem from '@tiptap/extension-task-item'
import TaskList from '@tiptap/extension-task-list'
import Typography from '@tiptap/extension-typography'
import StarterKit from '@tiptap/starter-kit'
import { ImageNode, ReferenceNode } from './nodes'
/**
* @public
@ -92,3 +93,8 @@ export const defaultExtensions: AnyExtension[] = [
...tableExtensions,
...taskListExtensions
]
/**
* @public
*/
export const serverExtensions: AnyExtension[] = [...defaultExtensions, ImageNode, ReferenceNode]

View File

@ -0,0 +1,103 @@
//
// 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 { Node, mergeAttributes } from '@tiptap/core'
import { getDataAttribute } from './utils'
/**
* @public
*/
export interface ImageOptions {
inline: boolean
HTMLAttributes: Record<string, any>
}
/**
* @public
*/
export const ImageNode = Node.create<ImageOptions>({
name: 'image',
addOptions () {
return {
inline: true,
HTMLAttributes: {}
}
},
inline () {
return this.options.inline
},
group () {
return this.options.inline ? 'inline' : 'block'
},
draggable: true,
selectable: true,
addAttributes () {
return {
'file-id': {
default: null
},
width: {
default: null
},
height: {
default: null
},
src: {
default: null
},
alt: {
default: null
},
title: {
default: null
},
align: getDataAttribute('align')
}
},
parseHTML () {
return [
{
tag: `img[data-type="${this.name}"]`
},
{
tag: 'img[src]'
}
]
},
renderHTML ({ node, HTMLAttributes }) {
const divAttributes = {
class: 'text-editor-image-container',
'data-type': this.name,
'data-align': node.attrs.align
}
const imgAttributes = mergeAttributes(
{
'data-type': this.name
},
this.options.HTMLAttributes,
HTMLAttributes
)
return ['div', divAttributes, ['img', imgAttributes]]
}
})

View File

@ -13,4 +13,6 @@
// limitations under the License.
//
export * from './image'
export * from './reference'
export { getDataAttribute } from './utils'

View File

@ -16,6 +16,7 @@
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
@ -59,47 +60,11 @@ export const ReferenceNode = Node.create({
addAttributes () {
return {
id: {
default: null,
parseHTML: (element) => element.getAttribute('data-id'),
renderHTML: (attributes) => {
if (attributes.id === null) {
return {}
}
id: getDataAttribute('id'),
return {
'data-id': attributes.id
}
}
},
objectclass: getDataAttribute('objectclass'),
objectclass: {
default: null,
parseHTML: (element) => element.getAttribute('data-objectclass'),
renderHTML: (attributes) => {
if (attributes.objectclass === null) {
return {}
}
return {
'data-objectclass': attributes.objectclass
}
}
},
label: {
default: null,
parseHTML: (element) => element.getAttribute('data-label'),
renderHTML: (attributes) => {
if (attributes.label === null) {
return {}
}
return {
'data-label': attributes.label
}
}
},
label: getDataAttribute('label'),
class: {
default: null

View File

@ -0,0 +1,38 @@
//
// 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 { Attribute } from '@tiptap/core'
/**
* @public
*/
export function getDataAttribute (name: string, def?: unknown | null): Partial<Attribute> {
const dataName = `data-${name}`
return {
default: def,
parseHTML: (element) => element.getAttribute(dataName),
renderHTML: (attributes) => {
// eslint-disable-next-line
if (!attributes[name]) {
return {}
}
return {
[dataName]: attributes[name]
}
}
}
}

View File

@ -54,13 +54,13 @@ export function openPanel (
}
export function closePanel (shoulRedirect: boolean = true): void {
currentLocation = undefined
panelstore.update(() => {
return { panel: undefined }
})
if (shoulRedirect) {
const loc = getLocation()
loc.fragment = undefined
currentLocation = undefined
navigate(loc)
}
}

View File

@ -15,9 +15,9 @@
import chunter, { Backlink } from '@hcengineering/chunter'
import { Class, Data, Doc, Ref, Tx, TxFactory } from '@hcengineering/core'
import { defaultExtensions, extractReferences, getHTML, parseHTML, ReferenceNode } from '@hcengineering/text'
import { extractReferences, getHTML, parseHTML, serverExtensions } from '@hcengineering/text'
const extensions = [...defaultExtensions, ReferenceNode]
const extensions = serverExtensions
export function getBacklinks (
backlinkId: Ref<Doc>,

View File

@ -1,9 +1,9 @@
import { MeasureContext, WorkspaceId } from '@hcengineering/core'
import { ContentTextAdapter } from '@hcengineering/server-core'
import { ReferenceNode, defaultExtensions, getText, yDocContentToNodes } from '@hcengineering/text'
import { getText, serverExtensions, yDocContentToNodes } from '@hcengineering/text'
import { Readable } from 'stream'
const extensions = [...defaultExtensions, ReferenceNode]
const extensions = serverExtensions
/**
* @public