Headers & Blockquote

This commit is contained in:
ilya-bkv 2023-03-22 10:47:51 +03:00
parent 5b4b4e8f2d
commit f50a072293
10 changed files with 183 additions and 35 deletions

View 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

View 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

View 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

View File

@ -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"
}

View File

@ -253,5 +253,7 @@
"Short opening": "Небольшое вступление, чтобы заинтересовать читателя",
"Write an article": "Написать статью",
"Enter URL address": "Введите адрес ссылки",
"Invalid url format": "Неверный формат ссылки"
"Invalid url format": "Неверный формат ссылки",
"Headers": "Заголовки",
"Quotes": "Цитаты"
}

View File

@ -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
}),

View File

@ -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;
}
}

View File

@ -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>
</>

View File

@ -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 {

View File

@ -15,10 +15,4 @@
display: flex;
align-items: center;
justify-content: center;
&.enter,
&.exitTo {
height: 0;
color: transparent;
}
}