Merge remote-tracking branch 'gitlab/editor' into editor

This commit is contained in:
bniwredyc 2023-03-24 14:41:35 +01:00
commit d5a5e79daa

View File

@ -1,4 +1,4 @@
import { createEffect, createSignal, Show } from 'solid-js' import { Switch, Match, createSignal, Show } from 'solid-js'
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
import styles from './EditorBubbleMenu.module.scss' import styles from './EditorBubbleMenu.module.scss'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
@ -7,8 +7,6 @@ import { createEditorTransaction } from 'solid-tiptap'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import validateUrl from '../../utils/validateUrl' import validateUrl from '../../utils/validateUrl'
type HeadingLevel = 1 | 2 | 3
type ActionName = 'heading' | 'bold' | 'italic' | 'blockquote' | 'isOrderedList' | 'isBulletList'
type BubbleMenuProps = { type BubbleMenuProps = {
editor: Editor editor: Editor
ref: (el: HTMLDivElement) => void ref: (el: HTMLDivElement) => void
@ -23,20 +21,26 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
const [prevUrl, setPrevUrl] = createSignal<string | null>(null) const [prevUrl, setPrevUrl] = createSignal<string | null>(null)
const [linkError, setLinkError] = createSignal<string | null>(null) const [linkError, setLinkError] = createSignal<string | null>(null)
const activeControl = (action: ActionName, actionLevel?: HeadingLevel, prevLink?: boolean) => const isActive = (name: string, attributes?: {}, checkPrevUrl?: boolean) =>
createEditorTransaction( createEditorTransaction(
() => props.editor, () => props.editor,
(editor) => editor && editor.isActive(action, actionLevel && { actionLevel }) (editor) => {
editor && editor.isActive(name, attributes)
if (checkPrevUrl) {
setPrevUrl(editor && editor.getAttributes('link').href)
}
}
) )
const isLink = createEditorTransaction( const isBold = isActive('bold')
// вызов этой ф-ии обусловлен не через хэлпер activeControl только проверкой установленна ли ссылка const isItalic = isActive('italic')
() => props.editor, const isH1 = isActive('heading', { level: 1 })
(editor) => { const isH2 = isActive('heading', { level: 2 })
editor && editor.isActive('link') const isH3 = isActive('heading', { level: 3 })
setPrevUrl(editor && editor.getAttributes('link').href) const isBlockQuote = isActive('blockquote')
} const isOrderedList = isActive('isOrderedList')
) const isBulletList = isActive('isBulletList')
const isLink = isActive('link', {}, true)
const clearLinkForm = () => { const clearLinkForm = () => {
setUrl('') setUrl('')
@ -46,7 +50,7 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
const handleSubmitLink = (e) => { const handleSubmitLink = (e) => {
e.preventDefault() e.preventDefault()
if (url().length > 1 && validateUrl(url())) { if (url().length > 1 && validateUrl(url())) {
props.editor.commands.toggleLink({ href: url() }) props.editor.chain().focus().toggleLink({ href: url() }).run()
clearLinkForm() clearLinkForm()
} else { } else {
setLinkError(t('Invalid url format')) setLinkError(t('Invalid url format'))
@ -65,160 +69,171 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
return ( return (
<> <>
<div ref={props.ref} class={styles.bubbleMenu}> <div ref={props.ref} class={styles.bubbleMenu}>
{linkEditorOpen() ? ( <Switch>
<> <Match when={linkEditorOpen()}>
<form onSubmit={(e) => handleSubmitLink(e)} class={styles.linkForm}> <>
<input <form onSubmit={(e) => handleSubmitLink(e)} class={styles.linkForm}>
type="text" <input
placeholder={t('Enter URL address')} type="text"
autofocus placeholder={t('Enter URL address')}
value={prevUrl() ?? null} autofocus
onChange={(e) => setUrl(e.currentTarget.value)} value={prevUrl() ? prevUrl() : null}
/> onChange={(e) => setUrl(e.currentTarget.value)}
<button type="submit"> />
<Icon name="status-done" /> <button type="submit">
</button> <Icon name="status-done" />
<button type="button" onClick={() => clearLinkForm()}> </button>
<Icon name="status-cancel" /> <button type="button" onClick={() => clearLinkForm()}>
</button> <Icon name="status-cancel" />
</form> </button>
{linkError() && <div class={styles.linkError}>{linkError()}</div>} </form>
</> {linkError() && <div class={styles.linkError}>{linkError()}</div>}
) : ( </>
<> </Match>
<div class={styles.dropDownHolder}> <Match when={!linkEditorOpen()}>
<>
<div class={styles.dropDownHolder}>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
})}
onClick={toggleTextSizePopup}
>
<Icon name="editor-text-size" />
<Icon name="down-triangle" class={styles.triangle} />
</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]: isH1()
})}
onClick={() => props.editor.chain().focus().toggleHeading({ level: 1 }).run()}
>
<Icon name="editor-h1" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH2()
})}
onClick={() => props.editor.chain().focus().toggleHeading({ level: 2 }).run()}
>
<Icon name="editor-h2" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH3()
})}
onClick={() => props.editor.chain().focus().toggleHeading({ level: 3 }).run()}
>
<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>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isBlockQuote()
})}
onClick={() => props.editor.chain().focus().toggleBlockquote().run()}
>
<Icon name="editor-quote" />
</button>
</div>
</div>
</Show>
</div>
<div class={styles.delimiter} />
<button <button
type="button" type="button"
class={clsx(styles.bubbleMenuButton, { class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen() [styles.bubbleMenuButtonActive]: isBold()
})} })}
onClick={toggleTextSizePopup} onClick={() => props.editor.chain().focus().toggleBold().run()}
> >
<Icon name="editor-text-size" /> <Icon name="editor-bold" />
<Icon name="down-triangle" class={styles.triangle} />
</button> </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]: activeControl('heading', 1)()
})}
onClick={() => props.editor.commands.toggleHeading({ level: 1 })}
>
<Icon name="editor-h1" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeControl('heading', 2)()
})}
onClick={() => props.editor.commands.toggleHeading({ level: 2 })}
>
<Icon name="editor-h2" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeControl('heading', 3)()
})}
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]: activeControl('blockquote')()
})}
onClick={() => props.editor.chain().focus().toggleBlockquote().run()}
>
<Icon name="editor-blockquote" />
</button>
</div>
</div>
</Show>
</div>
<div class={styles.delimiter} />
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeControl('bold')()
})}
onClick={() => props.editor.commands.toggleBold()}
>
<Icon name="editor-bold" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeControl('italic')()
})}
onClick={() => props.editor.commands.toggleItalic()}
>
<Icon name="editor-italic" />
</button>
<div class={styles.delimiter} />
<button
type="button"
onClick={(e) => {
setLinkEditorOpen(true)
}}
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isLink()
})}
>
<Icon name="editor-link" />
</button>
<button type="button" class={styles.bubbleMenuButton}>
<div class={styles.colorWheel} />
</button>
<button type="button" class={styles.bubbleMenuButton}>
<Icon name="editor-footnote" />
</button>
<div class={styles.delimiter} />
<div class={styles.dropDownHolder}>
<button <button
type="button" type="button"
class={clsx(styles.bubbleMenuButton, { [styles.bubbleMenuButtonActive]: listBubbleOpen() })} class={clsx(styles.bubbleMenuButton, {
onClick={toggleListPopup} [styles.bubbleMenuButtonActive]: isItalic()
})}
onClick={() => props.editor.chain().focus().toggleItalic().run()}
> >
<Icon name="editor-ul" /> <Icon name="editor-italic" />
<Icon name="down-triangle" class={styles.triangle} />
</button> </button>
<Show when={listBubbleOpen()}> <div class={styles.delimiter} />
<div class={styles.dropDown}> <button
<header>{t('Lists')}</header> type="button"
<div class={styles.actions}> onClick={(e) => {
<button setLinkEditorOpen(true)
type="button" }}
class={clsx(styles.bubbleMenuButton, { class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeControl('isBulletList') [styles.bubbleMenuButtonActive]: isLink()
})} })}
onClick={() => props.editor.commands.toggleBulletList()} >
> <Icon name="editor-link" />
<Icon name="editor-ul" /> </button>
</button> <button type="button" class={styles.bubbleMenuButton}>
<button <Icon name="editor-footnote" />
type="button" </button>
class={clsx(styles.bubbleMenuButton, { <div class={styles.delimiter} />
[styles.bubbleMenuButtonActive]: activeControl('isOrderedList') <div class={styles.dropDownHolder}>
})} <button
onClick={() => props.editor.commands.toggleOrderedList()} type="button"
> class={clsx(styles.bubbleMenuButton, {
<Icon name="editor-ol" /> [styles.bubbleMenuButtonActive]: listBubbleOpen()
</button> })}
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.chain().focus().toggleBulletList().run()}
>
<Icon name="editor-ul" />
</button>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isOrderedList()
})}
onClick={() => props.editor.chain().focus().toggleOrderedList().run()}
>
<Icon name="editor-ol" />
</button>
</div>
</div> </div>
</div> </Show>
</Show> </div>
</div> </>
</> </Match>
)} </Switch>
</div> </div>
</> </>
) )