/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { Color } from '@tiptap/extension-color' import { Link } from '@tiptap/extension-link' import { Placeholder } from '@tiptap/extension-placeholder' import { Table } from '@tiptap/extension-table' import { TableCell } from '@tiptap/extension-table-cell' import { TableHeader } from '@tiptap/extension-table-header' import { TableRow } from '@tiptap/extension-table-row' import { TaskItem } from '@tiptap/extension-task-item' import { TaskList } from '@tiptap/extension-task-list' import { Youtube } from '@tiptap/extension-youtube' import { EditorContent, mergeAttributes, useEditor } from '@tiptap/react' import { StarterKit } from '@tiptap/starter-kit' import { MarkdownSerializer } from 'prosemirror-markdown' import { useEffect } from 'react' import { Markdown } from 'tiptap-markdown' import { useTags } from '#components/Map/hooks/useTags' import { CustomHeading } from './Extensions/CustomHeading' import { CustomImage } from './Extensions/CustomImage' import { HashtagMention } from './Extensions/HashtagMention' import { suggestion } from './Extensions/suggestion' import { TextEditorMenu } from './TextEditorMenu' import type { Editor } from '@tiptap/react' import type { MarkdownSerializerState } from 'prosemirror-markdown' import type { Node as ProseMirrorNode } from 'prosemirror-model' interface RichTextEditorProps { labelTitle?: string labelStyle?: string containerStyle?: string defaultValue: string placeholder?: string showMenu?: boolean readOnly?: boolean updateFormValue?: (value: string) => void } interface ImageAttrs { src: string alt?: string title?: string style?: string } type NodeSerializerFn = ( state: MarkdownSerializerState, node: ProseMirrorNode, parent: ProseMirrorNode, index: number, ) => void /** * @category Input */ export function RichTextEditor({ labelTitle, labelStyle, containerStyle, defaultValue, placeholder, showMenu = true, readOnly = false, updateFormValue, }: RichTextEditorProps) { const handleChange = () => { if (updateFormValue) { if (editor) { updateFormValue(getStyledMarkdown(editor)) } } } const tags = useTags() const editor = useEditor({ editable: !readOnly, extensions: [ Color.configure({ types: ['textStyle', 'listItem'] }), Youtube.configure({ nocookie: true, allowFullscreen: true, addPasteHandler: true, height: undefined, width: undefined, modestBranding: true, }), StarterKit.configure({ bulletList: { keepMarks: true, keepAttributes: false, }, orderedList: { keepMarks: true, keepAttributes: false, }, heading: false, }), HashtagMention.configure({ HTMLAttributes: { class: 'mention' }, renderHTML: ({ node, options }) => { return [ 'span', mergeAttributes(options.HTMLAttributes, { 'data-id': node.attrs.id, }), `#${node.attrs.id}`, ] }, suggestion: { char: '#', items: ({ query }) => { return tags .map((tag) => tag.name) .filter((tag) => tag.toLowerCase().startsWith(query.toLowerCase())) .slice(0, 5) }, ...suggestion, }, }), Markdown.configure({ html: true, linkify: true, transformCopiedText: true, transformPastedText: true, }), Table.configure({ resizable: true, }), TableCell, TableHeader, TableRow, TaskList, TaskItem, CustomImage, Link, CustomHeading, Placeholder.configure({ placeholder, emptyEditorClass: 'is-editor-empty', }), ], content: defaultValue, onUpdate: handleChange, editorProps: { attributes: { class: `tw:h-full markdown tw:max-h-full ${readOnly ? `` : `tw:p-2`} tw:overflow-y-auto tw:overflow-x-hidden`, }, }, }) useEffect(() => { if (editor?.storage.markdown.getMarkdown() === '' || !editor?.storage.markdown.getMarkdown()) { editor?.commands.setContent(defaultValue) } }, [defaultValue, editor]) return (