chore(ui): block editor improvement part2 (#13362)

* chore(ui): block editor improvement part2

* chore: update placeholder logic for editor
This commit is contained in:
Sachin Chaurasiya 2023-10-10 17:10:23 +05:30 committed by GitHub
parent 63ac994371
commit dbe545d434
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 103 additions and 149 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

View File

@ -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<BubbleMenuProps> = ({ 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<BubbleMenuProps> = ({ 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<BubbleMenuProps> = ({ 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 (
<CoreBubbleMenu className="menu-wrapper" editor={editor}>
<Dropdown.Button
className="headings-dropdown"
icon={<i>H</i>}
menu={{
items: headings.map(({ label, level, onClick }) => ({
key: label,
icon: (
<Typography.Title
className="m-b-0"
level={(level + 2) as TitleProps['level']}>
{label}
</Typography.Title>
),
onClick,
})),
}}
type="text"
/>
{menuList.map(
({ icon: Icon, ariaLabel, className, disabled, onClick }) => (
<Tooltip key={ariaLabel} title={ariaLabel}>
@ -157,7 +117,11 @@ const BubbleMenu: FC<BubbleMenuProps> = ({ editor, toggleLink }) => {
disabled={disabled}
type="text"
onClick={onClick}>
<Icon className="d-flex" height={24} width={24} />
{isString(Icon) ? (
<Typography>{Icon}</Typography>
) : (
<Icon className="d-flex" height={24} width={24} />
)}
</Button>
</Tooltip>
)

View File

@ -130,7 +130,9 @@ export default forwardRef<
<div className="w-5" style={{ marginTop: '6px' }}>
{getEntityIcon(item.type)}
</div>
<Typography>{item.label}</Typography>
<Typography className="truncate w-max-200">
{item.label}
</Typography>
</Space>
</Space>
);

View File

@ -111,7 +111,7 @@ export default forwardRef<ExtensionRef, SuggestionProps<SuggestionItem>>(
key={item.id}
onClick={() => selectItem(index)}>
<ProfilePicture id="" name={item.name} type="circle" width="20" />
<Typography>{item.label}</Typography>
<Typography className="truncate w-max-200">{item.label}</Typography>
</Space>
))}
</Space>

View File

@ -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) =>

View File

@ -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;

View File

@ -39,33 +39,12 @@ export const EDITOR_OPTIONS: Partial<EditorOptions> = {
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({