improved heading and images for markdown
This commit is contained in:
parent
d7210662b3
commit
5457026bd3
|
@ -72,7 +72,7 @@ export function CommentFlat ({ item, ...props }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Comment ({
|
export default function Comment ({
|
||||||
item, children, replyOpen, includeParent,
|
item, children, replyOpen, includeParent, topLevel,
|
||||||
rootText, noComments, noReply, truncate, depth
|
rootText, noComments, noReply, truncate, depth
|
||||||
}) {
|
}) {
|
||||||
const [edit, setEdit] = useState()
|
const [edit, setEdit] = useState()
|
||||||
|
@ -173,7 +173,7 @@ export default function Comment ({
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
<Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>
|
<Text topLevel={topLevel} nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>
|
||||||
{truncate ? truncateString(item.text) : item.searchText || item.text}
|
{truncate ? truncateString(item.text) : item.searchText || item.text}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,6 +62,7 @@ export function DiscussionForm ({
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<MarkdownInput
|
<MarkdownInput
|
||||||
|
topLevel
|
||||||
label={<>{textLabel} <small className='text-muted ml-2'>optional</small></>}
|
label={<>{textLabel} <small className='text-muted ml-2'>optional</small></>}
|
||||||
name='text'
|
name='text'
|
||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export function InputSkeleton ({ label, hint }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MarkdownInput ({ label, groupClassName, ...props }) {
|
export function MarkdownInput ({ label, topLevel, groupClassName, ...props }) {
|
||||||
const [tab, setTab] = useState('write')
|
const [tab, setTab] = useState('write')
|
||||||
const [, meta] = useField(props)
|
const [, meta] = useField(props)
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ export function MarkdownInput ({ label, groupClassName, ...props }) {
|
||||||
</div>
|
</div>
|
||||||
<div className={tab !== 'preview' ? 'd-none' : 'form-group'}>
|
<div className={tab !== 'preview' ? 'd-none' : 'form-group'}>
|
||||||
<div className={`${styles.text} form-control`}>
|
<div className={`${styles.text} form-control`}>
|
||||||
{tab === 'preview' && <Text>{meta.value}</Text>}
|
{tab === 'preview' && <Text topLevel={topLevel}>{meta.value}</Text>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -92,14 +92,14 @@ function TopLevelItem ({ item, noReply, ...props }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ItemText ({ item }) {
|
function ItemText ({ item }) {
|
||||||
return <Text nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.searchText || item.text}</Text>
|
return <Text topLevel nofollow={item.sats + item.boost < NOFOLLOW_LIMIT}>{item.searchText || item.text}</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ItemFull ({ item, bio, ...props }) {
|
export default function ItemFull ({ item, bio, ...props }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? <Comment item={item} replyOpen includeParent noComments {...props} />
|
? <Comment topLevel item={item} replyOpen includeParent noComments {...props} />
|
||||||
: (
|
: (
|
||||||
<div className='mt-1'>{
|
<div className='mt-1'>{
|
||||||
bio
|
bio
|
||||||
|
|
|
@ -160,6 +160,7 @@ export default function JobForm ({ item, sub }) {
|
||||||
/>
|
/>
|
||||||
</BForm.Row>
|
</BForm.Row>
|
||||||
<MarkdownInput
|
<MarkdownInput
|
||||||
|
topLevel
|
||||||
label='description'
|
label='description'
|
||||||
name='text'
|
name='text'
|
||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import sub from '../lib/remark-sub'
|
||||||
import remarkDirective from 'remark-directive'
|
import remarkDirective from 'remark-directive'
|
||||||
import { visit } from 'unist-util-visit'
|
import { visit } from 'unist-util-visit'
|
||||||
import reactStringReplace from 'react-string-replace'
|
import reactStringReplace from 'react-string-replace'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
function myRemarkPlugin () {
|
function myRemarkPlugin () {
|
||||||
return (tree) => {
|
return (tree) => {
|
||||||
|
@ -27,17 +28,18 @@ function myRemarkPlugin () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Text ({ nofollow, children }) {
|
export default function Text ({ topLevel, nofollow, children }) {
|
||||||
// all the reactStringReplace calls are to facilitate search highlighting
|
// all the reactStringReplace calls are to facilitate search highlighting
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
components={{
|
components={{
|
||||||
h1: 'h6',
|
h1: ({children, node, ...props}) => topLevel ? <h1 {...props}>{children}</h1> : <h3 {...props}>{children}</h3>,
|
||||||
h2: 'h6',
|
h2: ({children, node, ...props}) => topLevel ? <h2 {...props}>{children}</h2> : <h4 {...props}>{children}</h4>,
|
||||||
h3: 'h6',
|
h3: ({children, node, ...props}) => topLevel ? <h3 {...props}>{children}</h3> : <h5 {...props}>{children}</h5>,
|
||||||
h4: 'h6',
|
h4: ({children, node, ...props}) => topLevel ? <h4 {...props}>{children}</h4> : <h6 {...props}>{children}</h6>,
|
||||||
h5: 'h6',
|
h5: ({children, node, ...props}) => topLevel ? <h5 {...props}>{children}</h5> : <h6 {...props}>{children}</h6>,
|
||||||
h6: 'h6',
|
h6: 'h6',
|
||||||
table: ({ node, ...props }) =>
|
table: ({ node, ...props }) =>
|
||||||
<div className='table-responsive'>
|
<div className='table-responsive'>
|
||||||
|
@ -79,7 +81,8 @@ export default function Text ({ nofollow, children }) {
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
img: ({node, ...props}) => <ZoomableImage topLevel={topLevel} {...props}/>
|
||||||
}}
|
}}
|
||||||
remarkPlugins={[gfm, mention, sub, remarkDirective, myRemarkPlugin]}
|
remarkPlugins={[gfm, mention, sub, remarkDirective, myRemarkPlugin]}
|
||||||
>
|
>
|
||||||
|
@ -88,3 +91,39 @@ export default function Text ({ nofollow, children }) {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ZoomableImage ({ src, topLevel, ...props }) {
|
||||||
|
if (!src) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMediaStyle = {
|
||||||
|
maxHeight: topLevel ? '75vh' : '25vh',
|
||||||
|
cursor: 'zoom-in'
|
||||||
|
}
|
||||||
|
|
||||||
|
// if image changes we need to update state
|
||||||
|
const [mediaStyle, setMediaStyle] = useState(defaultMediaStyle)
|
||||||
|
useEffect(() => {
|
||||||
|
setMediaStyle(defaultMediaStyle)
|
||||||
|
}, [src])
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (mediaStyle.cursor === 'zoom-in') {
|
||||||
|
setMediaStyle({
|
||||||
|
width: '100%',
|
||||||
|
cursor: 'zoom-out'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
setMediaStyle(defaultMediaStyle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <img
|
||||||
|
className={topLevel ? styles.topLevel : undefined}
|
||||||
|
style={mediaStyle}
|
||||||
|
src={src}
|
||||||
|
onClick={handleClick}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
|
@ -7,9 +7,13 @@
|
||||||
|
|
||||||
@media screen and (min-width: 767px) {
|
@media screen and (min-width: 767px) {
|
||||||
.text {
|
.text {
|
||||||
line-height: 130%;
|
line-height: 130%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text hr {
|
||||||
|
border-top: 1px solid var(--theme-clickToContextColor);
|
||||||
|
}
|
||||||
|
|
||||||
.text p {
|
.text p {
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
|
@ -21,21 +25,29 @@
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text pre > div {
|
.text pre>div {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text > *:last-child {
|
.text>*:last-child {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text blockquote > *:last-child {
|
.text blockquote>*:last-child {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text img {
|
.text img {
|
||||||
|
display: block;
|
||||||
|
margin-top: .5rem;
|
||||||
|
border-radius: .4rem;
|
||||||
|
width: min-content;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 300px;
|
}
|
||||||
|
|
||||||
|
.text img.topLevel {
|
||||||
|
margin-top: .75rem;
|
||||||
|
margin-bottom: .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text table {
|
.text table {
|
||||||
|
@ -46,4 +58,28 @@
|
||||||
border-left: 2px solid var(--theme-grey);
|
border-left: 2px solid var(--theme-grey);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
margin: 0 0 0.5rem 0.5rem !important;
|
margin: 0 0 0.5rem 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h1 {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h2 {
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h3 {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h4 {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h5 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text h6 {
|
||||||
|
font-size: .85rem;
|
||||||
}
|
}
|
|
@ -64,6 +64,7 @@ export function BioForm ({ handleSuccess, bio }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MarkdownInput
|
<MarkdownInput
|
||||||
|
topLevel
|
||||||
name='bio'
|
name='bio'
|
||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
minRows={6}
|
minRows={6}
|
||||||
|
|
Loading…
Reference in New Issue