Headers & Blockquote
This commit is contained in:
parent
5b4b4e8f2d
commit
f50a072293
3
public/icons/down-triangle.svg
Normal file
3
public/icons/down-triangle.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="13" height="6" viewBox="0 0 13 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 6L0.870836 -9.53674e-07L12.1292 -9.53674e-07L6.5 6Z" fill="#898C94"/>
|
||||
</svg>
|
After Width: | Height: | Size: 185 B |
3
public/icons/editor-blockquote.svg
Normal file
3
public/icons/editor-blockquote.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 16H0V0H2V16ZM4 5V3H16V5H4ZM4 7V9H16V7H4ZM4 13V11H16V13H4Z" fill="#898C94"/>
|
||||
</svg>
|
After Width: | Height: | Size: 231 B |
4
public/icons/editor-h3.svg
Normal file
4
public/icons/editor-h3.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="21" height="12" viewBox="0 0 21 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 11.7647H2.52101V7.02521H7.79832V11.7647H10.3193V0H7.79832V4.7395H2.52101V0H0V11.7647Z" fill="currentColor"/>
|
||||
<path d="M16.3474 12C18.7004 12 20.9189 11.042 20.9189 8.63866C20.9189 6.95798 19.8936 6.06723 18.7172 5.71429C19.7928 5.34454 20.4483 4.43697 20.4483 3.2605C20.4483 1.17647 18.6836 0.100841 16.3138 0.100841C14.9189 0.100841 13.6079 0.436975 12.5827 0.991597V3.34454C13.7088 2.63865 14.9357 2.31933 15.9609 2.31933C17.339 2.31933 18.0617 2.78992 18.0617 3.61345C18.0617 4.40336 17.3558 4.82353 16.2466 4.80672L14.6668 4.78992L14.6499 6.97479H16.5323C17.6752 6.97479 18.5155 7.31092 18.5155 8.28571C18.5155 9.36134 17.4399 9.7647 16.1457 9.78151C14.8348 9.79832 13.692 9.59664 12.381 8.87395V11.2269C13.692 11.7647 14.8852 12 16.3474 12Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 884 B |
|
@ -235,5 +235,7 @@
|
|||
"Short opening": "Short opening",
|
||||
"Write an article": "Write an article",
|
||||
"Enter URL address": "Enter URL address",
|
||||
"Invalid url format": "Invalid url format"
|
||||
"Invalid url format": "Invalid url format",
|
||||
"Headers": "Headers",
|
||||
"Quotes": "Quotes"
|
||||
}
|
||||
|
|
|
@ -253,5 +253,7 @@
|
|||
"Short opening": "Небольшое вступление, чтобы заинтересовать читателя",
|
||||
"Write an article": "Написать статью",
|
||||
"Enter URL address": "Введите адрес ссылки",
|
||||
"Invalid url format": "Неверный формат ссылки"
|
||||
"Invalid url format": "Неверный формат ссылки",
|
||||
"Headers": "Заголовки",
|
||||
"Quotes": "Цитаты"
|
||||
}
|
||||
|
|
|
@ -26,9 +26,9 @@ import { Image } from '@tiptap/extension-image'
|
|||
import { Paragraph } from '@tiptap/extension-paragraph'
|
||||
import Focus from '@tiptap/extension-focus'
|
||||
import { TrailingNode } from './extensions/TrailingNode'
|
||||
import './Prosemirror.scss'
|
||||
import { EditorBubbleMenu } from './EditorBubbleMenu'
|
||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||
import './Prosemirror.scss'
|
||||
|
||||
type EditorProps = {
|
||||
initialContent?: string
|
||||
|
@ -75,6 +75,9 @@ export const Editor = (props: EditorProps) => {
|
|||
Link.configure({
|
||||
openOnClick: false
|
||||
}),
|
||||
Heading.configure({
|
||||
levels: [1, 2, 3]
|
||||
}),
|
||||
BubbleMenu.configure({
|
||||
element: bubbleMenuRef.current
|
||||
}),
|
||||
|
|
|
@ -3,10 +3,15 @@
|
|||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
|
||||
|
||||
.bubbleMenuButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
opacity: 0.5;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
.bubbleMenuButtonActive {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -48,4 +53,48 @@
|
|||
color: red;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.dropDownHolder {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
.dropDown {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
top: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
|
||||
background: #fff;
|
||||
color: #898c94;
|
||||
|
||||
& > header {
|
||||
font-size: 10px;
|
||||
border-bottom: 1px solid #898c94;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: nowrap;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.bubbleMenuButton {
|
||||
min-width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropDownEnter,
|
||||
.dropDownExit {
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { createEffect, createSignal, Show } from 'solid-js'
|
||||
import type { Editor } from '@tiptap/core'
|
||||
import styles from './EditorBubbleMenu.module.scss'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { clsx } from 'clsx'
|
||||
import { createEditorTransaction } from 'solid-tiptap'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import validateUrl from '../../utils/validateUrl'
|
||||
|
||||
|
@ -14,6 +14,7 @@ type BubbleMenuProps = {
|
|||
|
||||
export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
||||
const { t } = useLocalize()
|
||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal<boolean>(false)
|
||||
const [linkEditorOpen, setLinkEditorOpen] = createSignal<boolean>(false)
|
||||
const [url, setUrl] = createSignal<string>('')
|
||||
const [prevUrl, setPrevUrl] = createSignal<string | null>(null)
|
||||
|
@ -23,6 +24,28 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
|||
() => props.editor,
|
||||
(editor) => editor && editor.isActive('bold')
|
||||
)
|
||||
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')
|
||||
)
|
||||
const isLink = createEditorTransaction(
|
||||
() => props.editor,
|
||||
(editor) => {
|
||||
|
@ -68,7 +91,7 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
|||
<button type="submit">
|
||||
<Icon name="status-done" />
|
||||
</button>
|
||||
<button role="button" onClick={() => clearLinkForm()}>
|
||||
<button type="button" onClick={() => clearLinkForm()}>
|
||||
<Icon name="status-cancel" />
|
||||
</button>
|
||||
</form>
|
||||
|
@ -76,9 +99,87 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<div class={styles.dropDownHolder}>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
||||
})}
|
||||
onClick={() => setTextSizeBubbleOpen(!textSizeBubbleOpen())}
|
||||
>
|
||||
<Icon name="editor-text-size" />
|
||||
<Icon name="down-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]: 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} />
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isBold()
|
||||
})}
|
||||
onClick={() => props.editor.commands.toggleBold()}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isItalic()
|
||||
})}
|
||||
onClick={() => props.editor.commands.toggleItalic()}
|
||||
>
|
||||
<Icon name="editor-italic" />
|
||||
</button>
|
||||
<div class={styles.delimiter} />
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
setLinkEditorOpen(true)
|
||||
}}
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
|
@ -87,32 +188,11 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
|||
>
|
||||
<Icon name="editor-link" />
|
||||
</button>
|
||||
<button class={clsx(styles.bubbleMenuButton)}>
|
||||
<Icon name="editor-text-size" />
|
||||
</button>
|
||||
<button
|
||||
class={clsx(styles.bubbleMenuButton, {
|
||||
[styles.bubbleMenuButtonActive]: isBold()
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
props.editor.commands.toggleBold()
|
||||
}}
|
||||
>
|
||||
<Icon name="editor-bold" />
|
||||
</button>
|
||||
<button class={styles.bubbleMenuButton}>
|
||||
<Icon name="editor-italic" />
|
||||
</button>
|
||||
<div class={styles.delimiter}>D</div>
|
||||
<button class={styles.bubbleMenuButton}>
|
||||
<Icon name="editor-link" />
|
||||
</button>
|
||||
<button class={styles.bubbleMenuButton}>
|
||||
<button type="button" class={styles.bubbleMenuButton}>
|
||||
<Icon name="editor-footnote" />
|
||||
</button>
|
||||
<div class={styles.delimiter} />
|
||||
<button class={styles.bubbleMenuButton}>
|
||||
<button type="button" class={styles.bubbleMenuButton}>
|
||||
<Icon name="editor-ul" />
|
||||
</button>
|
||||
</>
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
.ProseMirror {
|
||||
outline: none;
|
||||
|
||||
blockquote {
|
||||
border-left: 2px solid;
|
||||
@include font-size(1.6rem);
|
||||
|
||||
margin: 1.5em 0;
|
||||
padding-left: 1.6em;
|
||||
}
|
||||
}
|
||||
|
||||
.ProseMirror p.is-editor-empty:first-child::before {
|
||||
|
|
|
@ -15,10 +15,4 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.enter,
|
||||
&.exitTo {
|
||||
height: 0;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user