tiptap: improve katext

pull/64/head
fantasticit 2022-05-30 00:08:58 +08:00
parent b08e1be52f
commit 589eff0545
6 changed files with 146 additions and 74 deletions

View File

@ -20,8 +20,7 @@ export const KatexInputRegex = /^\$\$(.+)?\$\$$/;
export const Katex = Node.create({
name: 'katex',
group: 'inline',
inline: true,
group: 'block',
selectable: true,
atom: true,

View File

@ -1,9 +1,7 @@
.wrap {
display: inline-flex;
padding: 2px 8px;
font-size: 12px;
line-height: 16px;
display: flex;
padding: 1rem 4px;
font-size: 1em;
cursor: pointer;
transform: translateY(1px);
justify-content: center;
}

View File

@ -1,22 +1,20 @@
import { IconHelpCircle } from '@douyinfe/semi-icons';
import { Popover, Space, TextArea, Typography } from '@douyinfe/semi-ui';
import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames';
import { useUser } from 'data/user';
import { useToggle } from 'hooks/use-toggle';
import { convertColorToRGBA } from 'helpers/color';
import { Theme, ThemeEnum } from 'hooks/use-theme';
import katex from 'katex';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useMemo } from 'react';
import styles from './index.module.scss';
const { Text } = Typography;
export const KatexWrapper = ({ editor, node, updateAttributes }) => {
const isEditable = editor.isEditable;
const { text, defaultShowPicker, createUser } = node.attrs;
const { user } = useUser();
const ref = useRef<HTMLTextAreaElement>();
const [visible, toggleVisible] = useToggle(false);
export const KatexWrapper = ({ node }) => {
const { text } = node.attrs;
const { theme } = Theme.useHook();
const backgroundColor = useMemo(() => {
const color = `rgb(254, 242, 237)`;
if (theme === ThemeEnum.dark) return convertColorToRGBA(color, 0.75);
return color;
}, [theme]);
const formatText = useMemo(() => {
try {
@ -36,59 +34,15 @@ export const KatexWrapper = ({ editor, node, updateAttributes }) => {
[text, formatText]
);
const onVisibleChange = useCallback(
(value) => {
toggleVisible(value);
if (defaultShowPicker && user && createUser === user.name) {
updateAttributes({ defaultShowPicker: false });
}
},
[defaultShowPicker, toggleVisible, updateAttributes, createUser, user]
);
useEffect(() => {
if (defaultShowPicker && user && createUser === user.name) {
toggleVisible(true);
setTimeout(() => ref.current?.focus(), 100);
}
}, [defaultShowPicker, toggleVisible, createUser, user]);
return (
<NodeViewWrapper as="span" className={cls(styles.wrap, 'render-wrapper')} contentEditable={false}>
{isEditable ? (
<Popover
showArrow
position="bottomLeft"
spacing={12}
visible={visible}
onVisibleChange={onVisibleChange}
content={
<div style={{ width: 320 }}>
<TextArea
ref={ref}
autoFocus
placeholder="输入公式"
autosize
rows={3}
defaultValue={text}
onChange={(v) => updateAttributes({ text: v })}
style={{ marginBottom: 8 }}
/>
<Text type="tertiary" link={{ href: 'https://katex.org/', target: '_blank' }}>
<Space>
<IconHelpCircle />
</Space>
</Text>
</div>
}
trigger="click"
<NodeViewWrapper
className={cls(styles.wrap, 'render-wrapper')}
style={{
backgroundColor,
}}
contentEditable={false}
>
{content}
</Popover>
) : (
content
)}
</NodeViewWrapper>
);
};

View File

@ -25,6 +25,7 @@ import { Iframe } from 'tiptap/editor/menus/iframe';
import { Image } from 'tiptap/editor/menus/image';
import { Insert } from 'tiptap/editor/menus/insert';
import { Italic } from 'tiptap/editor/menus/italic';
import { Katex } from 'tiptap/editor/menus/katex';
import { Link } from 'tiptap/editor/menus/link';
import { Mind } from 'tiptap/editor/menus/mind';
import { OrderedList } from 'tiptap/editor/menus/ordered-list';
@ -35,7 +36,6 @@ import { Subscript } from 'tiptap/editor/menus/subscript';
import { Superscript } from 'tiptap/editor/menus/superscript';
import { Table } from 'tiptap/editor/menus/table';
import { TaskList } from 'tiptap/editor/menus/task-list';
import { Text } from 'tiptap/editor/menus/text';
import { TextColor } from 'tiptap/editor/menus/text-color';
import { Underline } from 'tiptap/editor/menus/underline';
import { Undo } from 'tiptap/editor/menus/undo';
@ -105,7 +105,7 @@ const _MenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
<Image editor={editor} />
<Iframe editor={editor} />
<Table editor={editor} />
{/* <Text editor={editor} /> */}
<Katex editor={editor} />
<Mind editor={editor} />
</Space>
</div>

View File

@ -0,0 +1,109 @@
import { IconCopy, IconDelete, IconEdit, IconHelpCircle } from '@douyinfe/semi-icons';
import { Button, Popover, Space, TextArea, Typography } from '@douyinfe/semi-ui';
import { Tooltip } from 'components/tooltip';
import { useUser } from 'data/user';
import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef } from 'react';
import { Divider } from 'tiptap/components/divider';
import { Katex } from 'tiptap/core/extensions/katex';
import { Editor } from 'tiptap/editor';
import { useAttributes } from 'tiptap/editor/hooks/use-attributes';
import { BubbleMenu } from 'tiptap/editor/views/bubble-menu';
import { copyNode, deleteNode } from 'tiptap/prose-utils';
const { Text } = Typography;
type KatexAttrs = {
text: string;
defaultShowPicker: boolean;
createUser: string;
};
export const KatexBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => {
const attrs = useAttributes<KatexAttrs, KatexAttrs>(editor, Katex.name, {
text: '',
defaultShowPicker: false,
createUser: '',
});
const { text, defaultShowPicker, createUser } = attrs;
const { user } = useUser();
const ref = useRef<HTMLTextAreaElement>();
const [visible, toggleVisible] = useToggle(false);
const shouldShow = useCallback(() => editor.isActive(Katex.name), [editor]);
const getRenderContainer = useCallback((node) => {
return node;
}, []);
const copyMe = useCallback(() => copyNode(Katex.name, editor), [editor]);
const deleteMe = useCallback(() => deleteNode(Katex.name, editor), [editor]);
const submit = useCallback(() => {
editor.chain().focus().setKatex({ text: ref.current.value, createUser }).run();
}, [editor, createUser]);
useEffect(() => {
if (defaultShowPicker && user && createUser === user.name) {
toggleVisible(true);
}
}, [defaultShowPicker, toggleVisible, createUser, user]);
useEffect(() => {
if (visible) {
setTimeout(() => ref.current?.focus(), 100);
}
}, [visible]);
return (
<BubbleMenu
className={'bubble-menu'}
editor={editor}
pluginKey="Katex-bubble-menu"
shouldShow={shouldShow}
getRenderContainer={getRenderContainer}
>
<Space spacing={4}>
<Tooltip content="复制">
<Button onClick={copyMe} icon={<IconCopy />} type="tertiary" theme="borderless" size="small" />
</Tooltip>
<Popover
showArrow
position="topLeft"
spacing={12}
visible={visible}
content={
<div style={{ width: 320 }}>
<TextArea
ref={ref}
autoFocus
placeholder="输入公式"
autosize
rows={3}
defaultValue={text}
style={{ marginBottom: 8 }}
/>
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
<Button onClick={submit}></Button>
<Text type="tertiary" link={{ href: 'https://katex.org/', target: '_blank' }}>
<Space>
<IconHelpCircle />
</Space>
</Text>
</Space>
</div>
}
trigger="click"
onVisibleChange={toggleVisible}
>
<Button size="small" type="tertiary" theme="borderless" icon={<IconEdit />} onClick={toggleVisible} />
</Popover>
<Divider />
<Tooltip content="删除" hideOnClick>
<Button size="small" type="tertiary" theme="borderless" icon={<IconDelete />} onClick={deleteMe} />
</Tooltip>
</Space>
</BubbleMenu>
);
};

View File

@ -0,0 +1,12 @@
import React from 'react';
import { Editor } from 'tiptap/editor';
import { KatexBubbleMenu } from './bubble';
export const Katex: React.FC<{ editor: Editor }> = ({ editor }) => {
return (
<>
<KatexBubbleMenu editor={editor} />
</>
);
};