import type { Editor } from '@tiptap/core' import { Show, createEffect, createSignal } from 'solid-js' import { Icon } from '~/components/_shared/Icon' import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' import { UploadedFile } from '~/types/upload' import { renderUploadedImage } from '~/utils/renderUploadedImage' import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler' import { Modal } from '../../Nav/Modal' import { InlineForm } from '../InlineForm' import { UploadModalContent } from '../UploadModalContent' import { Menu } from './Menu' import type { MenuItem } from './Menu/Menu' import styles from './EditorFloatingMenu.module.scss' type FloatingMenuProps = { editor: Editor ref: (el: HTMLDivElement) => void } const embedData = (data: string) => { const element = document.createRange().createContextualFragment(data) const { attributes } = element.firstChild as HTMLIFrameElement const result: { src: string; width?: string; height?: string } = { src: '' } for (let i = 0; i < attributes.length; i++) { const attribute = attributes.item(i) if (attribute?.name) { result[attribute.name as keyof typeof result] = attribute.value as string } } return result } export const EditorFloatingMenu = (props: FloatingMenuProps) => { const { t } = useLocalize() const { showModal, hideModal } = useUI() const [selectedMenuItem, setSelectedMenuItem] = createSignal() const [menuOpen, setMenuOpen] = createSignal(false) let menuRef: HTMLDivElement | undefined let plusButtonRef: HTMLButtonElement | undefined const handleEmbedFormSubmit = async (value: string) => { // TODO: add support instagram embed (blockquote) const emb = await embedData(value) props.editor .chain() .focus() .insertContent({ type: 'figure', attrs: { 'data-type': 'iframe' }, content: [ { type: 'iframe', attrs: { src: emb.src, width: emb.width, height: emb.height } }, { type: 'figcaption', content: [{ type: 'text', text: t('Description') }] } ] }) .run() } const validateEmbed = (value: string) => { const element = document.createRange().createContextualFragment(value) if (element.firstChild?.nodeName !== 'IFRAME') { return t('Error') } } createEffect(() => { switch (selectedMenuItem()) { case 'image': { showModal('uploadImage') return } case 'horizontal-rule': { props.editor.chain().focus().setHorizontalRule().run() setSelectedMenuItem() return } } }) const closeUploadModalHandler = () => { setSelectedMenuItem() setMenuOpen(false) setSelectedMenuItem() } useOutsideClickHandler({ containerRef: menuRef, handler: (e) => { if (plusButtonRef?.contains(e.target)) { return } if (menuOpen()) { setMenuOpen(false) setSelectedMenuItem() } } }) const handleUpload = (image: UploadedFile) => { renderUploadedImage(props.editor, image) hideModal() } return ( <>
(menuRef = el)}> setSelectedMenuItem(value as MenuItem)} /> setSelectedMenuItem()} validate={(val) => validateEmbed(val) || ''} onSubmit={handleEmbedFormSubmit} />
{ handleUpload(value as UploadedFile) setSelectedMenuItem() }} /> ) }