From ecafd6b20d2d2aa06a59fb1dc539f698a4e4d354 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Wed, 14 Feb 2024 10:38:36 -0500 Subject: [PATCH 01/26] Revert "Bump codecov/codecov-action from 3 to 4" This reverts commit e07c7e17faacdd075177c78dec78f4a38e274fce. --- .github/workflows/pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 2557f9442..2dc910f98 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -39,7 +39,7 @@ jobs: run: mypy python - name: Upload to Codecov.io - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v3 build: runs-on: ubuntu-latest From 81397254ecf81ef7b7fb521ca8764da672c88fad Mon Sep 17 00:00:00 2001 From: Tyler Barna <57914086+tylerbarna@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:15:59 -0600 Subject: [PATCH 02/26] Circulars Sort Results by Relevance (#1829) --- .../SortSelectorButton.tsx | 113 ++++++++++++++++++ .../_gcn.circulars._archive._index/route.tsx | 5 + app/routes/_gcn.circulars/circulars.server.ts | 17 ++- 3 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 app/routes/_gcn.circulars._archive._index/SortSelectorButton.tsx diff --git a/app/routes/_gcn.circulars._archive._index/SortSelectorButton.tsx b/app/routes/_gcn.circulars._archive._index/SortSelectorButton.tsx new file mode 100644 index 000000000..14d605627 --- /dev/null +++ b/app/routes/_gcn.circulars._archive._index/SortSelectorButton.tsx @@ -0,0 +1,113 @@ +/*! + * 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 { useSubmit } from '@remix-run/react' +import { + Button, + ButtonGroup, + CardBody, + Grid, + Icon, + Radio, +} from '@trussworks/react-uswds' +import classNames from 'classnames' +import type { ChangeEvent } from 'react' +import { useState } from 'react' + +import DetailsDropdownContent from '~/components/DetailsDropdownContent' + +const sortOptions = { circularID: 'Date', relevance: 'Relevance' } + +function SortButton({ + sort, + expanded, + ...props +}: { + sort?: string + expanded?: boolean +} & Omit[0], 'segmented' | 'children'>) { + const slimClasses = 'height-4 padding-y-0' + + return ( + + + + + ) +} + +export function SortSelector({ + form, + defaultValue, +}: { + form?: string + defaultValue?: string +}) { + const [showContent, setShowContent] = useState(false) + + const submit = useSubmit() + + function radioOnChange({ target }: ChangeEvent) { + setShowContent(false) + if (target.form) submit(target.form) + } + + const sanitizedValue = + defaultValue && defaultValue in sortOptions ? defaultValue : 'circularID' + + const SortRadioButtons = () => ( + <> + {Object.entries(sortOptions).map(([value, label]) => ( + + ))} + + ) + + return ( + <> + { + setShowContent((shown) => !shown) + }} + /> + + + + + + + + + + ) +} diff --git a/app/routes/_gcn.circulars._archive._index/route.tsx b/app/routes/_gcn.circulars._archive._index/route.tsx index 5de309a96..2cc3cb673 100644 --- a/app/routes/_gcn.circulars._archive._index/route.tsx +++ b/app/routes/_gcn.circulars._archive._index/route.tsx @@ -37,6 +37,7 @@ import CircularPagination from './CircularPagination' import CircularsHeader from './CircularsHeader' import CircularsIndex from './CircularsIndex' import { DateSelector } from './DateSelectorMenu' +import { SortSelector } from './SortSelectorButton' import Hint from '~/components/Hint' import { getFormDataString } from '~/lib/utils' @@ -52,12 +53,14 @@ export async function loader({ request: { url } }: LoaderFunctionArgs) { const endDate = searchParams.get('endDate') || undefined const page = parseInt(searchParams.get('page') || '1') const limit = clamp(parseInt(searchParams.get('limit') || '100'), 1, 100) + const sort = searchParams.get('sort') || 'circularId' const results = await search({ query, page: page - 1, limit, startDate, endDate, + sort, }) return { page, ...results } @@ -105,6 +108,7 @@ export default function () { const query = searchParams.get('query') || undefined const startDate = searchParams.get('startDate') || undefined const endDate = searchParams.get('endDate') || undefined + const sort = searchParams.get('sort') || 'circularID' let searchString = searchParams.toString() if (searchString) searchString = `?${searchString}` @@ -153,6 +157,7 @@ export default function () { defaultStartDate={startDate} defaultEndDate={endDate} /> + )} + {useFeature('CIRCULAR_VERSIONS') && ( + + Request Correction + + )} {useFeature('CIRCULAR_VERSIONS') && result?.history && result.history.length > 0 && ( diff --git a/app/routes/_gcn.circulars._archive._index/route.tsx b/app/routes/_gcn.circulars._archive._index/route.tsx index d6d71a7f1..8fb2a1961 100644 --- a/app/routes/_gcn.circulars._archive._index/route.tsx +++ b/app/routes/_gcn.circulars._archive._index/route.tsx @@ -28,7 +28,9 @@ import { useId, useState } from 'react' import { getUser } from '../_gcn._auth/user.server' import { circularRedirect, + createChangeRequest, get, + getChangeRequests, put, putVersion, search, @@ -40,6 +42,7 @@ import { DateSelector } from './DateSelectorMenu' import { SortSelector } from './SortSelectorButton' import Hint from '~/components/Hint' import { getFormDataString } from '~/lib/utils' +import { useModStatus } from '~/root' import searchImg from 'nasawds/src/img/usa-icons-bg/search--white.svg' @@ -62,43 +65,59 @@ export async function loader({ request: { url } }: LoaderFunctionArgs) { endDate, sort, }) - - return { page, ...results } + const requestedChangeCount = (await getChangeRequests()).length + return { page, ...results, requestedChangeCount } } export async function action({ request }: ActionFunctionArgs) { const data = await request.formData() const body = getFormDataString(data, 'body') const subject = getFormDataString(data, 'subject') + const intent = getFormDataString(data, 'intent') 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 - if (circularId) { - await putVersion( - { - body, - circularId: parseFloat(circularId), - subject, - }, - user - ) - result = await get(parseFloat(circularId)) - } else { - result = await put({ subject, body, submittedHow: 'web' }, user) + switch (intent) { + case 'correction': + if (!circularId) + throw new Response('circularId is required', { status: 400 }) + await createChangeRequest(parseFloat(circularId), body, subject, user) + result = null + break + case 'edit': + if (!circularId) + throw new Response('circularId is required', { status: 400 }) + await putVersion( + { + body, + circularId: parseFloat(circularId), + subject, + }, + user + ) + result = await get(parseFloat(circularId)) + break + case 'new': + result = await put({ subject, body, submittedHow: 'web' }, user) + break + default: + break } return result } export default function () { const newItem = useActionData() - const { items, page, totalPages, totalItems } = useLoaderData() + const { items, page, totalPages, totalItems, requestedChangeCount } = + useLoaderData() // Concatenate items from the action and loader functions const allItems = [...(newItem ? [newItem] : []), ...(items || [])] const [searchParams] = useSearchParams() + const userIsModerator = useModStatus() // Strip off the ?index param if we navigated here from a form. // See https://remix.run/docs/en/main/guides/index-query-param. @@ -122,6 +141,15 @@ export default function () { return ( <> + {userIsModerator && ( + <> +

+ Current pending corrections requested by submitters:{' '} + {requestedChangeCount} +

+ Review Requested Changes + + )}
null, +} + +export async function loader({ + params: { circularId }, + request, +}: LoaderFunctionArgs) { + if (!circularId) throw new Response(null, { status: 404 }) + + const user = await getUser(request) + if (!user) throw new Response(null, { status: 403 }) + const circular = await get(parseFloat(circularId)) + return { + formattedContributor: user ? formatAuthor(user) : '', + defaultBody: circular.body, + defaultSubject: circular.subject, + circularId: circular.circularId, + submitter: circular.submitter, + searchString: '', + } +} + +export default function () { + const data = useLoaderData() + return +} diff --git a/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx b/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx index 89841c6dd..f7d2b0c9c 100644 --- a/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx +++ b/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx @@ -110,6 +110,7 @@ export function CircularEditForm({ defaultBody, defaultSubject, searchString, + intent, }: { formattedContributor: string circularId?: number @@ -117,6 +118,7 @@ export function CircularEditForm({ defaultBody: string defaultSubject: string searchString: string + intent: 'correction' | 'edit' | 'new' }) { let formSearchString = '?index' if (searchString) { @@ -133,10 +135,28 @@ export function CircularEditForm({ const [showPreview, setShowPreview] = useState(false) const sending = Boolean(useNavigation().formData) const valid = subjectValid && bodyValid + let headerText, saveButtonText + + switch (intent) { + case 'correction': + headerText = 'Correct' + saveButtonText = 'Submit' + break + case 'edit': + headerText = 'Edit' + saveButtonText = 'Update' + break + case 'new': + headerText = 'New' + saveButtonText = 'Send' + break + } + return ( -

{circularId ? 'Edit' : 'New'} GCN Circular

+

{headerText} GCN Circular

+ {circularId && ( <> @@ -265,7 +285,7 @@ export function CircularEditForm({ Back {sending && (
diff --git a/app/routes/_gcn.circulars.edit.$circularId/route.tsx b/app/routes/_gcn.circulars.edit.$circularId/route.tsx index 874b73434..ad7809e32 100644 --- a/app/routes/_gcn.circulars.edit.$circularId/route.tsx +++ b/app/routes/_gcn.circulars.edit.$circularId/route.tsx @@ -45,5 +45,5 @@ export async function loader({ export default function () { const data = useLoaderData() - return + return } diff --git a/app/routes/_gcn.circulars.moderation.$circularId.$requestor.tsx b/app/routes/_gcn.circulars.moderation.$circularId.$requestor.tsx new file mode 100644 index 000000000..9640bc49a --- /dev/null +++ b/app/routes/_gcn.circulars.moderation.$circularId.$requestor.tsx @@ -0,0 +1,129 @@ +/*! + * 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 type { SEOHandle } from '@nasa-gcn/remix-seo' +import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node' +import { Form, redirect, useLoaderData } from '@remix-run/react' +import { Button, ButtonGroup } from '@trussworks/react-uswds' +import * as Diff from 'diff' + +import { getUser } from './_gcn._auth/user.server' +import { + approveChangeRequest, + deleteChangeRequest, + get, + getChangeRequest, + moderatorGroup, +} from './_gcn.circulars/circulars.server' +import { getFormDataString } from '~/lib/utils' +import type { BreadcrumbHandle } from '~/root/Title' + +export const handle: BreadcrumbHandle & SEOHandle = { + breadcrumb({ data }) { + if (data) { + return `${data.circular.circularId}` + } + }, + getSitemapEntries: () => null, +} + +export async function action({ + request, + params: { circularId, requestor }, +}: ActionFunctionArgs) { + const user = await getUser(request) + if (!user?.groups.includes(moderatorGroup)) + throw new Response(null, { status: 403 }) + const data = await request.formData() + const intent = getFormDataString(data, 'intent') + if (!intent || !circularId || !requestor) + throw new Response(null, { status: 400 }) + switch (intent) { + case 'approve': + await approveChangeRequest(parseFloat(circularId), requestor, user) + break + case 'reject': + await deleteChangeRequest(parseFloat(circularId), requestor, user) + break + default: + break + } + return redirect('/circulars/moderation') +} + +export async function loader({ + request, + params: { circularId, requestor }, +}: LoaderFunctionArgs) { + const user = await getUser(request) + if (!user?.groups.includes(moderatorGroup)) + throw new Response(null, { status: 403 }) + if (!circularId || !requestor) throw new Response(null, { status: 400 }) + const circular = await get(parseFloat(circularId)) + const correction = await getChangeRequest(parseFloat(circularId), requestor) + return { circular, correction } +} + +export default function () { + const { circular, correction } = useLoaderData() + + return ( + <> +

Circular {circular.circularId}

+

Subject

+ +

Body

+ + + + + + + + + ) +} + +function DiffedContent({ + oldString, + newString, +}: { + oldString: string + newString: string +}) { + const diff = Diff.diffWords(oldString, newString) + + return ( +
+
+        
+          {diff.map((part, index) => (
+            
+              {part.value}
+            
+          ))}
+        
+      
+
+ ) +} diff --git a/app/routes/_gcn.circulars.moderation._index.tsx b/app/routes/_gcn.circulars.moderation._index.tsx new file mode 100644 index 000000000..0271bf489 --- /dev/null +++ b/app/routes/_gcn.circulars.moderation._index.tsx @@ -0,0 +1,87 @@ +/*! + * 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 type { SEOHandle } from '@nasa-gcn/remix-seo' +import type { LoaderFunctionArgs } from '@remix-run/node' +import { Link, useLoaderData } from '@remix-run/react' +import { ButtonGroup, Grid } from '@trussworks/react-uswds' + +import { getUser } from './_gcn._auth/user.server' +import type { CircularChangeRequest } from './_gcn.circulars/circulars.lib' +import { + getChangeRequests, + moderatorGroup, +} from './_gcn.circulars/circulars.server' +import SegmentedCards from '~/components/SegmentedCards' +import type { BreadcrumbHandle } from '~/root/Title' + +export const handle: BreadcrumbHandle & SEOHandle = { + breadcrumb: 'Moderation', + getSitemapEntries: () => null, +} + +export async function loader({ request }: LoaderFunctionArgs) { + const user = await getUser(request) + if (!user || !user.groups.includes(moderatorGroup)) + throw new Response(null, { status: 403 }) + const changeRequests = await getChangeRequests() + return { + changeRequests, + } +} + +export default function () { + const { changeRequests } = useLoaderData() + + return ( + <> +

Pending Corrections

+ + + Back + + + + {changeRequests.map((correction) => ( + + ))} + + + ) +} + +function CircularChangeRequestRow({ + changeRequest, +}: { + changeRequest: CircularChangeRequest +}) { + return ( + +
+
+ Circular: + {changeRequest.circularId} +
+
+ Requestor: + {changeRequest.requestor} +
+
+
+ + Review + +
+
+ ) +} diff --git a/app/routes/_gcn.circulars.moderation.tsx b/app/routes/_gcn.circulars.moderation.tsx new file mode 100644 index 000000000..7df3c5755 --- /dev/null +++ b/app/routes/_gcn.circulars.moderation.tsx @@ -0,0 +1,25 @@ +/*! + * 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 type { SEOHandle } from '@nasa-gcn/remix-seo' +import { Outlet } from '@remix-run/react' + +import type { BreadcrumbHandle } from '~/root/Title' + +export const handle: BreadcrumbHandle & SEOHandle = { + breadcrumb: 'Moderation', + getSitemapEntries: () => null, +} + +export default function () { + return ( + <> +

Circulars Moderation

+ + + ) +} diff --git a/app/routes/_gcn.circulars.new.tsx b/app/routes/_gcn.circulars.new.tsx index 68678605a..367141a18 100644 --- a/app/routes/_gcn.circulars.new.tsx +++ b/app/routes/_gcn.circulars.new.tsx @@ -69,7 +69,7 @@ export default function () { return ( <> - + {isAuthorized || } ) diff --git a/app/routes/_gcn.circulars/circulars.server.ts b/app/routes/_gcn.circulars/circulars.server.ts index 12582177e..13176ccff 100644 --- a/app/routes/_gcn.circulars/circulars.server.ts +++ b/app/routes/_gcn.circulars/circulars.server.ts @@ -7,7 +7,7 @@ */ import { tables } from '@architect/functions' import type { DynamoDB } from '@aws-sdk/client-dynamodb' -import { type DynamoDBDocument } from '@aws-sdk/lib-dynamodb' +import { type DynamoDBDocument, paginateScan } from '@aws-sdk/lib-dynamodb' import { search as getSearch } from '@nasa-gcn/architect-functions-search' import { DynamoDBAutoIncrement, @@ -362,10 +362,9 @@ export async function createChangeRequest( circularId: number, body: string, subject: string, - request: Request + user?: User ) { validateCircular(subject, body) - const user = await getUser(request) if (!user) throw new Response('User is not signed in', { status: 403, @@ -382,23 +381,22 @@ export async function createChangeRequest( } /** - * Gets all change requests for a given circular - * @param circularId + * Get all pending change requests * @returns */ -export async function getChangeRequests( - circularId: number -): Promise { +export async function getChangeRequests(): Promise { const db = await tables() - return ( - await db.circulars_change_requests.query({ - KeyConditionExpression: 'circularId = :circularId', - ExpressionAttributeValues: { - ':circularId': circularId, - }, - ProjectionExpression: 'circularId, requestor, requestorSub', - }) - ).Items + const client = db._doc as unknown as DynamoDBDocument + const TableName = db.name('circulars_change_requests') + + const pages = paginateScan({ client }, { TableName }) + const changeRequests: CircularChangeRequest[] = [] + for await (const page of pages) { + const newRequests = page.Items as CircularChangeRequest[] + if (newRequests) changeRequests.push(...newRequests) + } + + return changeRequests } /** @@ -411,14 +409,13 @@ export async function getChangeRequests( * * @param circularId * @param requestorSub - * @param request + * @param user */ export async function deleteChangeRequest( circularId: number, requestorSub: string, - request: Request + user: User ): Promise { - const user = await getUser(request) if (!user) throw new Response(null, { status: 403 }) if (requestorSub !== user.sub || !user.groups.includes(moderatorGroup)) throw new Response( @@ -432,7 +429,6 @@ export async function deleteChangeRequest( /** * Delete a specific change request * @param circularId - * @param requestorSub */ async function deleteChangeRequestRaw( circularId: number, @@ -446,25 +442,21 @@ async function deleteChangeRequestRaw( } /** - * Applies a change request on behalf of another user. This + * Applies the change request on behalf of the original author. This * method creates a new version and deletes the change * request once completed * * Throws an HTTP error if: * - The current user is not a moderator - * - No change request is found with the provided requestor - * information * * @param circularId - * @param requestorSub - * @param request + * @param user */ export async function approveChangeRequest( circularId: number, requestorSub: string, - request: Request + user: User ) { - const user = await getUser(request) if (!user?.groups.includes(moderatorGroup)) throw new Response('User is not in the moderators group', { status: 403, @@ -485,7 +477,16 @@ export async function approveChangeRequest( await deleteChangeRequestRaw(circularId, requestorSub) } -async function getChangeRequest(circularId: number, requestorSub: string) { +/** + * Gets the change request for a given circular + * @param circularId + * @param requestorSub + * @returns + */ +export async function getChangeRequest( + circularId: number, + requestorSub: string +) { const db = await tables() const changeRequest = (await db.circulars_change_requests.get({ circularId, diff --git a/package-lock.json b/package-lock.json index 57790590b..3602f2d74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "color-convert": "^2.0.1", "cross-env": "^7.0.3", "dayjs": "^1.11.10", + "diff": "^5.2.0", "downshift": "^7.2.1", "email-validator": "^2.0.4", "github-slugger": "^2.0.0", @@ -76,6 +77,7 @@ "@testing-library/react": "^14.1.2", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/color-convert": "^2.0.1", + "@types/diff": "^5.0.9", "@types/grecaptcha": "^3.0.6", "@types/jest": "^29.5.5", "@types/lodash": "^4.14.199", @@ -5744,9 +5746,9 @@ "dev": true }, "node_modules/@emotion/hash": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", - "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", "dev": true }, "node_modules/@esbuild/android-arm": { @@ -9318,6 +9320,12 @@ "@types/ms": "*" } }, + "node_modules/@types/diff": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.0.9.tgz", + "integrity": "sha512-RWVEhh/zGXpAVF/ZChwNnv7r4rvqzJ7lYNSmZSVTxjV0PBLf6Qu7RNg+SUtkpzxmiNkjCx0Xn2tPp7FIkshJwQ==", + "dev": true + }, "node_modules/@types/estree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", @@ -9520,6 +9528,14 @@ "@types/node": "*" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -10687,6 +10703,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/babel-plugin-polyfill-corejs2": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", @@ -11832,6 +11865,55 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "optional": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/cpr": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cpr/-/cpr-3.0.1.tgz", @@ -12450,10 +12532,9 @@ } }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", - "dev": true, + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "engines": { "node": ">=0.3.1" } diff --git a/package.json b/package.json index 96ffb48d5..311f141b9 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "color-convert": "^2.0.1", "cross-env": "^7.0.3", "dayjs": "^1.11.10", + "diff": "^5.2.0", "downshift": "^7.2.1", "email-validator": "^2.0.4", "github-slugger": "^2.0.0", @@ -95,6 +96,7 @@ "@testing-library/react": "^14.1.2", "@trivago/prettier-plugin-sort-imports": "^4.2.1", "@types/color-convert": "^2.0.1", + "@types/diff": "^5.0.9", "@types/grecaptcha": "^3.0.6", "@types/jest": "^29.5.5", "@types/lodash": "^4.14.199", From 5ac40bca92e575d257b65a908f614a2620bd1d52 Mon Sep 17 00:00:00 2001 From: Courey Elliott Date: Fri, 16 Feb 2024 12:02:37 -0500 Subject: [PATCH 05/26] adjusting synonyms table and index (#1910) --- app.arc | 6 +++--- app/routes/_gcn.synonyms/synonyms.lib.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.arc b/app.arc index 3e3a753c5..2b700dd3e 100644 --- a/app.arc +++ b/app.arc @@ -84,7 +84,6 @@ circulars synonyms eventId *String - synonymId **String PointInTimeRecovery true auto_increment_metadata @@ -151,8 +150,9 @@ legacy_users name legacyAnnouncementReceivers synonyms - synonymId *String - name synonymsById + uuid *String + eventId **String + name synonymsByUuid @aws runtime nodejs20.x diff --git a/app/routes/_gcn.synonyms/synonyms.lib.ts b/app/routes/_gcn.synonyms/synonyms.lib.ts index 638fe925b..aeb1a8fb3 100644 --- a/app/routes/_gcn.synonyms/synonyms.lib.ts +++ b/app/routes/_gcn.synonyms/synonyms.lib.ts @@ -7,5 +7,5 @@ */ export interface Synonym { eventId: string - synonymId: string + uuid: string } From a8f04519152b95f7dfdde2dbd672b56eedd1c848 Mon Sep 17 00:00:00 2001 From: dakota002 Date: Fri, 16 Feb 2024 15:37:24 -0500 Subject: [PATCH 06/26] Improved button for corrections --- app/routes/_gcn.circulars._archive._index/route.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/routes/_gcn.circulars._archive._index/route.tsx b/app/routes/_gcn.circulars._archive._index/route.tsx index 8fb2a1961..45df2863d 100644 --- a/app/routes/_gcn.circulars._archive._index/route.tsx +++ b/app/routes/_gcn.circulars._archive._index/route.tsx @@ -141,14 +141,11 @@ export default function () { return ( <> - {userIsModerator && ( - <> -

- Current pending corrections requested by submitters:{' '} - {requestedChangeCount} -

- Review Requested Changes - + {userIsModerator && requestedChangeCount > 0 && ( + + Review {requestedChangeCount} Requested Change + {requestedChangeCount > 1 ? 's' : ''} + )}
Date: Fri, 16 Feb 2024 15:19:52 -0500 Subject: [PATCH 07/26] changing the name of the uuid field --- sandbox-seed.json | 180 +++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/sandbox-seed.json b/sandbox-seed.json index 8be8e4b9b..62683f20c 100644 --- a/sandbox-seed.json +++ b/sandbox-seed.json @@ -4754,363 +4754,363 @@ ], "synonyms": [ { - "synonymId": "471b4932-6bf7-46bd-8ae9-937579351d40", + "uuid": "471b4932-6bf7-46bd-8ae9-937579351d40", "eventId": "GRB 230911C" }, { - "synonymId": "471b4932-6bf7-46bd-8ae9-937579351d40", + "uuid": "471b4932-6bf7-46bd-8ae9-937579351d40", "eventId": "GRB 230728A" }, { - "synonymId": "471b4932-6bf7-46bd-8ae9-937579351d40", + "uuid": "471b4932-6bf7-46bd-8ae9-937579351d40", "eventId": "GRB 230919A" }, { - "synonymId": "090aa925-0276-47c0-96f9-a3027a9be761", + "uuid": "090aa925-0276-47c0-96f9-a3027a9be761", "eventId": "GRB 230904A" }, { - "synonymId": "090aa925-0276-47c0-96f9-a3027a9be761", + "uuid": "090aa925-0276-47c0-96f9-a3027a9be761", "eventId": "GRB 230824A" }, { - "synonymId": "090aa925-0276-47c0-96f9-a3027a9be761", + "uuid": "090aa925-0276-47c0-96f9-a3027a9be761", "eventId": "IceCube-230914A" }, { - "synonymId": "8773cbbb-a06a-4370-ae02-c3e896650ef6", + "uuid": "8773cbbb-a06a-4370-ae02-c3e896650ef6", "eventId": "GRB 230805B" }, { - "synonymId": "8773cbbb-a06a-4370-ae02-c3e896650ef6", + "uuid": "8773cbbb-a06a-4370-ae02-c3e896650ef6", "eventId": "GRB 230827.256" }, { - "synonymId": "8773cbbb-a06a-4370-ae02-c3e896650ef6", + "uuid": "8773cbbb-a06a-4370-ae02-c3e896650ef6", "eventId": "GRB 230803A" }, { - "synonymId": "8773cbbb-a06a-4370-ae02-c3e896650ef6", + "uuid": "8773cbbb-a06a-4370-ae02-c3e896650ef6", "eventId": "GRB 230918A" }, { - "synonymId": "5ed66644-3615-4870-9a09-d15a1dee2e0c", + "uuid": "5ed66644-3615-4870-9a09-d15a1dee2e0c", "eventId": "GRB 230818A" }, { - "synonymId": "5ed66644-3615-4870-9a09-d15a1dee2e0c", + "uuid": "5ed66644-3615-4870-9a09-d15a1dee2e0c", "eventId": "GRB 230816A" }, { - "synonymId": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", + "uuid": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", "eventId": "GRB 230916B" }, { - "synonymId": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", + "uuid": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", "eventId": "GRB 230812B" }, { - "synonymId": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", + "uuid": "fe00636d-ae9a-45fe-9941-9cbc6e84da04", "eventId": "GRB 230727A" }, { - "synonymId": "f9a356be-911c-414c-b58b-7fa870af6b6e", + "uuid": "f9a356be-911c-414c-b58b-7fa870af6b6e", "eventId": "GRB 230913A" }, { - "synonymId": "f9a356be-911c-414c-b58b-7fa870af6b6e", + "uuid": "f9a356be-911c-414c-b58b-7fa870af6b6e", "eventId": "GRB 230913.70" }, { - "synonymId": "8acd5039-f27c-4d0e-b82a-0b289ed02981", + "uuid": "8acd5039-f27c-4d0e-b82a-0b289ed02981", "eventId": "GRB 230918B" }, { - "synonymId": "8acd5039-f27c-4d0e-b82a-0b289ed02981", + "uuid": "8acd5039-f27c-4d0e-b82a-0b289ed02981", "eventId": "GRB 230906A" }, { - "synonymId": "5f8fcbda-2405-4e62-89b7-57ccdb08782f", + "uuid": "5f8fcbda-2405-4e62-89b7-57ccdb08782f", "eventId": "GRB 230804A" }, { - "synonymId": "5f8fcbda-2405-4e62-89b7-57ccdb08782f", + "uuid": "5f8fcbda-2405-4e62-89b7-57ccdb08782f", "eventId": "IceCube-230727A" }, { - "synonymId": "c43a6d5e-217b-44da-8354-c1be125e47b1", + "uuid": "c43a6d5e-217b-44da-8354-c1be125e47b1", "eventId": "GRB 230805A" }, { - "synonymId": "c43a6d5e-217b-44da-8354-c1be125e47b1", + "uuid": "c43a6d5e-217b-44da-8354-c1be125e47b1", "eventId": "GRB 230908A" }, { - "synonymId": "13e6c344-cc20-430d-b2a9-cbb453abd233", + "uuid": "13e6c344-cc20-430d-b2a9-cbb453abd233", "eventId": "GRB 230903A" }, { - "synonymId": "13e6c344-cc20-430d-b2a9-cbb453abd233", + "uuid": "13e6c344-cc20-430d-b2a9-cbb453abd233", "eventId": "IceCube-230823A" }, { - "synonymId": "13e6c344-cc20-430d-b2a9-cbb453abd233", + "uuid": "13e6c344-cc20-430d-b2a9-cbb453abd233", "eventId": "GRB 230728.12" }, { - "synonymId": "260ff8fc-7e86-4c35-8b5f-f0bbaeb88179", + "uuid": "260ff8fc-7e86-4c35-8b5f-f0bbaeb88179", "eventId": "GRB 230911D" }, { - "synonymId": "260ff8fc-7e86-4c35-8b5f-f0bbaeb88179", + "uuid": "260ff8fc-7e86-4c35-8b5f-f0bbaeb88179", "eventId": "GRB 230824.72" }, { - "synonymId": "94ea437d-ff4c-4cbb-8e1b-2ecdc87e737b", + "uuid": "94ea437d-ff4c-4cbb-8e1b-2ecdc87e737b", "eventId": "GRB 230904B" }, { - "synonymId": "94ea437d-ff4c-4cbb-8e1b-2ecdc87e737b", + "uuid": "94ea437d-ff4c-4cbb-8e1b-2ecdc87e737b", "eventId": "GRB 230917A" }, { - "synonymId": "4b9c2382-f5d5-4d4d-8dea-eccaa1c1f9d2", + "uuid": "4b9c2382-f5d5-4d4d-8dea-eccaa1c1f9d2", "eventId": "GRB 230815A" }, { - "synonymId": "4b9c2382-f5d5-4d4d-8dea-eccaa1c1f9d2", + "uuid": "4b9c2382-f5d5-4d4d-8dea-eccaa1c1f9d2", "eventId": "GRB 230902.00" }, { - "synonymId": "fb5e4482-020c-4907-8984-b4cff2b594c1", + "uuid": "fb5e4482-020c-4907-8984-b4cff2b594c1", "eventId": "GRB 230915A" }, { - "synonymId": "fb5e4482-020c-4907-8984-b4cff2b594c1", + "uuid": "fb5e4482-020c-4907-8984-b4cff2b594c1", "eventId": "GRB 230911A" }, { - "synonymId": "8f9e97c0-ebed-4616-9446-4851bd2de2cb", + "uuid": "8f9e97c0-ebed-4616-9446-4851bd2de2cb", "eventId": "GRB 230802A" }, { - "synonymId": "8f9e97c0-ebed-4616-9446-4851bd2de2cb", + "uuid": "8f9e97c0-ebed-4616-9446-4851bd2de2cb", "eventId": "GRB 230806A" }, { - "synonymId": "8040d18b-90f1-4a87-bdf8-c9e3867064a1", + "uuid": "8040d18b-90f1-4a87-bdf8-c9e3867064a1", "eventId": "GRB 230905B" }, { - "synonymId": "8040d18b-90f1-4a87-bdf8-c9e3867064a1", + "uuid": "8040d18b-90f1-4a87-bdf8-c9e3867064a1", "eventId": "GRB 230826A" }, { - "synonymId": "bac2b5ea-2b18-4270-92e1-ad9356842b04", + "uuid": "bac2b5ea-2b18-4270-92e1-ad9356842b04", "eventId": "GRB 230904C" }, { - "synonymId": "bac2b5ea-2b18-4270-92e1-ad9356842b04", + "uuid": "bac2b5ea-2b18-4270-92e1-ad9356842b04", "eventId": "GRB 230921A" }, { - "synonymId": "a26b7a7a-373a-41d2-a24a-6552919f9f40", + "uuid": "a26b7a7a-373a-41d2-a24a-6552919f9f40", "eventId": "GRB 230827B" }, { - "synonymId": "a26b7a7a-373a-41d2-a24a-6552919f9f40", + "uuid": "a26b7a7a-373a-41d2-a24a-6552919f9f40", "eventId": "GRB 230723A" }, { - "synonymId": "a26b7a7a-373a-41d2-a24a-6552919f9f40", + "uuid": "a26b7a7a-373a-41d2-a24a-6552919f9f40", "eventId": "GRB 230814A" }, { - "synonymId": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", + "uuid": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", "eventId": "GRB 230818B" }, { - "synonymId": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", + "uuid": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", "eventId": "GRB 230911B" }, { - "synonymId": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", + "uuid": "bee236c9-c6e5-4c1d-bb13-0a9f2c33439a", "eventId": "GRB 230812.79" }, { - "synonymId": "1e61d317-c2bf-469c-838a-ad1d79161fef", + "uuid": "1e61d317-c2bf-469c-838a-ad1d79161fef", "eventId": "GRB 230802B" }, { - "synonymId": "1e61d317-c2bf-469c-838a-ad1d79161fef", + "uuid": "1e61d317-c2bf-469c-838a-ad1d79161fef", "eventId": "GRB 230807A" }, { - "synonymId": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", + "uuid": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", "eventId": "IceCube-230707A" }, { - "synonymId": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", + "uuid": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", "eventId": "GRB 230824.10" }, { - "synonymId": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", + "uuid": "5cc74db8-78c0-4cf5-bf75-48f07a92cc31", "eventId": "IceCube-230724A" }, { - "synonymId": "d0066bdb-de52-4843-a3f1-813609ada880", + "uuid": "d0066bdb-de52-4843-a3f1-813609ada880", "eventId": "GRB 230828A" }, { - "synonymId": "d0066bdb-de52-4843-a3f1-813609ada880", + "uuid": "d0066bdb-de52-4843-a3f1-813609ada880", "eventId": "IceCube-230725A" }, { - "synonymId": "b4e3c88b-0eca-4402-94aa-e5f4ec1b4f91", + "uuid": "b4e3c88b-0eca-4402-94aa-e5f4ec1b4f91", "eventId": "GRB 230827A" }, { - "synonymId": "b4e3c88b-0eca-4402-94aa-e5f4ec1b4f91", + "uuid": "b4e3c88b-0eca-4402-94aa-e5f4ec1b4f91", "eventId": "GRB 230820.80" }, { - "synonymId": "4389eb57-8b14-42e4-952a-2e1da985ffbc", + "uuid": "4389eb57-8b14-42e4-952a-2e1da985ffbc", "eventId": "GRB 230808A" }, { - "synonymId": "4389eb57-8b14-42e4-952a-2e1da985ffbc", + "uuid": "4389eb57-8b14-42e4-952a-2e1da985ffbc", "eventId": "GRB 230916.18" }, { - "synonymId": "d0fb4eb0-9f75-43b6-ab57-cce09f0a330a", + "uuid": "d0fb4eb0-9f75-43b6-ab57-cce09f0a330a", "eventId": "GRB 230817A" }, { - "synonymId": "d0fb4eb0-9f75-43b6-ab57-cce09f0a330a", + "uuid": "d0fb4eb0-9f75-43b6-ab57-cce09f0a330a", "eventId": "GRB 230916.19" }, { - "synonymId": "70b23e5f-3a50-47ae-ab04-3fcb628f3419", + "uuid": "70b23e5f-3a50-47ae-ab04-3fcb628f3419", "eventId": "GRB 230820.82" }, { - "synonymId": "70b23e5f-3a50-47ae-ab04-3fcb628f3419", + "uuid": "70b23e5f-3a50-47ae-ab04-3fcb628f3419", "eventId": "GRB 230910A" }, { - "synonymId": "b47ee2bd-a230-4054-a109-59731cdcba2a", + "uuid": "b47ee2bd-a230-4054-a109-59731cdcba2a", "eventId": "GRB 230816.67" }, { - "synonymId": "b47ee2bd-a230-4054-a109-59731cdcba2a", + "uuid": "b47ee2bd-a230-4054-a109-59731cdcba2a", "eventId": "GRB 230820.81" }, { - "synonymId": "d015209c-9750-47c6-b8e9-180ae8a85f01", + "uuid": "d015209c-9750-47c6-b8e9-180ae8a85f01", "eventId": "GRB 230808B" }, { - "synonymId": "d015209c-9750-47c6-b8e9-180ae8a85f01", + "uuid": "d015209c-9750-47c6-b8e9-180ae8a85f01", "eventId": "ZTF23aaoohpy" }, { - "synonymId": "1c340fe1-b776-4318-84c6-5472597339f0", + "uuid": "1c340fe1-b776-4318-84c6-5472597339f0", "eventId": "GRB 230731A" }, { - "synonymId": "1c340fe1-b776-4318-84c6-5472597339f0", + "uuid": "1c340fe1-b776-4318-84c6-5472597339f0", "eventId": "GRB 230815B" }, { - "synonymId": "58867707-d4e8-41f2-83e8-eb547901d365", + "uuid": "58867707-d4e8-41f2-83e8-eb547901d365", "eventId": "GRB 230915B" }, { - "synonymId": "58867707-d4e8-41f2-83e8-eb547901d365", + "uuid": "58867707-d4e8-41f2-83e8-eb547901d365", "eventId": "GRB 230818.98" }, { - "synonymId": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", + "uuid": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", "eventId": "GRB 230916.13" }, { - "synonymId": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", + "uuid": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", "eventId": "GRB 230816.80" }, { - "synonymId": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", + "uuid": "ee2aabed-a4f9-410b-9f48-681c1adcbb78", "eventId": "GRB 230822A" }, { - "synonymId": "1b669a28-08ff-47f8-a3ae-b2572007ca99", + "uuid": "1b669a28-08ff-47f8-a3ae-b2572007ca99", "eventId": "GRB 230825.85" }, { - "synonymId": "1b669a28-08ff-47f8-a3ae-b2572007ca99", + "uuid": "1b669a28-08ff-47f8-a3ae-b2572007ca99", "eventId": "GRB 230820A" }, { - "synonymId": "be25482a-dd6a-4963-8952-11953dfa7b0c", + "uuid": "be25482a-dd6a-4963-8952-11953dfa7b0c", "eventId": "GRB 230821.64" }, { - "synonymId": "be25482a-dd6a-4963-8952-11953dfa7b0c", + "uuid": "be25482a-dd6a-4963-8952-11953dfa7b0c", "eventId": "GRB 230824B" }, { - "synonymId": "ff7ff6ea-0106-47bb-8ce0-600ebefdecfd", + "uuid": "ff7ff6ea-0106-47bb-8ce0-600ebefdecfd", "eventId": "GRB 230205A" }, { - "synonymId": "ff7ff6ea-0106-47bb-8ce0-600ebefdecfd", + "uuid": "ff7ff6ea-0106-47bb-8ce0-600ebefdecfd", "eventId": "LIGO/Virgo S230917af" }, { - "synonymId": "4d073e7c-7d58-4139-8e89-e058fc083423", + "uuid": "4d073e7c-7d58-4139-8e89-e058fc083423", "eventId": "GRB 230825.75" }, { - "synonymId": "4d073e7c-7d58-4139-8e89-e058fc083423", + "uuid": "4d073e7c-7d58-4139-8e89-e058fc083423", "eventId": "GRB 230916.12" }, { - "synonymId": "a3afaf30-bcc7-4f00-8048-6dcde44039aa", + "uuid": "a3afaf30-bcc7-4f00-8048-6dcde44039aa", "eventId": "GRB 230723B" }, { - "synonymId": "a3afaf30-bcc7-4f00-8048-6dcde44039aa", + "uuid": "a3afaf30-bcc7-4f00-8048-6dcde44039aa", "eventId": "GRB 230824.58" }, { - "synonymId": "6ffdc88b-9817-4318-8aaa-14b52794fb5d", + "uuid": "6ffdc88b-9817-4318-8aaa-14b52794fb5d", "eventId": "GRB 230916.11" }, { - "synonymId": "6ffdc88b-9817-4318-8aaa-14b52794fb5d", + "uuid": "6ffdc88b-9817-4318-8aaa-14b52794fb5d", "eventId": "GRB 230903.72" }, { - "synonymId": "9ca71208-0a81-47b0-88a0-88d103df330e", + "uuid": "9ca71208-0a81-47b0-88a0-88d103df330e", "eventId": "GRB 230811D" }, { - "synonymId": "9ca71208-0a81-47b0-88a0-88d103df330e", + "uuid": "9ca71208-0a81-47b0-88a0-88d103df330e", "eventId": "GRB 230916A" }, { - "synonymId": "3702f560-30fa-4aa8-88f0-d27205fd23c6", + "uuid": "3702f560-30fa-4aa8-88f0-d27205fd23c6", "eventId": "GRB 230905A" }, { - "synonymId": "3702f560-30fa-4aa8-88f0-d27205fd23c6", + "uuid": "3702f560-30fa-4aa8-88f0-d27205fd23c6", "eventId": "LIGO/Virgo S230802aq" }, { - "synonymId": "62fe8590-42e4-4917-afea-db6a0a84079a", + "uuid": "62fe8590-42e4-4917-afea-db6a0a84079a", "eventId": "GRB 230826.81" }, { - "synonymId": "62fe8590-42e4-4917-afea-db6a0a84079a", + "uuid": "62fe8590-42e4-4917-afea-db6a0a84079a", "eventId": "GRB 230812A" } ], From b1774430b4f9c17712c5139dde2ee3e53f1ca932 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Thu, 4 Jan 2024 17:34:55 -0500 Subject: [PATCH 08/26] Add Markdown editing and rendering support for Circulars This is currently hidden behind a feature flag until we have documentation and announcements for it. --- .../Body.tsx | 10 +- .../PlainTextBody.module.scss | 2 +- .../route.tsx | 10 +- .../_gcn.circulars._archive._index/route.tsx | 26 +- .../CircularEditForm.tsx | 90 ++-- .../RichEditor/GitLabIcon.module.scss | 15 + .../RichEditor/GitLabIcon.tsx | 28 ++ .../RichEditor/Tabs.module.scss | 35 ++ .../RichEditor/Tabs.tsx | 52 ++ .../RichEditor/exclusiveGroup.tsx | 40 ++ .../RichEditor/index.module.scss | 68 +++ .../RichEditor/index.tsx | 443 ++++++++++++++++++ .../_gcn.circulars.edit.$circularId/route.tsx | 1 + app/routes/_gcn.circulars.new.tsx | 6 + app/routes/_gcn.circulars/circulars.lib.ts | 4 + app/routes/_gcn.circulars/circulars.server.ts | 33 +- app/theme.scss | 10 + package-lock.json | 96 +--- package.json | 5 +- remix.config.js | 6 + 20 files changed, 843 insertions(+), 137 deletions(-) create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/GitLabIcon.module.scss create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/GitLabIcon.tsx create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/Tabs.module.scss create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/Tabs.tsx create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/exclusiveGroup.tsx create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/index.module.scss create mode 100644 app/routes/_gcn.circulars.edit.$circularId/RichEditor/index.tsx diff --git a/app/routes/_gcn.circulars.$circularId.($version)/Body.tsx b/app/routes/_gcn.circulars.$circularId.($version)/Body.tsx index f0779500d..7c11069ba 100644 --- a/app/routes/_gcn.circulars.$circularId.($version)/Body.tsx +++ b/app/routes/_gcn.circulars.$circularId.($version)/Body.tsx @@ -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' @@ -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, diff --git a/app/routes/_gcn.circulars.$circularId.($version)/PlainTextBody.module.scss b/app/routes/_gcn.circulars.$circularId.($version)/PlainTextBody.module.scss index 4fdf63545..8e0ca85b4 100644 --- a/app/routes/_gcn.circulars.$circularId.($version)/PlainTextBody.module.scss +++ b/app/routes/_gcn.circulars.$circularId.($version)/PlainTextBody.module.scss @@ -8,7 +8,7 @@ } & pre, - &code { + & code { margin: 0; } } diff --git a/app/routes/_gcn.circulars.$circularId.($version)/route.tsx b/app/routes/_gcn.circulars.$circularId.($version)/route.tsx index e87b1b6fb..3f638503b 100644 --- a/app/routes/_gcn.circulars.$circularId.($version)/route.tsx +++ b/app/routes/_gcn.circulars.$circularId.($version)/route.tsx @@ -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' @@ -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() const searchString = useSearchString() + const Body = + useFeature('CIRCULARS_MARKDOWN') && format === 'text/markdown' + ? MarkdownBody + : PlainTextBody const result = useRouteLoaderData( 'routes/_gcn.circulars.$circularId' @@ -141,7 +145,7 @@ export default function () {

GCN Circular {circularId}

- {body} + {body} ) } diff --git a/app/routes/_gcn.circulars._archive._index/route.tsx b/app/routes/_gcn.circulars._archive._index/route.tsx index 45df2863d..8cc346e20 100644 --- a/app/routes/_gcn.circulars._archive._index/route.tsx +++ b/app/routes/_gcn.circulars._archive._index/route.tsx @@ -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, @@ -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' @@ -74,16 +79,30 @@ 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': @@ -91,16 +110,15 @@ export async function action({ request }: ActionFunctionArgs) { 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 diff --git a/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx b/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx index f7d2b0c9c..e4b4a0721 100644 --- a/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx +++ b/app/routes/_gcn.circulars.edit.$circularId/CircularEditForm.tsx @@ -17,8 +17,7 @@ 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' @@ -26,13 +25,19 @@ 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, @@ -107,6 +112,7 @@ export function CircularEditForm({ formattedContributor, circularId, submitter, + defaultFormat, defaultBody, defaultSubject, searchString, @@ -115,6 +121,7 @@ export function CircularEditForm({ formattedContributor: string circularId?: number submitter?: string + defaultFormat?: CircularFormat defaultBody: string defaultSubject: string searchString: string @@ -151,6 +158,7 @@ export function CircularEditForm({ saveButtonText = 'Send' break } + const bodyPlaceholder = useBodyPlaceholder() return ( @@ -225,36 +233,52 @@ export function CircularEditForm({ - - setShowPreview(false)} - > - Edit - - setShowPreview(true)}> - Preview - - -