mirror of https://github.com/fantasticit/think.git
tiptap: improve katext
parent
b08e1be52f
commit
589eff0545
|
@ -20,8 +20,7 @@ export const KatexInputRegex = /^\$\$(.+)?\$\$$/;
|
|||
|
||||
export const Katex = Node.create({
|
||||
name: 'katex',
|
||||
group: 'inline',
|
||||
inline: true,
|
||||
group: 'block',
|
||||
selectable: true,
|
||||
atom: true,
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
{content}
|
||||
</Popover>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
<NodeViewWrapper
|
||||
className={cls(styles.wrap, 'render-wrapper')}
|
||||
style={{
|
||||
backgroundColor,
|
||||
}}
|
||||
contentEditable={false}
|
||||
>
|
||||
{content}
|
||||
</NodeViewWrapper>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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