From e0fd9b2ade1efd09536ddaee49122982b211409a Mon Sep 17 00:00:00 2001 From: Anna No Date: Thu, 28 Sep 2023 19:54:14 +0700 Subject: [PATCH 1/2] EZQMS-266: Commenting on document Signed-off-by: Anna No --- .../src/components/CollaboratorEditor.svelte | 87 ++----------- .../src/components/extension/nodeHighlight.ts | 117 +++++++++--------- .../src/components/extension/nodeUuid.ts | 56 ++++++--- .../text-editor/src/components/extensions.ts | 2 + packages/text-editor/src/index.ts | 7 ++ packages/text-editor/src/types.ts | 3 +- packages/theme/styles/_text-editor.scss | 8 ++ .../src/components/ObjectPresenter.svelte | 1 + 8 files changed, 129 insertions(+), 152 deletions(-) diff --git a/packages/text-editor/src/components/CollaboratorEditor.svelte b/packages/text-editor/src/components/CollaboratorEditor.svelte index 3ed19c7ea1..61ca51698e 100644 --- a/packages/text-editor/src/components/CollaboratorEditor.svelte +++ b/packages/text-editor/src/components/CollaboratorEditor.svelte @@ -24,17 +24,15 @@ import Collaboration from '@tiptap/extension-collaboration' import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import Placeholder from '@tiptap/extension-placeholder' - import BubbleMenu from '@tiptap/extension-bubble-menu' - import { generateId, getCurrentAccount, Markup } from '@hcengineering/core' + import { getCurrentAccount, Markup } from '@hcengineering/core' import { IntlString, translate } from '@hcengineering/platform' - import { Component, getPlatformColorForText, IconObjects, IconSize, themeStore } from '@hcengineering/ui' + import { getPlatformColorForText, IconObjects, IconSize, themeStore } from '@hcengineering/ui' import textEditorPlugin from '../plugin' import { CollaborationIds, TextFormatCategory, TextNodeAction } from '../types' import { calculateDecorations } from './diff/decorations' import { defaultExtensions } from './extensions' - import { NodeHighlightExtension, NodeHighlightType } from './extension/nodeHighlight' import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte' import StyleButton from './StyleButton.svelte' @@ -60,9 +58,9 @@ export let autoOverflow = false export let initialContent: string | undefined = undefined export let textNodeActions: TextNodeAction[] = [] - export let extensions: AnyExtension[] = [] - export let isNodeHighlightModeOn: boolean = false - export let onNodeHighlightType: (uuid: string) => NodeHighlightType = () => NodeHighlightType.WARNING + export let onExtensions: () => AnyExtension[] = () => [] + + let element: HTMLElement const ydoc = (getContext(CollaborationIds.Doc) as Y.Doc | undefined) ?? new Y.Doc() const contextProvider = getContext(CollaborationIds.Provider) as WebsocketProvider | undefined @@ -84,10 +82,6 @@ const currentUser = getCurrentAccount() - let currentTextNodeAction: TextNodeAction | undefined | null - let selectedNodeUuid: string | null | undefined - let textNodeActionMenuElement: HTMLElement - let element: HTMLElement let editor: Editor let placeHolderStr: string = '' @@ -177,18 +171,6 @@ } } - const getNodeUuid = () => { - if (editor.view.state.selection.empty) { - return null - } - if (!selectedNodeUuid) { - selectedNodeUuid = generateId() - editor.chain().setUuid(selectedNodeUuid!).run() - } - - return selectedNodeUuid - } - const DecorationExtension = Extension.create({ addProseMirrorPlugins () { return [ @@ -231,34 +213,7 @@ } }), DecorationExtension, - NodeHighlightExtension.configure({ - isHighlightModeOn: () => isNodeHighlightModeOn, - getNodeHighlightType: onNodeHighlightType, - onNodeSelected: (uuid: string | null) => { - if (selectedNodeUuid !== uuid) { - selectedNodeUuid = uuid - dispatch('node-selected', selectedNodeUuid) - } - } - }), - BubbleMenu.configure({ - pluginKey: 'text-node-action-menu', - element: textNodeActionMenuElement, - tippyOptions: { - maxWidth: '38rem', - onClickOutside: () => { - currentTextNodeAction = undefined - } - }, - shouldShow: (editor) => { - if (!editor) { - return false - } - - return !!currentTextNodeAction - } - }), - ...extensions + ...onExtensions() ], onTransaction: () => { // force re-render so `editor.isActive` works as expected @@ -296,24 +251,7 @@ let showDiff = true -
- {#if !!currentTextNodeAction} - { - currentTextNodeAction = undefined - }} - /> - {/if} -
+ {#if visible}
{#if isFormatting && !readonly} @@ -336,7 +274,7 @@ needFocus = true }} on:action={(event) => { - currentTextNodeAction = textNodeActions.find((action) => action.id === event.detail) + dispatch('action', { action: event.detail, editor }) needFocus = true }} /> @@ -517,13 +455,4 @@ position: sticky; top: 1.25rem; } - - .actionPanel { - margin: -0.5rem -0.25rem 0.5rem; - padding: 0.375rem; - background-color: var(--theme-comp-header-color); - border-radius: 0.5rem; - box-shadow: var(--theme-popup-shadow); - z-index: 1; - } diff --git a/packages/text-editor/src/components/extension/nodeHighlight.ts b/packages/text-editor/src/components/extension/nodeHighlight.ts index 5e930225cc..062ce5495b 100644 --- a/packages/text-editor/src/components/extension/nodeHighlight.ts +++ b/packages/text-editor/src/components/extension/nodeHighlight.ts @@ -1,5 +1,5 @@ import { Extension, getMarkRange, mergeAttributes } from '@tiptap/core' -import { Plugin, TextSelection } from 'prosemirror-state' +import { Plugin, PluginKey, TextSelection } from 'prosemirror-state' import { NodeUuidExtension, NodeUuidOptions } from './nodeUuid' export enum NodeHighlightType { @@ -7,7 +7,7 @@ export enum NodeHighlightType { SUCCESS = 'success', ERROR = 'error' } -interface NodeHighlightExtensionOptions extends NodeUuidOptions { +export interface NodeHighlightExtensionOptions extends NodeUuidOptions { getNodeHighlightType: (uuid: string) => NodeHighlightType | undefined | null isHighlightModeOn: () => boolean } @@ -15,74 +15,77 @@ interface NodeHighlightExtensionOptions extends NodeUuidOptions { /** * Extension allows to highlight nodes based on uuid */ -export const NodeHighlightExtension = Extension.create({ - addProseMirrorPlugins () { - const options = this.options - const plugins = [ - new Plugin({ - props: { - handleClick (view, pos) { - if (!options.isHighlightModeOn()) { - return - } - const { schema, doc, tr } = view.state +export const NodeHighlightExtension: Extension = + Extension.create({ + addProseMirrorPlugins () { + const options = this.options + const plugins = [ + ...(this.parent?.() ?? []), + new Plugin({ + key: new PluginKey('handle-node-highlight-click-plugin'), + props: { + handleClick (view, pos) { + if (!options.isHighlightModeOn()) { + return + } + const { schema, doc, tr } = view.state - const range = getMarkRange(doc.resolve(pos), schema.marks[NodeUuidExtension.name]) + const range = getMarkRange(doc.resolve(pos), schema.marks[NodeUuidExtension.name]) - if (range === null || range === undefined) { - return false - } + if (range === null || range === undefined) { + return false + } - const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)] + const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)] - view.dispatch(tr.setSelection(new TextSelection($start, $end))) + view.dispatch(tr.setSelection(new TextSelection($start, $end))) - return true + return true + } } - } - }) - ] + }) + ] - return plugins - }, + return plugins + }, - addExtensions () { - const options = this.options + addExtensions () { + const options = this.options - return [ - NodeUuidExtension.extend({ - addOptions () { - return { - ...this.parent?.(), - ...options - } - }, - addAttributes () { - return { - [NodeUuidExtension.name]: { - renderHTML: (attrs) => { - // get uuid from parent mark (NodeUuidExtension) attributes - const uuid = attrs[NodeUuidExtension.name] - const classAttrs: { class?: string } = {} + return [ + NodeUuidExtension.extend({ + addOptions () { + return { + ...this.parent?.(), + ...options + } + }, + addAttributes () { + return { + [NodeUuidExtension.name]: { + renderHTML: (attrs) => { + // get uuid from parent mark (NodeUuidExtension) attributes + const uuid = attrs[NodeUuidExtension.name] + const classAttrs: { class?: string } = {} - if (options.isHighlightModeOn()) { - const type = options.getNodeHighlightType(uuid) + if (options.isHighlightModeOn()) { + const type = options.getNodeHighlightType(uuid) - if (type === NodeHighlightType.ERROR) { - classAttrs.class = 'text-editor-highlighted-node-error' - } else if (type === NodeHighlightType.WARNING) { - classAttrs.class = 'text-editor-highlighted-node-warning' - } else if (type === NodeHighlightType.SUCCESS) { - classAttrs.class = 'text-editor-highlighted-node-success' + if (type === NodeHighlightType.ERROR) { + classAttrs.class = 'text-editor-highlighted-node-error' + } else if (type === NodeHighlightType.WARNING) { + classAttrs.class = 'text-editor-highlighted-node-warning' + } else if (type === NodeHighlightType.SUCCESS) { + classAttrs.class = 'text-editor-highlighted-node-success' + } } - } - return mergeAttributes(attrs, classAttrs) + return mergeAttributes(attrs, classAttrs) + } } } } - } - }) - ] - } -}) + }) + ] + } + }) diff --git a/packages/text-editor/src/components/extension/nodeUuid.ts b/packages/text-editor/src/components/extension/nodeUuid.ts index 7fae0b6a61..1014564e0e 100644 --- a/packages/text-editor/src/components/extension/nodeUuid.ts +++ b/packages/text-editor/src/components/extension/nodeUuid.ts @@ -1,27 +1,31 @@ -import { Mark, mergeAttributes } from '@tiptap/core' +import { Mark, getMarkAttributes, mergeAttributes } from '@tiptap/core' +import { Plugin, PluginKey } from 'prosemirror-state' const NAME = 'node-uuid' export interface NodeUuidOptions { HTMLAttributes: Record - onNodeSelected?: (uuid: string | null) => any + onNodeSelected?: (uuid: string | null) => void + onNodeClicked?: (uuid: string) => void } -declare module '@tiptap/core' { - interface Commands { - [NAME]: { - /** - * Add uuid mark - */ - setUuid: (uuid: string) => ReturnType - /** - * Unset uuid mark - */ - unsetUuid: () => ReturnType - } +export interface NodeUuidCommands { + [NAME]: { + /** + * Add uuid mark + */ + setUuid: (uuid: string) => ReturnType + /** + * Unset uuid mark + */ + unsetUuid: () => ReturnType } } +declare module '@tiptap/core' { + interface Commands extends NodeUuidCommands {} +} + export interface NodeUuidStorage { activeNodeUuid: string | null } @@ -67,6 +71,30 @@ export const NodeUuidExtension = Mark.create({ return ['span', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0] }, + addProseMirrorPlugins () { + const options = this.options + const plugins = [ + ...(this.parent?.() ?? []), + new Plugin({ + key: new PluginKey('handle-node-uuid-click-plugin'), + props: { + handleClick (view) { + const { schema } = view.state + + const attrs = getMarkAttributes(view.state, schema.marks[NAME]) + const nodeUuid = attrs?.[NAME] + + if (nodeUuid !== null || nodeUuid !== undefined) { + options.onNodeClicked?.(nodeUuid) + } + } + } + }) + ] + + return plugins + }, + addCommands () { return { setUuid: diff --git a/packages/text-editor/src/components/extensions.ts b/packages/text-editor/src/components/extensions.ts index a67035f480..716ff1945f 100644 --- a/packages/text-editor/src/components/extensions.ts +++ b/packages/text-editor/src/components/extensions.ts @@ -19,6 +19,7 @@ import Typography from '@tiptap/extension-typography' import { CompletionOptions } from '../Completion' import MentionList from './MentionList.svelte' import { SvelteRenderer } from './SvelteRenderer' +import { NodeUuidExtension } from './extension/nodeUuid' export const tableExtensions = [ Table.configure({ @@ -76,6 +77,7 @@ export const defaultExtensions: AnyExtension[] = [ openOnClick: true, HTMLAttributes: { class: 'cursor-pointer', rel: 'noopener noreferrer', target: '_blank' } }), + NodeUuidExtension, ...tableExtensions // ...taskListExtensions // Disable since tasks are not working properly now. ] diff --git a/packages/text-editor/src/index.ts b/packages/text-editor/src/index.ts index a0d1ebfe11..82be78b307 100644 --- a/packages/text-editor/src/index.ts +++ b/packages/text-editor/src/index.ts @@ -31,6 +31,13 @@ export * from './types' export { default as Collaboration } from './components/Collaboration.svelte' export { default as StyleButton } from './components/StyleButton.svelte' +export { + NodeHighlightExtension, + NodeHighlightExtensionOptions, + NodeHighlightType +} from './components/extension/nodeHighlight' +export { NodeUuidCommands, NodeUuidExtension, NodeUuidOptions, NodeUuidStorage } from './components/extension/nodeUuid' + addStringsLoader(textEditorId, async (lang: string) => { return await import(`../lang/${lang}.json`) }) diff --git a/packages/text-editor/src/types.ts b/packages/text-editor/src/types.ts index 88a8b9a8c0..447133dfa0 100644 --- a/packages/text-editor/src/types.ts +++ b/packages/text-editor/src/types.ts @@ -1,6 +1,6 @@ import { Asset, IntlString, Resource } from '@hcengineering/platform' import { Doc } from '@hcengineering/core' -import type { AnyComponent, AnySvelteComponent } from '@hcengineering/ui' +import type { AnySvelteComponent } from '@hcengineering/ui' /** * @public @@ -61,5 +61,4 @@ export interface TextNodeAction { id: string label?: IntlString icon: Asset | AnySvelteComponent - panel: AnyComponent } diff --git a/packages/theme/styles/_text-editor.scss b/packages/theme/styles/_text-editor.scss index 5f39f38369..2f01d6468b 100644 --- a/packages/theme/styles/_text-editor.scss +++ b/packages/theme/styles/_text-editor.scss @@ -14,6 +14,14 @@ .text-editor-highlighted-node-success { background-color: var(--theme-won-color); } + +.text-editor-popup { + background-color: var(--theme-comp-header-color); + border-radius: 0.5rem; + box-shadow: var(--theme-popup-shadow); + z-index: 1; +} + .proseH1 { margin-block-start: 1.25rem; margin-block-end: 1.25rem; diff --git a/plugins/view-resources/src/components/ObjectPresenter.svelte b/plugins/view-resources/src/components/ObjectPresenter.svelte index 3d726e6ea5..b04e315fe4 100644 --- a/plugins/view-resources/src/components/ObjectPresenter.svelte +++ b/plugins/view-resources/src/components/ObjectPresenter.svelte @@ -76,5 +76,6 @@ {noUnderline} {...props} on:accent-color + on:close /> {/if} From 7f15451a7e36fe50dcfa00695116968758998fdd Mon Sep 17 00:00:00 2001 From: Anna No Date: Fri, 29 Sep 2023 12:53:56 +0700 Subject: [PATCH 2/2] fix build Signed-off-by: Anna No --- .../src/components/extension/nodeHighlight.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/text-editor/src/components/extension/nodeHighlight.ts b/packages/text-editor/src/components/extension/nodeHighlight.ts index 062ce5495b..5b4e14542a 100644 --- a/packages/text-editor/src/components/extension/nodeHighlight.ts +++ b/packages/text-editor/src/components/extension/nodeHighlight.ts @@ -1,4 +1,4 @@ -import { Extension, getMarkRange, mergeAttributes } from '@tiptap/core' +import { Extension, Range, getMarkRange, mergeAttributes } from '@tiptap/core' import { Plugin, PluginKey, TextSelection } from 'prosemirror-state' import { NodeUuidExtension, NodeUuidOptions } from './nodeUuid' @@ -12,6 +12,11 @@ export interface NodeHighlightExtensionOptions extends NodeUuidOptions { isHighlightModeOn: () => boolean } +// eslint-disable-next-line @typescript-eslint/no-invalid-void-type +function isRange (range: Range | undefined | null | void): range is Range { + return range !== null && range !== undefined +} + /** * Extension allows to highlight nodes based on uuid */ @@ -32,11 +37,12 @@ export const NodeHighlightExtension: Extension = const range = getMarkRange(doc.resolve(pos), schema.marks[NodeUuidExtension.name]) - if (range === null || range === undefined) { + if (!isRange(range)) { return false } - const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)] + const { from, to } = range + const [$start, $end] = [doc.resolve(from), doc.resolve(to)] view.dispatch(tr.setSelection(new TextSelection($start, $end)))