From da0fbaa91f574d830b23c2f201f7d66625ad6cba Mon Sep 17 00:00:00 2001 From: John SJ Anderson Date: Thu, 31 Oct 2024 10:42:55 -0700 Subject: [PATCH 1/7] update marked from 0.7 to 14.1.3 [#134] The need for this update was mainly to get type information for `marked`; version 14.1.3 was current on the day when I made this change. (The library releases and updates versions _frequently_.) I did not carefully review the five years worth of changelogs between 0.7 and 14.1.3, but empirically the API doesn't seem to have changed and the code still works. --- package-lock.json | 19 ++++++++++--------- package.json | 2 +- static-site/src/util/parseMarkdown.js | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72891b173..4028fb901 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "luxon": "^3.4.4", "make-fetch-happen": "^11.1.1", "mapbox-gl": "^3.2.0", - "marked": "^0.7.0", + "marked": "^14.1.3", "mime": "^2.5.2", "neat-csv": "^7.0.0", "negotiator": "^0.6.2", @@ -23233,14 +23233,15 @@ } }, "node_modules/marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", + "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==", + "license": "MIT", "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">=0.10.0" + "node": ">= 18" } }, "node_modules/media-typer": { @@ -46889,9 +46890,9 @@ } }, "marked": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", - "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==" + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz", + "integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==" }, "media-typer": { "version": "0.3.0", diff --git a/package.json b/package.json index 901c06c59..71a2d9494 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "luxon": "^3.4.4", "make-fetch-happen": "^11.1.1", "mapbox-gl": "^3.2.0", - "marked": "^0.7.0", + "marked": "^14.1.3", "mime": "^2.5.2", "neat-csv": "^7.0.0", "negotiator": "^0.6.2", diff --git a/static-site/src/util/parseMarkdown.js b/static-site/src/util/parseMarkdown.js index e72baa9ce..c738c4d40 100644 --- a/static-site/src/util/parseMarkdown.js +++ b/static-site/src/util/parseMarkdown.js @@ -1,4 +1,4 @@ -import marked from "marked"; +import { marked } from "marked"; import sanitizeHtml from 'sanitize-html'; // All of these tags may not be necessary, this list was adopted from https://github.com/nextstrain/auspice/blob/master/src/util/parseMarkdown.js From 43a9b2d87e4922adabc5a38a1e2e6b4594c57453 Mon Sep 17 00:00:00 2001 From: John SJ Anderson Date: Thu, 31 Oct 2024 10:43:27 -0700 Subject: [PATCH 2/7] Add @types/sanitize-html [#134] --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index 4028fb901..926b26e1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@keyv/sqlite": "^3.6.6", "@renovatebot/pep440": "^2.1.20", "@smithy/node-http-handler": "^2.1.8", + "@types/sanitize-html": "^2.13.0", "argparse": "^1.0.10", "aws-sdk": "^2.908.0", "chalk": "^2.4.1", @@ -13055,6 +13056,15 @@ "@types/react": "*" } }, + "node_modules/@types/sanitize-html": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==", + "license": "MIT", + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -39186,6 +39196,14 @@ "@types/react": "*" } }, + "@types/sanitize-html": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==", + "requires": { + "htmlparser2": "^8.0.0" + } + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", diff --git a/package.json b/package.json index 71a2d9494..6de3d7c74 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@keyv/sqlite": "^3.6.6", "@renovatebot/pep440": "^2.1.20", "@smithy/node-http-handler": "^2.1.8", + "@types/sanitize-html": "^2.13.0", "argparse": "^1.0.10", "aws-sdk": "^2.908.0", "chalk": "^2.4.1", From fcfd141186d144ac4d52225e38ef7c39470a88ac Mon Sep 17 00:00:00 2001 From: John SJ Anderson Date: Thu, 31 Oct 2024 11:29:14 -0700 Subject: [PATCH 3/7] Convert blog to App Router [#134] Deletes old Pages Router blog files, as well as displayMarkdown component. --- static-site/app/blog/[id]/page.tsx | 77 +++++++ static-site/app/blog/[id]/styles.module.css | 71 +++++++ static-site/app/blog/page.tsx | 17 ++ static-site/app/blog/parseMarkdown.ts | 89 +++++++++ static-site/app/blog/utils.ts | 72 +++++++ static-site/pages/blog.jsx | 32 --- static-site/pages/blog/[id].jsx | 47 ----- static-site/src/templates/displayMarkdown.jsx | 189 ------------------ static-site/src/util/blogPosts.js | 9 + static-site/src/util/parseMarkdown.js | 10 + 10 files changed, 345 insertions(+), 268 deletions(-) create mode 100644 static-site/app/blog/[id]/page.tsx create mode 100644 static-site/app/blog/[id]/styles.module.css create mode 100644 static-site/app/blog/page.tsx create mode 100644 static-site/app/blog/parseMarkdown.ts create mode 100644 static-site/app/blog/utils.ts delete mode 100644 static-site/pages/blog.jsx delete mode 100644 static-site/pages/blog/[id].jsx delete mode 100644 static-site/src/templates/displayMarkdown.jsx diff --git a/static-site/app/blog/[id]/page.tsx b/static-site/app/blog/[id]/page.tsx new file mode 100644 index 000000000..3b0807526 --- /dev/null +++ b/static-site/app/blog/[id]/page.tsx @@ -0,0 +1,77 @@ +import { redirect } from "next/navigation"; +import React from "react"; + +import { BigSpacer } from "../../../components/spacers"; + +import { getBlogPosts, markdownToHtml } from "../utils"; + +import styles from "./styles.module.css"; + +// just to avoid having to repeat this in a couple method sigs... +interface BlogPostParams { + id: string; +} + +// return a list of params that will get handed to this page at build +// time, to statically build out all the blog posts +export function generateStaticParams(): BlogPostParams[] { + return getBlogPosts().map((post) => { + return { id: post.blogUrlName }; + }); +} + +export default async function BlogPost({ + params, +}: { + params: BlogPostParams; +}): Promise { + const { id } = params; + + // we need this list to build the archive list in the sidebar + const allBlogPosts = getBlogPosts(); + + // and then this is the specific post we're rendering + const blogPost = allBlogPosts.find((post) => post.blogUrlName === id); + + // if for some reason we didn't find the post, 404 on out + if (!blogPost) { + redirect("/404"); + } + + const html = await markdownToHtml(blogPost.mdstring); + + return ( + <> + + +
+
+
+ +

{blogPost.title}

+

{blogPost.author}

+
+
+
+
+

Blog Archives

+ +
+
+
+ + ); +} diff --git a/static-site/app/blog/[id]/styles.module.css b/static-site/app/blog/[id]/styles.module.css new file mode 100644 index 000000000..42ce1572e --- /dev/null +++ b/static-site/app/blog/[id]/styles.module.css @@ -0,0 +1,71 @@ +.blogPostTitle { + clear: both; /* this is to let the title fall under the floated date, not under it */ + color: black; + font-size: 3.5rem; + font-weight: 400; + line-height: 40px; + text-align: left; + width: 100%; +} + +.blogPostAuthor { + color: black; + font-size: 2rem; + font-weight: 300; + margin: 1rem 0 2rem; +} + +.blogPostDate { + color: black; + float: right; + font-size: 1.4rem; + font-weight: 300; + min-height: 2rem; +} + +.blogPostBody { + color: black; + font-size: 1.6rem; + font-weight: 300; + line-height: var(--niceLineHeight); + margin-top: 0px; + padding-bottom: 25px; + width: 100%; +} + +.blogPostBody img { + max-width: 100%; +} + +.blogPostBody h1 { + color: black; + font-size: 3rem; + margin-top: 20px; + text-align: left; +} +.blogPostBody h2 { + font-size: 2.4rem; + font-weight: 300; + margin-top: 10px; +} +.blogPostBody h3 { + font-size: 1.8rem; + font-weight: 300; + margin-top: 10px; +} +.blogPostBody p { + margin-top: 10px; +} +.blogPostBody li { + margin-left: 3rem; +} + +.blogSidebar { + font-size: 14px; +} +.blogSidebar ul { + list-style: none; +} +.blogSidebar ul li { + margin: 1.2rem 0; +} diff --git a/static-site/app/blog/page.tsx b/static-site/app/blog/page.tsx new file mode 100644 index 000000000..a0693b884 --- /dev/null +++ b/static-site/app/blog/page.tsx @@ -0,0 +1,17 @@ +import { redirect } from "next/navigation"; + +import { getBlogPosts } from "./utils"; + +export default function Index(): void { + const mostRecentPost = getBlogPosts()[0]; + + // _technically_ getBlogPosts() could return an empty array and then + // mostRecentPost would be undefined -- to make the type checker + // happy, if for some reason mostRecentPost is undefined, we will + // detect that and redirect to the 404 page + const redirectTo = mostRecentPost + ? `/blog/${mostRecentPost.blogUrlName}` + : `/404`; + + redirect(redirectTo); +} diff --git a/static-site/app/blog/parseMarkdown.ts b/static-site/app/blog/parseMarkdown.ts new file mode 100644 index 000000000..67086df3e --- /dev/null +++ b/static-site/app/blog/parseMarkdown.ts @@ -0,0 +1,89 @@ +import { marked } from "marked"; +import sanitizeHtml, { Attributes, IOptions, Tag } from "sanitize-html"; + +import { siteUrl } from "../../data/BaseConfig"; + +export default async function parseMarkdown(mdString: string): Promise { + const rawDescription = await marked.parse(mdString); + + const sanitizerConfig: IOptions = { + allowedTags, // see below + allowedAttributes: { "*": allowedAttributes }, // see below + nonTextTags: ["style", "script", "textarea", "option"], + transformTags: { + a: transformA, // see below + }, + }; + + const cleanDescription: string = sanitizeHtml( + rawDescription, + sanitizerConfig, + ); + + return cleanDescription; +} + +function transformA(tagName: string, attribs: Attributes): Tag { + // small helper to keep things dry + const _setAttribs: (attribs: Attributes) => void = (attribs) => { + attribs.target = "_blank"; + attribs.rel = "noreferrer nofollow"; + }; + + const href = attribs.href; + if (href) { + const baseUrl = new URL(siteUrl); + try { // sometimes the `href` isn't a valid URL… + const linkUrl = new URL(href); + if (linkUrl.hostname !== baseUrl.hostname) { + _setAttribs(attribs); + } + } catch { + _setAttribs(attribs); + } + } + + return { tagName, attribs }; +} + +// All of these tags may not be necessary, this list was adopted from https://github.com/nextstrain/auspice/blob/master/src/util/parseMarkdown.js +const allowedTags = ['div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'em', 'strong', 'del', 'ol', 'ul', 'li', 'a', 'img']; +allowedTags.push('#text', 'code', 'pre', 'hr', 'table', 'thead', 'tbody', 'th', 'tr', 'td', 'sub', 'sup'); +// We want to support SVG elements, requiring the following tags (we exclude "foreignObject", "style" and "script") +allowedTags.push("svg", "altGlyph", "altGlyphDef", "altGlyphItem", "animate", "animateColor", "animateMotion", "animateTransform"); +allowedTags.push("circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "feBlend", "feColorMatrix", "feComponentTransfer"); +allowedTags.push("feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feFlood", "feFuncA"); +allowedTags.push("feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset"); +allowedTags.push("fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence", "filter", "font", "font-face"); +allowedTags.push("font-face-format", "font-face-name", "font-face-src", "font-face-uri", "g", "glyph", "glyphRef"); +allowedTags.push("hkern", "image", "line", "linearGradient", "marker", "mask", "metadata", "missing-glyph", "mpath", "path"); +allowedTags.push("pattern", "polygon", "polyline", "radialGradient", "rect", "set", "stop", "switch", "symbol"); +allowedTags.push("text", "textPath", "title", "tref", "tspan", "use", "view", "vkern"); + +const allowedAttributes = ['href', 'src', 'width', 'height', 'alt']; +// We add the following Attributes for SVG via https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute +// Certain values have been excluded here, e.g. "style" +allowedAttributes.push("accent-height", "accumulate", "additive", "alignment-baseline", "allowReorder", "alphabetic", "amplitude", "arabic-form", "ascent", "attributeName", "attributeType", "autoReverse", "azimuth"); +allowedAttributes.push("baseFrequency", "baseline-shift", "baseProfile", "bbox", "begin", "bias", "by"); +allowedAttributes.push("calcMode", "cap-height", "class", "clip", "clipPathUnits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cursor", "cx", "cy"); +allowedAttributes.push("d", "decelerate", "descent", "diffuseConstant", "direction", "display", "divisor", "dominant-baseline", "dur", "dx", "dy"); +allowedAttributes.push("edgeMode", "elevation", "enable-background", "end", "exponent", "externalResourcesRequired"); +allowedAttributes.push("fill", "fill-opacity", "fill-rule", "filter", "filterRes", "filterUnits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "format", "from", "fr", "fx", "fy"); +allowedAttributes.push("g1", "g2", "glyph-name", "glyph-orientation-horizontal", "glyph-orientation-vertical", "glyphRef", "gradientTransform", "gradientUnits"); +allowedAttributes.push("hanging", "height", "href", "hreflang", "horiz-adv-x", "horiz-origin-x"); +allowedAttributes.push("id", "ideographic", "image-rendering", "in", "in2", "intercept"); +allowedAttributes.push("k", "k1", "k2", "k3", "k4", "kernelMatrix", "kernelUnitLength", "kerning", "keyPoints", "keySplines", "keyTimes"); +allowedAttributes.push("lang", "lengthAdjust", "letter-spacing", "lighting-color", "limitingConeAngle", "local"); +allowedAttributes.push("marker-end", "marker-mid", "marker-start", "markerHeight", "markerUnits", "markerWidth", "mask", "maskContentUnits", "maskUnits", "mathematical", "max", "media", "method", "min", "mode"); +allowedAttributes.push("name", "numOctaves"); +allowedAttributes.push("offset", "opacity", "operator", "order", "orient", "orientation", "origin", "overflow", "overline-position", "overline-thickness"); +allowedAttributes.push("panose-1", "paint-order", "path", "pathLength", "patternContentUnits", "patternTransform", "patternUnits", "ping", "pointer-events", "points", "pointsAtX", "pointsAtY", "pointsAtZ", "preserveAlpha", "preserveAspectRatio", "primitiveUnits"); +allowedAttributes.push("r", "radius", "referrerPolicy", "refX", "refY", "rel", "rendering-intent", "repeatCount", "repeatDur", "requiredExtensions", "requiredFeatures", "restart", "result", "rotate", "rx", "ry"); +allowedAttributes.push("scale", "seed", "shape-rendering", "slope", "spacing", "specularConstant", "specularExponent", "speed", "spreadMethod", "startOffset", "stdDeviation", "stemh", "stemv", "stitchTiles", "stop-color", "stop-opacity", "strikethrough-position", "strikethrough-thickness", "string", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "surfaceScale", "systemLanguage"); +allowedAttributes.push("tabindex", "tableValues", "target", "targetX", "targetY", "text-anchor", "text-decoration", "text-rendering", "textLength", "to", "transform", "type"); +allowedAttributes.push("u1", "u2", "underline-position", "underline-thickness", "unicode", "unicode-bidi", "unicode-range", "units-per-em"); +allowedAttributes.push("v-alphabetic", "v-hanging", "v-ideographic", "v-mathematical", "values", "vector-effect", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "viewBox", "viewTarget", "visibility"); +allowedAttributes.push("width", "widths", "word-spacing", "writing-mode"); +allowedAttributes.push("x", "x-height", "x1", "x2", "xChannelSelector"); +allowedAttributes.push("y", "y1", "y2", "yChannelSelector"); +allowedAttributes.push("z", "zoomAndPan"); diff --git a/static-site/app/blog/utils.ts b/static-site/app/blog/utils.ts new file mode 100644 index 000000000..c6a47de11 --- /dev/null +++ b/static-site/app/blog/utils.ts @@ -0,0 +1,72 @@ +import fs from "fs"; +import matter from "gray-matter"; +import path from "path"; +import { fileURLToPath } from "url"; + +import parseMarkdown from "./parseMarkdown"; + +export interface BlogPost { + author: string; + blogUrlName: string; + date: string; + mdstring: string; + sidebarTitle: string; + title: string; +} + +// Scans the ./static-site/content/blog directory for .md files and +// returns a chronologically-sorted array of posts, each with some +// basic metadata and the raw (unsanitized) markdown contents. +export function getBlogPosts(): BlogPost[] { + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + + const postsDirectory = path.join(__dirname, "..", "..", "content", "blog"); + + const markdownFiles = fs + .readdirSync(postsDirectory) + .filter((fileName) => fileName.endsWith(".md")); + + const blogPosts: BlogPost[] = markdownFiles + .map((fileName): BlogPost | false => { + const { data: frontmatter, content: mdstring } = matter( + fs.readFileSync(path.join(postsDirectory, fileName), "utf8"), + ); + + // Our blog posts have frontmatter which includes author, date + // (YYYY-MM-DD format), post title, and an optional sidebar title. + // If the sidebar title isn't provided, the post title will be + // used for the sidebar title. + const { author, date, title } = frontmatter; + + if (!author || !date || !title) { + // console warning printed server-side + console.warn( + `Blog post ${fileName} skipped due to empty/incomplete frontmatter`, + ); + // we will filter these `false` values out momentarily + return false; + } else { + const blogUrlName = fileName.replace(/\.md$/, ""); + const sidebarTitle: string = frontmatter.sidebarTitle || title; + return { author, date, title, blogUrlName, sidebarTitle, mdstring }; + } + }) + // type guard to filter out false entries generated because of bad frontmatter + .filter((post: false | BlogPost): post is BlogPost => { + return !!post; + }) + // YYYY-MM-DD strings sort alphabetically + .sort((a, b) => (a.date > b.date ? -1 : 1)); + + return blogPosts; +} + +export async function markdownToHtml(mdString:string): Promise { + try { + return await parseMarkdown(mdString) + } + catch(error) { + console.error(`Error parsing markdown: ${error}`); + return '

There was an error parsing markdown content.

'; + } +} diff --git a/static-site/pages/blog.jsx b/static-site/pages/blog.jsx deleted file mode 100644 index a02307bee..000000000 --- a/static-site/pages/blog.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useRouter } from "next/router"; -import { useEffect} from 'react'; -import { getBlogPosts } from "../src/util/blogPosts"; - - -/** - * Default export is a component which immediately redirects to the latest blog - * post. Note that we could achieve the same end by adding redirects to - * `next.config.mjs` but we run into issues (performance & technical) when - * running getBlogPosts there. - */ -export default function Index({redirectTo}) { - const router = useRouter(); - useEffect(() => { - if (redirectTo) router.replace(redirectTo); - }, [redirectTo, router]) - return null; -} - -/** - * Return the URL for the latest blog post - * - * Note that we cannot return a redirect property here as that's not - * useable for statically generated sites. See - * - */ -export async function getStaticProps() { - const latestPost = getBlogPosts()[0]; - return { - props: {redirectTo: `/blog/${latestPost.blogUrlName}`} - } -} diff --git a/static-site/pages/blog/[id].jsx b/static-site/pages/blog/[id].jsx deleted file mode 100644 index 859f0de2f..000000000 --- a/static-site/pages/blog/[id].jsx +++ /dev/null @@ -1,47 +0,0 @@ -import dynamic from 'next/dynamic' -import { getBlogPosts } from "../../src/util/blogPosts"; -const DisplayMarkdown = dynamic(() => import("../../src/templates/displayMarkdown"), {ssr: false}) - - -/** - * Generate a list of the appropriate blog URLs we want Next.JS to generate - * static pages for. - * - * Note that the docs - * - * indicate you can export arbitrary data from here and consume it in - * getStaticProps but the only field you can pass is the router param ("id" in - * this case). See - */ -export const getStaticPaths = (async () => { - const posts = getBlogPosts(); - const paths = posts.map((post) => ({params: { - id: post.blogUrlName, /* matches the `id` router param defined via `[id].jsx` */ - }})); - return { - paths, - fallback: false // any paths not defined will result in a 404 page - } -}) - -/** - * For a given id (matching the route /blog/id via the dynamic routing filename `blog/[id].jsx`) - * return the params to be parsed to the rendering component. - * - * Note: The context.params.id is set to one of the entries in the array returned from - * `getStaticPaths`. See - */ -export const getStaticProps = (async (context) => { - const posts = getBlogPosts(); - const thisPost = posts.find((post) => post.blogUrlName===context.params.id); - const sidebarData = posts.map((post) => { - return {date: post.date, blogUrlName: post.blogUrlName, sidebarTitle: post.sidebarTitle, selected: post===thisPost}; - }) - return { - props: {...thisPost, sidebarData} - }; -}) - -export default function Index(props) { - return () -} diff --git a/static-site/src/templates/displayMarkdown.jsx b/static-site/src/templates/displayMarkdown.jsx deleted file mode 100644 index 3ff652dc9..000000000 --- a/static-site/src/templates/displayMarkdown.jsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from "react"; -import Head from "next/head"; -import styled from "styled-components"; -import SEO from "../components/SEO/SEO"; -import NavBar from '../components/nav-bar'; -import Sidebar from "../components/Sidebar"; -import { CenteredContainer, MarkdownContent } from "../layouts/generalComponents"; -import UserDataWrapper from "../layouts/userDataWrapper"; -import Footer from "../components/Footer"; -import MainLayout from "../components/layout"; -import { parseMarkdown } from "../util/parseMarkdown"; - -export default class GenericTemplate extends React.Component { - constructor(props) { - super(props); - this.toggleSidebar = this.toggleSidebar.bind(this); - this.state = {mql: undefined, sidebarOpen: undefined, mobileDisplay: undefined}; - } - componentDidMount() { - /* window listener to see when width changes cross threshold to toggle sidebar */ - /* can't be in the constructor -- https://github.com/gatsbyjs/gatsby/issues/309 */ - const mql = window.matchMedia(`(min-width: 780px)`); - mql.addListener(() => this.setState({ - sidebarOpen: this.state.mql.matches, - mobileDisplay: !this.state.mql.matches - })); - this.setState({ - mql, - sidebarOpen: mql.matches, - mobileDisplay: !mql.matches - }); - } - toggleSidebar() { - this.setState({ - sidebarOpen: !this.state.sidebarOpen - }); - } - // - renderMobileTogglesAndShading() { - const iconSliders = ( - - - - ); - const iconX = ( - - - - ); - return ( -
- - - {this.state.sidebarOpen ? iconX : iconSliders} - - - -
- ); - } - - render() { - const {author, date, title, blogUrlName, mdstring, sidebarData} = this.props; - /* create a description for SEO from the blog metadata */ - const description = `Nextstrain blog post from ${date}; author(s): ${author}` - return ( - - - {title} - - - - - - - - - - - - {date} - {title} - {author} - - - -