import type { Editor } from '@tiptap/core' import CharacterCount from '@tiptap/extension-character-count' import Placeholder from '@tiptap/extension-placeholder' import clsx from 'clsx' import { type JSX, Show, createEffect, createSignal, onCleanup } from 'solid-js' import { createEditorTransaction, createTiptapEditor, useEditorHTML, useEditorIsEmpty, useEditorIsFocused } from 'solid-tiptap' import { Toolbar } from 'terracotta' import { Icon } from '~/components/_shared/Icon/Icon' import { Popover } from '~/components/_shared/Popover/Popover' import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' import { base } from '~/lib/editorOptions' import { InsertLinkForm } from '../InsertLinkForm/InsertLinkForm' import styles from '../SimplifiedEditor.module.scss' interface ControlProps { editor: Editor title: string key: string onChange: () => void isActive?: (editor: Editor) => boolean children: JSX.Element } function Control(props: ControlProps): JSX.Element { const handleClick = (ev?: MouseEvent) => { ev?.preventDefault() ev?.stopPropagation() props.onChange?.() } return ( {(triggerRef: (el: HTMLElement) => void) => ( )} ) } interface MiniEditorProps { content?: string onChange?: (content: string) => void limit?: number placeholder?: string } export default function MiniEditor(props: MiniEditorProps): JSX.Element { const [editorElement, setEditorElement] = createSignal() const [counter, setCounter] = createSignal(0) const [showLinkInput, setShowLinkInput] = createSignal(false) const [showSimpleMenu, setShowSimpleMenu] = createSignal(false) const { t } = useLocalize() const { showModal } = useUI() const editor = createTiptapEditor(() => ({ element: editorElement()!, extensions: [ ...base, Placeholder.configure({ emptyNodeClass: styles.emptyNode, placeholder: props.placeholder }), CharacterCount.configure({ limit: props.limit }) ], editorProps: { attributes: { class: styles.simplifiedEditorField } }, content: props.content || '' })) const isEmpty = useEditorIsEmpty(editor) const isFocused = useEditorIsFocused(editor) const isTextSelection = createEditorTransaction(editor, (instance) => !instance?.state.selection.empty) const html = useEditorHTML(editor) createEffect(() => setShowSimpleMenu(isTextSelection())) createEffect(() => { const textLength = editor()?.getText().length || 0 setCounter(textLength) const content = html() content && props.onChange?.(content) }) const handleLinkClick = () => { setShowLinkInput(!showLinkInput()) editor()?.chain().focus().run() } // Prevent focus loss when clicking inside the toolbar const handleMouseDownOnToolbar = (event: MouseEvent) => { event.preventDefault() // Prevent the default focus shift } const [toolbarElement, setToolbarElement] = createSignal() // Attach the event handler to the toolbar onCleanup(() => { toolbarElement()?.removeEventListener('mousedown', handleMouseDownOnToolbar) }) return (
{(instance) => (
setShowLinkInput(false)} />} >
instance.chain().focus().toggleBold().run()} title={t('Bold')} > instance.chain().focus().toggleItalic().run()} title={t('Italic')} > instance.chain().focus().toggleBlockquote().run()} title={t('Add blockquote')} > showModal('simplifiedEditorUploadImage')} title={t('Add image')} >
)}
0}> {counter()} / {props.limit || '∞'}
) }