popper-fix+textbubble-revert
All checks were successful
deploy / testbuild (push) Successful in 2m19s
deploy / Update templates on Mailgun (push) Has been skipped

This commit is contained in:
Untone 2024-10-09 22:35:45 +03:00
parent cd39b7b806
commit d28b4f2ab3
4 changed files with 347 additions and 509 deletions

View File

@ -133,12 +133,7 @@
"yjs": "13.6.19",
"y-prosemirror": "1.2.12"
},
"trustedDependencies": [
"@biomejs/biome",
"@swc/core",
"esbuild",
"protobufjs"
],
"trustedDependencies": ["@biomejs/biome", "@swc/core", "esbuild", "protobufjs"],
"dependencies": {
"form-data": "^4.0.0",
"idb": "^8.0.0",

View File

@ -4,6 +4,7 @@ import { A, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
import { isServer } from 'solid-js/web'
import usePopper from 'solid-popper'
import { useFeed } from '~/context/feed'
import { useLocalize } from '~/context/localize'
@ -13,7 +14,6 @@ import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui'
import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen'
import { processPrepositions } from '~/intl/prepositions'
import { isCyrillic } from '~/intl/translate'
import { createTooltip } from '~/lib/createTooltip'
import { getImageUrl } from '~/lib/getThumbUrl'
import { MediaItem } from '~/types/mediaitem'
import { capitalize } from '~/utils/capitalize'
@ -217,25 +217,25 @@ export const FullArticle = (props: Props) => {
element.setAttribute('href', 'javascript: void(0)')
}
const popperInstance = createTooltip(element, tooltip, {
placement: 'top',
modifiers: [
{
name: 'eventListeners',
options: { scroll: false }
},
{
name: 'offset',
options: {
offset: [0, 8]
const popperInstance = usePopper(
() => element,
() => tooltip,
{
placement: 'top',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 8]
}
},
{
name: 'flip',
options: { fallbackPlacements: ['top'] }
}
},
{
name: 'flip',
options: { fallbackPlacements: ['top'] }
}
]
})
]
}
)
tooltip.style.visibility = 'hidden'
let isTooltipVisible = false
@ -248,7 +248,7 @@ export const FullArticle = (props: Props) => {
isTooltipVisible = true
}
popperInstance.update()
popperInstance()?.update()
}
const handleDocumentClick = (e: MouseEvent) => {

View File

@ -1,10 +1,10 @@
import type { Editor } from '@tiptap/core'
import { clsx } from 'clsx'
import { Match, Show, Switch, createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js'
import { Match, Show, Switch, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { createEditorTransaction } from 'solid-tiptap'
import { Icon } from '~/components/_shared/Icon'
import { Popover } from '~/components/_shared/Popover'
import { useLocalize } from '~/context/localize'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import { MiniEditor } from '../MiniEditor'
import { InsertLinkForm } from './InsertLinkForm'
@ -20,445 +20,381 @@ type BubbleMenuProps = {
export const TextBubbleMenu = (props: BubbleMenuProps) => {
const { t } = useLocalize()
const isActive = createMemo(
() => (name: string, attributes?: Record<string, string | number>) =>
props.editor?.isActive(name, attributes)
)
const isActive = (name: string, attributes?: Record<string, unknown>) =>
createEditorTransaction(
() => props.editor,
(e) => e?.isActive(name, attributes)
)
const [menuState, setMenuState] = createSignal({
textSizeBubbleOpen: false,
listBubbleOpen: false,
linkEditorOpen: false,
footnoteEditorOpen: false,
footNote: undefined as string | undefined
})
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
const [listBubbleOpen, setListBubbleOpen] = createSignal(false)
const [linkEditorOpen, setLinkEditorOpen] = createSignal(false)
const [footnoteEditorOpen, setFootnoteEditorOpen] = createSignal(false)
const [footNote, setFootNote] = createSignal<string>()
createEffect(() => {
if (!props.shouldShow) {
setMenuState((prev) => ({
...prev,
footNote: undefined,
footnoteEditorOpen: false,
linkEditorOpen: false,
textSizeBubbleOpen: false,
listBubbleOpen: false
}))
setFootNote()
setFootnoteEditorOpen(false)
}
})
const activeStates = createMemo(() => ({
bold: isActive()('bold'),
italic: isActive()('italic'),
h1: isActive()('heading', { level: 2 }),
h2: isActive()('heading', { level: 3 }),
h3: isActive()('heading', { level: 4 }),
quote: isActive()('blockquote', { 'data-type': 'quote' }),
punchLine: isActive()('blockquote', { 'data-type': 'punchline' }),
orderedList: isActive()('orderedList'),
bulletList: isActive()('bulletList'),
link: isActive()('link'),
highlight: isActive()('highlight'),
footnote: isActive()('footnote'),
incut: isActive()('article')
// underline: isActive()('underline'),
}))
const isBold = isActive('bold')
const isItalic = isActive('italic')
const isH1 = isActive('heading', { level: 2 })
const isH2 = isActive('heading', { level: 3 })
const isH3 = isActive('heading', { level: 4 })
const isQuote = isActive('blockquote', { 'data-type': 'quote' })
const isPunchLine = isActive('blockquote', { 'data-type': 'punchline' })
const isOrderedList = isActive('isOrderedList')
const isBulletList = isActive('isBulletList')
const isLink = isActive('link')
const isHighlight = isActive('highlight')
const isFootnote = isActive('footnote')
const isIncut = isActive('article')
const togglePopup = (type: 'textSize' | 'list') => {
setMenuState((prev) => ({
...prev,
textSizeBubbleOpen: type === 'textSize' ? !prev.textSizeBubbleOpen : false,
listBubbleOpen: type === 'list' ? !prev.listBubbleOpen : false
}))
const toggleTextSizePopup = () => {
if (listBubbleOpen()) {
setListBubbleOpen(false)
}
setTextSizeBubbleOpen((prev) => !prev)
}
const toggleListPopup = () => {
if (textSizeBubbleOpen()) {
setTextSizeBubbleOpen(false)
}
setListBubbleOpen((prev) => !prev)
}
const handleKeyDown = (event: KeyboardEvent) => {
if (event.code === 'KeyK' && (event.metaKey || event.ctrlKey) && !props.editor.state.selection.empty) {
event.preventDefault()
setMenuState((prev) => ({ ...prev, linkEditorOpen: true }))
setLinkEditorOpen(true)
}
}
const updateCurrentFootnoteValue = createEditorTransaction(
() => props.editor,
(ed) => {
if (!activeStates().footnote) {
if (!isFootnote()) {
return
}
const value = ed.getAttributes('footnote').value
setMenuState((prev) => ({ ...prev, footNote: value }))
setFootNote(value)
}
)
const handleAddFootnote = (footnote: string) => {
if (menuState().footNote) {
props.editor?.chain().focus().updateFootnote({ value: footnote }).run()
const handleAddFootnote = (value: string) => {
if (footNote()) {
props.editor.chain().focus().updateFootnote({ value }).run()
} else {
props.editor?.chain().focus().setFootnote({ value: footnote }).run()
props.editor.chain().focus().setFootnote({ value }).run()
}
setMenuState((prev) => ({
...prev,
footNote: undefined,
linkEditorOpen: false,
footnoteEditorOpen: false
}))
setFootNote()
setFootnoteEditorOpen(false)
}
const handleOpenFootnoteEditor = () => {
updateCurrentFootnoteValue()
setMenuState((prev) => ({ ...prev, linkEditorOpen: false, footnoteEditorOpen: true }))
setFootnoteEditorOpen(true)
}
const handleSetPunchline = () => {
if (activeStates().punchLine) {
props.editor?.chain().focus().toggleBlockquote('punchline').run()
if (isPunchLine()) {
props.editor.chain().focus().toggleBlockquote('punchline').run()
}
props.editor?.chain().focus().toggleBlockquote('quote').run()
togglePopup('textSize')
props.editor.chain().focus().toggleBlockquote('quote').run()
toggleTextSizePopup()
}
const handleSetQuote = () => {
if (activeStates().quote) {
props.editor?.chain().focus().toggleBlockquote('quote').run()
if (isQuote()) {
props.editor.chain().focus().toggleBlockquote('quote').run()
}
props.editor?.chain().focus().toggleBlockquote('punchline').run()
togglePopup('textSize')
props.editor.chain().focus().toggleBlockquote('punchline').run()
toggleTextSizePopup()
}
onMount(() => {
window.addEventListener('keydown', handleKeyDown)
onCleanup(() => {
window.removeEventListener('keydown', handleKeyDown)
setMenuState((prev) => ({ ...prev, linkEditorOpen: false }))
})
})
const handleOpenLinkForm = () => {
props.editor?.chain().focus().addTextWrap({ class: 'highlight-fake-selection' }).run()
setMenuState((prev) => ({ ...prev, linkEditorOpen: true }))
}
const handleCloseLinkForm = () => {
setMenuState((prev) => ({ ...prev, linkEditorOpen: false }))
props.editor?.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
}
const handleFormat = (type: 'Bold' | 'Italic' | 'Underline', _attributes?: Record<string, unknown>) => {
props.editor?.chain().focus()[`toggle${type}`]().run()
}
const ListBubbleMenu = (props: BubbleMenuProps) => {
return (
<div class={styles.dropDown}>
<header>{t('Lists')}</header>
<div class={styles.actions}>
<Popover content={t('Bullet list')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().bulletList
})}
onClick={() => {
props.editor?.chain().focus().toggleBulletList().run()
togglePopup('list')
}}
>
<Icon name="editor-ul" />
</button>
)}
</Popover>
<Popover content={t('Ordered list')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().orderedList
})}
onClick={() => {
props.editor?.chain().focus().toggleOrderedList().run()
togglePopup('list')
}}
>
<Icon name="editor-ol" />
</button>
)}
</Popover>
</div>
</div>
)
}
const CommonMarkupBubbleMenu = (props: BubbleMenuProps) => {
return (
<>
<Popover content={t('Insert footnote')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().footnote
})}
onClick={handleOpenFootnoteEditor}
>
<Icon name="editor-footnote" />
</button>
)}
</Popover>
<div class={styles.delimiter} />
<div class={styles.dropDownHolder}>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: menuState().listBubbleOpen
})}
onClick={() => togglePopup('list')}
>
<Icon name="editor-ul" />
<Icon name="down-triangle" class={styles.triangle} />
</button>
<Show when={menuState().listBubbleOpen}>
<ListBubbleMenu {...props} />
</Show>
</div>
</>
)
}
const TextSizeBubbleMenu = (props: BubbleMenuProps) => {
return (
<div class={styles.dropDown}>
<header>{t('Headers')}</header>
<div class={styles.actions}>
<Popover content={t('Header 1')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().h1
})}
onClick={() => {
props.editor?.chain().focus().toggleHeading({ level: 2 }).run()
togglePopup('textSize')
}}
>
<Icon name="editor-h1" />
</button>
)}
</Popover>
<Popover content={t('Header 2')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().h2
})}
onClick={() => {
props.editor?.chain().focus().toggleHeading({ level: 3 }).run()
togglePopup('textSize')
}}
>
<Icon name="editor-h2" />
</button>
)}
</Popover>
<Popover content={t('Header 3')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().h3
})}
onClick={() => {
props.editor?.chain().focus().toggleHeading({ level: 4 }).run()
togglePopup('textSize')
}}
>
<Icon name="editor-h3" />
</button>
)}
</Popover>
</div>
<header>{t('Quotes')}</header>
<div class={styles.actions}>
<Popover content={t('Quote')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().quote
})}
onClick={handleSetPunchline}
>
<Icon name="editor-blockquote" />
</button>
)}
</Popover>
<Popover content={t('Punchline')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().punchLine
})}
onClick={handleSetQuote}
>
<Icon name="editor-quote" />
</button>
)}
</Popover>
</div>
<header>{t('squib')}</header>
<div class={styles.actions}>
<Popover content={t('Incut')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().incut
})}
onClick={() => {
props.editor?.chain().focus().toggleArticle().run()
togglePopup('textSize')
}}
>
<Icon name="editor-squib" />
</button>
)}
</Popover>
</div>
</div>
)
}
const BaseTextBubbleMenu = (props: BubbleMenuProps) => {
return (
<>
<Show when={!props.isCommonMarkup}>
<>
<div class={styles.dropDownHolder}>
<button
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: menuState().textSizeBubbleOpen
})}
onClick={() => togglePopup('textSize')}
>
<Icon name="editor-text-size" />
<Icon name="down-triangle" class={styles.triangle} />
</button>
<Show when={menuState().textSizeBubbleOpen}>
<TextSizeBubbleMenu {...props} />
</Show>
</div>
<div class={styles.delimiter} />
</>
</Show>
<Popover content={t('Bold')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().bold
})}
onClick={() => handleFormat('Bold')}
>
<Icon name="editor-bold" />
</button>
)}
</Popover>
<Popover content={t('Italic')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().italic
})}
onClick={() => handleFormat('Italic')}
>
<Icon name="editor-italic" />
</button>
)}
</Popover>
{/*<Popover content={t('Underline')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().underline
})}
onClick={() => handleFormat('Underline')}
>
<Icon name="editor-underline" />
</button>
)}
</Popover> */}
<Show when={!props.isCommonMarkup}>
<Popover content={t('Highlight')}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().highlight
})}
onClick={() => props.editor?.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
>
<div class={styles.toggleHighlight} />
</button>
)}
</Popover>
<div class={styles.delimiter} />
</Show>
<Popover content={<div class={styles.noWrap}>{t('Add url')}</div>}>
{(triggerRef: (el: HTMLElement) => void) => (
<button
ref={triggerRef}
type="button"
onClick={handleOpenLinkForm}
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: activeStates().link
})}
>
<Icon name="editor-link" />
</button>
)}
</Popover>
<Show when={!props.isCommonMarkup}>
<CommonMarkupBubbleMenu {...props} />
</Show>
</>
)
}
return (
<div
ref={props.ref}
class={clsx(styles.TextBubbleMenu, { [styles.growWidth]: menuState().footnoteEditorOpen })}
>
<div ref={props.ref} class={clsx(styles.TextBubbleMenu, { [styles.growWidth]: footnoteEditorOpen() })}>
<Switch>
<Match when={menuState().linkEditorOpen}>
<InsertLinkForm editor={props.editor} onClose={handleCloseLinkForm} />
<Match when={linkEditorOpen()}>
<InsertLinkForm editor={props.editor} onClose={() => setLinkEditorOpen(false)} />
</Match>
<Match when={menuState().footnoteEditorOpen}>
<Match when={footnoteEditorOpen()}>
<MiniEditor
placeholder={t('Enter footnote text')}
onSubmit={handleAddFootnote}
content={menuState().footNote}
onCancel={() => setMenuState((prev) => ({ ...prev, footnoteEditorOpen: false }))}
onSubmit={(value) => handleAddFootnote(value)}
content={footNote()}
onCancel={() => setFootnoteEditorOpen(false)}
/>
</Match>
<Match when={!(menuState().linkEditorOpen || menuState().footnoteEditorOpen)}>
<BaseTextBubbleMenu {...props} />
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
<>
<Show when={!props.isCommonMarkup}>
<>
<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}>
<Popover content={t('Header 1')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH1()
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
toggleTextSizePopup()
}}
>
<Icon name="editor-h1" />
</button>
)}
</Popover>
<Popover content={t('Header 2')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH2()
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
toggleTextSizePopup()
}}
>
<Icon name="editor-h2" />
</button>
)}
</Popover>
<Popover content={t('Header 3')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isH3()
})}
onClick={() => {
props.editor.chain().focus().toggleHeading({ level: 4 }).run()
toggleTextSizePopup()
}}
>
<Icon name="editor-h3" />
</button>
)}
</Popover>
</div>
<header>{t('Quotes')}</header>
<div class={styles.actions}>
<Popover content={t('Quote')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isQuote()
})}
onClick={handleSetPunchline}
>
<Icon name="editor-blockquote" />
</button>
)}
</Popover>
<Popover content={t('Punchline')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isPunchLine()
})}
onClick={handleSetQuote}
>
<Icon name="editor-quote" />
</button>
)}
</Popover>
</div>
<header>{t('squib')}</header>
<div class={styles.actions}>
<Popover content={t('Incut')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isIncut()
})}
onClick={() => {
props.editor.chain().focus().toggleArticle().run()
toggleTextSizePopup()
}}
>
<Icon name="editor-squib" />
</button>
)}
</Popover>
</div>
</div>
</Show>
</div>
<div class={styles.delimiter} />
</>
</Show>
<Popover content={t('Bold')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isBold()
})}
onClick={() => props.editor.chain().focus().toggleBold().run()}
>
<Icon name="editor-bold" />
</button>
)}
</Popover>
<Popover content={t('Italic')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isItalic()
})}
onClick={() => props.editor.chain().focus().toggleItalic().run()}
>
<Icon name="editor-italic" />
</button>
)}
</Popover>
<Show when={!props.isCommonMarkup}>
<Popover content={t('Highlight')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isHighlight()
})}
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
>
<div class={styles.toggleHighlight} />
</button>
)}
</Popover>
<div class={styles.delimiter} />
</Show>
<Popover content={<div class={styles.noWrap}>{t('Add url')}</div>}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
onClick={() => setLinkEditorOpen(true)}
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isLink()
})}
>
<Icon name="editor-link" />
</button>
)}
</Popover>
<Show when={!props.isCommonMarkup}>
<>
<Popover content={t('Insert footnote')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isFootnote()
})}
onClick={handleOpenFootnoteEditor}
>
<Icon name="editor-footnote" />
</button>
)}
</Popover>
<div class={styles.delimiter} />
<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}>
<Popover content={t('Bullet list')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isBulletList()
})}
onClick={() => {
props.editor.chain().focus().toggleBulletList().run()
toggleListPopup()
}}
>
<Icon name="editor-ul" />
</button>
)}
</Popover>
<Popover content={t('Ordered list')}>
{(triggerRef: (el: HTMLButtonElement) => void) => (
<button
ref={triggerRef}
type="button"
class={clsx(styles.bubbleMenuButton, {
[styles.bubbleMenuButtonActive]: isOrderedList()
})}
onClick={() => {
props.editor.chain().focus().toggleOrderedList().run()
toggleListPopup()
}}
>
<Icon name="editor-ol" />
</button>
)}
</Popover>
</div>
</div>
</Show>
</div>
</>
</Show>
</>
</Match>
</Switch>
</div>

