mirror of https://github.com/fantasticit/think.git
tiptap: improve katext
This commit is contained in:
parent
b08e1be52f
commit
589eff0545
|
@ -20,8 +20,7 @@ export const KatexInputRegex = /^\$\$(.+)?\$\$$/;
|
||||||
|
|
||||||
export const Katex = Node.create({
|
export const Katex = Node.create({
|
||||||
name: 'katex',
|
name: 'katex',
|
||||||
group: 'inline',
|
group: 'block',
|
||||||
inline: true,
|
|
||||||
selectable: true,
|
selectable: true,
|
||||||
atom: true,
|
atom: true,
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
padding: 2px 8px;
|
padding: 1rem 4px;
|
||||||
font-size: 12px;
|
font-size: 1em;
|
||||||
line-height: 16px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transform: translateY(1px);
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { useUser } from 'data/user';
|
import { convertColorToRGBA } from 'helpers/color';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { Theme, ThemeEnum } from 'hooks/use-theme';
|
||||||
import katex from 'katex';
|
import katex from 'katex';
|
||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Text } = Typography;
|
export const KatexWrapper = ({ node }) => {
|
||||||
|
const { text } = node.attrs;
|
||||||
export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
const { theme } = Theme.useHook();
|
||||||
const isEditable = editor.isEditable;
|
const backgroundColor = useMemo(() => {
|
||||||
const { text, defaultShowPicker, createUser } = node.attrs;
|
const color = `rgb(254, 242, 237)`;
|
||||||
const { user } = useUser();
|
if (theme === ThemeEnum.dark) return convertColorToRGBA(color, 0.75);
|
||||||
const ref = useRef<HTMLTextAreaElement>();
|
return color;
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
}, [theme]);
|
||||||
|
|
||||||
const formatText = useMemo(() => {
|
const formatText = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
|
@ -36,59 +34,15 @@ export const KatexWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
[text, formatText]
|
[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 (
|
return (
|
||||||
<NodeViewWrapper as="span" className={cls(styles.wrap, 'render-wrapper')} contentEditable={false}>
|
<NodeViewWrapper
|
||||||
{isEditable ? (
|
className={cls(styles.wrap, 'render-wrapper')}
|
||||||
<Popover
|
style={{
|
||||||
showArrow
|
backgroundColor,
|
||||||
position="bottomLeft"
|
}}
|
||||||
spacing={12}
|
contentEditable={false}
|
||||||
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"
|
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Popover>
|
|
||||||
) : (
|
|
||||||
content
|
|
||||||
)}
|
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ import { Iframe } from 'tiptap/editor/menus/iframe';
|
||||||
import { Image } from 'tiptap/editor/menus/image';
|
import { Image } from 'tiptap/editor/menus/image';
|
||||||
import { Insert } from 'tiptap/editor/menus/insert';
|
import { Insert } from 'tiptap/editor/menus/insert';
|
||||||
import { Italic } from 'tiptap/editor/menus/italic';
|
import { Italic } from 'tiptap/editor/menus/italic';
|
||||||
|
import { Katex } from 'tiptap/editor/menus/katex';
|
||||||
import { Link } from 'tiptap/editor/menus/link';
|
import { Link } from 'tiptap/editor/menus/link';
|
||||||
import { Mind } from 'tiptap/editor/menus/mind';
|
import { Mind } from 'tiptap/editor/menus/mind';
|
||||||
import { OrderedList } from 'tiptap/editor/menus/ordered-list';
|
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 { Superscript } from 'tiptap/editor/menus/superscript';
|
||||||
import { Table } from 'tiptap/editor/menus/table';
|
import { Table } from 'tiptap/editor/menus/table';
|
||||||
import { TaskList } from 'tiptap/editor/menus/task-list';
|
import { TaskList } from 'tiptap/editor/menus/task-list';
|
||||||
import { Text } from 'tiptap/editor/menus/text';
|
|
||||||
import { TextColor } from 'tiptap/editor/menus/text-color';
|
import { TextColor } from 'tiptap/editor/menus/text-color';
|
||||||
import { Underline } from 'tiptap/editor/menus/underline';
|
import { Underline } from 'tiptap/editor/menus/underline';
|
||||||
import { Undo } from 'tiptap/editor/menus/undo';
|
import { Undo } from 'tiptap/editor/menus/undo';
|
||||||
|
@ -105,7 +105,7 @@ const _MenuBar: React.FC<{ editor: Editor }> = ({ editor }) => {
|
||||||
<Image editor={editor} />
|
<Image editor={editor} />
|
||||||
<Iframe editor={editor} />
|
<Iframe editor={editor} />
|
||||||
<Table editor={editor} />
|
<Table editor={editor} />
|
||||||
{/* <Text editor={editor} /> */}
|
<Katex editor={editor} />
|
||||||
<Mind editor={editor} />
|
<Mind editor={editor} />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue