add descriptions to closet lists (formatted! :3)
This commit is contained in:
parent
4e00962edc
commit
15f10c615b
4 changed files with 108 additions and 0 deletions
|
@ -20,6 +20,7 @@
|
|||
"apollo-server-env": "^2.4.3",
|
||||
"aws-sdk": "^2.726.0",
|
||||
"dataloader": "^2.0.0",
|
||||
"dompurify": "^2.2.0",
|
||||
"emotion": "^10.0.27",
|
||||
"graphql": "^15.0.0",
|
||||
"honeycomb-beeline": "^2.2.0",
|
||||
|
@ -36,6 +37,7 @@
|
|||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"simple-markdown": "^0.7.2",
|
||||
"xmlrpc": "^1.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React from "react";
|
||||
import { css } from "emotion";
|
||||
import { Badge, Box, Center, Wrap, VStack } from "@chakra-ui/core";
|
||||
import { CheckIcon, EmailIcon, StarIcon } from "@chakra-ui/icons";
|
||||
import gql from "graphql-tag";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import SimpleMarkdown from "simple-markdown";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
import HangerSpinner from "./components/HangerSpinner";
|
||||
import { Heading1, Heading2, Heading3 } from "./util";
|
||||
|
@ -35,6 +38,7 @@ function UserItemsPage() {
|
|||
closetLists {
|
||||
id
|
||||
name
|
||||
description
|
||||
ownsOrWantsItems
|
||||
isDefaultList
|
||||
items {
|
||||
|
@ -237,6 +241,11 @@ function ClosetList({ closetList, isCurrentUser, showHeading }) {
|
|||
{closetList.name}
|
||||
</Heading3>
|
||||
)}
|
||||
{closetList.description && (
|
||||
<Box marginBottom="2">
|
||||
<MarkdownAndSafeHTML>{closetList.description}</MarkdownAndSafeHTML>
|
||||
</Box>
|
||||
)}
|
||||
{sortedItems.length > 0 ? (
|
||||
<ItemCardList>
|
||||
{sortedItems.map((item) => (
|
||||
|
@ -264,6 +273,77 @@ function ClosetList({ closetList, isCurrentUser, showHeading }) {
|
|||
);
|
||||
}
|
||||
|
||||
const unsafeMarkdownRules = {
|
||||
autolink: SimpleMarkdown.defaultRules.autolink,
|
||||
br: SimpleMarkdown.defaultRules.br,
|
||||
em: SimpleMarkdown.defaultRules.em,
|
||||
escape: SimpleMarkdown.defaultRules.escape,
|
||||
link: SimpleMarkdown.defaultRules.link,
|
||||
list: SimpleMarkdown.defaultRules.list,
|
||||
newline: SimpleMarkdown.defaultRules.newline,
|
||||
paragraph: SimpleMarkdown.defaultRules.paragraph,
|
||||
strong: SimpleMarkdown.defaultRules.strong,
|
||||
u: SimpleMarkdown.defaultRules.u,
|
||||
|
||||
// DANGER: We override Markdown's `text` rule to _not_ escape HTML. This is
|
||||
// intentional, to allow users to embed some limited HTML. DOMPurify is
|
||||
// responsible for sanitizing the HTML afterward. Do not use these rules
|
||||
// without sanitizing!!
|
||||
text: {
|
||||
...SimpleMarkdown.defaultRules.text,
|
||||
html: (node) => node.content,
|
||||
},
|
||||
};
|
||||
const markdownParser = SimpleMarkdown.parserFor(unsafeMarkdownRules);
|
||||
const unsafeMarkdownOutput = SimpleMarkdown.htmlFor(
|
||||
SimpleMarkdown.ruleOutput(unsafeMarkdownRules, "html")
|
||||
);
|
||||
|
||||
function MarkdownAndSafeHTML({ children }) {
|
||||
const htmlAndMarkdown = children;
|
||||
|
||||
const unsafeHtml = unsafeMarkdownOutput(markdownParser(htmlAndMarkdown));
|
||||
|
||||
const sanitizedHtml = DOMPurify.sanitize(unsafeHtml, {
|
||||
ALLOWED_TAGS: [
|
||||
"b",
|
||||
"i",
|
||||
"u",
|
||||
"strong",
|
||||
"em",
|
||||
"a",
|
||||
"p",
|
||||
"div",
|
||||
"br",
|
||||
"ol",
|
||||
"ul",
|
||||
"li",
|
||||
],
|
||||
ALLOWED_ATTR: ["href", "class"],
|
||||
// URL must either start with an approved host (external link), or with a
|
||||
// slash or hash (internal link).
|
||||
ALLOWED_URI_REGEXP: /^https?:\/\/(impress\.openneo\.net|impress-2020\.openneo\.net|www\.neopets\.com|neopets\.com)\/|^[\/#]/,
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
dangerouslySetInnerHTML={{ __html: sanitizedHtml }}
|
||||
className={css`
|
||||
.paragraph,
|
||||
ol,
|
||||
ul {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
margin-left: 2em;
|
||||
}
|
||||
`}
|
||||
></Box>
|
||||
);
|
||||
}
|
||||
|
||||
function NeopetsStarIcon(props) {
|
||||
// Converted from the Neopets favicon with https://www.vectorizer.io/.
|
||||
return (
|
||||
|
|
|
@ -21,6 +21,9 @@ const typeDefs = gql`
|
|||
id: ID!
|
||||
name: String
|
||||
|
||||
# A user-customized description. May contain Markdown and limited HTML.
|
||||
description: String
|
||||
|
||||
# Whether this is a list of items they own, or items they want.
|
||||
ownsOrWantsItems: OwnsOrWants!
|
||||
|
||||
|
@ -158,6 +161,7 @@ const resolvers = {
|
|||
.map((closetList) => ({
|
||||
id: closetList.id,
|
||||
name: closetList.name,
|
||||
description: closetList.description,
|
||||
ownsOrWantsItems: closetList.hangersOwned ? "OWNS" : "WANTS",
|
||||
isDefaultList: false,
|
||||
items: allClosetHangers
|
||||
|
@ -169,6 +173,7 @@ const resolvers = {
|
|||
closetListNodes.push({
|
||||
id: `user-${id}-default-list-OWNS`,
|
||||
name: "Not in a list",
|
||||
description: null,
|
||||
ownsOrWantsItems: "OWNS",
|
||||
isDefaultList: true,
|
||||
items: allClosetHangers
|
||||
|
@ -181,6 +186,7 @@ const resolvers = {
|
|||
closetListNodes.push({
|
||||
id: `user-${id}-default-list-WANTS`,
|
||||
name: "Not in a list",
|
||||
description: null,
|
||||
ownsOrWantsItems: "WANTS",
|
||||
isDefaultList: true,
|
||||
items: allClosetHangers
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -4694,6 +4694,14 @@
|
|||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@>=16.0.0":
|
||||
version "16.9.54"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.54.tgz#140280c31825287ee74e9da95285b91c5a2e471d"
|
||||
integrity sha512-GhawhYraQZpGFO2hVMArjPrYbnA/6+DS8SubK8IPhhVClmKqANihsRenOm5E0mvqK0m/BKoqVktA1O1+Xvlz9w==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/reactcss@*":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834"
|
||||
|
@ -8327,6 +8335,11 @@ domhandler@^2.3.0:
|
|||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
dompurify@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.0.tgz#51d34e76faa38b5d6b4e83a0678530f27fe3965c"
|
||||
integrity sha512-bqFOQ7XRmmozp0VsKdIEe8UwZYxj0yttz7l80GBtBqdVRY48cOpXH2J/CVO7AEkV51qY0EBVXfilec18mdmQ/w==
|
||||
|
||||
domutils@1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
||||
|
@ -16302,6 +16315,13 @@ signal-exit@^3.0.0, signal-exit@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
||||
|
||||
simple-markdown@^0.7.2:
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/simple-markdown/-/simple-markdown-0.7.2.tgz#896cc3e3dd9acd068d30e696bce70b0b97655665"
|
||||
integrity sha512-XfCvqqzMyzRj4L7eIxJgGaQ2Gaxr20GhTFMB+1yuY8q3xffjzmOg4Q5tC0kcaJPV42NNUHCQDaRK6jzi3/RhrA==
|
||||
dependencies:
|
||||
"@types/react" ">=16.0.0"
|
||||
|
||||
simple-swizzle@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
||||
|
|
Loading…
Reference in a new issue