View File

@ -1,93 +0,0 @@
export function createTooltip(referenceElement?: Element, tooltipElement?: HTMLElement, options = {}) {
const defaultOptions = {
placement: 'top',
offset: [0, 8],
flip: {
fallbackPlacements: ['top', 'bottom']
}
}
const config = { ...defaultOptions, ...options }
function updatePosition() {
if (!(referenceElement && tooltipElement)) return
const rect = referenceElement.getBoundingClientRect()
const tooltipRect = tooltipElement.getBoundingClientRect()
const offsetX = config.offset[0]
const offsetY = config.offset[1]
let placement = config.placement
let top = 0
let left = 0
// Базовое позиционирование
switch (placement) {
case 'top':
top = rect.top - tooltipRect.height - offsetY
left = rect.left + (rect.width - tooltipRect.width) / 2 + offsetX
break
case 'bottom':
top = rect.bottom + offsetY
left = rect.left + (rect.width - tooltipRect.width) / 2 + offsetX
break
// Добавьте case для 'left' и 'right', если необходимо
}
// Проверка на выход за границы окна и применение flip
if (top < 0 && config.flip.fallbackPlacements.includes('bottom')) {
top = rect.bottom + offsetY
placement = 'bottom'
} else if (top + tooltipRect.height > window.innerHeight && config.flip.fallbackPlacements.includes('top')) {
top = rect.top - tooltipRect.height - offsetY
placement = 'top'
}
// Применение позиции
tooltipElement.style.position = 'absolute'
tooltipElement.style.top = `${top}px`
tooltipElement.style.left = `${left}px`
tooltipElement.style.visibility = 'visible'
// Обновление класса для стрелки
tooltipElement.setAttribute('data-popper-placement', placement)
// Позиционирование стрелки
const arrow = tooltipElement.querySelector('[data-popper-arrow]') as HTMLElement
if (arrow) {
const arrowRect = arrow.getBoundingClientRect()
if (placement === 'top') {
arrow.style.bottom = '-4px'
arrow.style.left = `${tooltipRect.width / 2 - arrowRect.width / 2}px`
} else if (placement === 'bottom') {
arrow.style.top = '-4px'
arrow.style.left = `${tooltipRect.width / 2 - arrowRect.width / 2}px`
}
}
}
function showTooltip() {
if (tooltipElement) {
tooltipElement.style.visibility = 'visible'
updatePosition()
}
}
function hideTooltip() {
if (tooltipElement) tooltipElement.style.visibility = 'hidden'
}
referenceElement?.addEventListener('mouseenter', showTooltip)
referenceElement?.addEventListener('mouseleave', hideTooltip)
window.addEventListener('resize', updatePosition)
window.addEventListener('scroll', updatePosition)
return {
update: updatePosition,
destroy() {
referenceElement?.removeEventListener('mouseenter', showTooltip)
referenceElement?.removeEventListener('mouseleave', hideTooltip)
window.removeEventListener('resize', updatePosition)
window.removeEventListener('scroll', updatePosition)
}
}
}