diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-hashtag.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-hashtag.png new file mode 100644 index 00000000000..226908fe984 Binary files /dev/null and b/openmetadata-ui/src/main/resources/ui/src/assets/img/ic-format-hashtag.png differ diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx index c47b378815e..fa18d2f57c7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/BubbleMenu/BubbleMenu.tsx @@ -12,19 +12,14 @@ */ import { Editor } from '@tiptap/core'; import { BubbleMenu as CoreBubbleMenu } from '@tiptap/react'; -import { Button, Dropdown, Tooltip, Typography } from 'antd'; -import { TitleProps } from 'antd/lib/typography/Title'; +import { Button, Tooltip, Typography } from 'antd'; import classNames from 'classnames'; +import { isString } from 'lodash'; import React, { FC, useMemo } from 'react'; import { ReactComponent as FormatBoldIcon } from '../../../assets/svg/ic-format-bold.svg'; -import { ReactComponent as FormatBulletListIcon } from '../../../assets/svg/ic-format-bullet-list.svg'; -import { ReactComponent as FormatCodeBlockIcon } from '../../../assets/svg/ic-format-code-block.svg'; -import { ReactComponent as FormatDividerIcon } from '../../../assets/svg/ic-format-divider.svg'; import { ReactComponent as FormatInlineCodeIcon } from '../../../assets/svg/ic-format-inline-code.svg'; import { ReactComponent as FormatItalicIcon } from '../../../assets/svg/ic-format-italic.svg'; import { ReactComponent as FormatLinkIcon } from '../../../assets/svg/ic-format-link.svg'; -import { ReactComponent as FormatNumberListIcon } from '../../../assets/svg/ic-format-numbered-list.svg'; -import { ReactComponent as FormatQuoteIcon } from '../../../assets/svg/ic-format-quote.svg'; import { ReactComponent as FormatStrikeIcon } from '../../../assets/svg/ic-format-strike.svg'; interface BubbleMenuProps { @@ -33,8 +28,44 @@ interface BubbleMenuProps { } const BubbleMenu: FC = ({ editor, toggleLink }) => { - const { menuList, headings } = useMemo(() => { + const { menuList } = useMemo(() => { const menuList = [ + { + ariaLabel: 'Heading 1', + className: 'm-x-xs', + disabled: !editor + .can() + .chain() + .focus() + .toggleHeading({ level: 1 }) + .run(), + onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), + icon: 'H1', + }, + { + ariaLabel: 'Heading 2', + className: 'm-r-xs', + disabled: !editor + .can() + .chain() + .focus() + .toggleHeading({ level: 2 }) + .run(), + onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), + icon: 'H2', + }, + { + ariaLabel: 'Heading 3', + className: '', + disabled: !editor + .can() + .chain() + .focus() + .toggleHeading({ level: 3 }) + .run(), + onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), + icon: 'H3', + }, { ariaLabel: 'Bold', className: editor.isActive('bold') ? 'is-active' : '', @@ -57,7 +88,7 @@ const BubbleMenu: FC = ({ editor, toggleLink }) => { icon: FormatStrikeIcon, }, { - ariaLabel: 'Code', + ariaLabel: 'Inline code', className: editor.isActive('code') ? 'is-active' : '', disabled: !editor.can().chain().focus().toggleCode().run(), onClick: () => editor.chain().focus().toggleCode().run(), @@ -70,84 +101,13 @@ const BubbleMenu: FC = ({ editor, toggleLink }) => { onClick: () => toggleLink(), icon: FormatLinkIcon, }, - { - ariaLabel: 'Bullet List', - className: editor.isActive('bulletList') ? 'is-active' : '', - disabled: false, - onClick: () => editor.chain().focus().toggleBulletList().run(), - icon: FormatBulletListIcon, - }, - { - ariaLabel: 'Ordered List', - className: editor.isActive('orderedList') ? 'is-active' : '', - disabled: false, - onClick: () => editor.chain().focus().toggleOrderedList().run(), - icon: FormatNumberListIcon, - }, - { - ariaLabel: 'Code block', - className: editor.isActive('codeBlock') ? 'is-active' : '', - disabled: false, - onClick: () => editor.chain().focus().toggleCodeBlock().run(), - icon: FormatCodeBlockIcon, - }, - { - ariaLabel: 'Blockquote', - className: editor.isActive('blockquote') ? 'is-active' : '', - disabled: false, - onClick: () => editor.chain().focus().toggleBlockquote().run(), - icon: FormatQuoteIcon, - }, - { - ariaLabel: 'Horizontal Line', - className: '', - disabled: false, - onClick: () => editor.chain().focus().setHorizontalRule().run(), - icon: FormatDividerIcon, - }, ]; - const headings = [ - { - label: 'Heading 1', - onClick: () => editor.chain().focus().toggleHeading({ level: 1 }).run(), - level: 1, - }, - { - label: 'Heading 2', - onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(), - level: 2, - }, - { - label: 'Heading 3', - onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(), - level: 3, - }, - ]; - - return { menuList, headings }; + return { menuList }; }, [editor]); return ( - H} - menu={{ - items: headings.map(({ label, level, onClick }) => ({ - key: label, - icon: ( - - {label} - - ), - onClick, - })), - }} - type="text" - /> {menuList.map( ({ icon: Icon, ariaLabel, className, disabled, onClick }) => ( @@ -157,7 +117,11 @@ const BubbleMenu: FC = ({ editor, toggleLink }) => { disabled={disabled} type="text" onClick={onClick}> - + {isString(Icon) ? ( + {Icon} + ) : ( + + )} ) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/hashtag/HashList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/hashtag/HashList.tsx index 17a33f58ab7..e6a584c4579 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/hashtag/HashList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/hashtag/HashList.tsx @@ -130,7 +130,9 @@ export default forwardRef<
{getEntityIcon(item.type)}
- {item.label} + + {item.label} + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/mention/MentionList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/mention/MentionList.tsx index 2e0d07d02ac..9f6f79d8553 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/mention/MentionList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/mention/MentionList.tsx @@ -111,7 +111,7 @@ export default forwardRef>( key={item.id} onClick={() => selectItem(index)}> - {item.label} + {item.label} ))} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slashCommand/items.ts b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slashCommand/items.ts index eb6fd3f9be3..427f449601f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slashCommand/items.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/Extensions/slashCommand/items.ts @@ -11,6 +11,8 @@ * limitations under the License. */ import { Editor, Range } from '@tiptap/core'; +import HashtagImage from 'assets/img/ic-format-hashtag.png'; +import MentionImage from 'assets/svg/ic-mentions.svg'; import BulletListImage from '../../../../assets/img/ic-slash-bullet-list.png'; import DividerImage from '../../../../assets/img/ic-slash-divider.png'; import H1Image from '../../../../assets/img/ic-slash-h1.png'; @@ -19,21 +21,19 @@ import H3Image from '../../../../assets/img/ic-slash-h3.png'; import NumberedListImage from '../../../../assets/img/ic-slash-numbered-list.png'; import QuoteImage from '../../../../assets/img/ic-slash-quote.png'; import TextImage from '../../../../assets/img/ic-slash-text.png'; -import TaskListImage from '../../../../assets/img/ic-task-list.png'; import CodeBlockImage from '../../../../assets/svg/ic-format-code-block.svg'; export enum SuggestionItemType { - BASIC_BLOCKS = 'Basic blocks', + BASIC_BLOCKS = 'Basic', + ADVANCED_BLOCKS = 'Advanced', } export interface SuggestionItem { title: string; description: string; type: SuggestionItemType; - shortcut: string | null; imgSrc: string; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - command: (props: { editor: Editor; range: Range; props: any }) => void; + command: (props: { editor: Editor; range: Range }) => void; } export const getSuggestionItems = (props: { query: string; editor: Editor }) => @@ -41,8 +41,7 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => [ { title: 'Text', - description: 'Plain text', - shortcut: null, + description: 'Start writing with plain text', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).setNode('paragraph').run(); @@ -51,8 +50,7 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => }, { title: 'H1', - description: 'Heading 1', - shortcut: '#', + description: 'Big heading', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor @@ -66,8 +64,7 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => }, { title: 'H2', - description: 'Heading 2', - shortcut: '##', + description: 'Medium heading', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor @@ -81,8 +78,7 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => }, { title: 'H3', - description: 'Heading 3', - shortcut: '###', + description: 'Small heading', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor @@ -94,20 +90,9 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => }, imgSrc: H3Image, }, - { - title: 'Code Block', - description: 'Add a code block', - shortcut: '```', - type: SuggestionItemType.BASIC_BLOCKS, - command: ({ editor, range }) => { - editor.chain().focus().deleteRange(range).toggleCodeBlock().run(); - }, - imgSrc: CodeBlockImage, - }, { title: 'Bullet List', - description: 'Create a simple bulleted list', - shortcut: null, + description: 'Create a simple bullet list', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).toggleBulletList().run(); @@ -116,8 +101,7 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => }, { title: 'Numbered List', - description: 'Create a list with numbering', - shortcut: null, + description: 'Create a simple numbered list', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).toggleOrderedList().run(); @@ -125,34 +109,57 @@ export const getSuggestionItems = (props: { query: string; editor: Editor }) => imgSrc: NumberedListImage, }, { - title: 'Task List', - description: 'Create a task list', - shortcut: null, + title: 'Divider', + description: 'Insert a dividing line', type: SuggestionItemType.BASIC_BLOCKS, command: ({ editor, range }) => { - editor.chain().focus().deleteRange(range).toggleTaskList().run(); + editor + .chain() + .focus() + .deleteRange(range) + .setHardBreak() + .setHorizontalRule() + .run(); }, - imgSrc: TaskListImage, + imgSrc: DividerImage, }, + // advanced blocks + { + title: 'Code Block', + description: 'Add a block of code', + type: SuggestionItemType.ADVANCED_BLOCKS, + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleCodeBlock().run(); + }, + imgSrc: CodeBlockImage, + }, + { title: 'Quote', - description: 'Capture a quote', - shortcut: null, - type: SuggestionItemType.BASIC_BLOCKS, + description: 'Add a quote', + type: SuggestionItemType.ADVANCED_BLOCKS, command: ({ editor, range }) => { editor.chain().focus().deleteRange(range).toggleBlockquote().run(); }, imgSrc: QuoteImage, }, { - title: 'Divider', - description: 'Visually divide blocks', - shortcut: null, - type: SuggestionItemType.BASIC_BLOCKS, - command: ({ editor }) => { - editor.chain().focus().setHorizontalRule().run(); + title: 'Mention', + description: 'Add a user or team', + type: SuggestionItemType.ADVANCED_BLOCKS, + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).insertContent('@').run(); }, - imgSrc: DividerImage, + imgSrc: MentionImage, + }, + { + title: 'Link data asset', + description: 'Add a data asset', + type: SuggestionItemType.ADVANCED_BLOCKS, + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).insertContent('#').run(); + }, + imgSrc: HashtagImage, }, ] as SuggestionItem[] ).filter((item) => diff --git a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less index 2871ae445cb..b906b77a356 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/BlockEditor/block-editor.less @@ -211,6 +211,7 @@ .mention-menu-wrapper { max-height: 300px; + max-width: 300px; padding: 12px 0px; background-color: @white; border: @global-border; @@ -222,6 +223,7 @@ } .hashtag-menu-wrapper { max-height: 300px; + max-width: 300px; padding: 12px 0px; background-color: @white; border: @global-border; diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/BlockEditor.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/BlockEditor.constants.ts index 78bbbf610b5..b52a8fcf2e8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/BlockEditor.constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/BlockEditor.constants.ts @@ -39,33 +39,12 @@ export const EDITOR_OPTIONS: Partial = { showOnlyCurrent: false, emptyEditorClass: 'is-editor-empty', emptyNodeClass: 'is-node-empty', - placeholder: ({ node, editor: coreEditor }) => { + placeholder: ({ editor: coreEditor }) => { if (coreEditor.isDestroyed) { return ''; } - const headingPlaceholders: { - [key: number]: string; - } = { - 1: 'Heading 1', - 2: 'Heading 2', - 3: 'Heading 3', - }; - - if (node.type.name === 'heading') { - const level = node.attrs.level as number; - - return headingPlaceholders[level]; - } - - if ( - node.type.name === 'paragraph' && - coreEditor.getJSON().content?.length === 1 - ) { - return 'Type / to get started'; - } - - return 'Type / for commands'; + return 'Type "/" for commands...'; }, }), LinkExtension.configure({