Figure caption with editor menu

This commit is contained in:
ilia tapazukk 2023-05-11 11:43:14 +00:00
parent 9cbef1d0b7
commit aa28b2cbfd
5 changed files with 165 additions and 146 deletions

View File

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

View File

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

View File

@ -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'))

View File

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

View File

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