Editor improvements (#735)
* Allow focus on all parts of editor * Fix background and text colors * Restrict height of image in comment * Remove "Type *markdown*" placeholder it's a little misleading (can't do markdown links) and messes with focus to be replaced with a highlight menu in the future
This commit is contained in:
parent
c07daafb8d
commit
0b9ca6b7ee
|
@ -65,7 +65,7 @@ function ProfileComment(props: { comment: Comment; className?: string }) {
|
||||||
/>{' '}
|
/>{' '}
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
</p>
|
</p>
|
||||||
<Content content={content || text} />
|
<Content content={content || text} smallImage />
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import Placeholder from '@tiptap/extension-placeholder'
|
||||||
import {
|
import {
|
||||||
useEditor,
|
useEditor,
|
||||||
EditorContent,
|
EditorContent,
|
||||||
FloatingMenu,
|
|
||||||
JSONContent,
|
JSONContent,
|
||||||
Content,
|
Content,
|
||||||
Editor,
|
Editor,
|
||||||
|
@ -11,13 +10,11 @@ import {
|
||||||
import StarterKit from '@tiptap/starter-kit'
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
import { Image } from '@tiptap/extension-image'
|
import { Image } from '@tiptap/extension-image'
|
||||||
import { Link } from '@tiptap/extension-link'
|
import { Link } from '@tiptap/extension-link'
|
||||||
import { Mention } from '@tiptap/extension-mention'
|
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Linkify } from './linkify'
|
import { Linkify } from './linkify'
|
||||||
import { uploadImage } from 'web/lib/firebase/storage'
|
import { uploadImage } from 'web/lib/firebase/storage'
|
||||||
import { useMutation } from 'react-query'
|
import { useMutation } from 'react-query'
|
||||||
import { exhibitExts } from 'common/util/parse'
|
|
||||||
import { FileUploadButton } from './file-upload-button'
|
import { FileUploadButton } from './file-upload-button'
|
||||||
import { linkClass } from './site-link'
|
import { linkClass } from './site-link'
|
||||||
import { useUsers } from 'web/hooks/use-users'
|
import { useUsers } from 'web/hooks/use-users'
|
||||||
|
@ -31,6 +28,18 @@ import { Button } from './button'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
|
|
||||||
|
const DisplayImage = Image.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: 'max-h-60',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const DisplayLink = Link.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
class: clsx('no-underline !text-indigo-700', linkClass),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const proseClass = clsx(
|
const proseClass = clsx(
|
||||||
'prose prose-p:my-0 prose-ul:my-0 prose-ol:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
'prose prose-p:my-0 prose-ul:my-0 prose-ol:my-0 prose-li:my-0 prose-blockquote:not-italic max-w-none prose-quoteless leading-relaxed',
|
||||||
'font-light prose-a:font-light prose-blockquote:font-light'
|
'font-light prose-a:font-light prose-blockquote:font-light'
|
||||||
|
@ -64,15 +73,11 @@ export function useTextEditor(props: {
|
||||||
Placeholder.configure({
|
Placeholder.configure({
|
||||||
placeholder,
|
placeholder,
|
||||||
emptyEditorClass:
|
emptyEditorClass:
|
||||||
'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0',
|
'before:content-[attr(data-placeholder)] before:text-slate-500 before:float-left before:h-0 cursor-text',
|
||||||
}),
|
}),
|
||||||
CharacterCount.configure({ limit: max }),
|
CharacterCount.configure({ limit: max }),
|
||||||
Image,
|
simple ? DisplayImage : Image,
|
||||||
Link.configure({
|
DisplayLink,
|
||||||
HTMLAttributes: {
|
|
||||||
class: clsx('no-underline !text-indigo-700', linkClass),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
DisplayMention.configure({
|
DisplayMention.configure({
|
||||||
suggestion: mentionSuggestion(users),
|
suggestion: mentionSuggestion(users),
|
||||||
}),
|
}),
|
||||||
|
@ -132,15 +137,7 @@ export function TextEditor(props: {
|
||||||
<>
|
<>
|
||||||
{/* hide placeholder when focused */}
|
{/* hide placeholder when focused */}
|
||||||
<div className="relative w-full [&:focus-within_p.is-empty]:before:content-none">
|
<div className="relative w-full [&:focus-within_p.is-empty]:before:content-none">
|
||||||
{editor && (
|
<div className="rounded-lg border border-gray-300 bg-white shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500">
|
||||||
<FloatingMenu
|
|
||||||
editor={editor}
|
|
||||||
className={clsx(proseClass, '-ml-2 mr-2 w-full text-slate-300 ')}
|
|
||||||
>
|
|
||||||
Type <em>*markdown*</em>
|
|
||||||
</FloatingMenu>
|
|
||||||
)}
|
|
||||||
<div className="rounded-lg border border-gray-300 shadow-sm focus-within:border-indigo-500 focus-within:ring-1 focus-within:ring-indigo-500">
|
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
{/* Toolbar, with buttons for images and embeds */}
|
{/* Toolbar, with buttons for images and embeds */}
|
||||||
<div className="flex h-9 items-center gap-5 pl-4 pr-1">
|
<div className="flex h-9 items-center gap-5 pl-4 pr-1">
|
||||||
|
@ -168,7 +165,14 @@ export function TextEditor(props: {
|
||||||
<span className="sr-only">Embed an iframe</span>
|
<span className="sr-only">Embed an iframe</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto" />
|
{/* Spacer that also focuses editor on click */}
|
||||||
|
<div
|
||||||
|
className="grow cursor-text self-stretch"
|
||||||
|
onMouseDown={() =>
|
||||||
|
editor?.chain().focus('end').createParagraphNear().run()
|
||||||
|
}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -258,14 +262,19 @@ const useUploadMutation = (editor: Editor | null) =>
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
function RichContent(props: { content: JSONContent | string }) {
|
function RichContent(props: {
|
||||||
const { content } = props
|
content: JSONContent | string
|
||||||
|
smallImage?: boolean
|
||||||
|
}) {
|
||||||
|
const { content, smallImage } = props
|
||||||
const editor = useEditor({
|
const editor = useEditor({
|
||||||
editorProps: { attributes: { class: proseClass } },
|
editorProps: { attributes: { class: proseClass } },
|
||||||
extensions: [
|
extensions: [
|
||||||
// replace tiptap's Mention with ours, to add style and link
|
StarterKit,
|
||||||
...exhibitExts.filter((ex) => ex.name !== Mention.name),
|
smallImage ? DisplayImage : Image,
|
||||||
|
DisplayLink,
|
||||||
DisplayMention,
|
DisplayMention,
|
||||||
|
Iframe,
|
||||||
],
|
],
|
||||||
content,
|
content,
|
||||||
editable: false,
|
editable: false,
|
||||||
|
@ -276,13 +285,16 @@ function RichContent(props: { content: JSONContent | string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// backwards compatibility: we used to store content as strings
|
// backwards compatibility: we used to store content as strings
|
||||||
export function Content(props: { content: JSONContent | string }) {
|
export function Content(props: {
|
||||||
|
content: JSONContent | string
|
||||||
|
smallImage?: boolean
|
||||||
|
}) {
|
||||||
const { content } = props
|
const { content } = props
|
||||||
return typeof content === 'string' ? (
|
return typeof content === 'string' ? (
|
||||||
<div className="whitespace-pre-line font-light leading-relaxed">
|
<div className="whitespace-pre-line font-light leading-relaxed">
|
||||||
<Linkify text={content} />
|
<Linkify text={content} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<RichContent content={content} />
|
<RichContent {...props} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ export function FeedComment(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-[15px] text-gray-700">
|
<div className="mt-2 text-[15px] text-gray-700">
|
||||||
<Content content={content || text} />
|
<Content content={content || text} smallImage />
|
||||||
</div>
|
</div>
|
||||||
<Row className="mt-2 items-center gap-6 text-xs text-gray-500">
|
<Row className="mt-2 items-center gap-6 text-xs text-gray-500">
|
||||||
<Tipper comment={comment} tips={tips ?? {}} />
|
<Tipper comment={comment} tips={tips ?? {}} />
|
||||||
|
@ -394,8 +394,8 @@ export function CommentInput(props: {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'min-w-0 flex-1'}>
|
<div className={'min-w-0 flex-1'}>
|
||||||
<div className="pl-0.5 text-sm text-gray-500">
|
<div className="pl-0.5 text-sm">
|
||||||
<div className={'mb-1'}>
|
<div className="mb-1 text-gray-500">
|
||||||
{mostRecentCommentableBet && (
|
{mostRecentCommentableBet && (
|
||||||
<BetStatusText
|
<BetStatusText
|
||||||
contract={contract}
|
contract={contract}
|
||||||
|
|
|
@ -338,7 +338,7 @@ const GroupMessage = memo(function GroupMessage_(props: {
|
||||||
</Row>
|
</Row>
|
||||||
<div className="mt-2 text-black">
|
<div className="mt-2 text-black">
|
||||||
{comments.map((comment) => (
|
{comments.map((comment) => (
|
||||||
<Content content={comment.content || comment.text} />
|
<Content content={comment.content || comment.text} smallImage />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Row>
|
<Row>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user