From 8f31c6ec619f24750bdb70c4de79db042f595deb Mon Sep 17 00:00:00 2001 From: Victor Ilyushchenko Date: Fri, 17 Jan 2025 13:16:52 +0300 Subject: [PATCH] Fixed issues with loading comment mark schema in non collaborative contexts and added codeblock support (#7699) Signed-off-by: Victor Ilyushchenko --- packages/text/src/index.ts | 1 + packages/text/src/kits/server-kit.ts | 2 + packages/text/src/marks/inlineComment.ts | 87 +++++++++++++++++++ packages/text/src/nodes/codeblock.ts | 2 + packages/text/src/nodes/mermaid.ts | 1 + .../components/CollaborativeTextEditor.svelte | 9 +- .../src/components/extension/codeblock.ts | 2 + .../src/components/extension/inlineComment.ts | 82 ++++------------- .../src/components/extension/mermaid.ts | 1 + .../src/kits/editor-kit.ts | 10 ++- 10 files changed, 129 insertions(+), 68 deletions(-) create mode 100644 packages/text/src/marks/inlineComment.ts diff --git a/packages/text/src/index.ts b/packages/text/src/index.ts index d543c4f264..b3601a4b99 100644 --- a/packages/text/src/index.ts +++ b/packages/text/src/index.ts @@ -24,6 +24,7 @@ export * from './nodes' export * from './marks/code' export * from './marks/colors' export * from './marks/noteBase' +export * from './marks/inlineComment' export * from './markdown' export * from './markdown/serializer' export * from './markdown/parser' diff --git a/packages/text/src/kits/server-kit.ts b/packages/text/src/kits/server-kit.ts index e5366ba7b6..21ca67c146 100644 --- a/packages/text/src/kits/server-kit.ts +++ b/packages/text/src/kits/server-kit.ts @@ -37,6 +37,7 @@ import { MermaidExtension, mermaidOptions } from '../nodes/mermaid' import TextAlign from '@tiptap/extension-text-align' import TextStyle from '@tiptap/extension-text-style' import { BackgroundColor, TextColor } from '../marks/colors' +import { InlineCommentMark } from '../marks/inlineComment' const headingLevels: Level[] = [1, 2, 3, 4, 5, 6] @@ -84,6 +85,7 @@ export const ServerKit = Extension.create({ levels: headingLevels } }), + InlineCommentMark.configure({}), CodeBlockExtension.configure(codeBlockOptions), CodeExtension.configure(codeOptions), MermaidExtension.configure(mermaidOptions), diff --git a/packages/text/src/marks/inlineComment.ts b/packages/text/src/marks/inlineComment.ts new file mode 100644 index 0000000000..a2d942fc57 --- /dev/null +++ b/packages/text/src/marks/inlineComment.ts @@ -0,0 +1,87 @@ +// +// Copyright © 2025 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 { Mark } from '@tiptap/core' +import { Fragment, Node, Slice } from '@tiptap/pm/model' +import { Plugin, PluginKey } from '@tiptap/pm/state' + +export const InlineCommentMark = Mark.create({ + name: 'inline-comment', + excludes: '', + + inclusive: false, + + parseHTML () { + return [ + { + tag: 'span.proseInlineComment[data-inline-comment-thread]' + } + ] + }, + + renderHTML ({ HTMLAttributes, mark }) { + return ['span', { ...HTMLAttributes, class: 'proseInlineComment' }, 0] + }, + + addAttributes () { + const name = 'data-inline-comment-thread-id' + return { + thread: { + default: undefined, + parseHTML: (element) => { + return element.getAttribute(name) + }, + renderHTML: (attributes) => { + return { [name]: attributes.thread } + } + } + } + }, + + addProseMirrorPlugins () { + return [...(this.parent?.() ?? []), InlineCommentPasteFixPlugin()] + } +}) + +function removeMarkFromNode (node: Node, name: string): Node { + if (node.isText) { + return node.mark(node.marks.filter((mark) => mark.type.name !== name)) + } + + if (node.content.size > 0) { + const nodes: Node[] = [] + node.content.forEach((child) => { + nodes.push(removeMarkFromNode(child, name)) + }) + return node.copy(Fragment.fromArray(nodes)) + } + + return node +} + +export function InlineCommentPasteFixPlugin (): Plugin { + return new Plugin({ + key: new PluginKey('inline-comment-paste-fix-plugin'), + props: { + transformPasted: (slice) => { + const nodes: Node[] = [] + slice.content.forEach((node) => { + nodes.push(removeMarkFromNode(node, 'inline-comment')) + }) + return new Slice(Fragment.fromArray(nodes), slice.openStart, slice.openEnd) + } + } + }) +} diff --git a/packages/text/src/nodes/codeblock.ts b/packages/text/src/nodes/codeblock.ts index 4b438d24e9..29e686ee29 100644 --- a/packages/text/src/nodes/codeblock.ts +++ b/packages/text/src/nodes/codeblock.ts @@ -37,6 +37,8 @@ export const backtickInputRegex = /^```$/ export const tildeInputRegex = /^~~~$/ export const CodeBlockExtension = CodeBlock.extend({ + marks: 'inline-comment', + addAttributes () { return { language: { diff --git a/packages/text/src/nodes/mermaid.ts b/packages/text/src/nodes/mermaid.ts index dd128740d7..4290c60864 100644 --- a/packages/text/src/nodes/mermaid.ts +++ b/packages/text/src/nodes/mermaid.ts @@ -24,6 +24,7 @@ export const mermaidOptions: CodeBlockOptions = { export const MermaidExtension = CodeBlock.extend({ name: 'mermaid', group: 'block', + marks: 'inline-comment', parseHTML () { return [ diff --git a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte index 86c18ab186..8ff156d60a 100644 --- a/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte +++ b/plugins/text-editor-resources/src/components/CollaborativeTextEditor.svelte @@ -83,7 +83,7 @@ import { type FileAttachFunction } from './extension/types' import { completionConfig, inlineCommandsConfig } from './extensions' import { mermaidOptions } from './extension/mermaid' - import { InlineCommentExtension } from './extension/inlineComment' + import { InlineCommentCollaborationExtension } from './extension/inlineComment' export let object: Doc export let attribute: KeyedAttribute @@ -435,7 +435,12 @@ if (enableInlineComments) { optionalExtensions.push( - InlineCommentExtension.configure({ ydoc, boundary, popupContainer: editorPopupContainer, requestSideSpace }) + InlineCommentCollaborationExtension.configure({ + ydoc, + boundary, + popupContainer: editorPopupContainer, + requestSideSpace + }) ) } diff --git a/plugins/text-editor-resources/src/components/extension/codeblock.ts b/plugins/text-editor-resources/src/components/extension/codeblock.ts index 377dbed45c..905fe4eb08 100644 --- a/plugins/text-editor-resources/src/components/extension/codeblock.ts +++ b/plugins/text-editor-resources/src/components/extension/codeblock.ts @@ -36,6 +36,8 @@ export const codeBlockHighlightOptions: CodeBlockLowlightOptions = { } export const CodeBlockHighlighExtension = CodeBlockLowlight.extend({ + marks: 'inline-comment', + addCommands () { return { setCodeBlock: diff --git a/plugins/text-editor-resources/src/components/extension/inlineComment.ts b/plugins/text-editor-resources/src/components/extension/inlineComment.ts index a90803e208..52cb75df72 100644 --- a/plugins/text-editor-resources/src/components/extension/inlineComment.ts +++ b/plugins/text-editor-resources/src/components/extension/inlineComment.ts @@ -13,8 +13,20 @@ // limitations under the License. // +import chunter from '@hcengineering/chunter' +import core, { + type Account, + type Markup, + type Ref, + type Timestamp, + generateId, + getCurrentAccount +} from '@hcengineering/core' import { getResource } from '@hcengineering/platform' -import { type Editor, Mark } from '@tiptap/core' +import { type ActionContext } from '@hcengineering/presentation' +import type { AnySvelteComponent } from '@hcengineering/ui' +import { type Editor, Extension } from '@tiptap/core' +import { type Node } from '@tiptap/pm/model' import { type EditorState, Plugin, @@ -25,16 +37,10 @@ import { type Transaction } from '@tiptap/pm/state' import { Decoration, DecorationSet, type EditorView } from '@tiptap/pm/view' -import { SvelteRenderer } from '../node-view' -import type { AnySvelteComponent } from '@hcengineering/ui' -import { Fragment, Slice, type Node } from '@tiptap/pm/model' -import { type Account, type Markup, type Ref, type Timestamp, getCurrentAccount, generateId } from '@hcengineering/core' import tippy, { type Instance } from 'tippy.js' import 'tippy.js/animations/shift-toward.css' import { type Doc as YDoc, type Map as YMap } from 'yjs' -import core from '@hcengineering/core' -import { type ActionContext } from '@hcengineering/presentation' -import chunter from '@hcengineering/chunter' +import { SvelteRenderer } from '../node-view' interface InlineCommentExtensionOptions { boundary?: HTMLElement @@ -111,41 +117,10 @@ interface ThreadPresenterProps { handleResolveThread?: (() => void) | undefined } -const extensionName = 'inline-comment' - -export const InlineCommentExtension = Mark.create({ - name: 'inline-comment', - excludes: '', - - inclusive: false, - - parseHTML () { - return [ - { - tag: 'span.proseInlineComment[data-inline-comment-thread]' - } - ] - }, - - renderHTML ({ HTMLAttributes, mark }) { - return ['span', { ...HTMLAttributes, class: 'proseInlineComment' }, 0] - }, - - addAttributes () { - const name = 'data-inline-comment-thread-id' - return { - thread: { - default: undefined, - parseHTML: (element) => { - return element.getAttribute(name) - }, - renderHTML: (attributes) => { - return { [name]: attributes.thread } - } - } - } - }, +const extensionName = 'inlineCommentCollaboration' +export const InlineCommentCollaborationExtension = Extension.create({ + name: extensionName, addProseMirrorPlugins () { return [...(this.parent?.() ?? []), InlineCommentDecorator(this.options)] } @@ -180,13 +155,6 @@ export function InlineCommentDecorator (options: InlineCommentExtensionOptions): }, handleDOMEvents: { mousemove: handleInlineCommentMouseHover - }, - transformPasted: (slice) => { - const nodes: Node[] = [] - slice.content.forEach((node) => { - nodes.push(removeMarkFromNode(node, 'inline-comment')) - }) - return new Slice(Fragment.fromArray(nodes), slice.openStart, slice.openEnd) } }, state: { @@ -491,22 +459,6 @@ function handleInlineCommentMouseHover (view: EditorView, event: MouseEvent): vo updatePointerState(view, { hover: threadIds }) } -function removeMarkFromNode (node: Node, name: string): Node { - if (node.isText) { - return node.mark(node.marks.filter((mark) => mark.type.name !== name)) - } - - if (node.content.size > 0) { - const nodes: Node[] = [] - node.content.forEach((child) => { - nodes.push(removeMarkFromNode(child, name)) - }) - return node.copy(Fragment.fromArray(nodes)) - } - - return node -} - interface InlineCommentViewProps { siblings: InlineCommentView[] thread: Thread diff --git a/plugins/text-editor-resources/src/components/extension/mermaid.ts b/plugins/text-editor-resources/src/components/extension/mermaid.ts index dda454111d..f19c2f37fc 100644 --- a/plugins/text-editor-resources/src/components/extension/mermaid.ts +++ b/plugins/text-editor-resources/src/components/extension/mermaid.ts @@ -71,6 +71,7 @@ interface NodePatchSpec { export const MermaidExtension = CodeBlockLowlight.extend({ name: 'mermaid', group: 'block', + marks: 'inline-comment', draggable: true, selectable: true, diff --git a/plugins/text-editor-resources/src/kits/editor-kit.ts b/plugins/text-editor-resources/src/kits/editor-kit.ts index ca38044af0..3b9adc535a 100644 --- a/plugins/text-editor-resources/src/kits/editor-kit.ts +++ b/plugins/text-editor-resources/src/kits/editor-kit.ts @@ -15,7 +15,14 @@ import { type Class, type Doc, type Ref, type Space } from '@hcengineering/core' import { getResource } from '@hcengineering/platform' import { getBlobRef, getClient } from '@hcengineering/presentation' -import { BackgroundColor, CodeExtension, codeOptions, TextColor, TextStyle } from '@hcengineering/text' +import { + BackgroundColor, + CodeExtension, + codeOptions, + InlineCommentMark, + TextColor, + TextStyle +} from '@hcengineering/text' import textEditor, { type ActionContext, type ExtensionCreator, type TextEditorMode } from '@hcengineering/text-editor' import { type AnyExtension, type Editor, Extension } from '@tiptap/core' import { type Level } from '@tiptap/extension-heading' @@ -182,6 +189,7 @@ async function buildEditorKit (): Promise> { }) ], [110, EditableExtension], + [150, InlineCommentMark.configure({})], [200, CodeBlockHighlighExtension.configure(codeBlockHighlightOptions)], [210, CodeExtension.configure(codeOptions)], [220, HardBreakExtension.configure({ shortcuts: mode })]