markdown previews

This commit is contained in:
keyan 2021-07-01 18:51:58 -05:00
parent 05bd31fe7a
commit 605aac97ab
9 changed files with 122 additions and 16 deletions

View File

@ -104,13 +104,12 @@ export default function Comment ({ item, children, replyOpen, includeParent, cac
> >
{reply ? 'cancel' : 'reply'} {reply ? 'cancel' : 'reply'}
</div>} </div>}
{reply && <div className={reply ? styles.replyWrapper : 'd-none'}>
<div className={styles.replyWrapper}> <Reply
<Reply parentId={item.id} autoFocus={!replyOpen}
parentId={item.id} autoFocus={!replyOpen} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId}
onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />
/> </div>
</div>}
{children} {children}
<div className={`${styles.comments} ml-sm-1 ml-md-3`}> <div className={`${styles.comments} ml-sm-1 ml-md-3`}>
{item.comments && !noComments {item.comments && !noComments

View File

@ -3,9 +3,13 @@ import InputGroup from 'react-bootstrap/InputGroup'
import BootstrapForm from 'react-bootstrap/Form' import BootstrapForm from 'react-bootstrap/Form'
import Alert from 'react-bootstrap/Alert' import Alert from 'react-bootstrap/Alert'
import { Formik, Form as FormikForm, useFormikContext, useField } from 'formik' import { Formik, Form as FormikForm, useFormikContext, useField } from 'formik'
import { useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import copy from 'clipboard-copy' import copy from 'clipboard-copy'
import Thumb from '../svgs/thumb-up-fill.svg' import Thumb from '../svgs/thumb-up-fill.svg'
import { Nav } from 'react-bootstrap'
import Markdown from '../svgs/markdown-line.svg'
import styles from './form.module.css'
import Text from '../components/text'
export function SubmitButton ({ children, variant, ...props }) { export function SubmitButton ({ children, variant, ...props }) {
const { isSubmitting } = useFormikContext() const { isSubmitting } = useFormikContext()
@ -55,11 +59,60 @@ export function InputSkeleton ({ label }) {
) )
} }
export function Input ({ label, prepend, append, hint, showValid, groupClassName, ...props }) { export function MarkdownInput ({ label, groupClassName, ...props }) {
const [field, meta] = props.readOnly ? [{}, {}] : useField(props) const [tab, setTab] = useState('write')
const [, meta] = useField(props)
useEffect(() => {
!meta.value && setTab('write')
}, [meta.value])
return ( return (
<BootstrapForm.Group className={groupClassName}> <FormGroup label={label} className={groupClassName}>
<div className={`${styles.markdownInput} ${tab === 'write' ? styles.noTopLeftRadius : ''}`}>
<Nav variant='tabs' defaultActiveKey='write' activeKey={tab} onSelect={tab => setTab(tab)}>
<Nav.Item>
<Nav.Link eventKey='write'>write</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link eventKey='preview' disabled={!meta.value}>preview</Nav.Link>
</Nav.Item>
<a
className='ml-auto text-muted d-flex align-items-center'
href='https://guides.github.com/features/mastering-markdown/' target='_blank' rel='noreferrer'
>
<Markdown width={18} height={18} />
</a>
</Nav>
<div className={tab !== 'write' ? 'd-none' : ''}>
<InputInner
{...props}
/>
</div>
<div className='form-group'>
<div className={tab !== 'preview' ? 'd-none' : `${styles.text} form-control`}>
<Text>{meta.value}</Text>
</div>
</div>
</div>
</FormGroup>
)
}
function FormGroup ({ className, label, children }) {
return (
<BootstrapForm.Group className={className}>
{label && <BootstrapForm.Label>{label}</BootstrapForm.Label>} {label && <BootstrapForm.Label>{label}</BootstrapForm.Label>}
{children}
</BootstrapForm.Group>
)
}
function InputInner ({ prepend, append, hint, showValid, ...props }) {
const [field, meta] = props.readOnly ? [{}, {}] : useField(props)
return (
<>
<InputGroup hasValidation> <InputGroup hasValidation>
{prepend && ( {prepend && (
<InputGroup.Prepend> <InputGroup.Prepend>
@ -85,7 +138,15 @@ export function Input ({ label, prepend, append, hint, showValid, groupClassName
{hint} {hint}
</BootstrapForm.Text> </BootstrapForm.Text>
)} )}
</BootstrapForm.Group> </>
)
}
export function Input ({ label, groupClassName, ...props }) {
return (
<FormGroup label={label} className={groupClassName}>
<InputInner {...props} />
</FormGroup>
) )
} }

View File

@ -0,0 +1,12 @@
.noTopLeftRadius textarea {
border-top-left-radius: 0;
}
.markdownInput textarea {
margin-top: -1px;
}
.markdownInput .text {
margin-top: -1px;
height: auto;
}

View File

@ -1,4 +1,4 @@
import { Form, Input, SubmitButton } from '../components/form' import { Form, MarkdownInput, SubmitButton } from '../components/form'
import * as Yup from 'yup' import * as Yup from 'yup'
import { gql, useMutation } from '@apollo/client' import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css' import styles from './reply.module.css'
@ -62,7 +62,7 @@ export default function Reply ({ parentId, onSuccess, autoFocus }) {
} }
}} }}
> >
<Input <MarkdownInput
name='text' name='text'
as='textarea' as='textarea'
rows={4} rows={4}

