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..5b4e14542a 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 { Extension, Range, getMarkRange, mergeAttributes } from '@tiptap/core'
+import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
import { NodeUuidExtension, NodeUuidOptions } from './nodeUuid'
export enum NodeHighlightType {
@@ -7,82 +7,91 @@ export enum NodeHighlightType {
SUCCESS = 'success',
ERROR = 'error'
}
-interface NodeHighlightExtensionOptions extends NodeUuidOptions {
+export interface NodeHighlightExtensionOptions extends NodeUuidOptions {
getNodeHighlightType: (uuid: string) => NodeHighlightType | undefined | null
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
*/
-export const NodeHighlightExtension = Extension.create({
- addProseMirrorPlugins () {
- const options = this.options
- const plugins = [
- new Plugin({
- props: {
- handleClick (view, pos) {
- if (!options.isHighlightModeOn()) {
- return
+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])
+
+ if (!isRange(range)) {
+ return false
+ }
+
+ const { from, to } = range
+ const [$start, $end] = [doc.resolve(from), doc.resolve(to)]
+
+ view.dispatch(tr.setSelection(new TextSelection($start, $end)))
+
+ return true
}
- const { schema, doc, tr } = view.state
+ }
+ })
+ ]
- const range = getMarkRange(doc.resolve(pos), schema.marks[NodeUuidExtension.name])
+ return plugins
+ },
- if (range === null || range === undefined) {
- return false
+ 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 } = {}
- const [$start, $end] = [doc.resolve(range.from), doc.resolve(range.to)]
+ if (options.isHighlightModeOn()) {
+ const type = options.getNodeHighlightType(uuid)
- view.dispatch(tr.setSelection(new TextSelection($start, $end)))
-
- return true
- }
- }
- })
- ]
-
- return plugins
- },
-
- 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 } = {}
-
- 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,25 +1,29 @@
-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
+}
+
+export interface NodeUuidCommands {
+ [NAME]: {
+ /**
+ * Add uuid mark
+ */
+ setUuid: (uuid: string) => ReturnType
+ /**
+ * Unset uuid mark
+ */
+ unsetUuid: () => ReturnType
+ }
}
declare module '@tiptap/core' {
- interface Commands {
- [NAME]: {
- /**
- * Add uuid mark
- */
- setUuid: (uuid: string) => ReturnType
- /**
- * Unset uuid mark
- */
- unsetUuid: () => ReturnType
- }
- }
+ interface Commands extends NodeUuidCommands {}
}
export interface NodeUuidStorage {
@@ -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}