refactor: rename banner to callout

pull/29/head
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 './IconCountdown';
export * from './IconDrawBoard';
export * from './IconCallout';

View File

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

View File

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

View File

@ -1,9 +1,9 @@
import { Node } from './node';
export class Banner extends Node {
type = 'banner';
export class Callout extends Node {
type = 'callout';
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 { Attachment } from './nodes/attachment';
import { Banner } from './nodes/banner';
import { Callout } from './nodes/callout';
import { Status } from './nodes/status';
import { DocumentReference } from './nodes/document-reference';
import { DocumentChildren } from './nodes/document-children';
@ -55,7 +55,7 @@ export class Renderer {
this.nodes = [
Attachment,
Countdown,
Banner,
Callout,
Iframe,
Status,
Mention,

View File

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

View File

@ -1,13 +1,12 @@
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 tag = tokens[idx];
if (tag.nesting === 1) {
tag.attrSet('data-type', type);
tag.attrJoin('class', `banner banner-${type}`);
tag.attrJoin('class', `callout`);
}
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
*/
export default function markdownBanner(md) {
export default function markdownCallout(md) {
// create a custom container to each callout type
typesAvailable.forEach((type) => {
md.use(container, type, {

View File

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

View File

@ -32,7 +32,7 @@ import { Blockquote } from './menus/blockquote';
import { HorizontalRule } from './menus/horizontal-rule';
import { Search } from './menus/search';
import { Banner } from './menus/banner';
import { Callout } from './menus/callout';
import { Countdonw } from './menus/countdown';
import { DocumentReference } from './menus/document-reference';
import { Image } from './menus/image';
@ -88,7 +88,7 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => {
<HorizontalRule editor={editor} />
<Search editor={editor} />
<Banner editor={editor} />
<Callout editor={editor} />
<Countdonw editor={editor} />
<DocumentReference 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 { BubbleMenu } from '../../views/bubble-menu';
import { Divider } from '../../divider';
import { Banner } from '../../extensions/banner';
import { Callout } from '../../extensions/callout';
import { deleteNode } from '../../utils/delete-node';
import styles from './bubble.module.scss';
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 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(
(key, color) => {
return () => {
editor
.chain()
.updateAttributes(Banner.name, {
.updateAttributes(Callout.name, {
[key]: color,
})
.focus()
@ -37,18 +37,17 @@ export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
className={'bubble-menu'}
editor={editor}
pluginKey="banner-bubble-menu"
shouldShow={() => editor.isActive(Banner.name)}
shouldShow={() => editor.isActive(Callout.name)}
matchRenderContainer={(node) => node && node.id === 'js-bannber-container'}
>
<Space>
<Popover
spacing={10}
visible
style={{ padding: '0 12px 12px', overflow: 'hidden' }}
content={
<>
<section className={styles.colorWrap}>
<Text type="tertiary"></Text>
<Text type="secondary"></Text>
<div>
{TEXT_COLORS.map((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>
</section>
<section className={styles.colorWrap}>
<Text type="tertiary"></Text>
<Text type="secondary"></Text>
<div>
{BORDER_COLORS.map((color) => (
@ -71,7 +70,7 @@ export const BannerBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
</div>
</section>
<section className={styles.colorWrap}>
<Text type="tertiary"></Text>
<Text type="secondary"></Text>
<div>
{BACKGROUND_COLORS.map((color) => (
<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,
IconMath,
IconCountdown,
IconCallout,
} from 'components/icons';
import { GridSelect } from 'components/grid-select';
import { isTitleActive } from '../../utils/is-active';
@ -90,8 +91,8 @@ export const Insert: React.FC<{ editor: Editor }> = ({ editor }) => {
<IconStatus />
</Dropdown.Item>
<Dropdown.Item onClick={() => editor.chain().focus().setBanner({ type: 'info' }).run()}>
<IconInfo />
<Dropdown.Item onClick={() => editor.chain().focus().setCallout().run()}>
<IconCallout />
</Dropdown.Item>
<Dropdown.Divider />

View File

@ -19,6 +19,7 @@ import {
IconAttachment,
IconMath,
IconCountdown,
IconCallout,
} from 'components/icons';
import { createCountdown } from './countdown/service';
import { createOrToggleLink } from './link/service';
@ -237,14 +238,14 @@ export const QUICK_INSERT_ITEMS = [
},
{
key: '信息框',
key: '高亮块',
label: (
<Space>
<IconInfo />
<IconCallout />
</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 { BackgroundColor } from './extensions/background-color';
import { Banner } from './extensions/banner';
import { Blockquote } from './extensions/blockquote';
import { Bold } from './extensions/bold';
import { BulletList } from './extensions/bullet-list';
import { Callout } from './extensions/callout';
import { Code } from './extensions/code';
import { CodeBlock } from './extensions/code-block';
import { Color } from './extensions/color';
@ -57,10 +57,10 @@ import { Paste } from './extensions/paste';
export const BaseKit = [
Attachment,
BackgroundColor,
Banner,
Blockquote,
Bold,
BulletList,
Callout,
Code,
CodeBlock,
Color,

View File

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

View File

@ -1,12 +1,10 @@
import { useCallback } from 'react';
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react';
import { Popover } from '@douyinfe/semi-ui';
import cls from 'classnames';
import { useToggle } from 'hooks/use-toggle';
import { EmojiPicker } from 'components/emoji-picker';
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 onSelectEmoji = useCallback((emoji) => {