Merge branch 'feature/editor_refactoring_link_form' into 'editor'
Init Refactor See merge request discoursio/discoursio-webapp!44
This commit is contained in:
commit
2a1c7ce9c1
|
@ -1,12 +1,9 @@
|
||||||
import { createEffect } from 'solid-js'
|
import { createEffect } from 'solid-js'
|
||||||
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Blockquote } from '@tiptap/extension-blockquote'
|
import { Blockquote } from '@tiptap/extension-blockquote'
|
||||||
import { Bold } from '@tiptap/extension-bold'
|
import { Bold } from '@tiptap/extension-bold'
|
||||||
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
||||||
import * as Y from 'yjs'
|
|
||||||
import { WebrtcProvider } from 'y-webrtc'
|
|
||||||
import { Dropcursor } from '@tiptap/extension-dropcursor'
|
import { Dropcursor } from '@tiptap/extension-dropcursor'
|
||||||
import { Italic } from '@tiptap/extension-italic'
|
import { Italic } from '@tiptap/extension-italic'
|
||||||
import { Strike } from '@tiptap/extension-strike'
|
import { Strike } from '@tiptap/extension-strike'
|
||||||
|
|
|
@ -36,41 +36,6 @@
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.linkForm {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
padding: 6px 11px;
|
|
||||||
|
|
||||||
input {
|
|
||||||
margin: 0 12px 0 0;
|
|
||||||
padding: 0;
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: rgba(#000, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.linkError {
|
|
||||||
padding: 6px 11px;
|
|
||||||
color: red;
|
|
||||||
font-size: 0.7em;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -3rem;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 4px 10px rgba(#000, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropDownHolder {
|
.dropDownHolder {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Icon } from '../../_shared/Icon'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEditorTransaction } from 'solid-tiptap'
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import validateUrl from '../../../utils/validateUrl'
|
import { LinkForm } from './LinkForm'
|
||||||
|
|
||||||
type BubbleMenuProps = {
|
type BubbleMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
@ -38,44 +38,10 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
const isBulletList = isActive('isBulletList')
|
const isBulletList = isActive('isBulletList')
|
||||||
const isLink = isActive('link')
|
const isLink = isActive('link')
|
||||||
|
|
||||||
//TODO: вынести логику линки в отдельный компонент
|
|
||||||
const toggleLinkForm = () => {
|
const toggleLinkForm = () => {
|
||||||
setLinkError(null)
|
|
||||||
setLinkEditorOpen(true)
|
setLinkEditorOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentUrl = createEditorTransaction(
|
|
||||||
() => props.editor,
|
|
||||||
(editor) => {
|
|
||||||
return (editor && editor.getAttributes('link').href) || ''
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const clearLinkForm = () => {
|
|
||||||
if (currentUrl()) {
|
|
||||||
props.editor.chain().focus().unsetLink().run()
|
|
||||||
}
|
|
||||||
setUrl('')
|
|
||||||
setLinkEditorOpen(false)
|
|
||||||
}
|
|
||||||
const handleUrlChange = (value) => {
|
|
||||||
setUrl(value)
|
|
||||||
}
|
|
||||||
const handleSubmitLink = () => {
|
|
||||||
if (validateUrl(url())) {
|
|
||||||
props.editor.chain().focus().setLink({ href: url() }).run()
|
|
||||||
setLinkEditorOpen(false)
|
|
||||||
} else {
|
|
||||||
setLinkError(t('Invalid url format'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyPress = (event) => {
|
|
||||||
const key = event.key
|
|
||||||
if (key === 'Enter') handleSubmitLink()
|
|
||||||
if (key === 'Esc') clearLinkForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleTextSizePopup = () => {
|
const toggleTextSizePopup = () => {
|
||||||
if (listBubbleOpen()) setListBubbleOpen(false)
|
if (listBubbleOpen()) setListBubbleOpen(false)
|
||||||
setTextSizeBubbleOpen((prev) => !prev)
|
setTextSizeBubbleOpen((prev) => !prev)
|
||||||
|
@ -90,25 +56,7 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
<div ref={props.ref} class={styles.bubbleMenu}>
|
<div ref={props.ref} class={styles.bubbleMenu}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={linkEditorOpen()}>
|
<Match when={linkEditorOpen()}>
|
||||||
<>
|
<LinkForm editor={props.editor} onClose={() => setLinkEditorOpen(false)} />
|
||||||
<div class={styles.linkForm}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={t('Enter URL address')}
|
|
||||||
autofocus
|
|
||||||
value={currentUrl()}
|
|
||||||
onKeyPress={(e) => handleKeyPress(e)}
|
|
||||||
onChange={(e) => handleUrlChange(e.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
<button type="button" onClick={() => handleSubmitLink()} disabled={linkError() !== null}>
|
|
||||||
<Icon name="status-done" />
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => clearLinkForm()}>
|
|
||||||
{currentUrl() ? 'Ж' : <Icon name="status-cancel" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{linkError() && <div class={styles.linkError}>{linkError()}</div>}
|
|
||||||
</>
|
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!linkEditorOpen()}>
|
<Match when={!linkEditorOpen()}>
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
.LinkForm {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 6px 11px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
min-width: 200px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(#000, 0.3);
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkError {
|
||||||
|
padding: 6px 11px;
|
||||||
|
color: red;
|
||||||
|
font-size: 0.7em;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 10px rgba(#000, 0.25);
|
||||||
|
opacity: 0;
|
||||||
|
transition: height 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
height: 32px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
src/components/Editor/EditorBubbleMenu/LinkForm/LinkForm.tsx
Normal file
84
src/components/Editor/EditorBubbleMenu/LinkForm/LinkForm.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import styles from './LinkForm.module.scss'
|
||||||
|
import { Icon } from '../../../_shared/Icon'
|
||||||
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
import validateUrl from '../../../../utils/validateUrl'
|
||||||
|
import type { Editor } from '@tiptap/core'
|
||||||
|
import { createSignal } from 'solid-js'
|
||||||
|
import { useLocalize } from '../../../../context/localize'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
editor: Editor
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkForm = (props: Props) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const [url, setUrl] = createSignal('')
|
||||||
|
const [linkError, setLinkError] = createSignal('')
|
||||||
|
|
||||||
|
const currentUrl = createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(editor) => {
|
||||||
|
return (editor && editor.getAttributes('link').href) || ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const clearLinkForm = () => {
|
||||||
|
if (currentUrl()) {
|
||||||
|
props.editor.chain().focus().unsetLink().run()
|
||||||
|
}
|
||||||
|
setUrl('')
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUrlInput = (value) => {
|
||||||
|
setUrl(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveButtonClick = () => {
|
||||||
|
if (!validateUrl(url())) {
|
||||||
|
setLinkError(t('Invalid url format'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props.editor.chain().focus().setLink({ href: url() }).run()
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyPress = (event) => {
|
||||||
|
setLinkError('')
|
||||||
|
const key = event.key
|
||||||
|
|
||||||
|
if (key === 'Enter') {
|
||||||
|
handleSaveButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Esc') {
|
||||||
|
clearLinkForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={styles.LinkForm}>
|
||||||
|
<div class={styles.form}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t('Enter URL address')}
|
||||||
|
autofocus
|
||||||
|
value={currentUrl()}
|
||||||
|
onKeyPress={(e) => handleKeyPress(e)}
|
||||||
|
onInput={(e) => handleUrlInput(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<button type="button" onClick={handleSaveButtonClick} disabled={linkError() !== ''}>
|
||||||
|
<Icon name="status-done" />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => clearLinkForm()}>
|
||||||
|
{currentUrl() ? 'Ж' : <Icon name="status-cancel" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={clsx(styles.linkError, { [styles.visible]: Boolean(linkError()) })}>{linkError()}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/Editor/EditorBubbleMenu/LinkForm/index.ts
Normal file
1
src/components/Editor/EditorBubbleMenu/LinkForm/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { LinkForm } from './LinkForm'
|
|
@ -4,6 +4,10 @@
|
||||||
padding: 1rem 1.2rem;
|
padding: 1rem 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formHolder {
|
||||||
|
padding: 0 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
.saveBlock {
|
.saveBlock {
|
||||||
background: #f1f1f1;
|
background: #f1f1f1;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
|
|
@ -53,7 +53,7 @@ export const CreateView = () => {
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<div class="shift-content">
|
<div class="shift-content">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-lg-9 col-xl-8">
|
<div class="col-md-20 col-lg-18 col-xl-16">
|
||||||
<h4>Slug</h4>
|
<h4>Slug</h4>
|
||||||
<div class="pretty-form__item">
|
<div class="pretty-form__item">
|
||||||
<input
|
<input
|
||||||
|
@ -152,7 +152,7 @@ export const CreateView = () => {
|
||||||
Выберите заглавное изображение для статьи, тут сразу можно увидеть как карточка будет
|
Выберите заглавное изображение для статьи, тут сразу можно увидеть как карточка будет
|
||||||
выглядеть на главной странице
|
выглядеть на главной странице
|
||||||
</p>
|
</p>
|
||||||
<div class={styles.articlePreview}></div>
|
<div class={styles.articlePreview} />
|
||||||
|
|
||||||
<div class={styles.saveBlock}>
|
<div class={styles.saveBlock}>
|
||||||
<p>
|
<p>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user