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",
|
"apollo-server-env": "^2.4.3",
|
||||||
"aws-sdk": "^2.726.0",
|
"aws-sdk": "^2.726.0",
|
||||||
"dataloader": "^2.0.0",
|
"dataloader": "^2.0.0",
|
||||||
|
"dompurify": "^2.2.0",
|
||||||
"emotion": "^10.0.27",
|
"emotion": "^10.0.27",
|
||||||
"graphql": "^15.0.0",
|
"graphql": "^15.0.0",
|
||||||
"honeycomb-beeline": "^2.2.0",
|
"honeycomb-beeline": "^2.2.0",
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "3.4.1",
|
||||||
"react-transition-group": "^4.3.0",
|
"react-transition-group": "^4.3.0",
|
||||||
|
"simple-markdown": "^0.7.2",
|
||||||
"xmlrpc": "^1.3.2"
|
"xmlrpc": "^1.3.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { css } from "emotion";
|
||||||
import { Badge, Box, Center, Wrap, VStack } from "@chakra-ui/core";
|
import { Badge, Box, Center, Wrap, VStack } from "@chakra-ui/core";
|
||||||
import { CheckIcon, EmailIcon, StarIcon } from "@chakra-ui/icons";
|
import { CheckIcon, EmailIcon, StarIcon } from "@chakra-ui/icons";
|
||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
|
import SimpleMarkdown from "simple-markdown";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
|
||||||
import HangerSpinner from "./components/HangerSpinner";
|
import HangerSpinner from "./components/HangerSpinner";
|
||||||
import { Heading1, Heading2, Heading3 } from "./util";
|
import { Heading1, Heading2, Heading3 } from "./util";
|
||||||
|
@ -35,6 +38,7 @@ function UserItemsPage() {
|
||||||
closetLists {
|
closetLists {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
description
|
||||||
ownsOrWantsItems
|
ownsOrWantsItems
|
||||||
isDefaultList
|
isDefaultList
|
||||||
items {
|
items {
|
||||||
|
@ -237,6 +241,11 @@ function ClosetList({ closetList, isCurrentUser, showHeading }) {
|
||||||
{closetList.name}
|
{closetList.name}
|
||||||
</Heading3>
|
</Heading3>
|
||||||
)}
|
)}
|
||||||
|
{closetList.description && (
|
||||||
|
<Box marginBottom="2">
|
||||||
|
<MarkdownAndSafeHTML>{closetList.description}</MarkdownAndSafeHTML>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{sortedItems.length > 0 ? (
|
{sortedItems.length > 0 ? (
|
||||||
<ItemCardList>
|
<ItemCardList>
|
||||||
{sortedItems.map((item) => (
|
{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) {
|
function NeopetsStarIcon(props) {
|
||||||
// Converted from the Neopets favicon with https://www.vectorizer.io/.
|
// Converted from the Neopets favicon with https://www.vectorizer.io/.
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -21,6 +21,9 @@ const typeDefs = gql`
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
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.
|
# Whether this is a list of items they own, or items they want.
|
||||||
ownsOrWantsItems: OwnsOrWants!
|
ownsOrWantsItems: OwnsOrWants!
|
||||||
|
|
||||||
|
@ -158,6 +161,7 @@ const resolvers = {
|
||||||
.map((closetList) => ({
|
.map((closetList) => ({
|
||||||
id: closetList.id,
|
id: closetList.id,
|
||||||
name: closetList.name,
|
name: closetList.name,
|
||||||
|
description: closetList.description,
|
||||||
ownsOrWantsItems: closetList.hangersOwned ? "OWNS" : "WANTS",
|
ownsOrWantsItems: closetList.hangersOwned ? "OWNS" : "WANTS",
|
||||||
isDefaultList: false,
|
isDefaultList: false,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
@ -169,6 +173,7 @@ const resolvers = {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-OWNS`,
|
id: `user-${id}-default-list-OWNS`,
|
||||||
name: "Not in a list",
|
name: "Not in a list",
|
||||||
|
description: null,
|
||||||
ownsOrWantsItems: "OWNS",
|
ownsOrWantsItems: "OWNS",
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
@ -181,6 +186,7 @@ const resolvers = {
|
||||||
closetListNodes.push({
|
closetListNodes.push({
|
||||||
id: `user-${id}-default-list-WANTS`,
|
id: `user-${id}-default-list-WANTS`,
|
||||||
name: "Not in a list",
|
name: "Not in a list",
|
||||||
|
description: null,
|
||||||
ownsOrWantsItems: "WANTS",
|
ownsOrWantsItems: "WANTS",
|
||||||
isDefaultList: true,
|
isDefaultList: true,
|
||||||
items: allClosetHangers
|
items: allClosetHangers
|
||||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -4694,6 +4694,14 @@
|
||||||
"@types/prop-types" "*"
|
"@types/prop-types" "*"
|
||||||
csstype "^3.0.2"
|
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@*":
|
"@types/reactcss@*":
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834"
|
resolved "https://registry.yarnpkg.com/@types/reactcss/-/reactcss-1.2.3.tgz#af28ae11bbb277978b99d04d1eedfd068ca71834"
|
||||||
|
@ -8327,6 +8335,11 @@ domhandler@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
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:
|
domutils@1.5.1:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
|
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"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||||
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
|
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:
|
simple-swizzle@^0.2.2:
|
||||||
version "0.2.2"
|
version "0.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
|
||||||
|
|
Loading…
Reference in a new issue