diff --git a/packages/website/components/netlifyPartial.js b/packages/website/components/netlifyPartial.js index 7a2ba4e16b..ae97ccceff 100644 --- a/packages/website/components/netlifyPartial.js +++ b/packages/website/components/netlifyPartial.js @@ -1,33 +1,119 @@ import { MDXRemote } from 'next-mdx-remote' -import { useState, useEffect } from 'react' +import React, { useState, useEffect } from 'react' import Loading from './loading' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' + +/* + * There are two ways we can load content from the CMS as a partial + * + * Compiled Content + * This takes compiled JSX from the CMS and plugs it into the component as is. + * + * Templates with Metadata and Compiled Content + * For more complex content that needs more control and styling, we can use a template while passing in + * metadata from the frontmatter section of the CMS markdown into a custom React component. + * + * Template Naming Convention + * The template name corresponds to the content type from the CMS. For the image-grids content + * found at the api URI of https://blog.nft.storage/api/partials/image-grids/my-md-content, + * the template name would be 'image-grids' and should be added to the templates object + * as templates['image-grids']. Other content type examples could be + * templates['faq'], templates['my-new-type'] + * + * All data from the frontmatter can be found in the meta property as well as the + * main body content that can be found in meta.body. The body content can be + * rendered like + */ + +/** + * Templates + * @template {Object} T + * @param {T} obj + */ + +/** @type {any} */ +const templates = {} +/** + * Image Grid Template + * @param {Object} meta + */ +templates['image-grids'] = function ( + /** @type {{ + description: String | undefined; + images: []; + footer: String | undefined +}} */ meta +) { + return ( +
+ {meta.description && ( + + )} +
+ {meta.images.map( + (/** @type {{ src: string; alt: string | undefined; }} */ image) => ( + {image.alt} + ) + )} +
+ {meta.footer && ( + + )} + {/* For future reference, you can render main body content like so */} + {/* + {meta.body && + + } + */} +
+ ) +} /** * @typedef {Object} NetlifyPartialProps * @prop {string} [route] + * @prop {string} [template] * @prop {string} [className] * @prop {JSX.Element} [fallback] - */ - -/** * * @param {NetlifyPartialProps} props * @returns {JSX.Element} */ -export default function NetlifyPartial({ route, className, fallback }) { +export default function NetlifyPartial({ + route, + template, + className, + fallback, +}) { /** @type [any, null | any] */ const [content, setContent] = useState() + /** @type [any, null | any] */ + const [meta, setMeta] = useState() const [error, setError] = useState(false) useEffect(() => { - // TODO: Update fallback when we have the blog in production. - const host = - process.env.NEXT_PUBLIC_NETLIFY_CMS_ENDPOINT || 'https://blog.nft.storage' + const host = process.env.NEXT_PUBLIC_NETLIFY_CMS_ENDPOINT fetch(`${host}/api/partials/${route}`) .then(async (response) => { return await response.text() }) .then((text) => { const obj = JSON.parse(text) + setMeta(obj.props.partial.meta) setContent(obj.props.partial.content) }) .catch((e) => { @@ -41,7 +127,7 @@ export default function NetlifyPartial({ route, className, fallback }) { } return (
-

An unexpected error occured.

+

An unexpected error occurred.

) } @@ -54,6 +140,17 @@ export default function NetlifyPartial({ route, className, fallback }) { ) } + { + if (meta && template && templates[template]) { + let TemplateComponent = templates[template] + return ( +
+ +
+ ) + } + } + return ( content && (
diff --git a/packages/website/package.json b/packages/website/package.json index 254753436d..1e391f1b3c 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -37,6 +37,7 @@ "react-dom": "17.0.2", "react-icons": "^4.3.1", "react-if": "4.0.1", + "react-markdown": "^8.0.4", "react-query": "^3.34.15", "react-tiny-popover": "^7.0.1", "swagger-ui-react": "^4.1.3", diff --git a/packages/website/pages/stats.js b/packages/website/pages/stats.js index 1e98e4b10a..fabe906557 100644 --- a/packages/website/pages/stats.js +++ b/packages/website/pages/stats.js @@ -384,6 +384,12 @@ export default function Stats({ logos }) { className="netlify-partial-trusted-by-stats-page max-w-4xl mx-auto py-8 px-6 sm:px-16 text-center chicagoflf" fallback={} /> + {/* } + /> */}
diff --git a/yarn.lock b/yarn.lock index 3e77d7ff7c..881955fa19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4875,7 +4875,7 @@ resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz#72a26101dc567b0d68fd956cf42314556e42d601" integrity sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ== -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.0.0": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== @@ -16834,16 +16834,11 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@2.5.1, prettier@^2.5.1: +prettier@2.5.1, "prettier@>=2.2.1 <=2.3.0", prettier@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== -"prettier@>=2.2.1 <=2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" - integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== - pretty-error@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" @@ -17384,6 +17379,27 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-markdown@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.4.tgz#b5ff1f0f29ead71a7a6f98815eb1a70bcc2a036e" + integrity sha512-2oxHa6oDxc1apg/Gnc1Goh06t3B617xeywqI/92wmDV9FELI6ayRkwge7w7DoEqM0gRpZGTNU6xQG+YpJISnVg== + dependencies: + "@types/hast" "^2.0.0" + "@types/prop-types" "^15.0.0" + "@types/unist" "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^2.0.0" + prop-types "^15.0.0" + property-information "^6.0.0" + react-is "^18.0.0" + remark-parse "^10.0.0" + remark-rehype "^10.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.3.0" + unified "^10.0.0" + unist-util-visit "^4.0.0" + vfile "^5.0.0" + react-merge-refs@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" @@ -20016,12 +20032,7 @@ typedoc@^0.22.14: minimatch "^5.1.0" shiki "^0.10.1" -typescript@4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== - -typescript@4.5.3: +typescript@4.4.4, typescript@4.5.3: version "4.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.3.tgz#afaa858e68c7103317d89eb90c5d8906268d353c" integrity sha512-eVYaEHALSt+s9LbvgEv4Ef+Tdq7hBiIZgii12xXJnukryt3pMgJf6aKhoCZ3FWQsu6sydEnkg11fYXLzhLBjeQ==