Add extensions and implement ToolbarButton
This commit is contained in:
parent
516b34752f
commit
fc0a4b1165
2 changed files with 177 additions and 86 deletions
|
|
@ -61,6 +61,44 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0 $unit-2x;
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
padding: 0 $unit-2x;
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: $font-xlarge;
|
||||||
|
font-weight: $medium;
|
||||||
|
margin: $unit 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: $font-large;
|
||||||
|
font-weight: $medium;
|
||||||
|
margin: $unit 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $medium;
|
||||||
|
margin: $unit 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
border-radius: $item-corner-small;
|
||||||
|
background: var(--highlight-bg);
|
||||||
|
color: var(--highlight-text);
|
||||||
|
padding: 1px $unit-fourth;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
background: var(--input-bound-bg);
|
background: var(--input-bound-bg);
|
||||||
border-radius: $card-corner;
|
border-radius: $card-corner;
|
||||||
|
|
@ -180,26 +218,6 @@
|
||||||
padding: $unit;
|
padding: $unit;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
button {
|
|
||||||
background: var(--toolbar-item-bg);
|
|
||||||
border-radius: $bubble-menu-item-corner;
|
|
||||||
color: var(--toolbar-item-text);
|
|
||||||
font-weight: $medium;
|
|
||||||
font-size: $font-small;
|
|
||||||
padding: $unit-half $unit;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--toolbar-item-bg-hover);
|
|
||||||
color: var(--toolbar-item-text-hover);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--toolbar-item-bg-active);
|
|
||||||
color: var(--toolbar-item-text-active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
background: var(--toolbar-divider-bg);
|
background: var(--toolbar-divider-bg);
|
||||||
border-radius: $full-corner;
|
border-radius: $full-corner;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,29 @@
|
||||||
import { ComponentProps, useCallback, useEffect, useState } from 'react'
|
import { ComponentProps, useCallback, useEffect } from 'react'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useEditor, Editor as TiptapEditor, EditorContent } from '@tiptap/react'
|
import { useEditor, EditorContent } from '@tiptap/react'
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import Link from '@tiptap/extension-link'
|
import Link from '@tiptap/extension-link'
|
||||||
|
import Highlight from '@tiptap/extension-highlight'
|
||||||
|
import Typography from '@tiptap/extension-typography'
|
||||||
import Youtube from '@tiptap/extension-youtube'
|
import Youtube from '@tiptap/extension-youtube'
|
||||||
import CustomMention from '~extensions/CustomMention'
|
import CustomMention from '~extensions/CustomMention'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
|
||||||
import { mentionSuggestionOptions } from '~utils/mentionSuggestions'
|
import { mentionSuggestionOptions } from '~utils/mentionSuggestions'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent } from '@tiptap/core'
|
||||||
|
import ToolbarButton from '~components/common/ToolbarButton'
|
||||||
|
|
||||||
|
import BoldIcon from 'remixicon-react/BoldIcon'
|
||||||
|
import ItalicIcon from 'remixicon-react/ItalicIcon'
|
||||||
|
import StrikethroughIcon from 'remixicon-react/StrikethroughIcon'
|
||||||
|
import UnorderedListIcon from 'remixicon-react/ListUnorderedIcon'
|
||||||
|
import OrderedListIcon from '~public/icons/remix/list-ordered-2.svg'
|
||||||
|
import PaintbrushIcon from 'remixicon-react/PaintbrushLineIcon'
|
||||||
|
import H1Icon from 'remixicon-react/H1Icon'
|
||||||
|
import H2Icon from 'remixicon-react/H2Icon'
|
||||||
|
import H3Icon from 'remixicon-react/H3Icon'
|
||||||
|
import LinkIcon from 'remixicon-react/LinkIcon'
|
||||||
|
import YoutubeIcon from 'remixicon-react/YoutubeLineIcon'
|
||||||
import styles from './index.module.scss'
|
import styles from './index.module.scss'
|
||||||
|
|
||||||
interface Props extends ComponentProps<'div'> {
|
interface Props extends ComponentProps<'div'> {
|
||||||
|
|
@ -30,7 +44,7 @@ const Editor = ({
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const locale = router.locale || 'en'
|
const locale = router.locale || 'en'
|
||||||
|
|
||||||
const [editor, setEditor] = useState<TiptapEditor | undefined>(undefined)
|
// const [editor, setEditor] = useState<TiptapEditor | undefined>(undefined)
|
||||||
|
|
||||||
function isJSON(content?: string) {
|
function isJSON(content?: string) {
|
||||||
if (!content) return false
|
if (!content) return false
|
||||||
|
|
@ -44,50 +58,57 @@ const Editor = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
editor?.destroy()
|
// console.log('Recreating editor...')
|
||||||
const newEditor: TiptapEditor = new TiptapEditor({
|
// editor?.destroy()
|
||||||
content: formatContent(content),
|
// setEditor(newEditor)
|
||||||
editable: editable,
|
|
||||||
editorProps: {
|
|
||||||
attributes: {
|
|
||||||
class: classNames(
|
|
||||||
{
|
|
||||||
[styles.editor]: true,
|
|
||||||
[styles.bound]: bound,
|
|
||||||
},
|
|
||||||
className?.split(' ').map((c) => styles[c])
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
extensions: [
|
|
||||||
StarterKit,
|
|
||||||
Link,
|
|
||||||
CustomMention.configure({
|
|
||||||
renderLabel({ options, node }) {
|
|
||||||
return `${node.attrs.id.name[locale] ?? node.attrs.id.granblue_en}`
|
|
||||||
},
|
|
||||||
suggestion: mentionSuggestionOptions,
|
|
||||||
HTMLAttributes: {
|
|
||||||
class: classNames({
|
|
||||||
[styles.mention]: true,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Youtube.configure({
|
|
||||||
inline: false,
|
|
||||||
modestBranding: true,
|
|
||||||
interfaceLanguage: locale,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
onUpdate: ({ editor }) => {
|
|
||||||
const json = editor.getJSON()
|
|
||||||
if (onUpdate) onUpdate(json)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
setEditor(newEditor)
|
|
||||||
}, [content])
|
}, [content])
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: formatContent(content),
|
||||||
|
editable: editable,
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: classNames(
|
||||||
|
{
|
||||||
|
[styles.editor]: true,
|
||||||
|
[styles.bound]: bound,
|
||||||
|
},
|
||||||
|
className?.split(' ').map((c) => styles[c])
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extensions: [
|
||||||
|
StarterKit.configure({
|
||||||
|
heading: {
|
||||||
|
levels: [1, 2, 3],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Link,
|
||||||
|
Highlight,
|
||||||
|
Typography,
|
||||||
|
CustomMention.configure({
|
||||||
|
renderLabel({ options, node }) {
|
||||||
|
return `${node.attrs.id.name[locale] ?? node.attrs.id.granblue_en}`
|
||||||
|
},
|
||||||
|
suggestion: mentionSuggestionOptions,
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: classNames({
|
||||||
|
[styles.mention]: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Youtube.configure({
|
||||||
|
inline: false,
|
||||||
|
modestBranding: true,
|
||||||
|
interfaceLanguage: locale,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
onUpdate: ({ editor }) => {
|
||||||
|
const json = editor.getJSON()
|
||||||
|
if (onUpdate) onUpdate(json)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
function formatContent(content?: string) {
|
function formatContent(content?: string) {
|
||||||
if (!content) return ''
|
if (!content) return ''
|
||||||
if (isJSON(content)) return JSON.parse(content)
|
if (isJSON(content)) return JSON.parse(content)
|
||||||
|
|
@ -146,32 +167,84 @@ const Editor = ({
|
||||||
<section className={styles.wrapper}>
|
<section className={styles.wrapper}>
|
||||||
{editor && editable === true && (
|
{editor && editable === true && (
|
||||||
<nav className={styles.toolbar}>
|
<nav className={styles.toolbar}>
|
||||||
<button
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="bold"
|
||||||
|
icon={<BoldIcon />}
|
||||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||||
className={editor.isActive('bold') ? styles.active : ''}
|
/>
|
||||||
>
|
<ToolbarButton
|
||||||
bold
|
editor={editor}
|
||||||
</button>
|
action="italic"
|
||||||
<button
|
icon={<ItalicIcon />}
|
||||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||||
className={editor.isActive('italic') ? styles.active : ''}
|
/>
|
||||||
>
|
<ToolbarButton
|
||||||
italic
|
editor={editor}
|
||||||
</button>
|
action="strike"
|
||||||
<button
|
icon={<StrikethroughIcon />}
|
||||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||||
className={editor.isActive('strike') ? styles.active : ''}
|
/>
|
||||||
>
|
<ToolbarButton
|
||||||
strike
|
editor={editor}
|
||||||
</button>
|
action="highlight"
|
||||||
|
icon={<PaintbrushIcon />}
|
||||||
|
onClick={() => editor.chain().focus().toggleHighlight().run()}
|
||||||
|
/>
|
||||||
<div className={styles.divider} />
|
<div className={styles.divider} />
|
||||||
<button
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="heading"
|
||||||
|
level={1}
|
||||||
|
icon={<H1Icon />}
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="heading"
|
||||||
|
level={2}
|
||||||
|
icon={<H2Icon />}
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="heading"
|
||||||
|
level={3}
|
||||||
|
icon={<H3Icon />}
|
||||||
|
onClick={() =>
|
||||||
|
editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className={styles.divider} />
|
||||||
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="bulletList"
|
||||||
|
icon={<UnorderedListIcon />}
|
||||||
|
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||||
|
/>
|
||||||
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="orderedList"
|
||||||
|
icon={<OrderedListIcon />}
|
||||||
|
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||||
|
/>
|
||||||
|
<div className={styles.divider} />
|
||||||
|
<ToolbarButton
|
||||||
|
editor={editor}
|
||||||
|
action="link"
|
||||||
|
icon={<LinkIcon />}
|
||||||
onClick={setLink}
|
onClick={setLink}
|
||||||
className={editor.isActive('link') ? styles.active : ''}
|
/>
|
||||||
>
|
<ToolbarButton
|
||||||
+ link
|
editor={editor}
|
||||||
</button>
|
action="youtube"
|
||||||
<button onClick={addYoutubeVideo}>+ youtube</button>
|
icon={<YoutubeIcon />}
|
||||||
|
onClick={addYoutubeVideo}
|
||||||
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue