Figure caption with editor menu
This commit is contained in:
parent
9cbef1d0b7
commit
aa28b2cbfd
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect } from 'solid-js'
|
import { createEffect, createSignal } from 'solid-js'
|
||||||
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Blockquote } from '@tiptap/extension-blockquote'
|
import { Blockquote } from '@tiptap/extension-blockquote'
|
||||||
|
@ -57,6 +57,7 @@ const providers: Record<string, HocuspocusProvider> = {}
|
||||||
export const Editor = (props: EditorProps) => {
|
export const Editor = (props: EditorProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { user } = useSession()
|
const { user } = useSession()
|
||||||
|
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
||||||
|
|
||||||
const docName = `shout-${props.shoutId}`
|
const docName = `shout-${props.shoutId}`
|
||||||
|
|
||||||
|
@ -165,9 +166,8 @@ export const Editor = (props: EditorProps) => {
|
||||||
|
|
||||||
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
|
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
|
||||||
|
|
||||||
return (
|
setIsCommonMarkup(e.isActive('figure'))
|
||||||
view.hasFocus() && !empty && !isEmptyTextBlock && !e.isActive('image') && !e.isActive('figure')
|
return view.hasFocus() && !empty && !isEmptyTextBlock && !e.isActive('image')
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
BubbleMenu.configure({
|
||||||
|
@ -206,7 +206,11 @@ export const Editor = (props: EditorProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={(el) => (editorElRef.current = el)} />
|
<div ref={(el) => (editorElRef.current = el)} />
|
||||||
<TextBubbleMenu editor={editor()} ref={(el) => (textBubbleMenuRef.current = el)} />
|
<TextBubbleMenu
|
||||||
|
isCommonMarkup={isCommonMarkup()}
|
||||||
|
editor={editor()}
|
||||||
|
ref={(el) => (textBubbleMenuRef.current = el)}
|
||||||
|
/>
|
||||||
<ImageBubbleMenu editor={editor()} ref={(el) => (imageBubbleMenuRef.current = el)} />
|
<ImageBubbleMenu editor={editor()} ref={(el) => (imageBubbleMenuRef.current = el)} />
|
||||||
<EditorFloatingMenu editor={editor()} ref={(el) => (floatingMenuRef.current = el)} />
|
<EditorFloatingMenu editor={editor()} ref={(el) => (floatingMenuRef.current = el)} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { validateUrl } from '../../../utils/validateUrl'
|
||||||
|
|
||||||
type BubbleMenuProps = {
|
type BubbleMenuProps = {
|
||||||
editor: Editor
|
editor: Editor
|
||||||
|
isCommonMarkup: boolean
|
||||||
ref: (el: HTMLDivElement) => void
|
ref: (el: HTMLDivElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,89 +91,93 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!linkEditorOpen()}>
|
<Match when={!linkEditorOpen()}>
|
||||||
<>
|
<>
|
||||||
<div class={styles.dropDownHolder}>
|
<Show when={!props.isCommonMarkup}>
|
||||||
<button
|
<>
|
||||||
type="button"
|
<div class={styles.dropDownHolder}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={toggleTextSizePopup}
|
[styles.bubbleMenuButtonActive]: textSizeBubbleOpen()
|
||||||
>
|
})}
|
||||||
<Icon name="editor-text-size" />
|
onClick={toggleTextSizePopup}
|
||||||
<Icon name="down-triangle" class={styles.triangle} />
|
>
|
||||||
</button>
|
<Icon name="editor-text-size" />
|
||||||
<Show when={textSizeBubbleOpen()}>
|
<Icon name="down-triangle" class={styles.triangle} />
|
||||||
<div class={styles.dropDown}>
|
</button>
|
||||||
<header>{t('Headers')}</header>
|
<Show when={textSizeBubbleOpen()}>
|
||||||
<div class={styles.actions}>
|
<div class={styles.dropDown}>
|
||||||
<button
|
<header>{t('Headers')}</header>
|
||||||
type="button"
|
<div class={styles.actions}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isH1()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isH1()
|
||||||
props.editor.chain().focus().toggleHeading({ level: 1 }).run()
|
})}
|
||||||
toggleTextSizePopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleHeading({ level: 1 }).run()
|
||||||
>
|
toggleTextSizePopup()
|
||||||
<Icon name="editor-h1" />
|
}}
|
||||||
</button>
|
>
|
||||||
<button
|
<Icon name="editor-h1" />
|
||||||
type="button"
|
</button>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isH2()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isH2()
|
||||||
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
})}
|
||||||
toggleTextSizePopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleHeading({ level: 2 }).run()
|
||||||
>
|
toggleTextSizePopup()
|
||||||
<Icon name="editor-h2" />
|
}}
|
||||||
</button>
|
>
|
||||||
<button
|
<Icon name="editor-h2" />
|
||||||
type="button"
|
</button>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isH3()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isH3()
|
||||||
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
})}
|
||||||
toggleTextSizePopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleHeading({ level: 3 }).run()
|
||||||
>
|
toggleTextSizePopup()
|
||||||
<Icon name="editor-h3" />
|
}}
|
||||||
</button>
|
>
|
||||||
</div>
|
<Icon name="editor-h3" />
|
||||||
<header>{t('Quotes')}</header>
|
</button>
|
||||||
<div class={styles.actions}>
|
</div>
|
||||||
<button
|
<header>{t('Quotes')}</header>
|
||||||
type="button"
|
<div class={styles.actions}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isBlockQuote()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isBlockQuote()
|
||||||
props.editor.chain().focus().toggleBlockquote().run()
|
})}
|
||||||
toggleTextSizePopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleBlockquote().run()
|
||||||
>
|
toggleTextSizePopup()
|
||||||
<Icon name="editor-blockquote" />
|
}}
|
||||||
</button>
|
>
|
||||||
<button
|
<Icon name="editor-blockquote" />
|
||||||
type="button"
|
</button>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isBlockQuote()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isBlockQuote()
|
||||||
props.editor.chain().focus().toggleBlockquote().run()
|
})}
|
||||||
toggleTextSizePopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleBlockquote().run()
|
||||||
>
|
toggleTextSizePopup()
|
||||||
<Icon name="editor-quote" />
|
}}
|
||||||
</button>
|
>
|
||||||
</div>
|
<Icon name="editor-quote" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
<div class={styles.delimiter} />
|
||||||
</div>
|
</>
|
||||||
<div class={styles.delimiter} />
|
</Show>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
|
@ -191,15 +196,18 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
>
|
>
|
||||||
<Icon name="editor-italic" />
|
<Icon name="editor-italic" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
<Show when={!props.isCommonMarkup}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isHighlight()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#F6E3A1' }).run()}
|
[styles.bubbleMenuButtonActive]: isHighlight()
|
||||||
>
|
})}
|
||||||
<div class={styles.toggleHighlight} />
|
onClick={() => props.editor.chain().focus().toggleHighlight({ color: '#f6e3a1' }).run()}
|
||||||
</button>
|
>
|
||||||
|
<div class={styles.toggleHighlight} />
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
<div class={styles.delimiter} />
|
<div class={styles.delimiter} />
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -210,53 +218,57 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
>
|
>
|
||||||
<Icon name="editor-link" />
|
<Icon name="editor-link" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class={styles.bubbleMenuButton}>
|
<Show when={!props.isCommonMarkup}>
|
||||||
<Icon name="editor-footnote" />
|
<>
|
||||||
</button>
|
<button type="button" class={styles.bubbleMenuButton}>
|
||||||
<div class={styles.delimiter} />
|
<Icon name="editor-footnote" />
|
||||||
<div class={styles.dropDownHolder}>
|
</button>
|
||||||
<button
|
<div class={styles.delimiter} />
|
||||||
type="button"
|
<div class={styles.dropDownHolder}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: listBubbleOpen()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={toggleListPopup}
|
[styles.bubbleMenuButtonActive]: listBubbleOpen()
|
||||||
>
|
})}
|
||||||
<Icon name="editor-ul" />
|
onClick={toggleListPopup}
|
||||||
<Icon name="down-triangle" class={styles.triangle} />
|
>
|
||||||
</button>
|
<Icon name="editor-ul" />
|
||||||
<Show when={listBubbleOpen()}>
|
<Icon name="down-triangle" class={styles.triangle} />
|
||||||
<div class={styles.dropDown}>
|
</button>
|
||||||
<header>{t('Lists')}</header>
|
<Show when={listBubbleOpen()}>
|
||||||
<div class={styles.actions}>
|
<div class={styles.dropDown}>
|
||||||
<button
|
<header>{t('Lists')}</header>
|
||||||
type="button"
|
<div class={styles.actions}>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isBulletList()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isBulletList()
|
||||||
props.editor.chain().focus().toggleBulletList().run()
|
})}
|
||||||
toggleListPopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleBulletList().run()
|
||||||
>
|
toggleListPopup()
|
||||||
<Icon name="editor-ul" />
|
}}
|
||||||
</button>
|
>
|
||||||
<button
|
<Icon name="editor-ul" />
|
||||||
type="button"
|
</button>
|
||||||
class={clsx(styles.bubbleMenuButton, {
|
<button
|
||||||
[styles.bubbleMenuButtonActive]: isOrderedList()
|
type="button"
|
||||||
})}
|
class={clsx(styles.bubbleMenuButton, {
|
||||||
onClick={() => {
|
[styles.bubbleMenuButtonActive]: isOrderedList()
|
||||||
props.editor.chain().focus().toggleOrderedList().run()
|
})}
|
||||||
toggleListPopup()
|
onClick={() => {
|
||||||
}}
|
props.editor.chain().focus().toggleOrderedList().run()
|
||||||
>
|
toggleListPopup()
|
||||||
<Icon name="editor-ol" />
|
}}
|
||||||
</button>
|
>
|
||||||
</div>
|
<Icon name="editor-ol" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</>
|
||||||
</div>
|
</Show>
|
||||||
</>
|
</>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -27,8 +27,8 @@ export const UploadModalContent = (props: Props) => {
|
||||||
try {
|
try {
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const fileUrl = await handleFileUpload(file)
|
const fileUrl = await handleFileUpload(file)
|
||||||
setIsUploading(false)
|
|
||||||
props.onClose(fileUrl)
|
props.onClose(fileUrl)
|
||||||
|
setIsUploading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
setUploadError(t('Error'))
|
setUploadError(t('Error'))
|
||||||
|
|
|
@ -248,7 +248,7 @@ export const EditView = (props: EditViewProps) => {
|
||||||
<div class={styles.actions}>
|
<div class={styles.actions}>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
onClick={() => showModal('uploadImage')}
|
onClick={() => showModal('uploadCoverImage')}
|
||||||
value={coverImage() || form.coverImageUrl ? t('Add another image') : t('Add image')}
|
value={coverImage() || form.coverImageUrl ? t('Add another image') : t('Add image')}
|
||||||
/>
|
/>
|
||||||
<Show when={coverImage() ?? form.coverImageUrl}>
|
<Show when={coverImage() ?? form.coverImageUrl}>
|
||||||
|
@ -276,7 +276,7 @@ export const EditView = (props: EditViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<Modal variant="narrow" name="uploadImage">
|
<Modal variant="narrow" name="uploadCoverImage">
|
||||||
<UploadModalContent onClose={(value) => handleUploadModalContentCloseSetCover(value)} />
|
<UploadModalContent onClose={(value) => handleUploadModalContentCloseSetCover(value)} />
|
||||||
</Modal>
|
</Modal>
|
||||||
<Panel shoutId={props.shout.id} />
|
<Panel shoutId={props.shout.id} />
|
||||||
|
|
|
@ -11,6 +11,8 @@ export type ModalType =
|
||||||
| 'donate'
|
| 'donate'
|
||||||
| 'inviteToChat'
|
| 'inviteToChat'
|
||||||
| 'uploadImage'
|
| 'uploadImage'
|
||||||
|
| 'uploadCoverImage'
|
||||||
|
|
||||||
type WarnKind = 'error' | 'warn' | 'info'
|
type WarnKind = 'error' | 'warn' | 'info'
|
||||||
|
|
||||||
export interface Warning {
|
export interface Warning {
|
||||||
|
@ -26,7 +28,8 @@ export const MODALS: Record<ModalType, ModalType> = {
|
||||||
thank: 'thank',
|
thank: 'thank',
|
||||||
donate: 'donate',
|
donate: 'donate',
|
||||||
inviteToChat: 'inviteToChat',
|
inviteToChat: 'inviteToChat',
|
||||||
uploadImage: 'uploadImage'
|
uploadImage: 'uploadImage',
|
||||||
|
uploadCoverImage: 'uploadCoverImage'
|
||||||
}
|
}
|
||||||
|
|
||||||
const [modal, setModal] = createSignal<ModalType | null>(null)
|
const [modal, setModal] = createSignal<ModalType | null>(null)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user