Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Markdown editing and rendering support for Circulars #1913

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion app/routes/_gcn.circulars.$circularId.($version)/Body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { rehypeAstro } from '@nasa-gcn/remark-rehype-astro'
import classNames from 'classnames'
import type { Root } from 'mdast'
import { Fragment, createElement } from 'react'
import rehypeClassNames from 'rehype-class-names'
import rehypeReact from 'rehype-react'
import remarkGfm from 'remark-gfm'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import { type Plugin, unified } from 'unified'
Expand Down Expand Up @@ -50,9 +52,15 @@ export function MarkdownBody({
}) {
const { result } = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypeAstro)
.use(rehypeAutolinkLiteral)
.use(rehypeClassNames, {
ol: 'usa-list',
p: 'usa-paragraph',
table: 'usa-table',
ul: 'usa-list',
})
.use(rehypeReact, {
Fragment,
createElement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}

& pre,
&code {
& code {
margin: 0;
}
}
10 changes: 7 additions & 3 deletions app/routes/_gcn.circulars.$circularId.($version)/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import invariant from 'tiny-invariant'

import type { loader as parentLoader } from '../_gcn.circulars.$circularId/route'
import { get } from '../_gcn.circulars/circulars.server'
import { PlainTextBody } from './Body'
import { MarkdownBody, PlainTextBody } from './Body'
import { FrontMatter } from './FrontMatter'
import DetailsDropdownButton from '~/components/DetailsDropdownButton'
import DetailsDropdownContent from '~/components/DetailsDropdownContent'
Expand Down Expand Up @@ -60,9 +60,13 @@ export const headers: HeadersFunction = ({ loaderHeaders }) =>
pickHeaders(loaderHeaders, ['Link'])

export default function () {
const { circularId, body, bibcode, version, ...frontMatter } =
const { circularId, body, bibcode, version, format, ...frontMatter } =
useLoaderData<typeof loader>()
const searchString = useSearchString()
const Body =
useFeature('CIRCULARS_MARKDOWN') && format === 'text/markdown'
? MarkdownBody
: PlainTextBody

const result = useRouteLoaderData<typeof parentLoader>(
'routes/_gcn.circulars.$circularId'
Expand Down Expand Up @@ -141,7 +145,7 @@ export default function () {
</ButtonGroup>
<h1 className="margin-bottom-0">GCN Circular {circularId}</h1>
<FrontMatter {...frontMatter} />
<PlainTextBody className="margin-y-2">{body}</PlainTextBody>
<Body className="margin-y-2">{body}</Body>
</>
)
}
Expand Down
26 changes: 22 additions & 4 deletions app/routes/_gcn.circulars._archive._index/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import clamp from 'lodash/clamp'
import { useId, useState } from 'react'

import { getUser } from '../_gcn._auth/user.server'
import {
type CircularFormat,
circularFormats,
} from '../_gcn.circulars/circulars.lib'
import {
circularRedirect,
createChangeRequest,
Expand All @@ -41,6 +45,7 @@ import CircularsIndex from './CircularsIndex'
import { DateSelector } from './DateSelectorMenu'
import { SortSelector } from './SortSelectorButton'
import Hint from '~/components/Hint'
import { feature } from '~/lib/env.server'
import { getFormDataString } from '~/lib/utils'
import { useModStatus } from '~/root'

Expand Down Expand Up @@ -74,33 +79,46 @@ export async function action({ request }: ActionFunctionArgs) {
const body = getFormDataString(data, 'body')
const subject = getFormDataString(data, 'subject')
const intent = getFormDataString(data, 'intent')
let format
if (feature('CIRCULARS_MARKDOWN')) {
format = getFormDataString(data, 'format') as CircularFormat | undefined
if (format && !circularFormats.includes(format)) {
throw new Response('Invalid format', { status: 400 })
}
} else {
format = undefined
}
if (!body || !subject)
throw new Response('Body and subject are required', { status: 400 })
const user = await getUser(request)
const circularId = getFormDataString(data, 'circularId')

let result
const props = { body, subject, ...(format ? { format } : {}) }
switch (intent) {
case 'correction':
if (!circularId)
throw new Response('circularId is required', { status: 400 })
await createChangeRequest(parseFloat(circularId), body, subject, user)
await createChangeRequest(
{ circularId: parseFloat(circularId), ...props },
user
)
result = null
break
case 'edit':
if (!circularId)
throw new Response('circularId is required', { status: 400 })
await putVersion(
{
body,
circularId: parseFloat(circularId),
subject,
...props,
},
user
)
result = await get(parseFloat(circularId))
break
case 'new':
result = await put({ subject, body, submittedHow: 'web' }, user)
result = await put({ ...props, submittedHow: 'web' }, user)
break
default:
break
Expand Down
1 change: 1 addition & 0 deletions app/routes/_gcn.circulars.correction.$circularId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export async function loader({
formattedContributor: user ? formatAuthor(user) : '',
defaultBody: circular.body,
defaultSubject: circular.subject,
defaultFormat: circular.format,
circularId: circular.circularId,
submitter: circular.submitter,
searchString: '',
Expand Down
90 changes: 57 additions & 33 deletions app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,27 @@ import {
Textarea,
} from '@trussworks/react-uswds'
import classnames from 'classnames'
import type { ReactNode } from 'react'
import { useContext, useState } from 'react'
import { type ReactNode, useContext, useState } from 'react'
import { dedent } from 'ts-dedent'

import { AstroDataContext } from '../_gcn.circulars.$circularId.($version)/AstroDataContext'
import {
MarkdownBody,
PlainTextBody,
} from '../_gcn.circulars.$circularId.($version)/Body'
import { bodyIsValid, subjectIsValid } from '../_gcn.circulars/circulars.lib'
import {
type CircularFormat,
bodyIsValid,
subjectIsValid,
} from '../_gcn.circulars/circulars.lib'
import { RichEditor } from './RichEditor'
import {
SegmentedRadioButton,
SegmentedRadioButtonGroup,
} from './SegmentedRadioButton'
import { CircularsKeywords } from '~/components/CircularsKeywords'
import Spinner from '~/components/Spinner'
import { useFeature } from '~/root'

function SyntaxExample({
label,
Expand Down Expand Up @@ -107,6 +112,7 @@ export function CircularEditForm({
formattedContributor,
circularId,
submitter,
defaultFormat,
defaultBody,
defaultSubject,
searchString,
Expand All @@ -115,6 +121,7 @@ export function CircularEditForm({
formattedContributor: string
circularId?: number
submitter?: string
defaultFormat?: CircularFormat
defaultBody: string
defaultSubject: string
searchString: string
Expand Down Expand Up @@ -151,6 +158,7 @@ export function CircularEditForm({
saveButtonText = 'Send'
break
}
const bodyPlaceholder = useBodyPlaceholder()

return (
<AstroDataContext.Provider value={{ rel: 'noopener', target: '_blank' }}>
Expand Down Expand Up @@ -225,36 +233,52 @@ export function CircularEditForm({
<label hidden htmlFor="body">
Body
</label>
<SegmentedRadioButtonGroup>
<SegmentedRadioButton
defaultChecked
onClick={() => setShowPreview(false)}
>
Edit
</SegmentedRadioButton>
<SegmentedRadioButton onClick={() => setShowPreview(true)}>
Preview
</SegmentedRadioButton>
</SegmentedRadioButtonGroup>
<Textarea
hidden={showPreview}
name="body"
id="body"
aria-describedby="bodyDescription"
placeholder={useBodyPlaceholder()}
defaultValue={defaultBody}
required={true}
className={classnames('maxw-full', {
'usa-input--success': bodyValid,
})}
onChange={({ target: { value } }) => {
setBody(value)
}}
/>
{showPreview && (
<PlainTextBody className="border padding-1 margin-top-1">
{body}
</PlainTextBody>
{useFeature('CIRCULARS_MARKDOWN') ? (
<RichEditor
aria-describedby="bodyDescription"
placeholder={bodyPlaceholder}
defaultValue={defaultBody}
defaultMarkdown={defaultFormat === 'text/markdown'}
required={true}
className={bodyValid ? 'usa-input--success' : undefined}
onChange={({ target: { value } }) => {
setBody(value)
}}
/>
) : (
<>
<SegmentedRadioButtonGroup>
<SegmentedRadioButton
defaultChecked
onClick={() => setShowPreview(false)}
>
Edit
</SegmentedRadioButton>
<SegmentedRadioButton onClick={() => setShowPreview(true)}>
Preview
</SegmentedRadioButton>
</SegmentedRadioButtonGroup>
<Textarea
hidden={showPreview}
name="body"
id="body"
aria-describedby="bodyDescription"
placeholder={bodyPlaceholder}
defaultValue={defaultBody}
required={true}
className={classnames('maxw-full', {
'usa-input--success': bodyValid,
})}
onChange={({ target: { value } }) => {
setBody(value)
}}
/>
{showPreview && (
<PlainTextBody className="border padding-1 margin-top-1">
{body}
</PlainTextBody>
)}
</>
)}
<div className="text-base margin-bottom-1" id="bodyDescription">
<small>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

.icon {
display: inline-block;
color: inherit;
background-color: currentcolor;
mask-position: center;
mask-repeat: no-repeat;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import classNames from 'classnames'

import styles from './GitLabIcon.module.css'

export function GitLabIcon({
src,
className,
style,
...props
}: { src: string } & JSX.IntrinsicElements['span']) {
return (
<span
style={{
maskImage: `url(${src})`,
...style,
}}
className={classNames(styles.icon, className)}
{...props}
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*!
* Copyright © 2023 United States Government as represented by the
* Administrator of the National Aeronautics and Space Administration.
* All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/

.tabs {
:global(.usa-button-group__item) {
:global(.usa-button) {
box-shadow: 0px 0px 0px 0px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border: 2px solid;
padding-bottom: 14px;
width: auto;

&:not(.checked) {
margin-top: 2px;
}

&.checked {
padding-top: 14px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-color: transparent;
}
}

&:last-child :global(.usa-button) {
margin-left: -1px;
}
}
}
Loading
Loading