View File

@ -16,4 +16,17 @@
margin-top: .25rem; margin-top: .25rem;
width: 71px; width: 71px;
height: 38px; height: 38px;
}
.noTopLeftRadius textarea {
border-top-left-radius: 0;
}
.reply textarea {
margin-top: -1px;
}
.reply .text {
margin-top: -1px;
height: auto;
} }

View File

@ -1,5 +1,5 @@
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
import { Form, Input, SubmitButton } from '../components/form' import { Form, Input, MarkdownInput, SubmitButton } from '../components/form'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Link from 'next/link' import Link from 'next/link'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -44,7 +44,7 @@ export function DiscussionForm () {
required required
autoFocus autoFocus
/> />
<Input <MarkdownInput
label={<>text <small className='text-muted ml-2'>optional</small></>} label={<>text <small className='text-muted ml-2'>optional</small></>}
name='text' name='text'
as='textarea' as='textarea'

View File

@ -44,6 +44,10 @@ $container-max-widths: (
md: 720px, md: 720px,
lg: 900px, lg: 900px,
) !default; ) !default;
$nav-link-padding-y: .1rem;
$nav-tabs-link-active-bg: #fff;
$nav-tabs-link-hover-border-color: transparent;
$nav-tabs-link-active-border-color: #ced4da #ced4da $nav-tabs-link-active-bg;
@import "~bootstrap/scss/bootstrap"; @import "~bootstrap/scss/bootstrap";
@ -90,6 +94,21 @@ footer {
} }
} }
.nav-tabs {
border-bottom: 0;
}
.nav-tabs .nav-link {
font-size: 80%;
color: #6c757d;
z-index: 100;
position: relative;
}
.nav-tabs .nav-link.active {
height: 100%;
}
.form-label { .form-label {
font-size: 92%; font-size: 92%;
font-weight: bold; font-weight: bold;

1
svgs/markdown-fill.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm4 12.5v-4l2 2 2-2v4h2v-7h-2l-2 2-2-2H5v7h2zm11-3v-4h-2v4h-2l3 3 3-3h-2z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

1
svgs/markdown-line.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm1 2v14h16V5H4zm3 10.5H5v-7h2l2 2 2-2h2v7h-2v-4l-2 2-2-2v4zm11-3h2l-3 3-3-3h2v-4h2v4z"/></svg>

After

Width:  |  Height:  |  Size: 294 B