2023-11-13 18:56:47 +00:00
|
|
|
import type { Editor } from '@tiptap/core'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { Show, createEffect, createSignal } from 'solid-js'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2024-07-05 14:08:12 +00:00
|
|
|
import { renderUploadedImage } from '~/components/Editor/renderUploadedImage'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { Icon } from '~/components/_shared/Icon'
|
|
|
|
import { useLocalize } from '~/context/localize'
|
2024-06-24 17:50:27 +00:00
|
|
|
import { useUI } from '~/context/ui'
|
|
|
|
import { UploadedFile } from '~/types/upload'
|
2024-07-04 07:51:15 +00:00
|
|
|
import { useOutsideClickHandler } from '~/utils/useOutsideClickHandler'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Modal } from '../../Nav/Modal'
|
|
|
|
import { InlineForm } from '../InlineForm'
|
|
|
|
import { UploadModalContent } from '../UploadModalContent'
|
|
|
|
import { Menu } from './Menu'
|
2024-06-24 17:50:27 +00:00
|
|
|
import type { MenuItem } from './Menu/Menu'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
|
|
|
import styles from './EditorFloatingMenu.module.scss'
|
2023-05-04 04:43:52 +00:00
|
|
|
|
|
|
|
type FloatingMenuProps = {
|
|
|
|
editor: Editor
|
|
|
|
ref: (el: HTMLDivElement) => void
|
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const embedData = (data: string) => {
|
2023-11-13 16:48:04 +00:00
|
|
|
const element = document.createRange().createContextualFragment(data)
|
|
|
|
const { attributes } = element.firstChild as HTMLIFrameElement
|
2024-01-16 09:13:23 +00:00
|
|
|
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)
|
2024-06-24 17:50:27 +00:00
|
|
|
if (attribute?.name) {
|
|
|
|
result[attribute.name as keyof typeof result] = attribute.value as string
|
2024-05-01 14:33:37 +00:00
|
|
|
}
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
2023-11-13 16:48:04 +00:00
|
|
|
|
|
|
|
return result
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
|
|
|
const { t } = useLocalize()
|
2024-06-24 17:50:27 +00:00
|
|
|
const { showModal, hideModal } = useUI()
|
2023-05-04 16:59:38 +00:00
|
|
|
const [selectedMenuItem, setSelectedMenuItem] = createSignal<MenuItem | undefined>()
|
2023-05-04 04:43:52 +00:00
|
|
|
const [menuOpen, setMenuOpen] = createSignal<boolean>(false)
|
2024-06-24 17:50:27 +00:00
|
|
|
let menuRef: HTMLDivElement | undefined
|
|
|
|
let plusButtonRef: HTMLButtonElement | undefined
|
2023-05-04 04:43:52 +00:00
|
|
|
const handleEmbedFormSubmit = async (value: string) => {
|
|
|
|
// TODO: add support instagram embed (blockquote)
|
|
|
|
const emb = await embedData(value)
|
2024-01-16 09:13:23 +00:00
|
|
|
props.editor
|
|
|
|
.chain()
|
|
|
|
.focus()
|
|
|
|
.insertContent({
|
|
|
|
type: 'figure',
|
|
|
|
attrs: { 'data-type': 'iframe' },
|
|
|
|
content: [
|
|
|
|
{
|
|
|
|
type: 'iframe',
|
|
|
|
attrs: {
|
|
|
|
src: emb.src,
|
|
|
|
width: emb.width,
|
2024-06-26 08:22:05 +00:00
|
|
|
height: emb.height
|
|
|
|
}
|
2024-01-16 09:13:23 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'figcaption',
|
2024-06-26 08:22:05 +00:00
|
|
|
content: [{ type: 'text', text: t('Description') }]
|
|
|
|
}
|
|
|
|
]
|
2024-01-16 09:13:23 +00:00
|
|
|
})
|
|
|
|
.run()
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
|
2024-06-24 17:50:27 +00:00
|
|
|
const validateEmbed = (value: string) => {
|
2023-11-13 16:48:04 +00:00
|
|
|
const element = document.createRange().createContextualFragment(value)
|
|
|
|
if (element.firstChild?.nodeName !== 'IFRAME') {
|
2023-05-09 23:15:26 +00:00
|
|
|
return t('Error')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-04 04:43:52 +00:00
|
|
|
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()
|
2023-07-18 11:23:05 +00:00
|
|
|
setSelectedMenuItem()
|
2023-05-04 15:55:38 +00:00
|
|
|
return
|
|
|
|
}
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
})
|
2023-07-18 11:23:05 +00:00
|
|
|
|
2023-05-04 04:43:52 +00:00
|
|
|
const closeUploadModalHandler = () => {
|
2023-05-04 16:59:38 +00:00
|
|
|
setSelectedMenuItem()
|
2023-05-04 04:43:52 +00:00
|
|
|
setMenuOpen(false)
|
2024-01-17 15:33:25 +00:00
|
|
|
setSelectedMenuItem()
|
2023-05-04 04:43:52 +00:00
|
|
|
}
|
|
|
|
|
2023-05-05 20:05:50 +00:00
|
|
|
useOutsideClickHandler({
|
|
|
|
containerRef: menuRef,
|
2023-05-12 13:45:31 +00:00
|
|
|
handler: (e) => {
|
2024-06-24 17:50:27 +00:00
|
|
|
if (plusButtonRef?.contains(e.target)) {
|
2023-05-12 13:45:31 +00:00
|
|
|
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
|
|
|
}
|
2024-06-26 08:22:05 +00:00
|
|
|
}
|
2023-05-05 20:05:50 +00:00
|
|
|
})
|
|
|
|
|
2023-08-15 09:38:49 +00:00
|
|
|
const handleUpload = (image: UploadedFile) => {
|
|
|
|
renderUploadedImage(props.editor, image)
|
2024-06-24 17:50:27 +00:00
|
|
|
hideModal()
|
2023-05-09 04:58:00 +00:00
|
|
|
}
|
|
|
|
|
2023-05-04 04:43:52 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div ref={props.ref} class={styles.editorFloatingMenu}>
|
2024-06-24 17:50:27 +00:00
|
|
|
<button ref={(el) => (plusButtonRef = el)} type="button" onClick={() => setMenuOpen(!menuOpen())}>
|
2023-05-04 04:43:52 +00:00
|
|
|
<Icon name="editor-plus" />
|
|
|
|
</button>
|
|
|
|
<Show when={menuOpen()}>
|
2024-06-24 17:50:27 +00:00
|
|
|
<div class={styles.menuHolder} ref={(el) => (menuRef = el)}>
|
2023-05-04 04:43:52 +00:00
|
|
|
<Show when={!selectedMenuItem()}>
|
2024-06-24 17:50:27 +00:00
|
|
|
<Menu selectedItem={(value: string) => setSelectedMenuItem(value as MenuItem)} />
|
2023-05-04 04:43:52 +00:00
|
|
|
</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()}
|
2024-06-24 17:50:27 +00:00
|
|
|
validate={(val) => validateEmbed(val) || ''}
|
2023-05-04 04:43:52 +00:00
|
|
|
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) => {
|
2024-06-24 17:50:27 +00:00
|
|
|
handleUpload(value as UploadedFile)
|
2023-05-09 04:58:00 +00:00
|
|
|
setSelectedMenuItem()
|
|
|
|
}}
|
|
|
|
/>
|
2023-05-04 04:43:52 +00:00
|
|
|
</Modal>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|