microeditor: hide on blur
All checks were successful
deploy / testbuild (push) Successful in 2m13s
deploy / Update templates on Mailgun (push) Has been skipped

This commit is contained in:
Untone 2024-10-11 05:26:14 +03:00
parent 7e6ef23bd2
commit d485314b11
2 changed files with 64 additions and 11 deletions

View File

@ -1,8 +1,8 @@
import BubbleMenu from '@tiptap/extension-bubble-menu' import BubbleMenu from '@tiptap/extension-bubble-menu'
import Placeholder from '@tiptap/extension-placeholder' import Placeholder from '@tiptap/extension-placeholder'
import clsx from 'clsx' import clsx from 'clsx'
import { type JSX, createEffect, createSignal, on } from 'solid-js' import { type JSX, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap' import { createEditorTransaction, createTiptapEditor, useEditorHTML } from 'solid-tiptap'
import { minimal } from '~/lib/editorExtensions' import { minimal } from '~/lib/editorExtensions'
import { MicroBubbleMenu } from './Toolbar/MicroBubbleMenu' import { MicroBubbleMenu } from './Toolbar/MicroBubbleMenu'
@ -12,13 +12,18 @@ interface MicroEditorProps {
content?: string content?: string
onChange?: (content: string) => void onChange?: (content: string) => void
onSubmit?: (content: string) => void onSubmit?: (content: string) => void
onBlur?: () => void
placeholder?: string placeholder?: string
bordered?: boolean bordered?: boolean
shownAsLead?: boolean
focusOnMount?: boolean
} }
export const MicroEditor = (props: MicroEditorProps): JSX.Element => { export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
const [editorElement, setEditorElement] = createSignal<HTMLDivElement>() const [editorElement, setEditorElement] = createSignal<HTMLDivElement>()
const [bubbleMenuElement, setBubbleMenuElement] = createSignal<HTMLDivElement>() const [bubbleMenuElement, setBubbleMenuElement] = createSignal<HTMLDivElement>()
const [isBlurred, setIsBlurred] = createSignal(false)
let blurTimer: number | undefined
const editor = createTiptapEditor(() => ({ const editor = createTiptapEditor(() => ({
element: editorElement()!, element: editorElement()!,
@ -40,9 +45,54 @@ export const MicroEditor = (props: MicroEditorProps): JSX.Element => {
})) }))
const html = useEditorHTML(editor) const html = useEditorHTML(editor)
createEffect(on(html, (c?: string) => c && props.onChange?.(c))) createEffect(on(html, (c?: string) => c && props.onChange?.(c)))
const lostFocusEmpty = createEditorTransaction(
editor,
(e) => e && !e.isFocused && e?.view.state.doc.textContent.trim() === ''
)
createEffect(
on(
isBlurred,
(lost?: boolean) => {
if (lost && props.shownAsLead && props.onBlur) {
setTimeout(props.onBlur, 1000)
}
},
{ defer: true }
)
)
const handleBlur = () => {
blurTimer = window.setTimeout(() => {
const isEmpty = editor()?.view.state.doc.textContent.trim() === ''
if (isEmpty && props.shownAsLead && props.onBlur) {
props.onBlur()
}
setIsBlurred(true)
}, 100) // небольшая задержка для обработки кликов внутри редактора
}
const handleFocus = () => {
clearTimeout(blurTimer)
setIsBlurred(false)
}
createEffect(() => {
const editorInstance = editor()
if (editorInstance) {
editorInstance.on('blur', handleBlur)
editorInstance.on('focus', handleFocus)
}
})
onCleanup(() => {
clearTimeout(blurTimer)
editor()?.off('blur', handleBlur)
editor()?.off('focus', handleFocus)
})
onMount(() => props.focusOnMount && editor()?.commands.focus())
return ( return (
<div <div
class={clsx(styles.MiniEditor, { class={clsx(styles.MiniEditor, {

View File

@ -1,6 +1,6 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import deepEqual from 'fast-deep-equal' import deepEqual from 'fast-deep-equal'
import { Show, createEffect, createSignal, lazy, on, onCleanup, onMount } from 'solid-js' import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import { debounce } from 'throttle-debounce' import { debounce } from 'throttle-debounce'
import { EditorComponent } from '~/components/Editor/Editor' import { EditorComponent } from '~/components/Editor/Editor'
@ -22,16 +22,15 @@ import { LayoutType } from '~/types/common'
import { MediaItem } from '~/types/mediaitem' import { MediaItem } from '~/types/mediaitem'
import { clone } from '~/utils/clone' import { clone } from '~/utils/clone'
import { AutoSaveNotice } from '../Editor/AutoSaveNotice' import { AutoSaveNotice } from '../Editor/AutoSaveNotice'
import { Panel } from '../Editor/Panel/Panel'
import { AudioUploader } from '../Upload/AudioUploader' import { AudioUploader } from '../Upload/AudioUploader'
import { VideoUploader } from '../Upload/VideoUploader' import { VideoUploader } from '../Upload/VideoUploader'
import { Modal } from '../_shared/Modal' import { Modal } from '../_shared/Modal'
import { TableOfContents } from '../_shared/TableOfContents' import { TableOfContents } from '../_shared/TableOfContents'
import styles from '~/styles/views/EditView.module.scss' import styles from '~/styles/views/EditView.module.scss'
import { Panel } from '../Editor/Panel/Panel' import MicroEditor from '../Editor/MicroEditor'
import GrowingTextarea from '../_shared/GrowingTextarea/GrowingTextarea'
const MicroEditor = lazy(() => import('../Editor/MicroEditor'))
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
type Props = { type Props = {
shout: Shout shout: Shout
@ -63,8 +62,7 @@ export const EditView = (props: Props) => {
setFormErrors, setFormErrors,
saveDraft, saveDraft,
saveDraftToLocalStorage, saveDraftToLocalStorage,
getDraftFromLocalStorage, getDraftFromLocalStorage
isCollabMode
} = useEditorContext() } = useEditorContext()
const [subtitleInput, setSubtitleInput] = createSignal<HTMLTextAreaElement | undefined>() const [subtitleInput, setSubtitleInput] = createSignal<HTMLTextAreaElement | undefined>()
@ -266,6 +264,10 @@ export const EditView = (props: Props) => {
setIsLeadVisible(true) setIsLeadVisible(true)
} }
const hideLeadInput = () => {
setIsLeadVisible(false)
}
const HeadingActions = () => { const HeadingActions = () => {
return ( return (
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5"> <div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
@ -340,8 +342,10 @@ export const EditView = (props: Props) => {
</Show> </Show>
<Show when={isLeadVisible()}> <Show when={isLeadVisible()}>
<MicroEditor <MicroEditor
shownAsLead={isLeadVisible()}
placeholder={t('A short introduction to keep the reader interested')} placeholder={t('A short introduction to keep the reader interested')}
content={form.lead} content={form.lead}
onBlur={hideLeadInput}
onChange={(value: string) => handleInputChange('lead', value)} onChange={(value: string) => handleInputChange('lead', value)}
/> />
</Show> </Show>
@ -455,7 +459,6 @@ export const EditView = (props: Props) => {
shoutId={form.shoutId} shoutId={form.shoutId}
initialContent={form.body} initialContent={form.body}
onChange={(body: string) => handleInputChange('body', body)} onChange={(body: string) => handleInputChange('body', body)}
disableCollaboration={!isCollabMode()}
/> />
<Show when={draft()?.id}> <Show when={draft()?.id}>
<Panel shoutId={draft()?.id} /> <Panel shoutId={draft()?.id} />