'use client' import { ComponentProps, useCallback, useEffect } from 'react' import { useEditor, EditorContent } from '@tiptap/react' import { getCookie } from 'cookies-next' import { useTranslations } from 'next-intl' import StarterKit from '@tiptap/starter-kit' import Link from '@tiptap/extension-link' import Highlight from '@tiptap/extension-highlight' import Placeholder from '@tiptap/extension-placeholder' import Typography from '@tiptap/extension-typography' import Youtube from '@tiptap/extension-youtube' import CustomMention from '~extensions/CustomMention' import classNames from 'classnames' import { mentionSuggestionOptions } from '~utils/mentionSuggestions' 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' interface Props extends ComponentProps<'div'> { bound: boolean editable?: boolean content?: string onUpdate?: (content: JSONContent) => void } const Editor = ({ bound, className, content, editable, onUpdate, ...props }: Props) => { // Hooks: Locale const locale = (getCookie('NEXT_LOCALE') as string) || 'en' const t = useTranslations('common') useEffect(() => { editor?.commands.setContent(formatContent(content)) }, [content]) // Setup: Editor 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, Placeholder.configure({ emptyEditorClass: styles.empty, placeholder: t('modals.edit_team.placeholders.description'), }), Typography, CustomMention.configure({ renderLabel({ options, node }) { return `${node.attrs.id.name[locale] ?? node.attrs.id.granblue_en}` }, suggestion: mentionSuggestionOptions as any, HTMLAttributes: { class: classNames({ [styles.mention]: true, }), }, }), Youtube.configure({ inline: false, modestBranding: true, interfaceLanguage: locale, }), ], onUpdate: ({ editor }) => { const json = editor.getJSON() if (onUpdate) onUpdate(json) }, }) // Methods: Convenience function isJSON(content?: string) { if (!content) return false try { JSON.parse(content) } catch (e) { return false } return true } function formatContent(content?: string) { if (!content) return '' if (isJSON(content)) return JSON.parse(content) else { // Otherwise, create a new

tag after each double newline. // Add < br /> tags for single newlines. // Add a < br /> after each paragraph. const paragraphs = content.split('\n\n') const formatted = paragraphs .map((p) => { const lines = p.split('\n') return lines.join('
') }) .join('


') return formatted } } // Methods: Actions const setLink = useCallback(() => { const previousUrl = editor?.getAttributes('link').href const url = window.prompt('URL', previousUrl) // cancelled if (url === null) { return } // empty if (url === '') { editor?.chain().focus().extendMarkRange('link').unsetLink().run() return } // update link editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run() }, [editor]) const addYoutubeVideo = () => { const url = prompt('Enter YouTube URL') if (editor && url) { editor.commands.setYoutubeVideo({ src: url, width: 320, height: 180, }) } } // Methods: Rendering if (!editor) { return null } return (

{editor && editable === true && ( )}
) } Editor.defaultProps = { bound: false, editable: false, } export default Editor