Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extention katex #27

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/.vitepress/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ export function getLocaleConfig(lang: string) {
text: 'Emoji',
link: '/extensions/Emoji/index.md',
},
{
text: 'Katex',
link: '/extensions/Katex/index.md',
},
],
},
]
Expand Down
4 changes: 4 additions & 0 deletions docs/extensions/Emoji/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
---
description: Emoji

next:
text: Katex
link: /extensions/Katex/index.md
---

# Emoji
Expand Down
21 changes: 21 additions & 0 deletions docs/extensions/Katex/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
description: Katex
---

# Katex

- Katex Extension for Tiptap Editor.
- This extension allows you to add Katex math equations to your editor.
- This extension is based on [katex](https://katex.org/).

## Usage

```tsx
import { Katex } from 'reactjs-tiptap-editor'; // [!code ++]

const extensions = [
...,
// Import Extensions Here
Katex, // [!code ++]
];
```
1 change: 1 addition & 0 deletions docs/guide/bubble-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The system provides the following default bubble menus:
| BubbleMenuImage | Provides image-related operations like resizing, alignment, etc. | imageConfig |
| BubbleMenuVideo | Provides video-related operations like playback control, size adjustment, etc. | videoConfig |
| TableBubbleMenu | Provides table-related operations like adding/deleting rows and columns, merging cells, etc. | tableConfig |
| BubbleMenuKatex | The BubbleMenuKatex component provides operations related to rendering mathematical equations using the KaTeX library. It allows users to insert, edit, and format mathematical expressions within the editor. | katexConfig |
| ColumnsMenu | Provides multi-column layout operations like adjusting column numbers, widths, etc. | columnConfig |
| ContentMenu | Provides general content-related operations like copy, paste, delete, etc. | floatingMenuConfig |

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"clsx": "^2.1.1",
"deep-equal": "^2.2.3",
"echo-drag-handle-plugin": "^0.0.2",
"katex": "^0.16.11",
"lodash-unified": "^1.0.3",
"lucide-react": "^0.427.0",
"react-colorful": "^5.6.1",
Expand All @@ -123,6 +124,7 @@
"@eslint-react/eslint-plugin": "^1.10.1",
"@total-typescript/ts-reset": "^0.5.1",
"@types/deep-equal": "^1.0.4",
"@types/katex": "^0.16.7",
"@types/node": "^22.3.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
2 changes: 2 additions & 0 deletions playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import RichTextEditor, {
ImageUpload,
Indent,
Italic,
Katex,
LineHeight,
Link,
MoreMark,
Expand Down Expand Up @@ -108,6 +109,7 @@ const extensions = [
Table,
Iframe.configure({ spacer: true }),
Emoji,
Katex,
]

const DEFAULT = `<h1 style="text-align: center">Rich Text Editor</h1><p>A modern WYSIWYG rich text editor based on <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://github.com/scrumpy/tiptap">tiptap</a> and <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://ui.shadcn.com/">shadcn ui</a> for Reactjs</p><p></p><p style="text-align: center"></p><p style="text-align: center"><img height="auto" src="https://picsum.photos/1920/1080.webp?t=1" width="500"></p><p></p><div data-type="horizontalRule"><hr></div><h2>Demo</h2><p>👉<a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://reactjs-tiptap-editor.vercel.app/">Demo</a></p><h2>Features</h2><ul><li><p>Use <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://ui.shadcn.com/">shadcn ui</a> components</p></li><li><p>Markdown support</p></li><li><p>TypeScript support</p></li><li><p>I18n support</p></li><li><p>React support</p></li><li><p>Slash Commands</p></li><li><p>Multi Column</p></li><li><p>TailwindCss</p></li><li><p>Support emoji</p></li><li><p>Support iframe</p></li></ul><h2>Installation</h2><pre><code>pnpm add reactjs-tiptap-editor</code class="language-bash"></pre><p></p>`
Expand Down
19 changes: 19 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/components/BubbleMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Editor } from '@tiptap/core'
import { BubbleMenuImage, BubbleMenuLink, BubbleMenuText, BubbleMenuVideo, ContentMenu, TableBubbleMenu } from '@/components'
import ColumnsMenu from '@/extensions/MultiColumn/menus/ColumnsMenu'
import type { BubbleMenuProps as BubbleMenuPropsType } from '@/types'
import BubbleMenuKatex from '@/components/menus/components/BubbleMenuKatex'

export interface BubbleMenuComponentProps {
editor: Editor
Expand All @@ -27,6 +28,7 @@ export function BubbleMenu({ editor, disabled, bubbleMenu }: BubbleMenuComponent
extensionsNames.includes('link') && !bubbleMenu?.linkConfig?.hidden ? <BubbleMenuLink key="link" editor={editor} disabled={disabled} /> : null,
extensionsNames.includes('image') && !bubbleMenu?.imageConfig?.hidden ? <BubbleMenuImage key="image" editor={editor} disabled={disabled} /> : null,
extensionsNames.includes('video') && !bubbleMenu?.videoConfig?.hidden ? <BubbleMenuVideo key="video" editor={editor} disabled={disabled} /> : null,
extensionsNames.includes('katex') && !bubbleMenu?.katexConfig?.hidden ? <BubbleMenuKatex key="katex" editor={editor} disabled={disabled} /> : null,
!bubbleMenu?.floatingMenuConfig?.hidden ? <ContentMenu key="content" editor={editor} disabled={disabled} /> : null,
!bubbleMenu?.textConfig?.hidden ? <BubbleMenuText key="text" editor={editor} disabled={disabled} /> : null,
]
Expand Down
2 changes: 2 additions & 0 deletions src/components/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
Quote,
Redo2,
Replace,
Sigma,
SmilePlus,
SmilePlusIcon,
Sparkles,
Expand Down Expand Up @@ -151,4 +152,5 @@ export const icons = {
DeleteRow,
SearchAndReplace: Replace,
EmojiIcon: SmilePlusIcon,
KatexIcon: Sigma,
} as any
103 changes: 103 additions & 0 deletions src/components/menus/components/BubbleMenuKatex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { BubbleMenu } from '@tiptap/react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { HelpCircle, Pencil, Trash2 } from 'lucide-react'
import { Katex } from '@/extensions'
import { deleteNode } from '@/utils/delete-node'
import { useAttributes } from '@/hooks/useAttributes'
import type { IKatexAttrs } from '@/extensions/Katex'
import { Textarea } from '@/components/ui/textarea'
import { ActionButton } from '@/components/ActionButton'
import { Button } from '@/components/ui'

function BubbleMenuKatex({ editor, ...props }: any) {
const attrs = useAttributes<IKatexAttrs>(editor, Katex.name, {
text: '',
defaultShowPicker: false,
})
const { text, defaultShowPicker } = attrs
const ref: any = useRef<HTMLTextAreaElement>()
const [visible, toggleVisible] = useState(false)

const shouldShow = useCallback(() => editor.isActive(Katex.name), [editor])

const deleteMe = useCallback(() => deleteNode(Katex.name, editor), [editor])

const submit = useCallback(() => {
editor.chain().focus().setKatex({ text: ref.current.value }).run()
}, [editor])

useEffect(() => {
if (defaultShowPicker) {
toggleVisible(true)
editor.chain().updateAttributes(Katex.name, { defaultShowPicker: false }).focus().run()
}
}, [editor, defaultShowPicker, toggleVisible])

useEffect(() => {
if (visible) {
setTimeout(() => ref.current?.focus(), 200)
}
}, [visible])

return (
<BubbleMenu
editor={editor}
pluginKey="Katex-bubble-menu"
shouldShow={shouldShow}
tippyOptions={{
popperOptions: {
modifiers: [{ name: 'flip', enabled: false }],
},
placement: 'bottom-start',
offset: [-2, 16],
zIndex: 9999,
onHidden: () => {
toggleVisible(false)
},
}}
>
{props?.disabled
? (
<></>
)
: (
<div className="p-2 bg-white border rounded-lg shadow-sm dark:bg-black border-neutral-200 dark:border-neutral-800">
{visible
? (
<>
<Textarea
ref={ref}
autoFocus
placeholder="Formula text"
rows={3}
defaultValue={text}
style={{ marginBottom: 8 }}
/>
<div className="flex items-center justify-between gap-[6px]">
<Button onClick={submit} className="flex-1">Submit</Button>

<a href="https://katex.org/" target="_blank" rel="noreferrer noopener">
<HelpCircle size={16} />
</a>
</div>
</>
)
: (
<div className="flex items-center justify-center gap-[6px]">
<ActionButton tooltip="Edit" action={() => toggleVisible(!visible)}>
<Pencil size={16} />
</ActionButton>

<ActionButton tooltip="Delete" action={deleteMe}>
<Trash2 size={16} />
</ActionButton>
</div>
)}
</div>
)}

</BubbleMenu>
)
}

export default BubbleMenuKatex
24 changes: 24 additions & 0 deletions src/components/ui/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react'

import { cn } from '@/lib/utils'

export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
)
},
)
Textarea.displayName = 'Textarea'

export { Textarea }
Loading
Loading