webapp/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx

158 lines
4.4 KiB
TypeScript
Raw Normal View History

2023-11-13 18:56:47 +00:00
import type { Editor } from '@tiptap/core'
2024-02-04 11:25:21 +00:00
import type { MenuItem } from './Menu/Menu'
2024-02-04 11:25:21 +00:00
import { Show, createEffect, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { UploadedFile } from '../../../pages/types'
import { showModal } from '../../../stores/ui'
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Modal } from '../../Nav/Modal'
2024-02-04 11:25:21 +00:00
import { Icon } from '../../_shared/Icon'
import { InlineForm } from '../InlineForm'
import { UploadModalContent } from '../UploadModalContent'
import { Menu } from './Menu'
import styles from './EditorFloatingMenu.module.scss'
type FloatingMenuProps = {
editor: Editor
ref: (el: HTMLDivElement) => void
}
2024-02-05 15:04:23 +00:00
const embedData = (data) => {
2023-11-13 16:48:04 +00:00
const element = document.createRange().createContextualFragment(data)
const { attributes } = element.firstChild as HTMLIFrameElement
const result: { src: string; width?: string; height?: string } = { src: '' }
2023-11-13 16:48:04 +00:00
for (let i = 0; i < attributes.length; i++) {
2024-05-01 14:33:37 +00:00
const attribute = attributes.item(i)
if (attribute) {
result[attribute.name] = attribute.value
}
}
2023-11-13 16:48:04 +00:00
return result
}
export const EditorFloatingMenu = (props: FloatingMenuProps) => {
const { t } = useLocalize()
2023-05-04 16:59:38 +00:00
const [selectedMenuItem, setSelectedMenuItem] = createSignal<MenuItem | undefined>()
const [menuOpen, setMenuOpen] = createSignal<boolean>(false)
2023-05-09 04:58:00 +00:00
const menuRef: { current: HTMLDivElement } = { current: null }
const plusButtonRef: { current: HTMLButtonElement } = { current: null }
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()
}
2024-02-05 15:04:23 +00:00
const validateEmbed = (value) => {
2023-11-13 16:48:04 +00:00
const element = document.createRange().createContextualFragment(value)
if (element.firstChild?.nodeName !== 'IFRAME') {
return t('Error')
}
}
createEffect(() => {
2023-05-04 15:55:38 +00:00
switch (selectedMenuItem()) {
case 'image': {
showModal('uploadImage')
return
}
case 'horizontal-rule': {
props.editor.chain().focus().setHorizontalRule().run()
setSelectedMenuItem()
2023-05-04 15:55:38 +00:00
return
}
}
})
const closeUploadModalHandler = () => {
2023-05-04 16:59:38 +00:00
setSelectedMenuItem()
setMenuOpen(false)
2024-01-17 15:33:25 +00:00
setSelectedMenuItem()
}
2023-05-05 20:05:50 +00:00
useOutsideClickHandler({
containerRef: menuRef,
handler: (e) => {
if (plusButtonRef.current.contains(e.target)) {
return
}
2023-05-05 20:05:50 +00:00
if (menuOpen()) {
setMenuOpen(false)
2024-01-17 15:33:25 +00:00
setSelectedMenuItem()
2023-05-05 20:05:50 +00:00
}
},
2023-05-05 20:05:50 +00:00
})
const handleUpload = (image: UploadedFile) => {
renderUploadedImage(props.editor, image)
2023-05-09 04:58:00 +00:00
}
return (
<>
<div ref={props.ref} class={styles.editorFloatingMenu}>
2023-05-04 16:59:38 +00:00
<button
ref={(el) => (plusButtonRef.current = el)}
2023-05-04 16:59:38 +00:00
type="button"
onClick={() => setMenuOpen(!menuOpen())}
2023-05-04 16:59:38 +00:00
>
<Icon name="editor-plus" />
</button>
<Show when={menuOpen()}>
2023-05-05 20:05:50 +00:00
<div class={styles.menuHolder} ref={(el) => (menuRef.current = el)}>
<Show when={!selectedMenuItem()}>
2023-05-04 15:55:38 +00:00
<Menu selectedItem={(value: MenuItem) => setSelectedMenuItem(value)} />
</Show>
<Show when={selectedMenuItem() === 'embed'}>
<InlineForm
placeholder={t('Paste Embed code')}
showInput={true}
onClose={closeUploadModalHandler}
2023-05-04 16:59:38 +00:00
onClear={() => setSelectedMenuItem()}
validate={validateEmbed}
onSubmit={handleEmbedFormSubmit}
/>
</Show>
</div>
</Show>
</div>
<Modal variant="narrow" name="uploadImage" onClose={closeUploadModalHandler}>
2023-05-09 04:58:00 +00:00
<UploadModalContent
2023-05-09 11:25:40 +00:00
onClose={(value) => {
handleUpload(value)
2023-05-09 04:58:00 +00:00
setSelectedMenuItem()
}}
/>
</Modal>
</>
)
}