diff --git a/src/components/Editor/SimplifiedEditor.tsx b/src/components/Editor/SimplifiedEditor.tsx index 79db649f..2118e2a1 100644 --- a/src/components/Editor/SimplifiedEditor.tsx +++ b/src/components/Editor/SimplifiedEditor.tsx @@ -1,3 +1,4 @@ +import { Editor } from '@tiptap/core' import { Blockquote } from '@tiptap/extension-blockquote' import { Bold } from '@tiptap/extension-bold' import { BubbleMenu } from '@tiptap/extension-bubble-menu' @@ -10,7 +11,7 @@ import { Paragraph } from '@tiptap/extension-paragraph' import { Placeholder } from '@tiptap/extension-placeholder' import { Text } from '@tiptap/extension-text' import { clsx } from 'clsx' -import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' +import { Show, createEffect, createReaction, createSignal, on, onCleanup, onMount } from 'solid-js' import { Portal } from 'solid-js/web' import { createEditorTransaction, @@ -19,9 +20,9 @@ import { useEditorIsEmpty, useEditorIsFocused } from 'solid-tiptap' - import { useEditorContext } from '~/context/editor' import { useLocalize } from '~/context/localize' +import { useUI } from '~/context/ui' import { UploadedFile } from '~/types/upload' import { Button } from '../_shared/Button' import { Icon } from '../_shared/Icon' @@ -30,15 +31,12 @@ import { Modal } from '../_shared/Modal' import { Popover } from '../_shared/Popover' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' import { LinkBubbleMenuModule } from './LinkBubbleMenu' +import styles from './SimplifiedEditor.module.scss' import { TextBubbleMenu } from './TextBubbleMenu' import { UploadModalContent } from './UploadModalContent' import { Figcaption } from './extensions/Figcaption' import { Figure } from './extensions/Figure' -import { Editor } from '@tiptap/core' -import { useUI } from '~/context/ui' -import styles from './SimplifiedEditor.module.scss' - type Props = { placeholder: string initialContent?: string @@ -71,103 +69,27 @@ const SimplifiedEditor = (props: Props) => { const { showModal, hideModal } = useUI() const [counter, setCounter] = createSignal(0) const [shouldShowLinkBubbleMenu, setShouldShowLinkBubbleMenu] = createSignal(false) - const isCancelButtonVisible = createMemo(() => props.isCancelButtonVisible !== false) - const [editorElement, setEditorElement] = createSignal() const { editor, setEditor } = useEditorContext() const maxLength = props.maxLength ?? DEFAULT_MAX_LENGTH + let editorEl: HTMLDivElement | undefined let wrapperEditorElRef: HTMLElement | undefined let textBubbleMenuRef: HTMLDivElement | undefined let linkBubbleMenuRef: HTMLDivElement | undefined + // Extend the Figure extension to include Figcaption const ImageFigure = Figure.extend({ name: 'capturedImage', content: 'figcaption image' }) - createEffect( - on( - () => editorElement(), - (ee: HTMLDivElement | undefined) => { - if (ee && textBubbleMenuRef && linkBubbleMenuRef) { - const freshEditor = createTiptapEditor(() => ({ - element: ee, - editorProps: { - attributes: { - class: styles.simplifiedEditorField - } - }, - extensions: [ - Document, - Text, - Paragraph, - Bold, - Italic, - Link.extend({ - inclusive: false - }).configure({ - autolink: true, - openOnClick: false - }), - CharacterCount.configure({ - limit: props.noLimits ? null : maxLength - }), - Blockquote.configure({ - HTMLAttributes: { - class: styles.blockQuote - } - }), - BubbleMenu.configure({ - pluginKey: 'textBubbleMenu', - element: textBubbleMenuRef, - shouldShow: ({ view, state }) => { - if (!props.onlyBubbleControls) return false - const { selection } = state - const { empty } = selection - return view.hasFocus() && !empty - } - }), - BubbleMenu.configure({ - pluginKey: 'linkBubbleMenu', - element: linkBubbleMenuRef, - shouldShow: ({ state }) => { - const { selection } = state - const { empty } = selection - return !empty && shouldShowLinkBubbleMenu() - }, - tippyOptions: { - placement: 'bottom' - } - }), - ImageFigure, - Image, - Figcaption, - Placeholder.configure({ - emptyNodeClass: styles.emptyNode, - placeholder: props.placeholder - }) - ], - autofocus: props.autoFocus, - content: props.initialContent || null - })) - const editorInstance = freshEditor() - if (!editorInstance) return - setEditor(editorInstance) - } - }, - { defer: true } - ) - ) - const isEmpty = useEditorIsEmpty(() => editor()) const isFocused = useEditorIsFocused(() => editor()) const isActive = (name: string) => createEditorTransaction( () => editor(), - (ed) => { - return ed?.isActive(name) - } + (ed) => ed?.isActive(name) ) const html = useEditorHTML(() => editor()) @@ -205,16 +127,6 @@ const SimplifiedEditor = (props: Props) => { editor()?.commands.clearContent(true) } - createEffect(() => { - if (props.setClear) { - editor()?.commands.clearContent(true) - } - if (props.resetToInitial) { - editor()?.commands.clearContent(true) - if (props.initialContent) editor()?.commands.setContent(props.initialContent) - } - }) - const handleKeyDown = (event: KeyboardEvent) => { if (isEmpty() || !isFocused()) { return @@ -243,19 +155,89 @@ const SimplifiedEditor = (props: Props) => { window.removeEventListener('keydown', handleKeyDown) editor()?.destroy() }) + + console.debug('[SimplifiedEditor] mounted') + const freshEditor = createTiptapEditor(() => ({ + element: editorEl as HTMLDivElement, + editorProps: { + attributes: { + class: styles.simplifiedEditorField + } + }, + extensions: [ + Document, + Text, + Paragraph, + Bold, + Italic, + Link.extend({ + inclusive: false + }).configure({ + autolink: true, + openOnClick: false + }), + CharacterCount.configure({ + limit: props.noLimits ? null : maxLength + }), + Blockquote.configure({ + HTMLAttributes: { + class: styles.blockQuote + } + }), + BubbleMenu.configure({ + pluginKey: 'textBubbleMenu', + element: textBubbleMenuRef, + shouldShow: ({ view, state }) => { + if (!props.onlyBubbleControls) return false + const { selection } = state + return view.hasFocus() && !selection.empty + } + }), + BubbleMenu.configure({ + pluginKey: 'linkBubbleMenu', + element: linkBubbleMenuRef, + shouldShow: ({ state }) => + state.selection && !state.selection.empty && shouldShowLinkBubbleMenu(), + tippyOptions: { + placement: 'bottom' + } + }), + ImageFigure, + Image, + Figcaption, + Placeholder.configure({ + emptyNodeClass: styles.emptyNode, + placeholder: props.placeholder + }) + ], + autofocus: props.autoFocus, + content: props.initialContent || null + })) + const ed = freshEditor() + ed && setEditor(ed) }) - if (props.onChange) { - createEffect(() => { - props.onChange?.(html() || '') - }) - } + createReaction( + on( + editor, + (e) => { + e?.commands.clearContent(props.resetToInitial || props.setClear) + props.initialContent && e?.commands.setContent(props.initialContent) + }, + {} + ) + ) - createEffect(() => { - if (html()) { - setCounter(editor()?.storage.characterCount.characters()) - } - }) + createEffect( + on( + html, + (content) => { + content && setCounter(editor()?.storage.characterCount.characters()) + props.onChange?.(content || '') + }, + {} + ) + ) const maxHeightStyle = { overflow: 'auto', @@ -290,7 +272,7 @@ const SimplifiedEditor = (props: Props) => { 0}>
{props.label}
-
+
(editorEl = el)} />
@@ -361,7 +343,7 @@ const SimplifiedEditor = (props: Props) => {
- +