Compare commits
1 Commits
main
...
fivethirty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c85934d17a |
@ -1,8 +0,0 @@
|
|||||||
# Changesets
|
|
||||||
|
|
||||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
||||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
||||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
||||||
|
|
||||||
We have a quick list of common questions to get you started engaging with this project in
|
|
||||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json",
|
|
||||||
"changelog": [
|
|
||||||
"@changesets/changelog-github",
|
|
||||||
{ "repo": "datopian/portaljs" }
|
|
||||||
],
|
|
||||||
"commit": false,
|
|
||||||
"fixed": [],
|
|
||||||
"linked": [],
|
|
||||||
"access": "restricted",
|
|
||||||
"baseBranch": "main",
|
|
||||||
"updateInternalDependencies": "patch",
|
|
||||||
"ignore": []
|
|
||||||
}
|
|
||||||
39
.github/workflows/release.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
concurrency: release-${{ github.ref }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup Node.js 16.x
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16.x
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: npm ci
|
|
||||||
|
|
||||||
- name: Create Release Pull Request or Publish to npm
|
|
||||||
id: changesets
|
|
||||||
uses: changesets/action@v1
|
|
||||||
with:
|
|
||||||
publish: npm run release
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
# - name: Send a Discord notification if a publish happens
|
|
||||||
# if: steps.changesets.outputs.published == 'true'
|
|
||||||
# uses: Ilshidur/action-discord@0.3.2
|
|
||||||
# with:
|
|
||||||
# args: 'The project {{ EVENT_PAYLOAD.repository.full_name }} has been deployed.'
|
|
||||||
2
.gitignore
vendored
@ -4,7 +4,6 @@
|
|||||||
dist
|
dist
|
||||||
tmp
|
tmp
|
||||||
/out-tsc
|
/out-tsc
|
||||||
**/*.tgz
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
@ -17,7 +16,6 @@ node_modules
|
|||||||
*.launch
|
*.launch
|
||||||
.settings/
|
.settings/
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.obsidian
|
|
||||||
|
|
||||||
# IDE - VSCode
|
# IDE - VSCode
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|||||||
8
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"nrwl.angular-console",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"firsttris.vscode-jest-runner",
|
||||||
|
"dbaeumer.vscode-eslint"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ title: Developer docs for contributors
|
|||||||
|
|
||||||
## Our repository
|
## Our repository
|
||||||
|
|
||||||
https://github.com/datopian/datahub
|
https://github.com/datopian/portaljs
|
||||||
|
|
||||||
Structure:
|
Structure:
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ Structure:
|
|||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
You can start by checking our [issues board](https://github.com/datopian/datahub/issues).
|
You can start by checking our [issues board](https://github.com/datopian/portaljs/issues).
|
||||||
|
|
||||||
If you'd like to work on one of the issues you can:
|
If you'd like to work on one of the issues you can:
|
||||||
|
|
||||||
@ -26,16 +26,15 @@ If you'd like to work on one of the issues you can:
|
|||||||
3. Clone the forked repository to your machine.
|
3. Clone the forked repository to your machine.
|
||||||
4. Create a feature branch (e.g. `50-update-readme`, where `50` is the number of the related issue).
|
4. Create a feature branch (e.g. `50-update-readme`, where `50` is the number of the related issue).
|
||||||
5. Commit your changes to the feature branch.
|
5. Commit your changes to the feature branch.
|
||||||
6. Add changeset file describing the changes. (See section below)
|
6. Push the feature branch to your forked repository.
|
||||||
7. Push the feature branch to your forked repository.
|
7. Create a Pull Request against the original repository.
|
||||||
8. Create a Pull Request against the original repository.
|
|
||||||
- add a short description of the changes included in the PR
|
- add a short description of the changes included in the PR
|
||||||
9. Address review comments if requested by our demanding reviewers 😜.
|
8. Address review comments if requested by our demanding reviewers 😜.
|
||||||
|
|
||||||
If you have an idea for improvement, and it doesn't have a corresponding issue yet, simply submit a new one.
|
If you have an idea for improvement, and it doesn't have a corresponding issue yet, simply submit a new one.
|
||||||
|
|
||||||
> [!note]
|
> [!note]
|
||||||
> Join our [Discord channel](https://discord.gg/KZSf3FG4EZ) do discuss existing issues and to ask for help.
|
> Join our [Discord channel](https://discord.gg/rTxfCutu) do discuss existing issues and to ask for help.
|
||||||
|
|
||||||
## Nx
|
## Nx
|
||||||
|
|
||||||
@ -63,7 +62,6 @@ or you can use just:
|
|||||||
nx <target> <project>
|
nx <target> <project>
|
||||||
# e.g. npx nx serve ckan
|
# e.g. npx nx serve ckan
|
||||||
```
|
```
|
||||||
|
|
||||||
if you have the `nx` binary installed globally in your machine
|
if you have the `nx` binary installed globally in your machine
|
||||||
|
|
||||||
#### Running multiple tasks
|
#### Running multiple tasks
|
||||||
@ -176,23 +174,3 @@ To learn more see this [offical docs page](https://nx.dev/reference/nx-json).
|
|||||||
Each project also has it's own configuration file - `project.json`, where you can define and configure it's targets (and more).
|
Each project also has it's own configuration file - `project.json`, where you can define and configure it's targets (and more).
|
||||||
|
|
||||||
To learn more see this [offical docs page](https://nx.dev/reference/project-configuration).
|
To learn more see this [offical docs page](https://nx.dev/reference/project-configuration).
|
||||||
|
|
||||||
## Changesets and publishing packages
|
|
||||||
|
|
||||||
> This monorepo is set up with changesets versioning tool. See their [github repository](https://github.com/changesets/changesets) to learn more.
|
|
||||||
|
|
||||||
### What are Changesets?
|
|
||||||
|
|
||||||
Changesets are files that describe the intention of a contributor to bump a version of the package according to their changes. Changeset file holds two key bits of information: a version type (following semver), and change information to be added to a changelog.
|
|
||||||
|
|
||||||
### Adding changesets
|
|
||||||
|
|
||||||
In the root directory of the repo, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
npx changeset
|
|
||||||
```
|
|
||||||
|
|
||||||
Select the package that has been changed, the semver version that should be bumped with it and a description of your changes. Please make sure to add the most accurate but also concise information.
|
|
||||||
|
|
||||||
To learn about semantic versioning standards see [this semver doc page](https://semver.org/).
|
|
||||||
|
|||||||
76
README.md
@ -1,51 +1,31 @@
|
|||||||
<p align="center">
|
<h1 align="center">
|
||||||
Bugs, issues and suggestions re PortalJS framework
|
🌀 Portal.JS
|
||||||
<br />
|
<br />
|
||||||
<br /><a href="https://discord.gg/xfFDMPU9dC"><img src="https://dcbadge.vercel.app/api/server/xfFDMPU9dC" /></a>
|
Rapidly build rich data portals using a modern frontend framework
|
||||||
</p>
|
</h1>
|
||||||
|
|
||||||
## PortalJS framework
|
* [What is Portal.JS ?](#What-is-Portal.JS)
|
||||||
|
* [Features](#Features)
|
||||||
|
* [For developers](#For-developers)
|
||||||
|
* [Docs](#Docs)
|
||||||
|
* [Community](#Community)
|
||||||
|
* [Appendix](#Appendix)
|
||||||
|
* [What happened to Recline?](#What-happened-to-Recline?)
|
||||||
|
|
||||||
This repo and issue tracker are for
|
# What is Portal.JS
|
||||||
|
|
||||||
- PortalJS 🌀 - https://www.portaljs.com/
|
🌀 Portal.JS is a framework for rapidly building rich data portal frontends using a modern frontend approach. Portal.JS can be used to present a single dataset or build a full-scale data catalog/portal.
|
||||||
- DataHub Cloud ☁️ - https://datahub.io/
|
|
||||||
|
|
||||||
### Issues
|
Built in JavaScript and React on top of the popular [Next.js](https://nextjs.com/) framework. Portal.JS assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN](https://ckan.org/).
|
||||||
|
|
||||||
Found a bug: 👉 https://github.com/datopian/portaljs/issues/new
|
## Features
|
||||||
|
|
||||||
### Discussions
|
|
||||||
|
|
||||||
Got a suggestion, a question, want some support or just want to shoot the breeze 🙂
|
|
||||||
|
|
||||||
Head to the discussion forum: 👉 https://github.com/datopian/portaljs/discussions
|
|
||||||
|
|
||||||
### Chat on Discord
|
|
||||||
|
|
||||||
If you would prefer to get help via live chat check out our discord 👉
|
|
||||||
|
|
||||||
[Discord](https://discord.gg/xfFDMPU9dC)
|
|
||||||
|
|
||||||
### Docs
|
|
||||||
|
|
||||||
- For PortalJS go to https://www.portaljs.com/opensource
|
|
||||||
- For DataHub Cloud – https://datahub.io/docs
|
|
||||||
|
|
||||||
## PortalJS Cloud 🌀
|
|
||||||
|
|
||||||
PortalJS Cloud 🌀 is a platform for rapidly creating rich data portal and publishing systems using a modern frontend approach. PortalJS Cloud can be used to publish a single dataset or build a full-scale data catalog/portal.
|
|
||||||
|
|
||||||
PortalJS Cloud is built in JavaScript and React on top of the popular [Next.js](https://nextjs.org) framework. PortalJS Cloud assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN](https://ckan.org/), GitHub, Frictionless Data Packages and more.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. Wordpress) with a common internal API.
|
- 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. Wordpress) with a common internal API.
|
||||||
- 👩💻 Developer friendly: built with familiar frontend tech (JavaScript, React, Next.js).
|
- 👩💻 Developer friendly: built with familiar frontend tech (JavaScript, React, Next.js).
|
||||||
- 🔋 Batteries included: full set of portal components out of the box e.g. catalog search, dataset showcase, blog, etc.
|
- 🔋 Batteries included: full set of portal components out of the box e.g. catalog search, dataset showcase, blog, etc.
|
||||||
- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly.
|
- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly.
|
||||||
- 🧱 Extensible: quickly extend and develop/import your own React components
|
- 🧱 Extensible: quickly extend and develop/import your own React components
|
||||||
- 📝 Well documented: full set of documentation plus the documentation of Next.js.
|
- 📝 Well documented: full set of documentation plus the documentation of Next.js and Apollo.
|
||||||
|
|
||||||
### For developers
|
### For developers
|
||||||
|
|
||||||
@ -53,3 +33,25 @@ PortalJS Cloud is built in JavaScript and React on top of the popular [Next.js](
|
|||||||
- 🚀 Next.js framework: so everything in Next.js for free: Server Side Rendering, Static Site Generation, huge number of examples and integrations, etc.
|
- 🚀 Next.js framework: so everything in Next.js for free: Server Side Rendering, Static Site Generation, huge number of examples and integrations, etc.
|
||||||
- Server Side Rendering (SSR) => Unlimited number of pages, SEO and more whilst still using React.
|
- Server Side Rendering (SSR) => Unlimited number of pages, SEO and more whilst still using React.
|
||||||
- Static Site Generation (SSG) => Ultra-simple deployment, great performance, great lighthouse scores and more (good for small sites)
|
- Static Site Generation (SSG) => Ultra-simple deployment, great performance, great lighthouse scores and more (good for small sites)
|
||||||
|
|
||||||
|
#### **Check out the [Portal.JS website](https://portaljs.org/) for a gallery of live portals**
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
|
||||||
|
Access the Portal.JS documentation at:
|
||||||
|
|
||||||
|
https://portaljs.org/docs
|
||||||
|
|
||||||
|
- [Examples](https://portaljs.org/docs#examples)
|
||||||
|
|
||||||
|
# Community
|
||||||
|
|
||||||
|
If you have questions about anything related to Portal.JS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portal.js/discussions) or on our [Discord server](https://discord.gg/EeyfGrGu4U).
|
||||||
|
|
||||||
|
# Appendix
|
||||||
|
|
||||||
|
## What happened to Recline?
|
||||||
|
|
||||||
|
Portal.JS used to be Recline(JS). If you are looking for the old Recline codebase it still exists: see the [`recline` branch](https://github.com/datopian/portal.js/tree/recline). If you want context for the rename see [this issue](https://github.com/datopian/portal.js/issues/520).
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
This demo data portal is designed for https://hatespeechdata.com. It catalogs datasets annotated for hate speech, online abuse, and offensive language which are useful for training a natural language processing system to detect this online abuse.
|
This demo data portal is designed for https://hatespeechdata.com. It catalogs datasets annotated for hate speech, online abuse, and offensive language which are useful for training a natural language processing system to detect this online abuse.
|
||||||
|
|
||||||
The site is built on top of [PortalJS](https://portaljs.com/). It catalogs datasets and lists of offensive keywords. It also includes static pages. All of these are stored as markdown files inside the `content` folder.
|
The site is built on top of [PortalJS](https://portaljs.org/). It catalogs datasets and lists of offensive keywords. It also includes static pages. All of these are stored as markdown files inside the `content` folder.
|
||||||
|
|
||||||
- .md files inside `content/datasets/` will appear on the dataset list section of the homepage and be searchable as well as having a individual page in `datasets/<file name>`
|
- .md files inside `content/datasets/` will appear on the dataset list section of the homepage and be searchable as well as having a individual page in `datasets/<file name>`
|
||||||
- .md files inside `content/keywords/` will appear on the list of offensive keywords section of the homepage as well as having a individual page in `keywords/<file name>`
|
- .md files inside `content/keywords/` will appear on the list of offensive keywords section of the homepage as well as having a individual page in `keywords/<file name>`
|
||||||
@ -10,24 +10,12 @@ This is also a Next.JS project so you can use the following steps to run the web
|
|||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
To get started first clone this repo in your local machine like so:
|
To get started first install the npm dependencies:
|
||||||
|
|
||||||
```bash
|
|
||||||
npx create-next-app turing --example https://github.com/datopian/portaljs/tree/main/examples/turing
|
|
||||||
cd turing
|
|
||||||
```
|
|
||||||
|
|
||||||
Then install the npm dependencies:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, run this command, its related to [markdowndb](https://github.com/datopian/markdowndb):
|
|
||||||
```bash
|
|
||||||
npm run mddb
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, run the development server:
|
Next, run the development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -21,7 +21,7 @@ export function Footer() {
|
|||||||
<Container.Inner>
|
<Container.Inner>
|
||||||
<div className="flex flex-col items-center justify-between gap-6 sm:flex-row">
|
<div className="flex flex-col items-center justify-between gap-6 sm:flex-row">
|
||||||
<p className="text-sm font-medium text-zinc-800 dark:text-zinc-200">
|
<p className="text-sm font-medium text-zinc-800 dark:text-zinc-200">
|
||||||
Built with <a href='https://portaljs.com'>PortalJS 🌀</a>
|
Built with <a href='https://portaljs.org'>PortalJS 🌀</a>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-zinc-400 dark:text-zinc-500">
|
<p className="text-sm text-zinc-400 dark:text-zinc-500">
|
||||||
© {new Date().getFullYear()} Leon Derczynski. All rights
|
© {new Date().getFullYear()} Leon Derczynski. All rights
|
||||||
@ -140,7 +140,7 @@ function MobileNavigation(props) {
|
|||||||
</div>
|
</div>
|
||||||
<nav className="mt-6">
|
<nav className="mt-6">
|
||||||
<ul className="-my-2 divide-y divide-zinc-100 text-base text-zinc-800 dark:divide-zinc-100/5 dark:text-zinc-300">
|
<ul className="-my-2 divide-y divide-zinc-100 text-base text-zinc-800 dark:divide-zinc-100/5 dark:text-zinc-300">
|
||||||
<MobileNavItem href="https://github.com/datopian/portaljs/tree/main/examples/turing">
|
<MobileNavItem href="https://github.com/leondz/hatespeechdata">
|
||||||
View on Github <GithubIcon />
|
View on Github <GithubIcon />
|
||||||
</MobileNavItem>
|
</MobileNavItem>
|
||||||
</ul>
|
</ul>
|
||||||
@ -179,7 +179,7 @@ function DesktopNavigation(props) {
|
|||||||
return (
|
return (
|
||||||
<nav {...props}>
|
<nav {...props}>
|
||||||
<ul className="flex rounded-full bg-white/90 px-3 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10">
|
<ul className="flex rounded-full bg-white/90 px-3 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10">
|
||||||
<NavItem href="https://github.com/datopian/portaljs/tree/main/examples/turing">
|
<NavItem href="https://github.com/leondz/hatespeechdata">
|
||||||
View on Github <GithubIcon />
|
View on Github <GithubIcon />
|
||||||
</NavItem>
|
</NavItem>
|
||||||
</ul>
|
</ul>
|
||||||
@ -36,7 +36,7 @@ In the following page type `content/datasets/<name-of-the-file>.md`. if you want
|
|||||||
|
|
||||||
### Fill in content
|
### Fill in content
|
||||||
|
|
||||||
Copy the contents of `templates/dataset.md` or `templates/keywords.md` respectively to the camp below, filling out the fields with the correct data format. Everything below the second `---` will automatically get rendered into the page, so you may add any standard markdown fields e.g tables, headings, lists...
|
Copy the contents of `templates/dataset.md` or `templates/keywords.md` respectively to the camp below, filling out the fields with the correct data format
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -1,13 +1,13 @@
|
|||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import mdxmermaid from "mdx-mermaid";
|
import mdxmermaid from "mdx-mermaid";
|
||||||
import { h } from "hastscript";
|
import { h } from "hastscript";
|
||||||
import remarkCallouts from "@portaljs/remark-callouts";
|
import remarkCallouts from "@flowershow/remark-callouts";
|
||||||
import remarkEmbed from "@portaljs/remark-embed";
|
import remarkEmbed from "@flowershow/remark-embed";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import remarkSmartypants from "remark-smartypants";
|
import remarkSmartypants from "remark-smartypants";
|
||||||
import remarkToc from "remark-toc";
|
import remarkToc from "remark-toc";
|
||||||
import remarkWikiLink from "@portaljs/remark-wiki-link";
|
import remarkWikiLink from "@flowershow/remark-wiki-link";
|
||||||
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||||
import rehypeKatex from "rehype-katex";
|
import rehypeKatex from "rehype-katex";
|
||||||
import rehypeSlug from "rehype-slug";
|
import rehypeSlug from "rehype-slug";
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { MarkdownDB } from "mddb";
|
import { MarkdownDB } from "@flowershow/markdowndb";
|
||||||
|
|
||||||
const dbPath = "markdown.db";
|
const dbPath = "markdown.db";
|
||||||
|
|
||||||
BIN
examples/alan-turing-portal/markdown.db
Normal file
@ -12,6 +12,11 @@
|
|||||||
},
|
},
|
||||||
"browserslist": "defaults, not ie <= 11",
|
"browserslist": "defaults, not ie <= 11",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@flowershow/core": "^0.4.10",
|
||||||
|
"@flowershow/markdowndb": "^0.1.1",
|
||||||
|
"@flowershow/remark-callouts": "^1.0.0",
|
||||||
|
"@flowershow/remark-embed": "^1.0.0",
|
||||||
|
"@flowershow/remark-wiki-link": "^1.1.2",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@mapbox/rehype-prism": "^0.8.0",
|
"@mapbox/rehype-prism": "^0.8.0",
|
||||||
@ -19,10 +24,6 @@
|
|||||||
"@mdx-js/react": "^2.1.5",
|
"@mdx-js/react": "^2.1.5",
|
||||||
"@next/mdx": "^13.0.2",
|
"@next/mdx": "^13.0.2",
|
||||||
"@opentelemetry/api": "^1.4.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
"@portaljs/core": "^1.0.5",
|
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
|
||||||
"@portaljs/remark-embed": "^1.0.4",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.4",
|
"@tailwindcss/typography": "^0.5.4",
|
||||||
"@tanstack/react-table": "^8.8.5",
|
"@tanstack/react-table": "^8.8.5",
|
||||||
@ -34,7 +35,6 @@
|
|||||||
"focus-visible": "^5.2.0",
|
"focus-visible": "^5.2.0",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"hastscript": "^7.2.0",
|
"hastscript": "^7.2.0",
|
||||||
"mddb": "^0.1.9",
|
|
||||||
"mdx-mermaid": "^2.0.0-rc7",
|
"mdx-mermaid": "^2.0.0-rc7",
|
||||||
"mermaid": "^10.1.0",
|
"mermaid": "^10.1.0",
|
||||||
"next": "13.2.1",
|
"next": "13.2.1",
|
||||||
@ -60,12 +60,12 @@
|
|||||||
"tailwindcss": "^3.3.0"
|
"tailwindcss": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "18.16.0",
|
|
||||||
"@types/react": "18.2.0",
|
|
||||||
"@types/react-dom": "18.2.0",
|
|
||||||
"eslint": "8.26.0",
|
"eslint": "8.26.0",
|
||||||
"eslint-config-next": "13.0.2",
|
"eslint-config-next": "13.0.2",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.6"
|
"prettier-plugin-tailwindcss": "^0.2.6",
|
||||||
|
"@types/node": "18.16.0",
|
||||||
|
"@types/react": "18.2.0",
|
||||||
|
"@types/react-dom": "18.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,7 +5,8 @@ import { MDXRemote } from 'next-mdx-remote'
|
|||||||
import { Card } from '../components/Card'
|
import { Card } from '../components/Card'
|
||||||
import Head from 'next/head'
|
import Head from 'next/head'
|
||||||
import parse from '../lib/markdown'
|
import parse from '../lib/markdown'
|
||||||
import { Mermaid } from '@portaljs/core';
|
import { Mermaid } from '@flowershow/core';
|
||||||
|
import { Header } from '../components/Header';
|
||||||
|
|
||||||
export const getStaticProps = async ({ params }) => {
|
export const getStaticProps = async ({ params }) => {
|
||||||
const urlPath = params.slug ? params.slug.join('/') : ''
|
const urlPath = params.slug ? params.slug.join('/') : ''
|
||||||
@ -81,13 +82,15 @@ export default function DRDPage({ mdxSource }) {
|
|||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Header />
|
||||||
<Head>
|
<Head>
|
||||||
<title>{meta.title}</title>
|
<title>{meta.title}</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Container className="mt-9 relative">
|
<Container className="mt-16 lg:mt-32 relative">
|
||||||
|
<Header />
|
||||||
<article>
|
<article>
|
||||||
<header className="flex flex-col">
|
<header className="flex flex-col">
|
||||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
<h1 className="mt-6 text-4xl font-bold tracking-tight text-zinc-800 dark:text-zinc-100 sm:text-5xl">
|
||||||
{meta.title}
|
{meta.title}
|
||||||
</h1>
|
</h1>
|
||||||
<Card as="article">
|
<Card as="article">
|
||||||
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 566 B |
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**🚩 UPDATE April 2023: This example is now deprecated - though still works!. Please use the [new CKAN examples](https://github.com/datopian/portaljs/tree/main/examples)**
|
**🚩 UPDATE April 2023: This example is now deprecated - though still works!. Please use the [new CKAN examples](https://github.com/datopian/portaljs/tree/main/examples)**
|
||||||
|
|
||||||
This example shows how you can build a full data portal using a CKAN Backend with a Next.JS Frontend powered by Apollo, a full fledged guide is available as a [blog post](https://portaljs.com/blog/example-ckan-2021)
|
This example shows how you can build a full data portal using a CKAN Backend with a Next.JS Frontend powered by Apollo, a full fledged guide is available as a [blog post](https://portaljs.org/blog/example-ckan-2021)
|
||||||
|
|
||||||
## Developers
|
## Developers
|
||||||
|
|
||||||
|
|||||||
1
examples/ckan-example/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
DMS=https://demo.dev.datopian.com
|
||||||
@ -1,7 +1,7 @@
|
|||||||
This is a repo intended to serve as an example of a data catalog that get its data from a CKAN Instance.
|
This is a repo intended to serve as an example of a data catalog that get its data from a CKAN Instance.
|
||||||
|
|
||||||
```
|
```
|
||||||
npx create-next-app <app-name> --example https://github.com/datopian/datahub/tree/main/examples/ckan-ssg
|
npx create-next-app <app-name> --example https://github.com/datopian/portaljs/tree/main/examples/ckan-example
|
||||||
cd <app-name>
|
cd <app-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ npm run dev
|
|||||||
|
|
||||||
Congratulations, you now have something similar to this running on `http://localhost:4200`
|
Congratulations, you now have something similar to this running on `http://localhost:4200`
|
||||||

|

|
||||||
If you go to any one of those pages by clicking on `More info` you will see something similar to this
|
If yo go to any one of those pages by clicking on `More info` you will see something similar to this
|
||||||

|

|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
@ -10,8 +10,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@portaljs/ckan": "^0.0.2",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"next": "13.3.1",
|
"next": "13.3.1",
|
||||||
"next-seo": "^6.0.0",
|
"next-seo": "^6.0.0",
|
||||||
"octokit": "^2.0.14",
|
"octokit": "^2.0.14",
|
||||||
@ -22,14 +20,14 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/node": "18.16.0",
|
|
||||||
"@types/react": "18.0.38",
|
|
||||||
"@types/react-dom": "18.0.11",
|
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "8.39.0",
|
|
||||||
"eslint-config-next": "13.3.1",
|
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"typescript": "5.0.4"
|
"eslint": "8.39.0",
|
||||||
|
"eslint-config-next": "13.3.1",
|
||||||
|
"typescript": "5.0.4",
|
||||||
|
"@types/node": "18.16.0",
|
||||||
|
"@types/react": "18.0.38",
|
||||||
|
"@types/react-dom": "18.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,9 +11,8 @@ import {
|
|||||||
ServerIcon,
|
ServerIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
} from '@heroicons/react/20/solid';
|
} from '@heroicons/react/20/solid';
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
const dms = getConfig().publicRuntimeConfig.DMS;
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -26,12 +25,14 @@ const formatter = new Intl.DateTimeFormat('en-US', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const ckan = new CKAN(backend_url)
|
|
||||||
const { dataset } = context.query;
|
const { dataset } = context.query;
|
||||||
const _dataset = await ckan.getDatasetDetails(dataset as string)
|
const response = await fetch(
|
||||||
|
`${dms}/api/3/action/package_show?id=${dataset}`
|
||||||
|
);
|
||||||
|
const _dataset = await response.json();
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
dataset: _dataset,
|
dataset: _dataset.result,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import getConfig from 'next/config';
|
import getConfig from 'next/config';
|
||||||
import styles from './index.module.css';
|
import styles from './index.module.css';
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS
|
const dms = getConfig().publicRuntimeConfig.DMS
|
||||||
|
|
||||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@ -16,11 +15,12 @@ const formatter = new Intl.DateTimeFormat('en-US', {
|
|||||||
|
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
export async function getServerSideProps() {
|
||||||
const ckan = new CKAN(backend_url)
|
const response = await fetch(`${dms}/api/3/action/package_search`)
|
||||||
const { datasets } = await ckan.packageSearch({ limit: 1000, offset: 0, groups:[], orgs: [], tags: []})
|
const datasets = await response.json()
|
||||||
const datasetsWithDetails = await Promise.all(datasets.map(async (dataset) => {
|
const datasetsWithDetails = await Promise.all(datasets.result.results.map(async (dataset) => {
|
||||||
const _dataset = await ckan.getDatasetDetails(dataset.name)
|
const response = await fetch(`${dms}/api/3/action/package_show?id=` + dataset.name)
|
||||||
return _dataset
|
const json = await response.json()
|
||||||
|
return json.result
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -79,7 +79,7 @@ export function Index({ datasets }) {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-gray-200">
|
<tbody className="divide-y divide-gray-200">
|
||||||
{datasets.map((dataset) => (
|
{datasets.map((dataset) => (
|
||||||
<tr key={dataset.name}>
|
<tr>
|
||||||
<td className="px-3 py-4 text-sm text-gray-500">
|
<td className="px-3 py-4 text-sm text-gray-500">
|
||||||
{dataset.title}
|
{dataset.title}
|
||||||
</td>
|
</td>
|
||||||
@ -1,21 +0,0 @@
|
|||||||
import { MDXRemote } from 'next-mdx-remote';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { Mermaid } from '@portaljs/core';
|
|
||||||
|
|
||||||
// Custom components/renderers to pass to MDX.
|
|
||||||
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
|
|
||||||
// to handle import statements. Instead, you must include components in scope
|
|
||||||
// here.
|
|
||||||
const components = {
|
|
||||||
Table: dynamic(() => import('@portaljs/components').then(mod => mod.Table)),
|
|
||||||
Catalog: dynamic(() => import('@portaljs/components').then(mod => mod.Catalog)),
|
|
||||||
FlatUiTable: dynamic(() => import('@portaljs/components').then(mod => mod.FlatUiTable)),
|
|
||||||
mermaid: Mermaid,
|
|
||||||
Vega: dynamic(() => import('@portaljs/components').then(mod => mod.Vega)),
|
|
||||||
VegaLite: dynamic(() => import('@portaljs/components').then(mod => mod.VegaLite)),
|
|
||||||
LineChart: dynamic(() => import('@portaljs/components').then(mod => mod.LineChart)),
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
export default function DRD({ source }: { source: any }) {
|
|
||||||
return <MDXRemote {...source} components={components} />;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
# Test
|
|
||||||
|
|
||||||
Test Data Rich Stories
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import matter from "gray-matter";
|
|
||||||
import mdxmermaid from "mdx-mermaid";
|
|
||||||
import { h } from "hastscript";
|
|
||||||
import remarkCallouts from "@portaljs/remark-callouts";
|
|
||||||
import remarkEmbed from "@portaljs/remark-embed";
|
|
||||||
import remarkGfm from "remark-gfm";
|
|
||||||
import remarkMath from "remark-math";
|
|
||||||
import remarkSmartypants from "remark-smartypants";
|
|
||||||
import remarkToc from "remark-toc";
|
|
||||||
import remarkWikiLink from "@portaljs/remark-wiki-link";
|
|
||||||
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
|
||||||
import rehypeKatex from "rehype-katex";
|
|
||||||
import rehypeSlug from "rehype-slug";
|
|
||||||
import rehypePrismPlus from "rehype-prism-plus";
|
|
||||||
|
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse a markdown or MDX file to an MDX source form + front matter data
|
|
||||||
*
|
|
||||||
* @source: the contents of a markdown or mdx file
|
|
||||||
* @format: used to indicate to next-mdx-remote which format to use (md or mdx)
|
|
||||||
* @returns: { mdxSource: mdxSource, frontMatter: ...}
|
|
||||||
*/
|
|
||||||
const parse = async function (source, format, scope) {
|
|
||||||
const { content, data, excerpt } = matter(source, {
|
|
||||||
excerpt: (file, options) => {
|
|
||||||
// Generate an excerpt for the file
|
|
||||||
file.excerpt = file.content.split("\n\n")[0];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const mdxSource = await serialize(
|
|
||||||
{ value: content, path: format },
|
|
||||||
{
|
|
||||||
// Optionally pass remark/rehype plugins
|
|
||||||
mdxOptions: {
|
|
||||||
remarkPlugins: [
|
|
||||||
remarkEmbed,
|
|
||||||
remarkGfm,
|
|
||||||
[remarkSmartypants, { quotes: false, dashes: "oldschool" }],
|
|
||||||
remarkMath,
|
|
||||||
remarkCallouts,
|
|
||||||
remarkWikiLink,
|
|
||||||
[
|
|
||||||
remarkToc,
|
|
||||||
{
|
|
||||||
heading: "Table of contents",
|
|
||||||
tight: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[mdxmermaid, {}],
|
|
||||||
],
|
|
||||||
rehypePlugins: [
|
|
||||||
rehypeSlug,
|
|
||||||
[
|
|
||||||
rehypeAutolinkHeadings,
|
|
||||||
{
|
|
||||||
properties: { className: 'heading-link' },
|
|
||||||
test(element) {
|
|
||||||
return (
|
|
||||||
["h2", "h3", "h4", "h5", "h6"].includes(element.tagName) &&
|
|
||||||
element.properties?.id !== "table-of-contents" &&
|
|
||||||
element.properties?.className !== "blockquote-heading"
|
|
||||||
);
|
|
||||||
},
|
|
||||||
content() {
|
|
||||||
return [
|
|
||||||
h(
|
|
||||||
"svg",
|
|
||||||
{
|
|
||||||
xmlns: "http:www.w3.org/2000/svg",
|
|
||||||
fill: "#ab2b65",
|
|
||||||
viewBox: "0 0 20 20",
|
|
||||||
className: "w-5 h-5",
|
|
||||||
},
|
|
||||||
[
|
|
||||||
h("path", {
|
|
||||||
fillRule: "evenodd",
|
|
||||||
clipRule: "evenodd",
|
|
||||||
d: "M9.493 2.853a.75.75 0 00-1.486-.205L7.545 6H4.198a.75.75 0 000 1.5h3.14l-.69 5H3.302a.75.75 0 000 1.5h3.14l-.435 3.148a.75.75 0 001.486.205L7.955 14h2.986l-.434 3.148a.75.75 0 001.486.205L12.456 14h3.346a.75.75 0 000-1.5h-3.14l.69-5h3.346a.75.75 0 000-1.5h-3.14l.435-3.147a.75.75 0 00-1.486-.205L12.045 6H9.059l.434-3.147zM8.852 7.5l-.69 5h2.986l.69-5H8.852z",
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[rehypeKatex, { output: "mathml" }],
|
|
||||||
[rehypePrismPlus, { ignoreMissing: true }],
|
|
||||||
],
|
|
||||||
format,
|
|
||||||
},
|
|
||||||
scope,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
mdxSource: mdxSource,
|
|
||||||
frontMatter: data,
|
|
||||||
excerpt,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default parse;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
reactStrictMode: true,
|
|
||||||
publicRuntimeConfig: {
|
|
||||||
DMS: process.env.DMS
|
|
||||||
? process.env.DMS.replace(/\/?$/, '')
|
|
||||||
: 'https://demo.dev.datopian.com/',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = nextConfig;
|
|
||||||
13799
examples/ckan/package-lock.json
generated
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "ckan",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "next dev",
|
|
||||||
"prebuild": "npm run mddb",
|
|
||||||
"build": "next build",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "next lint",
|
|
||||||
"mddb": "mddb ./content"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
|
||||||
"@heroicons/react": "^2.0.18",
|
|
||||||
"@portaljs/ckan": "^0.0.2",
|
|
||||||
"@portaljs/components": "0.1.6",
|
|
||||||
"@portaljs/core": "^1.0.5",
|
|
||||||
"@portaljs/remark-callouts": "^1.0.5",
|
|
||||||
"@portaljs/remark-embed": "^1.0.4",
|
|
||||||
"@portaljs/remark-wiki-link": "^1.0.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
|
||||||
"@types/node": "20.2.3",
|
|
||||||
"@types/react": "18.2.6",
|
|
||||||
"@types/react-dom": "18.2.4",
|
|
||||||
"autoprefixer": "10.4.14",
|
|
||||||
"eslint": "8.41.0",
|
|
||||||
"eslint-config-next": "13.4.3",
|
|
||||||
"isomorphic-unfetch": "^4.0.2",
|
|
||||||
"mddb": "^0.1.9",
|
|
||||||
"next": "13.4.3",
|
|
||||||
"next-mdx-remote": "^4.4.1",
|
|
||||||
"papaparse": "^5.4.1",
|
|
||||||
"postcss": "8.4.23",
|
|
||||||
"react": "18.2.0",
|
|
||||||
"react-dom": "18.2.0",
|
|
||||||
"react-query": "^3.39.3",
|
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
|
||||||
"rehype-katex": "^6.0.3",
|
|
||||||
"rehype-prism-plus": "^1.5.1",
|
|
||||||
"rehype-slug": "^5.1.0",
|
|
||||||
"remark-math": "^5.1.1",
|
|
||||||
"remark-smartypants": "^2.0.0",
|
|
||||||
"remark-toc": "^8.0.1",
|
|
||||||
"tailwindcss": "3.3.2",
|
|
||||||
"typescript": "5.0.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
import Head from "next/head";
|
|
||||||
import { CKAN, Dataset } from "@portaljs/ckan";
|
|
||||||
import {
|
|
||||||
ChevronRightIcon,
|
|
||||||
HomeIcon,
|
|
||||||
PaperClipIcon,
|
|
||||||
} from "@heroicons/react/20/solid";
|
|
||||||
import Link from "next/link";
|
|
||||||
import getConfig from "next/config";
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS
|
|
||||||
|
|
||||||
export const getServerSideProps = async (context: any) => {
|
|
||||||
try {
|
|
||||||
const datasetName = context.params?.dataset;
|
|
||||||
if (!datasetName) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const dataset = await ckan.getDatasetDetails(datasetName as string);
|
|
||||||
if (!dataset) {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
props: { dataset },
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
return {
|
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function DatasetPage({
|
|
||||||
dataset,
|
|
||||||
}: {
|
|
||||||
dataset: Dataset;
|
|
||||||
}): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<title>{`${dataset.title || dataset.name} - Dataset`}</title>
|
|
||||||
<meta name="description" content="Generated by create next app" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
</Head>
|
|
||||||
<main className="flex min-h-screen flex-col items-center justify-between p-24 bg-zinc-900">
|
|
||||||
<div className="bg-white p-8 my-4 rounded-lg">
|
|
||||||
<nav className="flex px-4 py-8" aria-label="Breadcrumb">
|
|
||||||
<ol role="list" className="flex items-center space-x-4">
|
|
||||||
<li>
|
|
||||||
<div>
|
|
||||||
<Link href="/" className="text-gray-400 hover:text-gray-500">
|
|
||||||
<HomeIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Home</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ChevronRightIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
|
|
||||||
aria-current={"page"}
|
|
||||||
>
|
|
||||||
{dataset.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
{dataset && (
|
|
||||||
<div>
|
|
||||||
<div className="px-4 sm:px-0">
|
|
||||||
<h3 className="text-base font-semibold leading-7 text-gray-900">
|
|
||||||
{dataset.title || dataset.name}
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 max-w-2xl text-sm leading-6 text-gray-500">
|
|
||||||
Dataset details
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 border-t border-gray-100">
|
|
||||||
<dl className="divide-y divide-gray-100">
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Title
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.title}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
{dataset.tags && dataset.tags.length > 0 && (
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Tags
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.tags.map((tag) => tag.display_name).join(", ")}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{dataset.tags && dataset.tags.length > 0 && (
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
URL
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.url}
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
{dataset.notes && (
|
|
||||||
<>
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Description
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
|
||||||
{dataset.notes}
|
|
||||||
</dd>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
|
|
||||||
<dt className="text-sm font-medium leading-6 text-gray-900">
|
|
||||||
Files
|
|
||||||
</dt>
|
|
||||||
<dd className="mt-2 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
|
|
||||||
<ul
|
|
||||||
role="list"
|
|
||||||
className="divide-y divide-gray-100 rounded-md border border-gray-200"
|
|
||||||
>
|
|
||||||
{dataset.resources.map((resource) => (
|
|
||||||
<li key={resource.id} className="flex items-center justify-between py-4 pl-4 pr-5 text-sm leading-6">
|
|
||||||
<div className="flex w-0 flex-1 items-center">
|
|
||||||
<PaperClipIcon
|
|
||||||
className="h-5 w-5 flex-shrink-0 text-gray-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
<div className="ml-4 flex min-w-0 flex-1 gap-2">
|
|
||||||
<span className="truncate font-medium">
|
|
||||||
{resource.name || resource.id}
|
|
||||||
</span>
|
|
||||||
<span className="flex-shrink-0 text-gray-400">
|
|
||||||
{resource.size}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-4 flex-shrink-0">
|
|
||||||
<a
|
|
||||||
href={resource.url}
|
|
||||||
className="font-medium hover:text-indigo-500 mr-4"
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import fetch from 'isomorphic-unfetch';
|
|
||||||
|
|
||||||
const Cors = async (req: any, res: any) => {
|
|
||||||
const { url } = req.query;
|
|
||||||
try {
|
|
||||||
const resProxy = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
Range: 'bytes=0-5132288',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await resProxy.text();
|
|
||||||
return res.status(200).send(data);
|
|
||||||
} catch (error: any) {
|
|
||||||
res.status(400).send(error.toString());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Cors;
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import {
|
|
||||||
CKAN,
|
|
||||||
DatasetSearchForm,
|
|
||||||
ListOfDatasets,
|
|
||||||
PackageSearchOptions,
|
|
||||||
Organization,
|
|
||||||
Group,
|
|
||||||
} from '@portaljs/ckan';
|
|
||||||
import getConfig from 'next/config';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
|
||||||
|
|
||||||
export async function getServerSideProps() {
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const groups = await ckan.getGroupsWithDetails();
|
|
||||||
const orgs = await ckan.getOrgsWithDetails();
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
groups,
|
|
||||||
orgs,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Home({
|
|
||||||
orgs,
|
|
||||||
groups,
|
|
||||||
}: {
|
|
||||||
orgs: Organization[];
|
|
||||||
groups: Group[];
|
|
||||||
}) {
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const [options, setOptions] = useState<PackageSearchOptions>({
|
|
||||||
offset: 0,
|
|
||||||
limit: 5,
|
|
||||||
tags: [],
|
|
||||||
groups: [],
|
|
||||||
orgs: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<main className="py-12 bg-zinc-900">
|
|
||||||
<DatasetSearchForm
|
|
||||||
options={options}
|
|
||||||
setOptions={setOptions}
|
|
||||||
groups={groups}
|
|
||||||
orgs={orgs}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="bg-white p-8 mx-auto my-4 rounded-lg"
|
|
||||||
style={{ width: 'min(1100px, 95vw)' }}
|
|
||||||
>
|
|
||||||
<ListOfDatasets
|
|
||||||
options={options}
|
|
||||||
setOptions={setOptions}
|
|
||||||
ckan={ckan}
|
|
||||||
/>{' '}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import { existsSync, promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import parse from '../../lib/markdown';
|
|
||||||
|
|
||||||
import DataRichDocument from '../../components/DataRichDocument';
|
|
||||||
import clientPromise from '../../lib/mddb';
|
|
||||||
import getConfig from 'next/config';
|
|
||||||
import { CKAN } from '@portaljs/ckan';
|
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
|
||||||
const contentDir = path.join(process.cwd(), '/content/');
|
|
||||||
const contentFolders = await fs.readdir(contentDir, 'utf8');
|
|
||||||
const paths = contentFolders.map((folder: string) => ({
|
|
||||||
params: { path: [folder.split('.')[0]] },
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
fallback: false,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const backend_url = getConfig().publicRuntimeConfig.DMS;
|
|
||||||
|
|
||||||
export const getStaticProps = async (context) => {
|
|
||||||
const mddb = await clientPromise;
|
|
||||||
const storyFile = await mddb.getFileByUrl(context.params.path);
|
|
||||||
const md = await fs.readFile(
|
|
||||||
`${process.cwd()}/${storyFile.file_path}`,
|
|
||||||
'utf8'
|
|
||||||
);
|
|
||||||
|
|
||||||
const ckan = new CKAN(backend_url);
|
|
||||||
const datasets = storyFile.metadata.datasets ? await Promise.all(
|
|
||||||
storyFile.metadata.datasets.map(
|
|
||||||
async (datasetName: string) => await ckan.getDatasetDetails(datasetName)
|
|
||||||
)
|
|
||||||
) : [];
|
|
||||||
const orgs = storyFile.metadata.orgs ? await Promise.all(
|
|
||||||
storyFile.metadata.orgs.map(
|
|
||||||
async (orgName: string) => await ckan.getOrgDetails(orgName)
|
|
||||||
)
|
|
||||||
) : [];
|
|
||||||
|
|
||||||
let { mdxSource, frontMatter } = await parse(md, '.mdx', { datasets, orgs });
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
mdxSource,
|
|
||||||
frontMatter: JSON.stringify(frontMatter),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function DatasetPage({ mdxSource, frontMatter }) {
|
|
||||||
frontMatter = JSON.parse(frontMatter);
|
|
||||||
return (
|
|
||||||
<main className="flex min-h-screen flex-col justify-between p-16 bg-zinc-900">
|
|
||||||
<div className="bg-white p-8 my-4 rounded-lg">
|
|
||||||
<div className="prose mx-auto py-8">
|
|
||||||
<header>
|
|
||||||
<div className="mb-6">
|
|
||||||
<>
|
|
||||||
<h1 className="mb-2">{frontMatter.title}</h1>
|
|
||||||
{frontMatter.author && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Author: </span>
|
|
||||||
<span className="my-0">{frontMatter.author}</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.description && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Description: </span>
|
|
||||||
<span className="description my-0">
|
|
||||||
{frontMatter.description}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.modified && (
|
|
||||||
<p className="my-0">
|
|
||||||
<span className="font-semibold">Modified: </span>
|
|
||||||
<span className="description my-0">
|
|
||||||
{new Date(frontMatter.modified).toLocaleDateString()}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{frontMatter.files && (
|
|
||||||
<section className="py-6">
|
|
||||||
<h2 className="mt-0">Data files</h2>
|
|
||||||
<table className="table-auto">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>File</th>
|
|
||||||
<th>Format</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{frontMatter.files.map((f) => {
|
|
||||||
const fileName = f.split('/').slice(-1);
|
|
||||||
return (
|
|
||||||
<tr key={`resources-list-${f}`}>
|
|
||||||
<td>
|
|
||||||
<a target="_blank" href={f}>
|
|
||||||
{fileName}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{fileName[0]
|
|
||||||
.split('.')
|
|
||||||
.slice(-1)[0]
|
|
||||||
.toUpperCase()}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<DataRichDocument source={mdxSource} />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@import "@portaljs/remark-callouts/styles.css";
|
|
||||||
|
|
||||||
/* mathjax */
|
|
||||||
.math-inline > mjx-container > svg {
|
|
||||||
display: inline;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* smooth scrolling in modern browsers */
|
|
||||||
html {
|
|
||||||
scroll-behavior: smooth !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* tooltip fade-out clip */
|
|
||||||
.tooltip-body::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
|
|
||||||
height: 1.2rem; /* ($top + $height)/$line-height is the number of lines we want to clip tooltip text at*/
|
|
||||||
width: 10rem;
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
rgba(255, 255, 255, 0),
|
|
||||||
rgba(255, 255, 255, 1) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(h2, h3, h4, h5, h6):not(.blogitem-title) {
|
|
||||||
margin-left: -2rem !important;
|
|
||||||
padding-left: 2rem !important;
|
|
||||||
scroll-margin-top: 4.5rem;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading-link {
|
|
||||||
padding: 1px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
margin: auto 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
background: #1e293b;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.light .heading-link {
|
|
||||||
/* border: 1px solid #ab2b65; */
|
|
||||||
/* background: none; */
|
|
||||||
background: #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:is(h2, h3, h4, h5, h6):not(.blogitem-title):hover .heading-link {
|
|
||||||
opacity: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading-link svg {
|
|
||||||
transform: scale(0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 640px) {
|
|
||||||
.heading-link {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es5",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": false,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"incremental": true,
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
|
||||||
"exclude": ["node_modules"]
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
This example creates a portal/showcase for a single dataset. The dataset should be a [Frictionless dataset (data package)][fd] i.e. there should be a `datapackage.json`.
|
This example creates a portal/showcase for a single dataset. The dataset should be a [Frictionless dataset (data package)][fd] i.e. there should be a `datapackage.json`.
|
||||||
|
|
||||||
[fd]: https://specs.frictionlessdata.io/data-package/
|
[fd]: https://frictionlessdata.io/data-packages/
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,11 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-generic-ballot",
|
|
||||||
"name": "congress-generic-ballot",
|
|
||||||
"displayName": "congress-generic-<span class=\"lastword\">ballot</span>",
|
|
||||||
"articles": [
|
|
||||||
{
|
|
||||||
"date": "2023-05-11T14:35:40.000Z",
|
|
||||||
"title": "Do Voters Want Democrats Or Republicans In Congress?",
|
|
||||||
"url": "https://projects.fivethirtyeight.com/congress-generic-ballot-polls/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"https://projects.fivethirtyeight.com/generic-ballot-data/generic_polllist.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls/data/generic_ballot_averages.csv"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-forecasts",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-forecasts",
|
||||||
"name": "nba-forecasts",
|
"name": "nba-forecasts",
|
||||||
"displayName": "nba-<span class=\"lastword\">forecasts</span>",
|
"displayName": "nba-<span class=\"lastword\">forecasts</span>",
|
||||||
"articles": [
|
"articles": [
|
||||||
{
|
{
|
||||||
"date": "2023-05-11T11:15:46.000Z",
|
"date": "2023-05-08T22:33:43.000Z",
|
||||||
"title": "2022-23 NBA Predictions",
|
"title": "2022-23 NBA Predictions",
|
||||||
"url": "https://projects.fivethirtyeight.com/2023-nba-predictions/"
|
"url": "https://projects.fivethirtyeight.com/2023-nba-predictions/"
|
||||||
}
|
}
|
||||||
@ -31,13 +15,61 @@
|
|||||||
"https://projects.fivethirtyeight.com/nba-model/nba_elo_latest.csv"
|
"https://projects.fivethirtyeight.com/nba-model/nba_elo_latest.csv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fivethirtyeight/data/tree/master/soccer-spi",
|
||||||
|
"name": "soccer-spi",
|
||||||
|
"displayName": "soccer-<span class=\"lastword\">spi</span>",
|
||||||
|
"articles": [
|
||||||
|
{
|
||||||
|
"date": "2023-05-08T22:17:18.000Z",
|
||||||
|
"title": "Club Soccer Predictions",
|
||||||
|
"url": "https://projects.fivethirtyeight.com/soccer-predictions/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"https://projects.fivethirtyeight.com/soccer-api/club/spi_matches.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/soccer-api/club/spi_matches_latest.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/soccer-api/club/spi_global_rankings.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/soccer-api/international/spi_matches_intl.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/soccer-api/international/spi_global_rankings_intl.csv"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
|
||||||
|
"name": "polls",
|
||||||
|
"displayName": "<span class=\"lastword\">polls</span>",
|
||||||
|
"articles": [
|
||||||
|
{
|
||||||
|
"date": "2023-05-08T20:36:59.000Z",
|
||||||
|
"title": "Latest Polls",
|
||||||
|
"url": "https://projects.fivethirtyeight.com/polls/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-raptor",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-raptor",
|
||||||
"name": "nba-raptor",
|
"name": "nba-raptor",
|
||||||
"displayName": "nba-<span class=\"lastword\">raptor</span>",
|
"displayName": "nba-<span class=\"lastword\">raptor</span>",
|
||||||
"articles": [
|
"articles": [
|
||||||
{
|
{
|
||||||
"date": "2023-05-11T11:13:20.000Z",
|
"date": "2023-05-08T11:15:48.000Z",
|
||||||
"title": "The Best NBA Players, According To RAPTOR",
|
"title": "The Best NBA Players, According To RAPTOR",
|
||||||
"rowspan": 3,
|
"rowspan": 3,
|
||||||
"url": "https://projects.fivethirtyeight.com/nba-player-ratings/"
|
"url": "https://projects.fivethirtyeight.com/nba-player-ratings/"
|
||||||
@ -60,32 +92,13 @@
|
|||||||
"https://projects.fivethirtyeight.com/nba-model/2023/latest_RAPTOR_by_player.csv"
|
"https://projects.fivethirtyeight.com/nba-model/2023/latest_RAPTOR_by_player.csv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/soccer-spi",
|
|
||||||
"name": "soccer-spi",
|
|
||||||
"displayName": "soccer-<span class=\"lastword\">spi</span>",
|
|
||||||
"articles": [
|
|
||||||
{
|
|
||||||
"date": "2023-05-11T05:25:51.000Z",
|
|
||||||
"title": "Club Soccer Predictions",
|
|
||||||
"url": "https://projects.fivethirtyeight.com/soccer-predictions/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"https://projects.fivethirtyeight.com/soccer-api/club/spi_matches.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/soccer-api/club/spi_matches_latest.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/soccer-api/club/spi_global_rankings.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/soccer-api/international/spi_matches_intl.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/soccer-api/international/spi_global_rankings_intl.csv"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nhl-forecasts",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/nhl-forecasts",
|
||||||
"name": "nhl-forecasts",
|
"name": "nhl-forecasts",
|
||||||
"displayName": "nhl-<span class=\"lastword\">forecasts</span>",
|
"displayName": "nhl-<span class=\"lastword\">forecasts</span>",
|
||||||
"articles": [
|
"articles": [
|
||||||
{
|
{
|
||||||
"date": "2023-05-11T04:53:22.000Z",
|
"date": "2023-05-08T04:18:20.000Z",
|
||||||
"title": "2022-23 NHL Predictions",
|
"title": "2022-23 NHL Predictions",
|
||||||
"url": "https://projects.fivethirtyeight.com/2023-nhl-predictions/"
|
"url": "https://projects.fivethirtyeight.com/2023-nhl-predictions/"
|
||||||
}
|
}
|
||||||
@ -101,7 +114,7 @@
|
|||||||
"displayName": "mlb-<span class=\"lastword\">elo</span>",
|
"displayName": "mlb-<span class=\"lastword\">elo</span>",
|
||||||
"articles": [
|
"articles": [
|
||||||
{
|
{
|
||||||
"date": "2023-05-11T02:35:49.000Z",
|
"date": "2023-05-08T02:25:55.000Z",
|
||||||
"title": "2023 MLB Predictions",
|
"title": "2023 MLB Predictions",
|
||||||
"url": "https://projects.fivethirtyeight.com/2023-mlb-predictions/"
|
"url": "https://projects.fivethirtyeight.com/2023-mlb-predictions/"
|
||||||
}
|
}
|
||||||
@ -111,6 +124,22 @@
|
|||||||
"https://projects.fivethirtyeight.com/mlb-api/mlb_elo_latest.csv"
|
"https://projects.fivethirtyeight.com/mlb-api/mlb_elo_latest.csv"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-generic-ballot",
|
||||||
|
"name": "congress-generic-ballot",
|
||||||
|
"displayName": "congress-generic-<span class=\"lastword\">ballot</span>",
|
||||||
|
"articles": [
|
||||||
|
{
|
||||||
|
"date": "2023-05-02T13:48:41.000Z",
|
||||||
|
"title": "Do Voters Want Democrats Or Republicans In Congress?",
|
||||||
|
"url": "https://projects.fivethirtyeight.com/congress-generic-ballot-polls/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"https://projects.fivethirtyeight.com/generic-ballot-data/generic_polllist.csv",
|
||||||
|
"https://projects.fivethirtyeight.com/polls/data/generic_ballot_averages.csv"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-demographics",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/congress-demographics",
|
||||||
"name": "congress-demographics",
|
"name": "congress-demographics",
|
||||||
@ -166,35 +195,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/polls",
|
|
||||||
"name": "polls",
|
|
||||||
"displayName": "<span class=\"lastword\">polls</span>",
|
|
||||||
"articles": [
|
|
||||||
{
|
|
||||||
"date": "2023-05-11T14:35:40.000Z",
|
|
||||||
"title": "Latest Polls",
|
|
||||||
"url": "https://projects.fivethirtyeight.com/polls/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/president_primary_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/president_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/senate_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/house_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/governor_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/president_approval_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/polls-page/data/generic_ballot_polls_historical.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/2020-primary-data/pres_primary_avgs_2020.csv",
|
|
||||||
"https://projects.fivethirtyeight.com/2020-general-data/presidential_poll_averages_2020.csv"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/nfl-elo",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/nfl-elo",
|
||||||
"name": "nfl-elo",
|
"name": "nfl-elo",
|
||||||
@ -211,6 +211,18 @@
|
|||||||
"https://projects.fivethirtyeight.com/nfl-api/nfl_elo_latest.csv"
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/world-cup-2022",
|
||||||
"name": "world-cup-2022",
|
"name": "world-cup-2022",
|
||||||
@ -227,6 +239,18 @@
|
|||||||
"https://projects.fivethirtyeight.com/soccer-api/international/2022/wc_forecasts.csv"
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/election-deniers",
|
||||||
"name": "election-deniers",
|
"name": "election-deniers",
|
||||||
@ -347,6 +371,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/redlining",
|
||||||
"name": "redlining",
|
"name": "redlining",
|
||||||
@ -371,6 +407,42 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/trump-approval-ratings",
|
||||||
"name": "trump-approval-ratings",
|
"name": "trump-approval-ratings",
|
||||||
@ -403,6 +475,18 @@
|
|||||||
"https://projects.fivethirtyeight.com/congress-tracker-data/csv/vote_predictions.csv"
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/election-forecasts-2020",
|
||||||
"name": "election-forecasts-2020",
|
"name": "election-forecasts-2020",
|
||||||
@ -473,6 +557,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/covid-geography",
|
||||||
"name": "covid-geography",
|
"name": "covid-geography",
|
||||||
@ -512,6 +608,18 @@
|
|||||||
"https://projects.fivethirtyeight.com/endorsements-2020-data/endorsements-2020.csv"
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/impeachment-polls",
|
||||||
"name": "impeachment-polls",
|
"name": "impeachment-polls",
|
||||||
@ -603,6 +711,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/nba-draymond",
|
||||||
"name": "nba-draymond",
|
"name": "nba-draymond",
|
||||||
@ -643,6 +763,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/trump-lawsuits",
|
||||||
"name": "trump-lawsuits",
|
"name": "trump-lawsuits",
|
||||||
@ -825,6 +957,18 @@
|
|||||||
"https://projects.fivethirtyeight.com/congress-model-2018/governor_state_forecast.csv"
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/primary-candidates-2018",
|
||||||
"name": "primary-candidates-2018",
|
"name": "primary-candidates-2018",
|
||||||
@ -1035,6 +1179,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/next-bechdel",
|
||||||
"name": "next-bechdel",
|
"name": "next-bechdel",
|
||||||
@ -1169,6 +1325,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fivethirtyeight/data/tree/master/undefeated-boxers",
|
||||||
|
"name": "undefeated-boxers",
|
||||||
|
"displayName": "undefeated-<span class=\"lastword\">boxers</span>",
|
||||||
|
"articles": [
|
||||||
|
{
|
||||||
|
"date": "2017-08-18T18:47:32.000Z",
|
||||||
|
"title": "Mayweather Is Defined By The Zero Next To His Name",
|
||||||
|
"url": "https://fivethirtyeight.com/features/mayweather-is-defined-by-the-zero-next-to-his-name/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/chess-transfers",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/chess-transfers",
|
||||||
"name": "chess-transfers",
|
"name": "chess-transfers",
|
||||||
@ -1452,6 +1620,37 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/tarantino",
|
||||||
"name": "tarantino",
|
"name": "tarantino",
|
||||||
@ -1970,6 +2169,18 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/world-cup-predictions",
|
||||||
"name": "world-cup-predictions",
|
"name": "world-cup-predictions",
|
||||||
@ -2127,18 +2338,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/undefeated-boxers",
|
|
||||||
"name": "undefeated-boxers",
|
|
||||||
"displayName": "undefeated-<span class=\"lastword\">boxers</span>",
|
|
||||||
"articles": [
|
|
||||||
{
|
|
||||||
"date": "2017-08-18T18:47:32.000Z",
|
|
||||||
"title": "Mayweather Is Defined By The Zero Next To His Name",
|
|
||||||
"url": "https://fivethirtyeight.com/features/mayweather-is-defined-by-the-zero-next-to-his-name/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "https://github.com/fivethirtyeight/data/tree/master/march-madness-predictions",
|
"url": "https://github.com/fivethirtyeight/data/tree/master/march-madness-predictions",
|
||||||
"name": "march-madness-predictions",
|
"name": "march-madness-predictions",
|
||||||
@ -1,9 +1,6 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
serverRuntimeConfig: {
|
|
||||||
github_pat: process.env.GITHUB_PAT ? process.env.GITHUB_PAT : null,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = nextConfig
|
module.exports = nextConfig
|
||||||
4282
examples/fiverthirtyeight-example/package-lock.json
generated
Normal file
26
examples/fiverthirtyeight-example/package.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "fiverthirtyeight-example",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "20.1.1",
|
||||||
|
"@types/react": "18.2.6",
|
||||||
|
"@types/react-dom": "18.2.4",
|
||||||
|
"autoprefixer": "10.4.14",
|
||||||
|
"eslint": "8.40.0",
|
||||||
|
"eslint-config-next": "13.4.1",
|
||||||
|
"next": "13.4.1",
|
||||||
|
"postcss": "8.4.23",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"tailwindcss": "3.3.2",
|
||||||
|
"timeago.js": "^4.0.2",
|
||||||
|
"typescript": "5.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import '@/styles/globals.css'
|
import '@/styles/globals.css'
|
||||||
import '@portaljs/ckan/styles.css'
|
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
13
examples/fiverthirtyeight-example/pages/api/hello.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
type Data = {
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<Data>
|
||||||
|
) {
|
||||||
|
res.status(200).json({ name: 'John Doe' })
|
||||||
|
}
|
||||||
195
examples/fiverthirtyeight-example/pages/index.tsx
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import Image from 'next/image';
|
||||||
|
import { Inter } from 'next/font/google';
|
||||||
|
import { format } from 'timeago.js'
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
|
interface Article {
|
||||||
|
date: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dataset {
|
||||||
|
url: string;
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
articles: Article[];
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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>{' '}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-start">
|
||||||
|
<a
|
||||||
|
className="border border-zinc-900 font-light px-4 py-1 text-sm transition hover:bg-zinc-900 hover:text-white"
|
||||||
|
href={dataset.url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
info
|
||||||
|
</a>
|
||||||
|
{/*
|
||||||
|
<button>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className="w-12 h-12 text-blue-400 hover:text-blue-300 transition mt-1"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm-.53 14.03a.75.75 0 001.06 0l3-3a.75.75 0 10-1.06-1.06l-1.72 1.72V8.25a.75.75 0 00-1.5 0v5.69l-1.72-1.72a.75.75 0 00-1.06 1.06l3 3z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DesktopItem({dataset} : { dataset: Dataset}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{dataset.articles.map((article, index) => (
|
||||||
|
<tr 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">
|
||||||
|
{index === 0 && (
|
||||||
|
<a
|
||||||
|
className="border border-zinc-900 font-light px-[25px] py-2.5 text-sm transition hover:bg-zinc-900 hover:text-white"
|
||||||
|
href={dataset.url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
info
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
{/*
|
||||||
|
<td>
|
||||||
|
<button>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className="w-12 h-12 text-blue-400 hover:text-blue-300 transition mt-1"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm-.53 14.03a.75.75 0 001.06 0l3-3a.75.75 0 10-1.06-1.06l-1.72 1.72V8.25a.75.75 0 00-1.5 0v5.69l-1.72-1.72a.75.75 0 00-1.06 1.06l3 3z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</td>*/}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps() {
|
||||||
|
const jsonDirectory = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'/datasets.json'
|
||||||
|
);
|
||||||
|
const datasetString = await fs.readFile(jsonDirectory, 'utf8');
|
||||||
|
const datasets = JSON.parse(datasetString)
|
||||||
|
return {
|
||||||
|
props: { datasets },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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}`}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-[40px] font-bold text-zinc-800 text-center">
|
||||||
|
Our Data
|
||||||
|
</h1>
|
||||||
|
<p className="max-w-2xl text-lg text-center text-zinc-700">
|
||||||
|
We’re sharing the data and code behind some of our articles and
|
||||||
|
graphics. We hope you’ll use it to check our work and to create
|
||||||
|
stories and visualizations of your own.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<article className="w-full px-2 md:hidden py-4">{datasets.map(dataset => <MobileItem 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>
|
||||||
|
<th className="uppercase text-left font-light text-xs pb-3">
|
||||||
|
data set
|
||||||
|
</th>
|
||||||
|
<th className="uppercase text-left font-light text-xs pb-3">
|
||||||
|
related content
|
||||||
|
</th>
|
||||||
|
<th className="uppercase text-left font-light text-xs pb-3">
|
||||||
|
last updated
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>{datasets.map(dataset => <DesktopItem dataset={dataset} />)}</tbody>
|
||||||
|
</table>
|
||||||
|
<p className="text-[13px] py-8">
|
||||||
|
Unless otherwise noted, our data sets are available under the{' '}
|
||||||
|
<a
|
||||||
|
className="text-blue-400 hover:underline"
|
||||||
|
href="http://creativecommons.org/licenses/by/4.0/"
|
||||||
|
>
|
||||||
|
Creative Commons Attribution 4.0 International license
|
||||||
|
</a>
|
||||||
|
, and the code is available under the{' '}
|
||||||
|
<a
|
||||||
|
className="text-blue-400 hover:underline"
|
||||||
|
href="http://opensource.org/licenses/MIT"
|
||||||
|
>
|
||||||
|
MIT license
|
||||||
|
</a>
|
||||||
|
. If you find this information useful, please{' '}
|
||||||
|
<a
|
||||||
|
className="text-blue-400 hover:underline"
|
||||||
|
href="mailto:data@fivethirtyeight.com"
|
||||||
|
>
|
||||||
|
let us know
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 629 B |