refactor: rename banner to callout

This commit is contained in:
fantasticit 2022-04-25 14:01:39 +08:00
parent 1786e21fc8
commit 20db8fcf57
19 changed files with 91 additions and 88 deletions

View File

@ -0,0 +1,14 @@
import { Icon } from '@douyinfe/semi-ui';
export const IconCallout: React.FC<{ style?: React.CSSProperties }> = ({ style = {} }) => {
return (
<Icon
style={style}
svg={
<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<path d="M4 2a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H4Zm16 2v6H4V4h16ZM3 16a1 1 0 0 1 1-1h16a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm1 4a1 1 0 1 0 0 2h8a1 1 0 1 0 0-2H4Z"></path>
</svg>
}
/>
);
};

View File

@ -50,3 +50,4 @@ export * from './IconSup';
export * from './IconGlobe'; export * from './IconGlobe';
export * from './IconCountdown'; export * from './IconCountdown';
export * from './IconDrawBoard'; export * from './IconDrawBoard';
export * from './IconCallout';

View File

@ -1,18 +1,18 @@
import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'; import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react'; import { ReactNodeViewRenderer } from '@tiptap/react';
import { BannerWrapper } from '../wrappers/banner'; import { CalloutWrapper } from '../wrappers/callout';
import { getDatasetAttribute } from '../utils/dataset'; import { getDatasetAttribute } from '../utils/dataset';
declare module '@tiptap/core' { declare module '@tiptap/core' {
interface Commands<ReturnType> { interface Commands<ReturnType> {
banner: { banner: {
setBanner: (attrs) => ReturnType; setCallout: () => ReturnType;
}; };
} }
} }
export const Banner = Node.create({ export const Callout = Node.create({
name: 'banner', name: 'callout',
content: 'paragraph+', content: 'paragraph+',
group: 'block', group: 'block',
defining: true, defining: true,
@ -20,17 +20,6 @@ export const Banner = Node.create({
addAttributes() { addAttributes() {
return { return {
type: {
default: 'info',
rendered: false,
parseHTML: getDatasetAttribute('type'),
renderHTML: (attributes) => {
return {
'data-type': attributes.type,
'class': `banner banner-${attributes.type}`,
};
},
},
emoji: { emoji: {
default: '✅', default: '✅',
}, },
@ -49,7 +38,7 @@ export const Banner = Node.create({
addOptions() { addOptions() {
return { return {
HTMLAttributes: { HTMLAttributes: {
class: 'banner', class: 'callout',
}, },
}; };
}, },
@ -68,14 +57,14 @@ export const Banner = Node.create({
addCommands() { addCommands() {
return { return {
setBanner: setCallout:
(attributes) => () =>
({ commands, editor }) => { ({ commands, editor }) => {
const { type = null } = editor.getAttributes(this.name); const { type = null } = editor.getAttributes(this.name);
if (type) { if (type) {
commands.lift(this.name); commands.lift(this.name);
} else { } else {
return commands.toggleWrap(this.name, attributes); return commands.toggleWrap(this.name);
} }
}, },
}; };
@ -94,6 +83,6 @@ export const Banner = Node.create({
}, },
addNodeView() { addNodeView() {
return ReactNodeViewRenderer(BannerWrapper); return ReactNodeViewRenderer(CalloutWrapper);
}, },
}); });

View File

@ -115,19 +115,19 @@ export const Paste = Extension.create({
return false; return false;
}, },
// clipboardTextSerializer: (slice) => { clipboardTextSerializer: (slice) => {
// const doc = slice.content; const doc = slice.content;
// if (!doc) { if (!doc) {
// return ''; return '';
// } }
// const content = prosemirrorToMarkdown({ const content = prosemirrorToMarkdown({
// content: doc, content: doc,
// }); });
// return content; return content;
// }, },
}, },
}), }),
]; ];

View File

@ -1,9 +1,9 @@
import { Node } from './node'; import { Node } from './node';
export class Banner extends Node { export class Callout extends Node {
type = 'banner'; type = 'callout';
matching() { matching() {
return this.DOMNode.nodeName === 'DIV' && this.DOMNode.classList.contains('banner'); return this.DOMNode.nodeName === 'DIV' && this.DOMNode.classList.contains('callout');
} }
} }

View File

@ -1,7 +1,7 @@
// 自定义节点 // 自定义节点
import { Iframe } from './nodes/iframe'; import { Iframe } from './nodes/iframe';
import { Attachment } from './nodes/attachment'; import { Attachment } from './nodes/attachment';
import { Banner } from './nodes/banner'; import { Callout } from './nodes/callout';
import { Status } from './nodes/status'; import { Status } from './nodes/status';
import { DocumentReference } from './nodes/document-reference'; import { DocumentReference } from './nodes/document-reference';
import { DocumentChildren } from './nodes/document-children'; import { DocumentChildren } from './nodes/document-children';
@ -55,7 +55,7 @@ export class Renderer {
this.nodes = [ this.nodes = [
Attachment, Attachment,
Countdown, Countdown,
Banner, Callout,
Iframe, Iframe,
Status, Status,
Mention, Mention,

View File

@ -8,7 +8,7 @@ import katex from './markdownKatex';
import tasklist from './markdownTaskList'; import tasklist from './markdownTaskList';
import splitMixedLists from './markedownSplitMixedList'; import splitMixedLists from './markedownSplitMixedList';
import markdownUnderline from './markdownUnderline'; import markdownUnderline from './markdownUnderline';
import markdownBanner from './markdownBanner'; import markdownCallout from './markdownCallout';
import { markdownItTable } from './markdownTable'; import { markdownItTable } from './markdownTable';
import { createMarkdownContainer } from './markdownItContainer'; import { createMarkdownContainer } from './markdownItContainer';
@ -33,7 +33,7 @@ const markdown = markdownit('commonmark')
.use(emoji) .use(emoji)
.use(katex) .use(katex)
// 以下为自定义节点 // 以下为自定义节点
.use(markdownBanner) .use(markdownCallout)
.use(markdownAttachment) .use(markdownAttachment)
.use(markdownCountdown) .use(markdownCountdown)
.use(markdownIframe) .use(markdownIframe)

View File

@ -1,13 +1,12 @@
import container from 'markdown-it-container'; import container from 'markdown-it-container';
export const typesAvailable = ['info', 'warning', 'danger', 'success']; const typesAvailable = ['callout'];
const buildRender = (type) => (tokens, idx, options, env, slf) => { const buildRender = (type) => (tokens, idx, options, env, slf) => {
const tag = tokens[idx]; const tag = tokens[idx];
if (tag.nesting === 1) { if (tag.nesting === 1) {
tag.attrSet('data-type', type); tag.attrJoin('class', `callout`);
tag.attrJoin('class', `banner banner-${type}`);
} }
return slf.renderToken(tokens, idx, options, env, slf); return slf.renderToken(tokens, idx, options, env, slf);
@ -16,7 +15,7 @@ const buildRender = (type) => (tokens, idx, options, env, slf) => {
/** /**
* @param {object} md Markdown object * @param {object} md Markdown object
*/ */
export default function markdownBanner(md) { export default function markdownCallout(md) {
// create a custom container to each callout type // create a custom container to each callout type
typesAvailable.forEach((type) => { typesAvailable.forEach((type) => {
md.use(container, type, { md.use(container, type, {

View File

@ -1,8 +1,8 @@
import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown'; import { MarkdownSerializer as ProseMirrorMarkdownSerializer, defaultMarkdownSerializer } from 'prosemirror-markdown';
import { Attachment } from '../../extensions/attachment'; import { Attachment } from '../../extensions/attachment';
import { Banner } from '../../extensions/banner';
import { Bold } from '../../extensions/bold'; import { Bold } from '../../extensions/bold';
import { BulletList } from '../../extensions/bullet-list'; import { BulletList } from '../../extensions/bullet-list';
import { Callout } from '../../extensions/callout';
import { Code } from '../../extensions/code'; import { Code } from '../../extensions/code';
import { CodeBlock } from '../../extensions/code-block'; import { CodeBlock } from '../../extensions/code-block';
import { Countdown } from '../../extensions/countdown'; import { Countdown } from '../../extensions/countdown';
@ -92,14 +92,6 @@ const SerializerConfig = {
nodes: { nodes: {
[Attachment.name]: renderCustomContainer('attachment'), [Attachment.name]: renderCustomContainer('attachment'),
[Banner.name]: (state, node) => {
state.write(`:::${node.attrs.type || 'info'}\n`);
state.ensureNewLine();
state.renderContent(node);
state.ensureNewLine();
state.write(':::');
state.closeBlock(node);
},
blockquote: (state, node) => { blockquote: (state, node) => {
if (node.attrs.multiline) { if (node.attrs.multiline) {
state.write('>>>'); state.write('>>>');
@ -113,6 +105,14 @@ const SerializerConfig = {
} }
}, },
[BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list, [BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list,
[Callout.name]: (state, node) => {
state.write(`:::callout\n`);
state.ensureNewLine();
state.renderContent(node);
state.ensureNewLine();
state.write(':::');
state.closeBlock(node);
},
[CodeBlock.name]: (state, node) => { [CodeBlock.name]: (state, node) => {
state.write(`\`\`\`${node.attrs.language || ''}\n`); state.write(`\`\`\`${node.attrs.language || ''}\n`);
state.text(node.textContent, false); state.text(node.textContent, false);

View File

@ -32,7 +32,7 @@ import { Blockquote } from './menus/blockquote';
import { HorizontalRule } from './menus/horizontal-rule'; import { HorizontalRule } from './menus/horizontal-rule';
import { Search } from './menus/search'; import { Search } from './menus/search';
import { Banner } from './menus/banner'; import { Callout } from './menus/callout';
import { Countdonw } from './menus/countdown'; import { Countdonw } from './menus/countdown';
import { DocumentReference } from './menus/document-reference'; import { DocumentReference } from './menus/document-reference';
import { Image } from './menus/image'; import { Image } from './menus/image';
@ -88,7 +88,7 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
<HorizontalRule editor={editor} /> <HorizontalRule editor={editor} />
<Search editor={editor} /> <Search editor={editor} />
<Banner editor={editor} /> <Callout editor={editor} />
<Countdonw editor={editor} /> <Countdonw editor={editor} />
<DocumentReference editor={editor} /> <DocumentReference editor={editor} />
<Image editor={editor} /> <Image editor={editor} />

View File

@ -1,15 +0,0 @@
import React from 'react';
import { Editor } from '@tiptap/core';
import { BannerBubbleMenu } from './bubble';
export const Banner: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<>
<BannerBubbleMenu editor={editor} />
</>
);
};

View File

@ -5,7 +5,7 @@ import { Tooltip } from 'components/tooltip';
import { IconDrawBoard } from 'components/icons'; import { IconDrawBoard } from 'components/icons';
import { BubbleMenu } from '../../views/bubble-menu'; import { BubbleMenu } from '../../views/bubble-menu';
import { Divider } from '../../divider'; import { Divider } from '../../divider';
import { Banner } from '../../extensions/banner'; import { Callout } from '../../extensions/callout';
import { deleteNode } from '../../utils/delete-node'; import { deleteNode } from '../../utils/delete-node';
import styles from './bubble.module.scss'; import styles from './bubble.module.scss';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -16,13 +16,13 @@ const TEXT_COLORS = ['#d83931', '#de7802', '#dc9b04', '#2ea121', '#245bdb', '#64
const BORDER_COLORS = ['#fbbfbc', '#fed4a4', '#fff67a', '#b7edb1', '#bacefd', '#cdb2fa', '#dee0e3']; const BORDER_COLORS = ['#fbbfbc', '#fed4a4', '#fff67a', '#b7edb1', '#bacefd', '#cdb2fa', '#dee0e3'];
const BACKGROUND_COLORS = ['#fef1f1', '#feead2', '#ffc', '#d9f5d6', '#e1eaff', '#ece2fe', '#f2f3f5']; const BACKGROUND_COLORS = ['#fef1f1', '#feead2', '#ffc', '#d9f5d6', '#e1eaff', '#ece2fe', '#f2f3f5'];
export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => { export const CalloutBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
const setColor = useCallback( const setColor = useCallback(
(key, color) => { (key, color) => {
return () => { return () => {
editor editor
.chain() .chain()
.updateAttributes(Banner.name, { .updateAttributes(Callout.name, {
[key]: color, [key]: color,
}) })
.focus() .focus()
@ -37,18 +37,17 @@ export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
className={'bubble-menu'} className={'bubble-menu'}
editor={editor} editor={editor}
pluginKey="banner-bubble-menu" pluginKey="banner-bubble-menu"
shouldShow={() => editor.isActive(Banner.name)} shouldShow={() => editor.isActive(Callout.name)}
matchRenderContainer={(node) => node && node.id === 'js-bannber-container'} matchRenderContainer={(node) => node && node.id === 'js-bannber-container'}
> >
<Space> <Space>
<Popover <Popover
spacing={10} spacing={10}
visible
style={{ padding: '0 12px 12px', overflow: 'hidden' }} style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={ content={
<> <>
<section className={styles.colorWrap}> <section className={styles.colorWrap}>
<Text type="tertiary"></Text> <Text type="secondary"></Text>
<div> <div>
{TEXT_COLORS.map((color) => ( {TEXT_COLORS.map((color) => (
<div className={styles.color} style={{ color: color }} onClick={setColor('textColor', color)}> <div className={styles.color} style={{ color: color }} onClick={setColor('textColor', color)}>
@ -58,7 +57,7 @@ export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
</div> </div>
</section> </section>
<section className={styles.colorWrap}> <section className={styles.colorWrap}>
<Text type="tertiary"></Text> <Text type="secondary"></Text>
<div> <div>
{BORDER_COLORS.map((color) => ( {BORDER_COLORS.map((color) => (
@ -71,7 +70,7 @@ export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
</div> </div>
</section> </section>
<section className={styles.colorWrap}> <section className={styles.colorWrap}>
<Text type="tertiary"></Text> <Text type="secondary"></Text>
<div> <div>
{BACKGROUND_COLORS.map((color) => ( {BACKGROUND_COLORS.map((color) => (
<div <div

View File

@ -0,0 +1,15 @@
import React from 'react';
import { Editor } from '@tiptap/core';
import { CalloutBubbleMenu } from './bubble';
export const Callout: React.FC<{ editor: Editor }> = ({ editor }) => {
if (!editor) {
return null;
}
return (
<>
<CalloutBubbleMenu editor={editor} />
</>
);
};

View File

@ -15,6 +15,7 @@ import {
IconAttachment, IconAttachment,
IconMath, IconMath,
IconCountdown, IconCountdown,
IconCallout,
} from 'components/icons'; } from 'components/icons';
import { GridSelect } from 'components/grid-select'; import { GridSelect } from 'components/grid-select';
import { isTitleActive } from '../../utils/is-active'; import { isTitleActive } from '../../utils/is-active';
@ -90,8 +91,8 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
<IconStatus /> <IconStatus />
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Item onClick={() => editor.chain().focus().setBanner({ type: 'info' }).run()}> <Dropdown.Item onClick={() => editor.chain().focus().setCallout().run()}>
<IconInfo /> <IconCallout />
</Dropdown.Item> </Dropdown.Item>
<Dropdown.Divider /> <Dropdown.Divider />

View File

@ -19,6 +19,7 @@ import {
IconAttachment, IconAttachment,
IconMath, IconMath,
IconCountdown, IconCountdown,
IconCallout,
} from 'components/icons'; } from 'components/icons';
import { createCountdown } from './countdown/service'; import { createCountdown } from './countdown/service';
import { createOrToggleLink } from './link/service'; import { createOrToggleLink } from './link/service';
@ -237,14 +238,14 @@ export const QUICK_INSERT_ITEMS = [
}, },
{ {
key: '信息框', key: '高亮块',
label: ( label: (
<Space> <Space>
<IconInfo /> <IconCallout />
</Space> </Space>
), ),
command: (editor: Editor) => editor.chain().focus().setBanner({ type: 'info' }).run(), command: (editor: Editor) => editor.chain().focus().setCallout().run(),
}, },
{ {

View File

@ -1,9 +1,9 @@
import { Attachment } from './extensions/attachment'; import { Attachment } from './extensions/attachment';
import { BackgroundColor } from './extensions/background-color'; import { BackgroundColor } from './extensions/background-color';
import { Banner } from './extensions/banner';
import { Blockquote } from './extensions/blockquote'; import { Blockquote } from './extensions/blockquote';
import { Bold } from './extensions/bold'; import { Bold } from './extensions/bold';
import { BulletList } from './extensions/bullet-list'; import { BulletList } from './extensions/bullet-list';
import { Callout } from './extensions/callout';
import { Code } from './extensions/code'; import { Code } from './extensions/code';
import { CodeBlock } from './extensions/code-block'; import { CodeBlock } from './extensions/code-block';
import { Color } from './extensions/color'; import { Color } from './extensions/color';
@ -57,10 +57,10 @@ import { Paste } from './extensions/paste';
export const BaseKit = [ export const BaseKit = [
Attachment, Attachment,
BackgroundColor, BackgroundColor,
Banner,
Blockquote, Blockquote,
Bold, Bold,
BulletList, BulletList,
Callout,
Code, Code,
CodeBlock, CodeBlock,
Color, Color,

View File

@ -1,5 +1,6 @@
.wrap { .wrap {
line-height: 0; line-height: 0;
margin-top: 0.75em;
.innerWrap { .innerWrap {
display: flex; display: flex;
@ -28,7 +29,7 @@
} }
p { p {
margin-top: .25em; margin-top: 0.25em;
} }
p:first-child { p:first-child {

View File

@ -1,12 +1,10 @@
import { useCallback } from 'react';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'; import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
import { Popover } from '@douyinfe/semi-ui';
import cls from 'classnames'; import cls from 'classnames';
import { useToggle } from 'hooks/use-toggle';
import { EmojiPicker } from 'components/emoji-picker'; import { EmojiPicker } from 'components/emoji-picker';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { useCallback, useEffect, useMemo } from 'react';
export const BannerWrapper = ({ node, updateAttributes }) => { export const CalloutWrapper = ({ node, updateAttributes }) => {
const { emoji, textColor, borderColor, backgroundColor } = node.attrs; const { emoji, textColor, borderColor, backgroundColor } = node.attrs;
const onSelectEmoji = useCallback((emoji) => { const onSelectEmoji = useCallback((emoji) => {