[example/538] - individual pages (#865)
* [example/538] - individual pages * [examples/538][sm] - force inclusion of classes * [examples/538] - changes requested by demenech
This commit is contained in:
parent
3f350f8fcd
commit
f610c953e7
23
examples/fiverthirtyeight-example/components/Breadcrumbs.tsx
Normal file
23
examples/fiverthirtyeight-example/components/Breadcrumbs.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import Link from "next/link";
|
||||
|
||||
function HomeIcon({ className = "" }) {
|
||||
return <div className={`inline-block w-4 ${className}`}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M 12 2 A 1 1 0 0 0 11.289062 2.296875 L 1.203125 11.097656 A 0.5 0.5 0 0 0 1 11.5 A 0.5 0.5 0 0 0 1.5 12 L 4 12 L 4 20 C 4 20.552 4.448 21 5 21 L 9 21 C 9.552 21 10 20.552 10 20 L 10 14 L 14 14 L 14 20 C 14 20.552 14.448 21 15 21 L 19 21 C 19.552 21 20 20.552 20 20 L 20 12 L 22.5 12 A 0.5 0.5 0 0 0 23 11.5 A 0.5 0.5 0 0 0 22.796875 11.097656 L 12.716797 2.3027344 A 1 1 0 0 0 12.710938 2.296875 A 1 1 0 0 0 12 2 z"/></svg></div>
|
||||
}
|
||||
|
||||
export default function Breadcrumbs({ links }: { links: { title: string, href?: string, target?: string }[] }) {
|
||||
const current = links.at(-1);
|
||||
|
||||
return <div className="flex items-center uppercase font-black text-xs">
|
||||
<Link className="flex items-center" href='/'><HomeIcon /></Link>
|
||||
|
||||
{/* {links.length > 1 && links.slice(0, -1).map((link) => {
|
||||
return <>
|
||||
<span className="mx-4">/</span>
|
||||
<Link href={link.href}>{link.title}</Link>
|
||||
</>
|
||||
})} */}
|
||||
|
||||
<span className="mx-4">/</span>
|
||||
<span>{current?.title}</span>
|
||||
</div >
|
||||
}
|
||||
@ -211,18 +211,6 @@
|
||||
"https://projects.fivethirtyeight.com/nfl-api/nfl_elo_latest.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/checking-our-work-data",
|
||||
"name": "checking-our-work-data",
|
||||
"displayName": "checking-our-work-<span class=\"lastword\">data</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2023-02-02T16:30:00.000Z",
|
||||
"title": "How Good Are FiveThirtyEight Forecasts?",
|
||||
"url": "https://projects.fivethirtyeight.com/checking-our-work/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/world-cup-2022",
|
||||
"name": "world-cup-2022",
|
||||
@ -239,18 +227,6 @@
|
||||
"https://projects.fivethirtyeight.com/soccer-api/international/2022/wc_forecasts.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/covid-19-polls",
|
||||
"name": "covid-19-polls",
|
||||
"displayName": "covid-19-<span class=\"lastword\">polls</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2022-11-29T20:20:08.000Z",
|
||||
"title": "How Americans View Biden’s Response To The Coronavirus Crisis",
|
||||
"url": "https://projects.fivethirtyeight.com/coronavirus-polls/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/election-deniers",
|
||||
"name": "election-deniers",
|
||||
@ -371,18 +347,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/nfl-elo-game",
|
||||
"name": "nfl-elo-game",
|
||||
"displayName": "nfl-elo-<span class=\"lastword\">game</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2022-02-14T03:03:26.000Z",
|
||||
"title": "Can You Beat FiveThirtyEight’s NFL Forecasts?",
|
||||
"url": "https://projects.fivethirtyeight.com/2021-nfl-forecasting-game/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/redlining",
|
||||
"name": "redlining",
|
||||
@ -407,42 +371,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/negro-leagues-player-ratings",
|
||||
"name": "negro-leagues-player-ratings",
|
||||
"displayName": "negro-leagues-player-<span class=\"lastword\">ratings</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2021-02-25T11:00:00.000Z",
|
||||
"title": "The Negro League Stars That MLB Kept Out — And Is Finally Recognizing",
|
||||
"url": "https://projects.fivethirtyeight.com/negro-leagues-mlb/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/police-settlements",
|
||||
"name": "police-settlements",
|
||||
"displayName": "police-<span class=\"lastword\">settlements</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2021-02-22T11:00:49.000Z",
|
||||
"title": "Cities Spend Millions On Police Misconduct Every Year. Here’s Why It’s So Difficult to Hold Departments Accountable.",
|
||||
"url": "https://fivethirtyeight.com/features/police-misconduct-costs-cities-millions-every-year-but-thats-where-the-accountability-ends/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/superbowl-ads",
|
||||
"name": "superbowl-ads",
|
||||
"displayName": "superbowl-<span class=\"lastword\">ads</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2021-02-04T16:11:00.000Z",
|
||||
"title": "According To Super Bowl Ads, Americans Love America, Animals And Sex",
|
||||
"url": "https://projects.fivethirtyeight.com/super-bowl-ads/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/trump-approval-ratings",
|
||||
"name": "trump-approval-ratings",
|
||||
@ -475,18 +403,6 @@
|
||||
"https://projects.fivethirtyeight.com/congress-tracker-data/csv/vote_predictions.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/election-results",
|
||||
"name": "election-results",
|
||||
"displayName": "election-<span class=\"lastword\">results</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2020-11-03T05:33:43.000Z",
|
||||
"title": "2020 Election Forecast",
|
||||
"url": "https://projects.fivethirtyeight.com/2020-election-forecast/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/election-forecasts-2020",
|
||||
"name": "election-forecasts-2020",
|
||||
@ -557,18 +473,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/WNBA-stats",
|
||||
"name": "WNBA-stats",
|
||||
"displayName": "WNBA-<span class=\"lastword\">stats</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2020-05-27T19:06:43.000Z",
|
||||
"title": "It’s Time To Give Basketball’s Other GOAT Her Due",
|
||||
"url": "https://fivethirtyeight.com/features/its-time-to-give-basketballs-other-goat-her-due/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/covid-geography",
|
||||
"name": "covid-geography",
|
||||
@ -608,18 +512,6 @@
|
||||
"https://projects.fivethirtyeight.com/endorsements-2020-data/endorsements-2020.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/nba-player-advanced-metrics",
|
||||
"name": "nba-player-advanced-metrics",
|
||||
"displayName": "nba-player-advanced-<span class=\"lastword\">metrics</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2020-03-09T15:27:37.000Z",
|
||||
"title": "Luka Dončić And The Mavs Are Pushing The Limits Of Offensive Efficiency",
|
||||
"url": "https://fivethirtyeight.com/features/luka-doncic-and-the-mavs-are-pushing-the-limits-of-offensive-efficiency/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/impeachment-polls",
|
||||
"name": "impeachment-polls",
|
||||
@ -711,18 +603,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/candidate-emails",
|
||||
"name": "candidate-emails",
|
||||
"displayName": "candidate-<span class=\"lastword\">emails</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2019-07-11T15:33:15.000Z",
|
||||
"title": "What Our Inbox Tells Us About How Democrats Are Tackling Trump",
|
||||
"url": "https://fivethirtyeight.com/features/which-democrats-are-campaigning-on-trump/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-draymond",
|
||||
"name": "nba-draymond",
|
||||
@ -763,18 +643,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/twitter-overlap",
|
||||
"name": "twitter-overlap",
|
||||
"displayName": "twitter-<span class=\"lastword\">overlap</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2019-06-12T15:24:07.000Z",
|
||||
"title": "Which 2020 Candidates Have The Most In Common … On Twitter?",
|
||||
"url": "https://fivethirtyeight.com/features/which-2020-candidates-have-the-most-in-common-on-twitter/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/trump-lawsuits",
|
||||
"name": "trump-lawsuits",
|
||||
@ -957,18 +825,6 @@
|
||||
"https://projects.fivethirtyeight.com/congress-model-2018/governor_state_forecast.csv"
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/actblue-analysis",
|
||||
"name": "actblue-analysis",
|
||||
"displayName": "actblue-<span class=\"lastword\">analysis</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2018-10-25T17:31:42.000Z",
|
||||
"title": "How ActBlue Is Trying To Turn Small Donations Into A Blue Wave",
|
||||
"url": "https://fivethirtyeight.com/features/how-actblue-is-trying-to-turn-small-donations-into-a-blue-wave"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/primary-candidates-2018",
|
||||
"name": "primary-candidates-2018",
|
||||
@ -1179,18 +1035,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/redistricting-atlas-data",
|
||||
"name": "redistricting-atlas-data",
|
||||
"displayName": "redistricting-atlas-<span class=\"lastword\">data</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2018-01-25T11:00:00.000Z",
|
||||
"title": "The Atlas Of Redistricting",
|
||||
"url": "https://projects.fivethirtyeight.com/redistricting-maps/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/next-bechdel",
|
||||
"name": "next-bechdel",
|
||||
@ -1620,37 +1464,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/uber-tlc-foil-response",
|
||||
"name": "uber-tlc-foil-response",
|
||||
"displayName": "uber-tlc-foil-<span class=\"lastword\">response</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2015-12-09T16:19:40.000Z",
|
||||
"title": "Is Uber Making NYC Rush-Hour Traffic Worse?",
|
||||
"rowspan": 4,
|
||||
"url": "https://fivethirtyeight.com/features/is-uber-making-nyc-rush-hour-traffic-worse/"
|
||||
},
|
||||
{
|
||||
"date": "2015-10-13T20:44:12.000Z",
|
||||
"title": "Uber Is Taking Millions Of Manhattan Rides Away From Taxis",
|
||||
"rowspan": 0,
|
||||
"url": "https://fivethirtyeight.com/features/uber-is-taking-millions-of-manhattan-rides-away-from-taxis/"
|
||||
},
|
||||
{
|
||||
"date": "2015-08-28T10:30:36.000Z",
|
||||
"title": "Public Transit Should Be Uber’s New Best Friend",
|
||||
"rowspan": 0,
|
||||
"url": "https://fivethirtyeight.com/features/public-transit-should-be-ubers-new-best-friend/"
|
||||
},
|
||||
{
|
||||
"date": "2015-08-10T18:06:17.000Z",
|
||||
"title": "Uber Is Serving New York’s Outer Boroughs More Than Taxis Are",
|
||||
"rowspan": 0,
|
||||
"url": "https://fivethirtyeight.com/features/uber-is-serving-new-yorks-outer-boroughs-more-than-taxis-are/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/tarantino",
|
||||
"name": "tarantino",
|
||||
@ -2169,18 +1982,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/comma-survey-data",
|
||||
"name": "comma-survey-data",
|
||||
"displayName": "comma-survey-<span class=\"lastword\">data</span>",
|
||||
"articles": [
|
||||
{
|
||||
"date": "2014-06-17T16:28:55.000Z",
|
||||
"title": "Elitist, Superfluous, Or Popular? We Polled Americans on the Oxford Comma",
|
||||
"url": "http://fivethirtyeight.com/features/elitist-superfluous-or-popular-we-polled-americans-on-the-oxford-comma/"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fivethirtyeight/data/tree/master/world-cup-predictions",
|
||||
"name": "world-cup-predictions",
|
||||
|
||||
38
examples/fiverthirtyeight-example/lib/octokit.ts
Normal file
38
examples/fiverthirtyeight-example/lib/octokit.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Octokit } from 'octokit';
|
||||
|
||||
export interface GithubProject {
|
||||
owner: string;
|
||||
repo: string;
|
||||
branch: string;
|
||||
files: string[];
|
||||
readme: string;
|
||||
description?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export async function getProjectReadme(
|
||||
owner: string,
|
||||
repo: string,
|
||||
branch: string,
|
||||
readme: string,
|
||||
github_pat?: string
|
||||
) {
|
||||
const octokit = new Octokit({ auth: github_pat });
|
||||
try {
|
||||
const response = await octokit.rest.repos.getContent({
|
||||
owner,
|
||||
repo,
|
||||
path: readme,
|
||||
ref: branch,
|
||||
});
|
||||
const data = response.data as { content?: string };
|
||||
const fileContent = data.content ? data.content : '';
|
||||
if (fileContent === '') {
|
||||
return null;
|
||||
}
|
||||
const decodedContent = Buffer.from(fileContent, 'base64').toString();
|
||||
return decodedContent;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,9 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
serverRuntimeConfig: {
|
||||
github_pat: process.env.GITHUB_PAT ? process.env.GITHUB_PAT : null,
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
2590
examples/fiverthirtyeight-example/package-lock.json
generated
2590
examples/fiverthirtyeight-example/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,8 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@portaljs/components": "^0.1.0",
|
||||
"@tailwindcss/typography": "^0.5.9",
|
||||
"@types/node": "20.1.1",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/react-dom": "18.2.4",
|
||||
@ -16,9 +18,17 @@
|
||||
"eslint": "8.40.0",
|
||||
"eslint-config-next": "13.4.1",
|
||||
"next": "13.4.1",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"next-seo": "^6.0.0",
|
||||
"octokit": "^2.0.14",
|
||||
"postcss": "8.4.23",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"remark-code-frontmatter": "^1.0.0",
|
||||
"remark-extract-frontmatter": "^3.2.0",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"tailwindcss": "3.3.2",
|
||||
"timeago.js": "^4.0.2",
|
||||
"typescript": "5.0.4"
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import '@/styles/globals.css'
|
||||
import '@portaljs/components/styles.css'
|
||||
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
|
||||
@ -11,6 +11,25 @@ export default function Document() {
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<header className="max-w-5xl mx-auto mt-8 w-full">
|
||||
<div className="border-b-2 pb-2.5 mx-2 border-zinc-800">
|
||||
<h1>
|
||||
<span className="sr-only">FiveThirtyEight</span>
|
||||
<a
|
||||
className="flex gap-x-2 items-center"
|
||||
href="http://fivethirtyeight.com"
|
||||
>
|
||||
<img
|
||||
width="197"
|
||||
height="25"
|
||||
alt="FiveThirtyEight"
|
||||
src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MjEgNTMuNzYiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojMDEwMTAxO308L3N0eWxlPjwvZGVmcz48dGl0bGU+QXJ0Ym9hcmQgOTU8L3RpdGxlPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTAgMGgyNXY4SDl2MTBoMTV2OEg5djE3SDBWMHpNMzEgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdIMzF6bTUtMzZoOHY4aC04ek0xNzkgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdoLTE3em01LTM2aDh2OGgtOHpNMzE2IDM2aDVWMThoLTV2LThoMTN2MjZoNHY3aC0xN3ptNS0zNmg4djhoLTh6TTU0IDI3VjEwaDh2MTVsNCA5Ljk4aDFMNzEgMjVWMTBoOHYxN2wtNyAxNkg2MWwtNy0xNnpNMTExIDQzSDk3LjQyQzg5LjIzIDQzIDg1IDM5LjE5IDg1IDMxLjE3VjIyYzAtNy41NyA0LjMtMTMgMTMtMTMgOS4zMyAwIDEzIDUuMDcgMTMgMTR2N0g5NHYxLjc0YzAgMi42MiAxIDQuMjYgMy40MiA0LjI2SDExMXpNOTQgMjNoOHYtMS41NWMwLTIuNjItMS4wNi01LjQ1LTQuMTMtNS40NS0yLjc5IDAtMy44NyAyLjItMy44NyA1LjQ1ek0xMjUgOGgtMTBWMGgyOXY4aC0xMHYzNWgtOVY4ek0yMDIgNDNWMTBoOHY0YzEuMTQtMi40NSAzLjc1LTQgNy4yMi00SDIyMHY4aC02Yy0yLjg0IDAtNCAuOTQtNCAzLjlWNDN6TTI0NSA0M2gtNC44NEMyMzMuMDUgNDMgMjMwIDM5LjMxIDIzMCAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0gyNDV6TTQyMSA0M2gtNC44NEM0MDkuMDUgNDMgNDA2IDM5LjMxIDQwNiAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0g0MjF6TTI1NC4yNiA1My43Nmw0LjYxLTkuNUwyNTEgMjdWMTBoOHYxNWw0IDEwaDFsNC0xMFYxMGg4djE3bC0xMi4zIDI2Ljc2aC05LjQ0ek0yODQgMGgyNXY4aC0xNnY5aDE1djhoLTE1djEwaDE2djhoLTI1VjB6TTMzNyA0OHYtMmgxNi4xYzIgMCAyLjktLjE4IDIuOS0xLjI3di0uMzRjMC0xLjA4LS45MS0xLjM5LTIuOS0xLjM5SDM0MHYtNWw1LTVjLTUuMjktMS40OC04LTUuNDMtOC0xMXYtMWMwLTcuNTYgNC40NC0xMiAxNC0xMmEyMS45MyAyMS45MyAwIDAgMSA1Ljk1IDFMMzYxIDRsNSAzLTQgNmMxLjM3IDEuOTMgMyA0LjkzIDMgOHYxYzAgNy0zLjMgMTAuNjYtMTIgMTFsLTMgNGg2YzUuOTIgMCA5IDIuNjIgOSA3LjY4di4xMWMwIDUuMDYtMi43MSA4LjIxLTguNjIgOC4yMWgtMTNjLTQuMjkgMC02LjM4LTEuODQtNi4zOC01em0xOS0yNXYtM2MwLTMuMy0xLjMzLTQtNS00cy01IC43LTUgNHYzYzAgMy4zIDEuMzkgNCA1IDRzNS0uNyA1LTR6TTM4MCA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuNC00IDctNCA2LjI2IDAgOSAzLjA4IDkgMTAuNzZWNDNoLThWMjJjMC0zLjEzLTEuMDctNS00LTVzLTQgMS44Ny00IDV6TTE1NyA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuOTEtNCA3LjQ5LTQgNi4yNiAwIDguNTEgMy4xMyA4LjUxIDEwLjgxVjQzaC04VjIxYzAtMy4xMy0xLjA3LTQuNDQtNC00LjQ0cy00IDIuMjYtNCA1LjM5eiIvPjwvc3ZnPg=="
|
||||
/>{' '}
|
||||
by PortalJS
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
import { NextSeo } from 'next-seo';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import getConfig from 'next/config';
|
||||
import { getProjectReadme, GithubProject } from '@/lib/octokit';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import extract from 'remark-extract-frontmatter';
|
||||
import { Dataset } from '..';
|
||||
import { GetStaticProps } from 'next';
|
||||
import { Table } from '@portaljs/components';
|
||||
import Breadcrumbs from '@/components/Breadcrumbs';
|
||||
import { ReactMarkdown } from 'react-markdown/lib/react-markdown';
|
||||
import remarkFrontmatter from 'remark-frontmatter';
|
||||
|
||||
export default function DatasetPage({
|
||||
dataset,
|
||||
}: {
|
||||
dataset: Dataset & {
|
||||
readme: string | null;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<NextSeo title={`${dataset.name} page`} />
|
||||
<main className="max-w-5xl px-2 prose mx-auto my-8 prose-thead:border-b-4 prose-table:max-w-5xl prose-table:overflow-scroll prose-thead:overflow-scroll prose-tbody:overflow-scroll prose-thead:pb-2 prose-thead:border-zinc-900 prose-th:uppercase prose-th:text-left prose-th:font-light prose-th:text-xs">
|
||||
<Breadcrumbs links={[{ title: dataset.name, href: '' }]} />
|
||||
<h1 className="uppercase mb-0 mt-16">{dataset.name}</h1>
|
||||
<p className="mb-8">
|
||||
<span className="font-semibold">Repository:</span>{' '}
|
||||
<a target="_blank" href={dataset.url}>
|
||||
{dataset.url}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h2 className="mb-0 mt-10">FILES</h2>
|
||||
<div className="inline-block min-w-full py-2 align-middle">
|
||||
<table className="min-w-full divide-y divide-gray-300">
|
||||
<thead className="border-b-4 pb-2 border-zinc-900">
|
||||
<tr>
|
||||
<th
|
||||
className="uppercase text-left font-light text-xs pb-3"
|
||||
scope="col"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200">
|
||||
{dataset.files?.map((file) => (
|
||||
<tr key={file}>
|
||||
<td className="whitespace-nowrap text-left py-4 text-sm text-gray-500">
|
||||
<a href={file}>{file.split('/').slice(-1)}</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{dataset.files && dataset.files.length > 0 && (
|
||||
<>
|
||||
<h2 className="mb-0 mt-10">DATA PREVIEWS</h2>
|
||||
{dataset.files?.map((file) => (
|
||||
<div key={file} className="preview-table my-8">
|
||||
<h3>{file.split('/').slice(-1)}</h3>
|
||||
<Table url={file} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{dataset.readme && (
|
||||
<>
|
||||
<h2 className="uppercase font-black">Readme</h2>
|
||||
{dataset.readme && (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[
|
||||
remarkFrontmatter,
|
||||
remarkGfm,
|
||||
[extract, { remove: true }],
|
||||
]}
|
||||
>
|
||||
{dataset.readme}
|
||||
</ReactMarkdown>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const datasetsFile = path.join(process.cwd(), 'datasets.json');
|
||||
const datasets = await fs.readFile(datasetsFile, 'utf8');
|
||||
|
||||
return {
|
||||
paths: JSON.parse(datasets).map((dataset: Dataset) => {
|
||||
return {
|
||||
params: { datasetName: dataset.name },
|
||||
};
|
||||
}),
|
||||
fallback: false, // can also be true or 'blocking'
|
||||
};
|
||||
}
|
||||
|
||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||
const datasetsFile = path.join(process.cwd(), 'datasets.json');
|
||||
const datasetsString = await fs.readFile(datasetsFile, 'utf8');
|
||||
const datasets: Dataset[] = JSON.parse(datasetsString);
|
||||
const dataset: Dataset | undefined = datasets.find(
|
||||
(_dataset) => _dataset.name === params?.datasetName
|
||||
);
|
||||
const github_pat = getConfig().serverRuntimeConfig.github_pat;
|
||||
const readmes = await Promise.all(['/README.md', '/readme.md', '/Readme.md'].map(async (readme) => await getProjectReadme(
|
||||
'fivethirtyeight',
|
||||
'data',
|
||||
'master',
|
||||
dataset?.name + readme,
|
||||
github_pat
|
||||
)));
|
||||
const readme = readmes.find(item => item !== null)
|
||||
if (!readme) console.log('Readme not found for ' + dataset?.name)
|
||||
return {
|
||||
props: {
|
||||
dataset: {
|
||||
...dataset,
|
||||
readme,
|
||||
files: dataset && dataset.files ? dataset.files : null,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
@ -1,33 +1,36 @@
|
||||
import Image from 'next/image';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { format } from 'timeago.js'
|
||||
import { format } from 'timeago.js';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
interface Article {
|
||||
export interface Article {
|
||||
date: string;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface Dataset {
|
||||
export interface Dataset {
|
||||
url: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
articles: Article[];
|
||||
files?: string[];
|
||||
}
|
||||
|
||||
export function MobileItem({dataset} : { dataset: Dataset}) {
|
||||
export function MobileItem({ dataset }: { dataset: Dataset }) {
|
||||
return (
|
||||
<div className="flex gap-x-2 pb-2 py-4 items-center justify-between border-b border-zinc-600">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-light">{dataset.name}</span>
|
||||
{dataset.articles.map((article) => (
|
||||
<div key={article.title} className='py-1 flex flex-col'>
|
||||
<div key={article.title} className="py-1 flex flex-col">
|
||||
<span className="font-bold hover:underline">{article.title}</span>
|
||||
<span className="font-light text-base">{format(article.date)}</span>{' '}
|
||||
<span className="font-light text-base">
|
||||
{format(article.date)}
|
||||
</span>{' '}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@ -39,6 +42,12 @@ export function MobileItem({dataset} : { dataset: Dataset}) {
|
||||
>
|
||||
info
|
||||
</a>
|
||||
<a
|
||||
className="ml-2 border border-zinc-900 font-light px-4 py-1 text-sm transition hover:bg-zinc-900 hover:text-white"
|
||||
href={`/datasets/${dataset.name}`}
|
||||
>
|
||||
explore
|
||||
</a>
|
||||
{/*
|
||||
<button>
|
||||
<svg
|
||||
@ -59,18 +68,25 @@ export function MobileItem({dataset} : { dataset: Dataset}) {
|
||||
);
|
||||
}
|
||||
|
||||
export function DesktopItem({dataset} : { dataset: Dataset}) {
|
||||
export function DesktopItem({ dataset }: { dataset: Dataset }) {
|
||||
return (
|
||||
<>
|
||||
{dataset.articles.map((article, index) => (
|
||||
<tr key={article.url} className={`${index === (dataset.articles.length - 1) ? 'border-b' : ''} border-zinc-400`}>
|
||||
<tr
|
||||
key={article.url}
|
||||
className={`${
|
||||
index === dataset.articles.length - 1 ? 'border-b' : ''
|
||||
} border-zinc-400`}
|
||||
>
|
||||
<td className="py-8 font-light">{index === 0 ? dataset.name : ''}</td>
|
||||
<td>
|
||||
<a className="py-8 font-bold hover:underline" href={article.url}>
|
||||
{article.title}
|
||||
</a>
|
||||
</td>
|
||||
<td className="py-8 font-light text-base min-w-[120px]">{format(article.date)}</td>
|
||||
<td className="py-8 font-light text-base min-w-[120px]">
|
||||
{format(article.date)}
|
||||
</td>
|
||||
<td className="py-8">
|
||||
{index === 0 && (
|
||||
<a
|
||||
@ -82,6 +98,16 @@ export function DesktopItem({dataset} : { dataset: Dataset}) {
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-8">
|
||||
{index === 0 && (
|
||||
<a
|
||||
className="ml-2 border border-zinc-900 font-light px-[25px] py-2.5 text-sm transition hover:bg-zinc-900 hover:text-white"
|
||||
href={`/datasets/${dataset.name}`}
|
||||
>
|
||||
explore
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
{/*
|
||||
<td>
|
||||
<button>
|
||||
@ -106,35 +132,17 @@ export function DesktopItem({dataset} : { dataset: Dataset}) {
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const jsonDirectory = path.join(
|
||||
process.cwd(),
|
||||
'/datasets.json'
|
||||
);
|
||||
const jsonDirectory = path.join(process.cwd(), '/datasets.json');
|
||||
const datasetString = await fs.readFile(jsonDirectory, 'utf8');
|
||||
const datasets = JSON.parse(datasetString)
|
||||
const datasets = JSON.parse(datasetString);
|
||||
return {
|
||||
props: { datasets },
|
||||
};
|
||||
}
|
||||
|
||||
export default function Home( { datasets }: { datasets: Dataset[] }) {
|
||||
export default function Home({ datasets }: { datasets: Dataset[] }) {
|
||||
return (
|
||||
<>
|
||||
<header className="max-w-5xl mx-auto mt-8 w-full">
|
||||
<div className="border-b-2 pb-2.5 mx-2 border-zinc-800">
|
||||
<h1>
|
||||
<span className="sr-only">FiveThirtyEight</span>
|
||||
<a className='flex gap-x-2 items-center' href="http://fivethirtyeight.com">
|
||||
<img
|
||||
width="197"
|
||||
height="25"
|
||||
alt="FiveThirtyEight"
|
||||
src="data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMSIgZGF0YS1uYW1lPSJMYXllciAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0MjEgNTMuNzYiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojMDEwMTAxO308L3N0eWxlPjwvZGVmcz48dGl0bGU+QXJ0Ym9hcmQgOTU8L3RpdGxlPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTAgMGgyNXY4SDl2MTBoMTV2OEg5djE3SDBWMHpNMzEgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdIMzF6bTUtMzZoOHY4aC04ek0xNzkgMzZoNVYxOGgtNXYtOGgxM3YyNmg0djdoLTE3em01LTM2aDh2OGgtOHpNMzE2IDM2aDVWMThoLTV2LThoMTN2MjZoNHY3aC0xN3ptNS0zNmg4djhoLTh6TTU0IDI3VjEwaDh2MTVsNCA5Ljk4aDFMNzEgMjVWMTBoOHYxN2wtNyAxNkg2MWwtNy0xNnpNMTExIDQzSDk3LjQyQzg5LjIzIDQzIDg1IDM5LjE5IDg1IDMxLjE3VjIyYzAtNy41NyA0LjMtMTMgMTMtMTMgOS4zMyAwIDEzIDUuMDcgMTMgMTR2N0g5NHYxLjc0YzAgMi42MiAxIDQuMjYgMy40MiA0LjI2SDExMXpNOTQgMjNoOHYtMS41NWMwLTIuNjItMS4wNi01LjQ1LTQuMTMtNS40NS0yLjc5IDAtMy44NyAyLjItMy44NyA1LjQ1ek0xMjUgOGgtMTBWMGgyOXY4aC0xMHYzNWgtOVY4ek0yMDIgNDNWMTBoOHY0YzEuMTQtMi40NSAzLjc1LTQgNy4yMi00SDIyMHY4aC02Yy0yLjg0IDAtNCAuOTQtNCAzLjlWNDN6TTI0NSA0M2gtNC44NEMyMzMuMDUgNDMgMjMwIDM5LjMxIDIzMCAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0gyNDV6TTQyMSA0M2gtNC44NEM0MDkuMDUgNDMgNDA2IDM5LjMxIDQwNiAzMS44NVYxOGgtNnYtOGg2VjNoOHY3aDd2OGgtN2wtLjA3IDEzLjkzYzAgMi4yMi45MyA0LjA3IDMuNjYgNC4wN0g0MjF6TTI1NC4yNiA1My43Nmw0LjYxLTkuNUwyNTEgMjdWMTBoOHYxNWw0IDEwaDFsNC0xMFYxMGg4djE3bC0xMi4zIDI2Ljc2aC05LjQ0ek0yODQgMGgyNXY4aC0xNnY5aDE1djhoLTE1djEwaDE2djhoLTI1VjB6TTMzNyA0OHYtMmgxNi4xYzIgMCAyLjktLjE4IDIuOS0xLjI3di0uMzRjMC0xLjA4LS45MS0xLjM5LTIuOS0xLjM5SDM0MHYtNWw1LTVjLTUuMjktMS40OC04LTUuNDMtOC0xMXYtMWMwLTcuNTYgNC40NC0xMiAxNC0xMmEyMS45MyAyMS45MyAwIDAgMSA1Ljk1IDFMMzYxIDRsNSAzLTQgNmMxLjM3IDEuOTMgMyA0LjkzIDMgOHYxYzAgNy0zLjMgMTAuNjYtMTIgMTFsLTMgNGg2YzUuOTIgMCA5IDIuNjIgOSA3LjY4di4xMWMwIDUuMDYtMi43MSA4LjIxLTguNjIgOC4yMWgtMTNjLTQuMjkgMC02LjM4LTEuODQtNi4zOC01em0xOS0yNXYtM2MwLTMuMy0xLjMzLTQtNS00cy01IC43LTUgNHYzYzAgMy4zIDEuMzkgNCA1IDRzNS0uNyA1LTR6TTM4MCA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuNC00IDctNCA2LjI2IDAgOSAzLjA4IDkgMTAuNzZWNDNoLThWMjJjMC0zLjEzLTEuMDctNS00LTVzLTQgMS44Ny00IDV6TTE1NyA0M2gtOFYwaDh2MTRjMS4xNC0yLjY3IDMuOTEtNCA3LjQ5LTQgNi4yNiAwIDguNTEgMy4xMyA4LjUxIDEwLjgxVjQzaC04VjIxYzAtMy4xMy0xLjA3LTQuNDQtNC00LjQ0cy00IDIuMjYtNCA1LjM5eiIvPjwvc3ZnPg=="
|
||||
/> by PortalJS
|
||||
</a>
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<main
|
||||
className={`flex min-h-screen flex-col items-center max-w-5xl mx-auto pt-20 px-2.5 ${inter.className}`}
|
||||
>
|
||||
@ -148,7 +156,11 @@ export default function Home( { datasets }: { datasets: Dataset[] }) {
|
||||
stories and visualizations of your own.
|
||||
</p>
|
||||
</div>
|
||||
<article className="w-full px-2 md:hidden py-4">{datasets.map(dataset => <MobileItem key={dataset.name} dataset={dataset} />)}</article>
|
||||
<article className="w-full px-2 md:hidden py-4">
|
||||
{datasets.map((dataset) => (
|
||||
<MobileItem key={dataset.name} dataset={dataset} />
|
||||
))}
|
||||
</article>
|
||||
<table className="w-full mt-10 mb-4 hidden md:table">
|
||||
<thead className="border-b-4 pb-2 border-zinc-900">
|
||||
<tr>
|
||||
@ -163,7 +175,11 @@ export default function Home( { datasets }: { datasets: Dataset[] }) {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{datasets.map(dataset => <DesktopItem key={dataset.name} dataset={dataset} />)}</tbody>
|
||||
<tbody>
|
||||
{datasets.map((dataset) => (
|
||||
<DesktopItem key={dataset.name} dataset={dataset} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-[13px] py-8">
|
||||
Unless otherwise noted, our data sets are available under the{' '}
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.preview-table > div {
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
@ -14,5 +14,5 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user