2023-03-22 07:47:51 +00:00
|
|
|
import { createEffect, createSignal, Show } from 'solid-js'
|
2023-03-08 16:35:13 +00:00
|
|
|
import type { Editor } from '@tiptap/core'
|
2023-03-08 17:44:09 +00:00
|
|
|
import styles from './EditorBubbleMenu.module.scss'
|
|
|
|
import { Icon } from '../_shared/Icon'
|
|
|
|
import { clsx } from 'clsx'
|
2023-03-13 12:26:25 +00:00
|
|
|
import { createEditorTransaction } from 'solid-tiptap'
|
2023-03-20 09:19:14 +00:00
|
|
|
import { useLocalize } from '../../context/localize'
|
|
|
|
import validateUrl from '../../utils/validateUrl'
|
2023-03-22 12:07:28 +00:00
|
|
|
import list from '../Feed/List'
|
2023-03-08 16:35:13 +00:00
|
|
|
|
|
|
|
type BubbleMenuProps = {
|
|
|
|
editor: Editor
|
|
|
|
ref: (el: HTMLDivElement) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
2023-03-20 09:19:14 +00:00
|
|
|
const { t } = useLocalize()
|
2023-03-22 07:47:51 +00:00
|
|
|
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal<boolean>(false)
|
2023-03-22 12:07:28 +00:00
|
|
|
const [listBubbleOpen, setListBubbleOpen] = createSignal<boolean>(false)
|
2023-03-20 09:19:14 +00:00
|
|
|
const [linkEditorOpen, setLinkEditorOpen] = createSignal<boolean>(false)
|
|
|
|
const [url, setUrl] = createSignal<string>('')
|
|
|
|
const [prevUrl, setPrevUrl] = createSignal<string | null>(null)
|
|
|
|
const [linkError, setLinkError] = createSignal<string | null>(null)
|
|
|
|
|
2023-03-13 12:26:25 +00:00
|
|
|
const isBold = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('bold')
|
|
|
|
)
|
2023-03-22 07:47:51 +00:00
|
|
|
const isItalic = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('italic')
|
|
|
|
)
|
|
|
|
|
|
|
|
//props.editor.isActive('heading', { level: 1 }) - либо инлайново либо как-то возвращать что активно
|
|
|
|
const isHOne = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('heading', { level: 1 })
|
|
|
|
)
|
|
|
|
const isHTwo = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('heading', { level: 2 })
|
|
|
|
)
|
|
|
|
const isHThree = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('heading', { level: 3 })
|
|
|
|
)
|
|
|
|
const isBlockQuote = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('blockquote')
|
|
|
|
)
|
2023-03-22 12:07:28 +00:00
|
|
|
const isOrderedList = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('isOrderedList')
|
|
|
|
)
|
|
|
|
const isBulletList = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => editor && editor.isActive('isBulletList')
|
|
|
|
)
|
|
|
|
|
2023-03-20 09:19:14 +00:00
|
|
|
const isLink = createEditorTransaction(
|
|
|
|
() => props.editor,
|
|
|
|
(editor) => {
|
|
|
|
editor && editor.isActive('link')
|
|
|
|
setPrevUrl(editor && editor.getAttributes('link').href)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const clearLinkForm = () => {
|
|
|
|
setUrl('')
|
|
|
|
setLinkEditorOpen(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleSubmitLink = (e) => {
|
|
|
|
e.preventDefault()
|
|
|
|
if (url().length === 0) {
|
|
|
|
props.editor.chain().focus().unsetLink().run()
|
|
|
|
clearLinkForm()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (url().length > 1 && validateUrl(url())) {
|
|
|
|
props.editor.commands.toggleLink({ href: url() })
|
|
|
|
clearLinkForm()
|
|
|
|
} else {
|
|
|
|
setLinkError(t('Invalid url format'))
|
|
|
|
}
|
|
|
|
}
|
2023-03-13 12:26:25 +00:00
|
|
|
|
2023-03-22 12:07:28 +00:00
|
|
|
const toggleTextSizePopup = () => {
|
|
|
|
if (listBubbleOpen()) setListBubbleOpen(false)
|
|
|
|
setTextSizeBubbleOpen((prev) => !prev)
|
|
|
|
}
|
|
|
|
const toggleListPopup = () => {
|
|
|
|
if (textSizeBubbleOpen()) setTextSizeBubbleOpen(false)
|
|
|
|
setListBubbleOpen((prev) => !prev)
|
|
|
|
}
|
|
|
|
|
2023-03-08 16:35:13 +00:00
|
|
|
return (
|
2023-03-20 09:19:14 +00:00
|
|
|
<>
|
|
|
|
<div ref={props.ref} class={styles.bubbleMenu}>
|
|
|
|
{linkEditorOpen() ? (
|
|
|
|
<>
|
|
|
|
<form onSubmit={(e) => handleSubmitLink(e)} class={styles.linkForm}>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder={t('Enter URL address')}
|
|
|
|
autofocus
|
|
|
|
value={prevUrl() ? prevUrl() : null}
|
|
|
|
onChange={(e) => setUrl(e.currentTarget.value)}
|
|
|
|
/>
|
|
|
|
<button type="submit">
|
|
|
|
<Icon name="status-done" />
|
|
|
|
</button>
|
2023-03-22 07:47:51 +00:00
|
|
|
<button type="button" onClick={() => clearLinkForm()}>
|
2023-03-20 09:19:14 +00:00
|
|
|
<Icon name="status-cancel" />
|
|
|
|
</button>
|
|
|
|
</form>
|
|
|
|
{linkError() && <div class={styles.linkError}>{linkError()}</div>}
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<>
|
2023-03-22 07:47:51 +00:00
|
|
|
<div class={styles.dropDownHolder}>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
|
|
|
})}
|
2023-03-22 12:07:28 +00:00
|
|
|
onClick={toggleTextSizePopup}
|
2023-03-22 07:47:51 +00:00
|
|
|
>
|
|
|
|
<Icon name="editor-text-size" />
|
2023-03-22 12:07:28 +00:00
|
|
|
<Icon name="down-triangle" class={styles.triangle} />
|
2023-03-22 07:47:51 +00:00
|
|
|
</button>
|
|
|
|
<Show when={textSizeBubbleOpen()}>
|
|
|
|
<div class={styles.dropDown}>
|
|
|
|
<header>{t('Headers')}</header>
|
|
|
|
<div class={styles.actions}>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isHOne()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.commands.toggleHeading({ level: 1 })}
|
|
|
|
>
|
|
|
|
<Icon name="editor-h1" />
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isHTwo()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.commands.toggleHeading({ level: 2 })}
|
|
|
|
>
|
|
|
|
<Icon name="editor-h2" />
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isHThree()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.commands.toggleHeading({ level: 3 })}
|
|
|
|
>
|
|
|
|
<Icon name="editor-h3" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
<header>{t('Quotes')}</header>
|
|
|
|
<div class={styles.actions}>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isBlockQuote()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.chain().focus().toggleBlockquote().run()}
|
|
|
|
>
|
|
|
|
<Icon name="editor-blockquote" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
<div class={styles.delimiter} />
|
2023-03-20 09:19:14 +00:00
|
|
|
<button
|
2023-03-22 07:47:51 +00:00
|
|
|
type="button"
|
2023-03-20 09:19:14 +00:00
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
2023-03-22 07:47:51 +00:00
|
|
|
[styles.bubbleMenuButtonActive]: isBold()
|
2023-03-20 09:19:14 +00:00
|
|
|
})}
|
2023-03-22 07:47:51 +00:00
|
|
|
onClick={() => props.editor.commands.toggleBold()}
|
2023-03-20 09:19:14 +00:00
|
|
|
>
|
2023-03-22 07:47:51 +00:00
|
|
|
<Icon name="editor-bold" />
|
2023-03-20 09:19:14 +00:00
|
|
|
</button>
|
|
|
|
<button
|
2023-03-22 07:47:51 +00:00
|
|
|
type="button"
|
2023-03-20 09:19:14 +00:00
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
2023-03-22 07:47:51 +00:00
|
|
|
[styles.bubbleMenuButtonActive]: isItalic()
|
2023-03-20 09:19:14 +00:00
|
|
|
})}
|
2023-03-22 07:47:51 +00:00
|
|
|
onClick={() => props.editor.commands.toggleItalic()}
|
2023-03-20 09:19:14 +00:00
|
|
|
>
|
|
|
|
<Icon name="editor-italic" />
|
|
|
|
</button>
|
2023-03-22 07:47:51 +00:00
|
|
|
<div class={styles.delimiter} />
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
onClick={(e) => {
|
|
|
|
setLinkEditorOpen(true)
|
|
|
|
}}
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isLink()
|
|
|
|
})}
|
|
|
|
>
|
2023-03-20 09:19:14 +00:00
|
|
|
<Icon name="editor-link" />
|
|
|
|
</button>
|
2023-03-22 07:47:51 +00:00
|
|
|
<button type="button" class={styles.bubbleMenuButton}>
|
2023-03-20 09:19:14 +00:00
|
|
|
<Icon name="editor-footnote" />
|
|
|
|
</button>
|
|
|
|
<div class={styles.delimiter} />
|
2023-03-22 12:07:28 +00:00
|
|
|
<div class={styles.dropDownHolder}>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, { [styles.bubbleMenuButtonActive]: listBubbleOpen() })}
|
|
|
|
onClick={toggleListPopup}
|
|
|
|
>
|
|
|
|
<Icon name="editor-ul" />
|
|
|
|
<Icon name="down-triangle" class={styles.triangle} />
|
|
|
|
</button>
|
|
|
|
<Show when={listBubbleOpen()}>
|
|
|
|
<div class={styles.dropDown}>
|
|
|
|
<header>{t('Lists')}</header>
|
|
|
|
<div class={styles.actions}>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isBulletList()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.commands.toggleBulletList()}
|
|
|
|
>
|
|
|
|
<Icon name="editor-ul" />
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class={clsx(styles.bubbleMenuButton, {
|
|
|
|
[styles.bubbleMenuButtonActive]: isOrderedList()
|
|
|
|
})}
|
|
|
|
onClick={() => props.editor.commands.toggleOrderedList()}
|
|
|
|
>
|
|
|
|
<Icon name="editor-ol" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
2023-03-20 09:19:14 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</>
|
2023-03-08 16:35:13 +00:00
|
|
|
)
|
|
|
|
}
|