From 510c30c1ff4c23d0952a4ea2f677347c58d08288 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Wed, 11 Oct 2023 15:46:59 -0400 Subject: [PATCH 01/13] Remove rel="external" from AstroData links GCN Circulars frequently rely on monospaced ASCII tables, and the external link icon disrupts layout. --- .../circulars.$circularId/AstroData.components.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/routes/circulars.$circularId/AstroData.components.tsx b/app/routes/circulars.$circularId/AstroData.components.tsx index 9bcbeefcec..a03b299811 100644 --- a/app/routes/circulars.$circularId/AstroData.components.tsx +++ b/app/routes/circulars.$circularId/AstroData.components.tsx @@ -20,11 +20,7 @@ export function GcnCircular({ export function Arxiv({ children, value }: JSX.IntrinsicElements['data']) { return ( - + {children} ) @@ -32,7 +28,7 @@ export function Arxiv({ children, value }: JSX.IntrinsicElements['data']) { export function Doi({ children, value }: JSX.IntrinsicElements['data']) { return ( - + {children} ) @@ -40,11 +36,7 @@ export function Doi({ children, value }: JSX.IntrinsicElements['data']) { export function Tns({ children, value }: JSX.IntrinsicElements['data']) { return ( - + {children} ) From 49d633929da7a6f24d81b59c6bdd5142e0354b24 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Wed, 11 Oct 2023 15:55:39 -0400 Subject: [PATCH 02/13] Clean up whitespace --- app.arc | 2 +- app/components/ClientSampleCode.tsx | 2 +- app/email-incoming/circulars/index.ts | 6 +++--- app/routes/docs.notices.producers.mdx | 8 ++++---- app/routes/missions.icecube/route.mdx | 2 +- .../user.endorsements/endorsements.server.ts | 12 +++++------ app/theme.scss | 20 +++++++++---------- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/app.arc b/app.arc index 6adc8ed533..282fee09a1 100644 --- a/app.arc +++ b/app.arc @@ -76,7 +76,7 @@ legacy_users email_notification_subscription topic *String name byTopic - + email_notification recipient *String name byRecipient diff --git a/app/components/ClientSampleCode.tsx b/app/components/ClientSampleCode.tsx index abbe07ce54..24401bc1d6 100644 --- a/app/components/ClientSampleCode.tsx +++ b/app/components/ClientSampleCode.tsx @@ -473,7 +473,7 @@ export function ClientSampleCode({ listTopics ? ` // List all topics - consumer.Subscription.ForEach(topic => Console.WriteLine(topic)); + consumer.Subscription.ForEach(topic => Console.WriteLine(topic)); ` : '' } diff --git a/app/email-incoming/circulars/index.ts b/app/email-incoming/circulars/index.ts index dbebd58b5a..7cc500cdd5 100644 --- a/app/email-incoming/circulars/index.ts +++ b/app/email-incoming/circulars/index.ts @@ -159,7 +159,7 @@ function successMessage( explanation: string ) { return `Your GCN Circular from ${userEmail} (subject: ${subject}) was received and distributed. - + ${explanation}` } @@ -169,7 +169,7 @@ function failedMessage( explanation: string ) { return `Your GCN Circular from ${userEmail} (subject: ${subject}) was not processed for the following reasons: - + ${explanation} If you believe this to be a mistake, please contact us using the form at ${origin}/contact, and we will look into resolving it as soon as possible.` @@ -184,7 +184,7 @@ const sharedEmailBody = ` As of April 12, 2023, GCN Circulars are being administered through the new General Coordinates Network (GCN; ${origin}), and no longer through the GCN Classic service (https://gcn.gsfc.nasa.gov). - + The new GCN Circulars allow you to: - Browse and search Circulars in our all-new archive. diff --git a/app/routes/docs.notices.producers.mdx b/app/routes/docs.notices.producers.mdx index f229c01478..4c72fe9774 100644 --- a/app/routes/docs.notices.producers.mdx +++ b/app/routes/docs.notices.producers.mdx @@ -23,10 +23,10 @@ also happy to work with the mission teams to help construct your alerts. - Sign in / Sign up + Sign in / Sign up - Decide which of your team members will have programmatic access to produce your alerts. - Make sure that they have all signed in at least once to the [GCN website](https://gcn.nasa.gov/login) + Decide which of your team members will have programmatic access to produce your alerts. + Make sure that they have all signed in at least once to the [GCN website](https://gcn.nasa.gov/login) and the [GCN test website](https://test.gcn.nasa.gov/login). @@ -92,6 +92,6 @@ or by sending text to the [GCN Team](https://heasarc.gsfc.nasa.gov/cgi-bin/Feedb Work with the [GCN Team](/contact) - to draft a community announcement, which the GCN Team will circulate. + to draft a community announcement, which the GCN Team will circulate. diff --git a/app/routes/missions.icecube/route.mdx b/app/routes/missions.icecube/route.mdx index ea5bbddd7e..05a661e621 100644 --- a/app/routes/missions.icecube/route.mdx +++ b/app/routes/missions.icecube/route.mdx @@ -48,7 +48,7 @@ The IceCube realtime alert point of contact can be reached at roc@icecube.wisc.e IceCube has been sending alerts generated by realtime on astrophysical neutrino searches[^1] [^2] over the GCN classic system through the [Astrophysical Multimessenger Observatory Network (AMON)](https://www.amon.psu.edu). Work is underway -to update and transition these to the new GCN Kafka system. +to update and transition these to the new GCN Kafka system. [Detailed Descriptions and Examples are available.](https://gcn.gsfc.nasa.gov/amon.html)
diff --git a/app/routes/user.endorsements/endorsements.server.ts b/app/routes/user.endorsements/endorsements.server.ts index 910bf47308..0c23aa9fbd 100644 --- a/app/routes/user.endorsements/endorsements.server.ts +++ b/app/routes/user.endorsements/endorsements.server.ts @@ -29,7 +29,7 @@ import { origin } from '~/lib/env.server' const fromName = 'GCN Endorsements' // Call-to-action const endorsementsCTA = ` - + View your pending endorsements here: ${origin}/user/endorsements` // models @@ -143,8 +143,8 @@ export class EndorsementsServer { subject: 'GCN Peer Endorsements: New Request', body: dedent`You have a new peer endorsement request for NASA's General Coordinates Network (GCN) from ${ this.#currentUserEmail - }. - + }. + ${note && `Comments from the user: ${note}`} Approval of an endorsement means that the requestor, ${ @@ -154,7 +154,7 @@ export class EndorsementsServer { Please approve this request if you are familiar with the requester, and agree with the criteria. Thank you for your contributions to the GCN community. If you are not familiar with this user, or believe it to be spam, you may reject or report the endorsement request. - + View all of your pending endorsement requests here: ${origin}/user/endorsements`, }) } @@ -226,7 +226,7 @@ export class EndorsementsServer { ) requestorMessage += ` - + As an approved user, you may now submit GCN Circulars at ${origin}/circulars/new and be requested for endorsement by other users.` + endorsementsCTA } else if (status === 'reported') @@ -256,7 +256,7 @@ export class EndorsementsServer { subject: `GCN Peer Endorsements: Endorsement ${status}`, body: dedent`Your changes to ${requestorEmail}'s peer endorsement request have been processed. They will receive an email as well to confirm the new status. - + No further action is required on your part for this user's request.` + endorsementsCTA, }) diff --git a/app/theme.scss b/app/theme.scss index 46ee062c94..e05841a8c6 100644 --- a/app/theme.scss +++ b/app/theme.scss @@ -31,7 +31,7 @@ h1:first-child { } /* - * Size and position the NASA meatball logo in the header. + * Size and position the NASA meatball logo in the header. */ #site-logo { @@ -331,8 +331,8 @@ input.react-tags__combobox-input:focus { } /* - Hero formatting -*/ + * Hero formatting + */ .gcn-hero-background { background-image: none; @@ -374,24 +374,24 @@ input.react-tags__combobox-input:focus { } /* - General Responsiveness in missions page -*/ + * General Responsiveness in missions page + */ .overflow-table { overflow-x: scroll; } -/* - Bottom aligned header rows -*/ +/* + * Bottom aligned header rows + */ .bottom-aligned { display: inline; vertical-align: bottom; } /* - Fully span the row width for segmented columns -*/ + * Fully span the row width for segmented columns + */ .full-width-span { flex: 1 1 100%; } From 8d85b362777267d02fa9aace4207847a8c32b497 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Thu, 12 Oct 2023 13:46:34 -0400 Subject: [PATCH 03/13] Revert "Bump @trussworks/react-uswds from 5.1.1 to 5.4.0" This reverts commit 180d4d73d6750d730ad0a836f177faefe21c494f. Fixes #1522. --- package-lock.json | 23 +++++++++++++++-------- package.json | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e8254b2e3c..b5e48df8da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@remix-run/css-bundle": "^2.0.1", "@remix-run/node": "^2.0.0", "@remix-run/react": "^2.0.1", - "@trussworks/react-uswds": "^5.4.0", + "@trussworks/react-uswds": "^5.1.1", "addresscompiler": "^1.0.1", "aws-lambda-ses-forwarder": "github:lpsinger/aws-lambda-ses-forwarder#aws-sdk-v3", "classnames": "^2.3.2", @@ -6782,14 +6782,14 @@ } }, "node_modules/@trussworks/react-uswds": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-5.4.0.tgz", - "integrity": "sha512-2cAZ1xg36eCPP0nwsc9NblQYhPb6+KC0IqiEBB5Hwe9B6QyOWqbt2VODKgaqcCTGS7HXbwSpKPznERv+kr9eIA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-5.1.1.tgz", + "integrity": "sha512-SAlgEIFGCIKyV5aQjIi8dBUwLGdqNkbDHDvXPPWyySlhJkut5MNmZQ73BpFTxke6WqJreljkApPubMWUPiynlw==", "engines": { "node": ">= 16.20.0" }, "peerDependencies": { - "@uswds/uswds": "^3.6.0", + "@uswds/uswds": "3.1.0", "react": "^16.x || ^17.x || ^18.x", "react-dom": "^16.x || ^17.x || ^18.x" } @@ -7405,12 +7405,13 @@ "dev": true }, "node_modules/@uswds/uswds": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.6.1.tgz", - "integrity": "sha512-KDr3r4xvbvQ1X05tfacid42m/vLjAAt8N3q2/LDuujjrrBxEdHgK9ROftsesuSBoaD2Fss4lKxS0dPojLzdbbw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.1.0.tgz", + "integrity": "sha512-6XTeaQD/ipc3x4713mud4Rrr+lRc4nJ1Qw5Oy35dbVEXuKr7DjN4EBoAkbze9OoV0UdmAIvoxolBF/UcpFVKOg==", "peer": true, "dependencies": { "classlist-polyfill": "1.0.3", + "domready": "1.0.8", "object-assign": "4.1.1", "receptor": "1.0.0", "resolve-id-refs": "0.1.0" @@ -9940,6 +9941,12 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/domready": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/domready/-/domready-1.0.8.tgz", + "integrity": "sha512-uIzsOJUNk+AdGE9a6VDeessoMCzF8RrZvJCX/W8QtyfgdR6Uofn/MvRonih3OtCO79b2VDzDOymuiABrQ4z3XA==", + "peer": true + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", diff --git a/package.json b/package.json index 7244587bb9..a7db89609d 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@remix-run/css-bundle": "^2.0.1", "@remix-run/node": "^2.0.0", "@remix-run/react": "^2.0.1", - "@trussworks/react-uswds": "^5.4.0", + "@trussworks/react-uswds": "^5.1.1", "addresscompiler": "^1.0.1", "aws-lambda-ses-forwarder": "github:lpsinger/aws-lambda-ses-forwarder#aws-sdk-v3", "classnames": "^2.3.2", From ba58631f453e65951ba6ad0b10d1dd0e88543bf6 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Fri, 13 Oct 2023 15:16:26 -0400 Subject: [PATCH 04/13] Add preview and syntax reference for GCN Circulars editor --- .../AstroData.components.tsx | 20 +-- .../AstroDataContext.tsx | 38 +++++ .../{PlainTextBody.tsx => Body.tsx} | 42 ++++- .../PlainTextBody.module.scss | 5 + app/routes/circulars.$circularId/route.tsx | 4 +- app/routes/circulars.new.tsx | 161 ++++++++++++++++-- remix.config.js | 2 + 7 files changed, 241 insertions(+), 31 deletions(-) create mode 100644 app/routes/circulars.$circularId/AstroDataContext.tsx rename app/routes/circulars.$circularId/{PlainTextBody.tsx => Body.tsx} (62%) diff --git a/app/routes/circulars.$circularId/AstroData.components.tsx b/app/routes/circulars.$circularId/AstroData.components.tsx index a03b299811..73fe4e10b5 100644 --- a/app/routes/circulars.$circularId/AstroData.components.tsx +++ b/app/routes/circulars.$circularId/AstroData.components.tsx @@ -5,39 +5,33 @@ * * SPDX-License-Identifier: Apache-2.0 */ -import { Link } from '@remix-run/react' +import { AstroDataLink } from './AstroDataContext' export function GcnCircular({ children, value, }: JSX.IntrinsicElements['data']) { - return ( - - {children} - - ) + return {children} } export function Arxiv({ children, value }: JSX.IntrinsicElements['data']) { return ( - + {children} - + ) } export function Doi({ children, value }: JSX.IntrinsicElements['data']) { return ( - - {children} - + {children} ) } export function Tns({ children, value }: JSX.IntrinsicElements['data']) { return ( - + {children} - + ) } diff --git a/app/routes/circulars.$circularId/AstroDataContext.tsx b/app/routes/circulars.$circularId/AstroDataContext.tsx new file mode 100644 index 0000000000..08fc0324b4 --- /dev/null +++ b/app/routes/circulars.$circularId/AstroDataContext.tsx @@ -0,0 +1,38 @@ +/*! + * 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 { Link, type LinkProps } from '@remix-run/react' +import classNames from 'classnames' +import { createContext, useContext } from 'react' + +export type AstroDataContextProps = Pick< + JSX.IntrinsicElements['a'], + 'rel' | 'target' +> + +export const AstroDataContext = createContext({}) + +export function AstroDataLink({ + children, + className, + rel: origRel, + ...props +}: Omit) { + const context = useContext(AstroDataContext) + const rel = [origRel, context.rel].filter(Boolean).join(' ') || undefined + + return ( + + {children} + + ) +} diff --git a/app/routes/circulars.$circularId/PlainTextBody.tsx b/app/routes/circulars.$circularId/Body.tsx similarity index 62% rename from app/routes/circulars.$circularId/PlainTextBody.tsx rename to app/routes/circulars.$circularId/Body.tsx index ffa0574373..f0779500da 100644 --- a/app/routes/circulars.$circularId/PlainTextBody.tsx +++ b/app/routes/circulars.$circularId/Body.tsx @@ -6,15 +6,17 @@ * SPDX-License-Identifier: Apache-2.0 */ import { rehypeAstro } from '@nasa-gcn/remark-rehype-astro' -import { Link } from '@remix-run/react' +import classNames from 'classnames' import type { Root } from 'mdast' import { Fragment, createElement } from 'react' import rehypeReact from 'rehype-react' +import remarkParse from 'remark-parse' import remarkRehype from 'remark-rehype' import { type Plugin, unified } from 'unified' import { u } from 'unist-builder' import { AstroData } from './AstroData' +import { AstroDataLink } from './AstroDataContext' import rehypeAutolinkLiteral from './rehypeAutolinkLiteral' import styles from './PlainTextBody.module.css' @@ -30,16 +32,44 @@ function LinkWrapper({ }: Omit) { if (props.href) { return ( - + {children} - + ) } else { return {children} } } -export function PlainTextBody({ children }: { children: string }) { +export function MarkdownBody({ + className, + children, +}: { + className?: string + children: string +}) { + const { result } = unified() + .use(remarkParse) + .use(remarkRehype) + .use(rehypeAstro) + .use(rehypeAutolinkLiteral) + .use(rehypeReact, { + Fragment, + createElement, + components: { a: LinkWrapper, data: AstroData }, + }) + .processSync(children) + + return
{result}
+} + +export function PlainTextBody({ + className, + children, +}: { + className?: string + children: string +}) { const tree = u('root', [u('code', children)]) const { result } = unified() @@ -54,5 +84,7 @@ export function PlainTextBody({ children }: { children: string }) { }) .processSync() - return
{result}
+ return ( +
{result}
+ ) } diff --git a/app/routes/circulars.$circularId/PlainTextBody.module.scss b/app/routes/circulars.$circularId/PlainTextBody.module.scss index a1f72255c1..4fdf63545d 100644 --- a/app/routes/circulars.$circularId/PlainTextBody.module.scss +++ b/app/routes/circulars.$circularId/PlainTextBody.module.scss @@ -6,4 +6,9 @@ white-space: pre-wrap; background: none; } + + & pre, + &code { + margin: 0; + } } diff --git a/app/routes/circulars.$circularId/route.tsx b/app/routes/circulars.$circularId/route.tsx index 5c32b2c166..047ac03bb7 100644 --- a/app/routes/circulars.$circularId/route.tsx +++ b/app/routes/circulars.$circularId/route.tsx @@ -11,8 +11,8 @@ import { Link, useLoaderData } from '@remix-run/react' import { Button, ButtonGroup, Icon } from '@trussworks/react-uswds' import { get } from '../circulars/circulars.server' +import { PlainTextBody } from './Body' import { FrontMatter } from './FrontMatter' -import { PlainTextBody } from './PlainTextBody' import { origin } from '~/lib/env.server' import { getCanonicalUrlHeaders, pickHeaders } from '~/lib/headers.server' import { useSearchString } from '~/lib/utils' @@ -93,7 +93,7 @@ export default function () {

GCN Circular {circularId}

- {body} + {body} ) } diff --git a/app/routes/circulars.new.tsx b/app/routes/circulars.new.tsx index 463a1c851e..966b5b2b0b 100644 --- a/app/routes/circulars.new.tsx +++ b/app/routes/circulars.new.tsx @@ -23,14 +23,17 @@ import { Modal, ModalFooter, ModalHeading, + Table, TextInput, Textarea, } from '@trussworks/react-uswds' import classnames from 'classnames' -import { useState } from 'react' +import { type ReactNode, useContext, useState } from 'react' import { dedent } from 'ts-dedent' import { getUser } from './_auth/user.server' +import { AstroDataContext } from './circulars.$circularId/AstroDataContext' +import { MarkdownBody, PlainTextBody } from './circulars.$circularId/Body' import { bodyIsValid, formatAuthor, @@ -82,6 +85,46 @@ function useBodyPlaceholder() { `) } +function ToggleButton>({ + defaultValue, + options, + onChange, +}: { + defaultValue: keyof T + options: T + onChange?: (value: keyof T) => void +}) { + const [value, setValue] = useState(defaultValue) + + return ( + + {Object.entries(options).map(([key, label]) => ( + + ))} + + ) +} + +function useStateToggle(value: boolean) { + const [state, setState] = useState(value) + + function toggle() { + setState((state) => !state) + } + + return [state, toggle] as const +} + export default function () { const { isAuthenticated, isAuthorized, formattedAuthor } = useLoaderData() @@ -99,17 +142,16 @@ export default function () { const [subjectValid, setSubjectValid] = useState( subjectIsValid(defaultSubject) ) - const [bodyValid, setBodyValid] = useState(bodyIsValid(defaultBody)) - const [showKeywords, setShowKeywords] = useState(false) + const [body, setBody] = useState(defaultBody) + const bodyValid = bodyIsValid(defaultBody) + const [showKeywords, toggleShowKeywords] = useStateToggle(false) + const [showBodySyntax, toggleShowBodySyntax] = useStateToggle(false) + const [showPreview, setShowPreview] = useState(false) const sending = Boolean(useNavigation().formData) const valid = subjectValid && bodyValid - function toggleShowKeywords() { - setShowKeywords(!showKeywords) - } - return ( - <> +

New GCN Circular

@@ -169,7 +211,15 @@ export default function () { + { + setShowPreview(value === 'preview') + }} + />