[#809,docs][xl]: add LHS sidebar to docs
This commit is contained in:
parent
0f65e253da
commit
91caeff6c3
44
site/components/DocsPagination.tsx
Normal file
44
site/components/DocsPagination.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
export default function DocsPagination({ prev = '', next = '' }) {
|
||||||
|
return (
|
||||||
|
<div className="w-full flex my-20">
|
||||||
|
{prev && (
|
||||||
|
<a href={prev} className="mr-10 no-underline">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
className="w-6 h-6 inline mr-2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M15.75 19.5L8.25 12l7.5-7.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Prev
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{next && (
|
||||||
|
<a href={next} className="no-underline ml-auto">
|
||||||
|
Next Lesson
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className="w-6 h-6 inline ml-2"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -55,13 +55,13 @@ export default function Layout({
|
|||||||
title,
|
title,
|
||||||
tableOfContents = [],
|
tableOfContents = [],
|
||||||
urlPath,
|
urlPath,
|
||||||
siteMap = []
|
sidebarTree = []
|
||||||
}: {
|
}: {
|
||||||
children;
|
children;
|
||||||
title?: string;
|
title?: string;
|
||||||
tableOfContents?;
|
tableOfContents?;
|
||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
siteMap?: [];
|
sidebarTree?: [];
|
||||||
}) {
|
}) {
|
||||||
// const { toc } = children.props;
|
// const { toc } = children.props;
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
@ -134,7 +134,7 @@ export default function Layout({
|
|||||||
href={`#${subSection.id}`}
|
href={`#${subSection.id}`}
|
||||||
className={
|
className={
|
||||||
isActive(subSection)
|
isActive(subSection)
|
||||||
? 'text-sky-500'
|
? 'text-secondary'
|
||||||
: 'hover:text-slate-600 dark:hover:text-slate-300'
|
: 'hover:text-slate-600 dark:hover:text-slate-300'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -153,7 +153,7 @@ export default function Layout({
|
|||||||
{/* LHS NAVIGATION */}
|
{/* LHS NAVIGATION */}
|
||||||
{/* {showSidebar && ( */}
|
{/* {showSidebar && ( */}
|
||||||
<div className="hidden lg:block fixed z-20 w-[18rem] top-[4.6rem] right-auto bottom-0 left-[max(0px,calc(50%-44rem))] pt-8 pl-8 overflow-y-auto">
|
<div className="hidden lg:block fixed z-20 w-[18rem] top-[4.6rem] right-auto bottom-0 left-[max(0px,calc(50%-44rem))] pt-8 pl-8 overflow-y-auto">
|
||||||
<SiteToc currentPath={urlPath} nav={siteMap} />
|
<SiteToc currentPath={urlPath} nav={sidebarTree} />
|
||||||
</div>
|
</div>
|
||||||
{/* )} */}
|
{/* )} */}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { MDXRemote } from "next-mdx-remote";
|
import { MDXRemote } from 'next-mdx-remote';
|
||||||
import layouts from "layouts";
|
import layouts from 'layouts';
|
||||||
|
import DocsPagination from './DocsPagination';
|
||||||
|
|
||||||
export default function MDXPage({ source, frontMatter }) {
|
export default function MDXPage({ source, frontMatter }) {
|
||||||
const Layout = ({ children }) => {
|
const Layout = ({ children }) => {
|
||||||
@ -32,7 +33,7 @@ export default function MDXPage({ source, frontMatter }) {
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Layout>
|
<Layout>
|
||||||
<MDXRemote {...source} />
|
<MDXRemote {...source} components={{ DocsPagination }} />
|
||||||
</Layout>
|
</Layout>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link.js";
|
import Link from 'next/link.js';
|
||||||
import clsx from "clsx";
|
import clsx from 'clsx';
|
||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from '@headlessui/react';
|
||||||
|
|
||||||
export interface NavItem {
|
export interface NavItem {
|
||||||
name: string;
|
name: string;
|
||||||
@ -40,15 +40,11 @@ function sortNavGroupChildren(items: Array<NavItem | NavGroup>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const SiteToc: React.FC<Props> = ({ currentPath, nav }) => {
|
export const SiteToc: React.FC<Props> = ({ currentPath, nav }) => {
|
||||||
function isActiveItem(item: NavItem) {
|
|
||||||
return item.href === currentPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav data-testid="lhs-sidebar" className="flex flex-col space-y-3 text-sm">
|
<nav data-testid="lhs-sidebar" className="flex flex-col space-y-3 text-sm">
|
||||||
{/* {sortNavGroupChildren(nav).map((n) => ( */}
|
{/* {sortNavGroupChildren(nav).map((n) => ( */}
|
||||||
{nav.map((n) => (
|
{nav.map((n) => (
|
||||||
<NavComponent item={n} isActive={false} />
|
<NavComponent item={n} currentPath={currentPath} />
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
@ -56,30 +52,34 @@ export const SiteToc: React.FC<Props> = ({ currentPath, nav }) => {
|
|||||||
|
|
||||||
const NavComponent: React.FC<{
|
const NavComponent: React.FC<{
|
||||||
item: NavItem | NavGroup;
|
item: NavItem | NavGroup;
|
||||||
isActive: boolean;
|
currentPath: string;
|
||||||
}> = ({ item, isActive }) => {
|
}> = ({ item, currentPath }) => {
|
||||||
|
function isActiveItem(item: NavItem) {
|
||||||
|
return item.href === "/" + currentPath;
|
||||||
|
}
|
||||||
|
|
||||||
return !isNavGroup(item) ? (
|
return !isNavGroup(item) ? (
|
||||||
<Link
|
<Link
|
||||||
key={item.name}
|
key={item.name}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
isActive
|
isActiveItem(item)
|
||||||
? "text-sky-500"
|
? 'text-secondary'
|
||||||
: "font-normal text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300",
|
: 'font-normal text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-300',
|
||||||
"block"
|
'block'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<Disclosure as="div" key={item.name} className="flex flex-col space-y-3">
|
<Disclosure as="div" key={item.name} className="flex flex-col space-y-3" defaultOpen={true}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<div>
|
<div>
|
||||||
<Disclosure.Button className="group w-full flex items-center text-left text-md font-medium text-slate-900 dark:text-white">
|
<Disclosure.Button className="group w-full flex items-center text-left text-md font-medium text-slate-900 dark:text-white">
|
||||||
<svg
|
<svg
|
||||||
className={clsx(
|
className={clsx(
|
||||||
open ? "text-slate-400 rotate-90" : "text-slate-300",
|
open ? 'text-slate-400 rotate-90' : 'text-slate-300',
|
||||||
"h-3 w-3 mr-2 flex-shrink-0 transform transition-colors duration-150 ease-in-out group-hover:text-slate-400"
|
'h-3 w-3 mr-2 flex-shrink-0 transform transition-colors duration-150 ease-in-out group-hover:text-slate-400'
|
||||||
)}
|
)}
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -99,7 +99,7 @@ const NavComponent: React.FC<{
|
|||||||
<Disclosure.Panel className="flex flex-col space-y-3 pl-5 mt-3">
|
<Disclosure.Panel className="flex flex-col space-y-3 pl-5 mt-3">
|
||||||
{/* {sortNavGroupChildren(item.children).map((subItem) => ( */}
|
{/* {sortNavGroupChildren(item.children).map((subItem) => ( */}
|
||||||
{item.children.map((subItem) => (
|
{item.children.map((subItem) => (
|
||||||
<NavComponent item={subItem} isActive={false} />
|
<NavComponent item={subItem} currentPath={currentPath} />
|
||||||
))}
|
))}
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|||||||
66
site/content/docs/creating-new-datasets.md
Normal file
66
site/content/docs/creating-new-datasets.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Creating new datasets
|
||||||
|
|
||||||
|
So far, the PortalJS app we created only has a single page displaying a dataset. Data catalogs and data portals generally showcase many different datasets.
|
||||||
|
|
||||||
|
Let's explore how to add and display more datasets to our portal.
|
||||||
|
|
||||||
|
## Pages in PortalJS
|
||||||
|
|
||||||
|
As you have seen, in this example a dataset page is just a markdown file on disk plus a data file.
|
||||||
|
|
||||||
|
To create a new data showcase page we just create a new markdown file in the `content/` folder and a new data file in the `public/` folder.
|
||||||
|
|
||||||
|
Let's do that now. Create a `content/my-incredible-dataset` folder, and inside this new folder create a `index.md` file with the following content:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# My Incredible Dataset
|
||||||
|
|
||||||
|
This is my incredible dataset.
|
||||||
|
|
||||||
|
## Chart
|
||||||
|
|
||||||
|
<LineChart
|
||||||
|
title="US Population By Decade"
|
||||||
|
xAxis="Year"
|
||||||
|
yAxis="Population (mi)"
|
||||||
|
data="my-incredible-data.csv"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, create a file in `public/` named `my-incredible-data.csv` and put the following content inside it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Year,Population (mi)
|
||||||
|
1980,227
|
||||||
|
1990,249
|
||||||
|
2000,281
|
||||||
|
2010,309
|
||||||
|
2020,331
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that pages are associated with a route based on their pathname, so, to see the new data page, access http://localhost:3000/my-incredible-dataset from the browser. You should see the following:
|
||||||
|
|
||||||
|
<img src="/assets/docs/my-incredible-dataset.png" />
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> In this tutorial we opted for storing content as markdown files and data as CSV files in the app, but PortalJS can have metadata, data and content stored anywhere.
|
||||||
|
|
||||||
|
## Create an index page
|
||||||
|
|
||||||
|
Now, let's create an index page. First, create a new folder `content/my-awesome-dataset/` and move `content/index.md` to it. Then, create a new file `content/index.md` and put the following content inside it:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Welcome to my data portal!
|
||||||
|
|
||||||
|
List of available datasets:
|
||||||
|
|
||||||
|
- [My Awesome Dataset](/my-awesome-dataset)
|
||||||
|
- [My Incredible Dataset](/my-incredible-dataset)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
From the browser, access http://localhost:3000. You should see the following:
|
||||||
|
|
||||||
|
<img src="/assets/docs/datasets-index-page.png" />
|
||||||
|
|
||||||
|
<DocsPagination prev="/docs" next="/docs/searching-datasets" />
|
||||||
56
site/content/docs/deploying-your-portaljs-app.md
Normal file
56
site/content/docs/deploying-your-portaljs-app.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Deploying your PortalJS app
|
||||||
|
|
||||||
|
Finally, let's learn how to deploy PortalJS apps to Vercel or Cloudflare Pages.
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> Although we are using Vercel and Cloudflare Pages in this tutorial, you can deploy apps in any hosting solution you want as a static website by running `npm run export` and distributing the contents of the `out/` folder.
|
||||||
|
|
||||||
|
## Push to a GitHub repo
|
||||||
|
|
||||||
|
The PortalJS app we built up to this point is stored locally. To allow Vercel or Cloudflare Pages to deploy it, we have to push it to GitHub (or another SCM supported by these hosting solutions).
|
||||||
|
|
||||||
|
- Create a new repository under your GitHub account
|
||||||
|
- Add the new remote origin to your PortalJS app
|
||||||
|
- Push the app to the repository
|
||||||
|
|
||||||
|
If you are not sure about how to do it, follow this guide: https://nextjs.org/learn/basics/deploying-nextjs-app/github
|
||||||
|
|
||||||
|
> [!tip]
|
||||||
|
> You can also deploy using our Vercel deploy button. In this case, a new repository will be created under your GitHub account automatically.
|
||||||
|
> [Click here](#one-click-deploy) to scroll to the deploy button.
|
||||||
|
|
||||||
|
## Deploy to Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy a PortalJS app is to use Vercel, a serverless platform for static and hybrid applications developed by the creators of Next.js.
|
||||||
|
|
||||||
|
To deploy your PortalJS app:
|
||||||
|
|
||||||
|
- Create a Vercel account by going to https://vercel.com/signup and choosing "Continue with GitHub"
|
||||||
|
- Import the repository you created for the PortalJS app at https://vercel.com/new
|
||||||
|
- During the setup process you can use the default settings - no need to change anything.
|
||||||
|
|
||||||
|
When you deploy, your PortalJS app will start building. It should finish in under a minute.
|
||||||
|
|
||||||
|
When it’s done, you’ll get deployment URLs. Click on one of the URLs and you should see your PortaJS app live.
|
||||||
|
|
||||||
|
>[!tip]
|
||||||
|
> You can find a more in-depth explanation about this process at https://nextjs.org/learn/basics/deploying-nextjs-app/deploy
|
||||||
|
|
||||||
|
### One-Click Deploy
|
||||||
|
|
||||||
|
You can instantly deploy our example app to your Vercel account by clicking the button below:
|
||||||
|
|
||||||
|
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Flearn-example&project-name=my-data-portal&repository-name=my-data-portal&demo-title=PortalJS%20Learn%20Example&demo-description=PortalJS%20Learn%20Example%20-%20https%3A%2F%2Fportaljs.org%2Fdocs&demo-url=learn-example.portaljs.org&demo-image=https%3A%2F%2Fportaljs.org%2Fassets%2Fexamples%2Fbasic-example.png)
|
||||||
|
|
||||||
|
This will create a new repository on your GitHub account and deploy it to Vercel. If you are following the tutorial, you can replicate the changes done on your local app to this new repository.
|
||||||
|
|
||||||
|
## Deploy to Cloudflare Pages
|
||||||
|
|
||||||
|
To deploy your PortalJS app to Cloudflare Pages, follow this guide:
|
||||||
|
|
||||||
|
https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#deploy-with-cloudflare-pages-1
|
||||||
|
|
||||||
|
Note that you don't have to change anything - just follow the steps, choosing the repository you created.
|
||||||
|
|
||||||
|
<DocsPagination prev="/docs/showing-metadata" />
|
||||||
|
|
||||||
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
showSidebar: true
|
|
||||||
---
|
|
||||||
|
|
||||||
# Getting Started
|
# Getting Started
|
||||||
|
|
||||||
Welcome to the PortalJS documentation!
|
Welcome to the PortalJS documentation!
|
||||||
@ -55,262 +51,4 @@ As soon as you save the file, the browser automatically updates the page with th
|
|||||||
|
|
||||||
<img src="/assets/docs/editing-the-page-1.png" />
|
<img src="/assets/docs/editing-the-page-1.png" />
|
||||||
|
|
||||||
## Creating new datasets
|
<DocsPagination next="/docs/creating-new-datasets" />
|
||||||
|
|
||||||
So far, the PortalJS app we created only has a single page displaying a dataset. Data catalogs and data portals generally showcase many different datasets.
|
|
||||||
|
|
||||||
Let's explore how to add and display more datasets to our portal.
|
|
||||||
|
|
||||||
### Pages in PortalJS
|
|
||||||
|
|
||||||
As you have seen, in this example a dataset page is just a markdown file on disk plus a data file.
|
|
||||||
|
|
||||||
To create a new data showcase page we just create a new markdown file in the `content/` folder and a new data file in the `public/` folder.
|
|
||||||
|
|
||||||
Let's do that now. Create a `content/my-incredible-dataset` folder, and inside this new folder create a `index.md` file with the following content:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# My Incredible Dataset
|
|
||||||
|
|
||||||
This is my incredible dataset.
|
|
||||||
|
|
||||||
## Chart
|
|
||||||
|
|
||||||
<LineChart
|
|
||||||
title="US Population By Decade"
|
|
||||||
xAxis="Year"
|
|
||||||
yAxis="Population (mi)"
|
|
||||||
data="my-incredible-data.csv"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, create a file in `public/` named `my-incredible-data.csv` and put the following content inside it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
Year,Population (mi)
|
|
||||||
1980,227
|
|
||||||
1990,249
|
|
||||||
2000,281
|
|
||||||
2010,309
|
|
||||||
2020,331
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that pages are associated with a route based on their pathname, so, to see the new data page, access http://localhost:3000/my-incredible-dataset from the browser. You should see the following:
|
|
||||||
|
|
||||||
<img src="/assets/docs/my-incredible-dataset.png" />
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> In this tutorial we opted for storing content as markdown files and data as CSV files in the app, but PortalJS can have metadata, data and content stored anywhere.
|
|
||||||
|
|
||||||
### Create an index page
|
|
||||||
|
|
||||||
Now, let's create an index page. First, create a new folder `content/my-awesome-dataset/` and move `content/index.md` to it. Then, create a new file `content/index.md` and put the following content inside it:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Welcome to my data portal!
|
|
||||||
|
|
||||||
List of available datasets:
|
|
||||||
|
|
||||||
- [My Awesome Dataset](/my-awesome-dataset)
|
|
||||||
- [My Incredible Dataset](/my-incredible-dataset)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
From the browser, access http://localhost:3000. You should see the following:
|
|
||||||
|
|
||||||
<img src="/assets/docs/datasets-index-page.png" />
|
|
||||||
|
|
||||||
## Search page
|
|
||||||
|
|
||||||
Typing out every link in the index page will get cumbersome eventually, and as the portal grows, finding the datasets you are looking for on the index page will become harder and harder, for that we will need search functionality.
|
|
||||||
|
|
||||||
### Creating a search page
|
|
||||||
|
|
||||||
Luckily we have a component for that. Change your `content/index.md` file to this:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Welcome to my data portal!
|
|
||||||
|
|
||||||
List of available datasets:
|
|
||||||
|
|
||||||
<Catalog datasets={datasets} />
|
|
||||||
```
|
|
||||||
|
|
||||||
Before you refresh the page, however, you will need to run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run mddb
|
|
||||||
```
|
|
||||||
|
|
||||||
This example makes use of the [markdowndb](https://github.com/datopian/markdowndb) library. For now the only thing you need to know is that you should run the command above everytime you make some change to `/content`.
|
|
||||||
|
|
||||||
From the browser, access http://localhost:3000. You should see the following, you now have a searchable automatic list of your datasets:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
To make this catalog look even better, we can change the text that is being displayed for each dataset to a title. Let's do that by adding the "title" [frontmatter field](https://daily-dev-tips.com/posts/what-exactly-is-frontmatter/) to the first dataset in the list. Change `content/my-awesome-dataset/index.md` to the following:
|
|
||||||
|
|
||||||
```
|
|
||||||
---
|
|
||||||
title: 'My awesome dataset'
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Awesome Dataset
|
|
||||||
|
|
||||||
Built with PortalJS
|
|
||||||
|
|
||||||
## Table
|
|
||||||
|
|
||||||
<Table url="data.csv" />
|
|
||||||
```
|
|
||||||
|
|
||||||
Rerun `npm run mddb` and, from the browser, access http://localhost:3000. You should see the title appearing instead of the folder name:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Any frontmatter attribute that you add will automatically get indexed and be usable in the search box.
|
|
||||||
|
|
||||||
### Adding filters
|
|
||||||
|
|
||||||
Sometimes contextual search is not enough. Let's add a filter. To do so, lets add a new metadata field called "group", add it to your `content/my-incredible-dataset/index.md` like so:
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
---
|
|
||||||
group: 'Incredible'
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Incredible Dataset
|
|
||||||
|
|
||||||
This is my incredible dataset.
|
|
||||||
|
|
||||||
## Chart
|
|
||||||
|
|
||||||
<LineChart
|
|
||||||
title="US Population By Decade"
|
|
||||||
xAxis="Year"
|
|
||||||
yAxis="Population (mi)"
|
|
||||||
data="my-incredible-data.csv"
|
|
||||||
/>
|
|
||||||
```
|
|
||||||
|
|
||||||
Also add it to your `content/my-awesome-dataset/index.md` like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
---
|
|
||||||
title: 'My awesome dataset'
|
|
||||||
group: 'Awesome'
|
|
||||||
---
|
|
||||||
|
|
||||||
# My Awesome Dataset
|
|
||||||
|
|
||||||
Built with PortalJS
|
|
||||||
|
|
||||||
## Table
|
|
||||||
|
|
||||||
<Table url="data.csv" />
|
|
||||||
```
|
|
||||||
|
|
||||||
Now on your `content/index.md` you can add a "facet" to the `Catalog` component, like so:
|
|
||||||
|
|
||||||
```
|
|
||||||
# Welcome to my data portal!
|
|
||||||
|
|
||||||
List of available datasets:
|
|
||||||
|
|
||||||
<Catalog datasets={datasets} facets={['group']}/>
|
|
||||||
```
|
|
||||||
|
|
||||||
You now have a filter in your page with all possible values automatically added to it.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Showing metadata
|
|
||||||
|
|
||||||
If you go now to `http://localhost:3000/my-awesome-dataset`, you will see that we now have two titles on the page. That's because `title` is one of the default metadata fields supported by PortalJS.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Change the content inside `/content/my-awesome-dataset/index.md` to this.
|
|
||||||
|
|
||||||
```
|
|
||||||
---
|
|
||||||
title: 'My awesome dataset'
|
|
||||||
author: 'Rufus Pollock'
|
|
||||||
description: 'An awesome dataset displaying some awesome data'
|
|
||||||
modified: '2023-05-04'
|
|
||||||
files: ['data.csv']
|
|
||||||
groups: ['Awesome']
|
|
||||||
---
|
|
||||||
|
|
||||||
Built with PortalJS
|
|
||||||
|
|
||||||
## Table
|
|
||||||
|
|
||||||
<Table url="data.csv" />
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you refresh the page at `http://localhost:3000/my-awesome-dataset` you should see something like this at the top:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
These are the standard metadata fields that will be shown at the top of the page if you add them.
|
|
||||||
|
|
||||||
- `title` that gets displayed as a big header at the top of the page
|
|
||||||
- `author`, `description`, and `modified` which gets displayed below the title
|
|
||||||
- `files` that get displayed as a table with two columns: `File` which is linked directly to the file, and `Format` which show the file format.
|
|
||||||
|
|
||||||
## Deploying your PortalJS app
|
|
||||||
|
|
||||||
Finally, let's learn how to deploy PortalJS apps to Vercel or Cloudflare Pages.
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> Although we are using Vercel and Cloudflare Pages in this tutorial, you can deploy apps in any hosting solution you want as a static website by running `npm run export` and distributing the contents of the `out/` folder.
|
|
||||||
|
|
||||||
### Push to a GitHub repo
|
|
||||||
|
|
||||||
The PortalJS app we built up to this point is stored locally. To allow Vercel or Cloudflare Pages to deploy it, we have to push it to GitHub (or another SCM supported by these hosting solutions).
|
|
||||||
|
|
||||||
- Create a new repository under your GitHub account
|
|
||||||
- Add the new remote origin to your PortalJS app
|
|
||||||
- Push the app to the repository
|
|
||||||
|
|
||||||
If you are not sure about how to do it, follow this guide: https://nextjs.org/learn/basics/deploying-nextjs-app/github
|
|
||||||
|
|
||||||
> [!tip]
|
|
||||||
> You can also deploy using our Vercel deploy button. In this case, a new repository will be created under your GitHub account automatically.
|
|
||||||
> [Click here](#one-click-deploy) to scroll to the deploy button.
|
|
||||||
|
|
||||||
### Deploy to Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy a PortalJS app is to use Vercel, a serverless platform for static and hybrid applications developed by the creators of Next.js.
|
|
||||||
|
|
||||||
To deploy your PortalJS app:
|
|
||||||
|
|
||||||
- Create a Vercel account by going to https://vercel.com/signup and choosing "Continue with GitHub"
|
|
||||||
- Import the repository you created for the PortalJS app at https://vercel.com/new
|
|
||||||
- During the setup process you can use the default settings - no need to change anything.
|
|
||||||
|
|
||||||
When you deploy, your PortalJS app will start building. It should finish in under a minute.
|
|
||||||
|
|
||||||
When it’s done, you’ll get deployment URLs. Click on one of the URLs and you should see your PortaJS app live.
|
|
||||||
|
|
||||||
>[!tip]
|
|
||||||
> You can find a more in-depth explanation about this process at https://nextjs.org/learn/basics/deploying-nextjs-app/deploy
|
|
||||||
|
|
||||||
#### One-Click Deploy
|
|
||||||
|
|
||||||
You can instantly deploy our example app to your Vercel account by clicking the button below:
|
|
||||||
|
|
||||||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdatopian%2Fportaljs%2Ftree%2Fmain%2Fexamples%2Flearn-example&project-name=my-data-portal&repository-name=my-data-portal&demo-title=PortalJS%20Learn%20Example&demo-description=PortalJS%20Learn%20Example%20-%20https%3A%2F%2Fportaljs.org%2Fdocs&demo-url=learn-example.portaljs.org&demo-image=https%3A%2F%2Fportaljs.org%2Fassets%2Fexamples%2Fbasic-example.png)
|
|
||||||
|
|
||||||
This will create a new repository on your GitHub account and deploy it to Vercel. If you are following the tutorial, you can replicate the changes done on your local app to this new repository.
|
|
||||||
|
|
||||||
### Deploy to Cloudflare Pages
|
|
||||||
|
|
||||||
To deploy your PortalJS app to Cloudflare Pages, follow this guide:
|
|
||||||
|
|
||||||
https://developers.cloudflare.com/pages/framework-guides/deploy-a-nextjs-site/#deploy-with-cloudflare-pages-1
|
|
||||||
|
|
||||||
Note that you don't have to change anything - just follow the steps, choosing the repository you created.
|
|
||||||
|
|
||||||
105
site/content/docs/searching-datasets.md
Normal file
105
site/content/docs/searching-datasets.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Searching datasets
|
||||||
|
|
||||||
|
Typing out every link in the index page will get cumbersome eventually, and as the portal grows, finding the datasets you are looking for on the index page will become harder and harder, for that we will need search functionality.
|
||||||
|
|
||||||
|
## Creating a search page
|
||||||
|
|
||||||
|
Luckily we have a component for that. Change your `content/index.md` file to this:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Welcome to my data portal!
|
||||||
|
|
||||||
|
List of available datasets:
|
||||||
|
|
||||||
|
<Catalog datasets={datasets} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Before you refresh the page, however, you will need to run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run mddb
|
||||||
|
```
|
||||||
|
|
||||||
|
This example makes use of the [markdowndb](https://github.com/datopian/markdowndb) library. For now the only thing you need to know is that you should run the command above everytime you make some change to `/content`.
|
||||||
|
|
||||||
|
From the browser, access http://localhost:3000. You should see the following, you now have a searchable automatic list of your datasets:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To make this catalog look even better, we can change the text that is being displayed for each dataset to a title. Let's do that by adding the "title" [frontmatter field](https://daily-dev-tips.com/posts/what-exactly-is-frontmatter/) to the first dataset in the list. Change `content/my-awesome-dataset/index.md` to the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: 'My awesome dataset'
|
||||||
|
---
|
||||||
|
|
||||||
|
# My Awesome Dataset
|
||||||
|
|
||||||
|
Built with PortalJS
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
<Table url="data.csv" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Rerun `npm run mddb` and, from the browser, access http://localhost:3000. You should see the title appearing instead of the folder name:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Any frontmatter attribute that you add will automatically get indexed and be usable in the search box.
|
||||||
|
|
||||||
|
## Adding filters
|
||||||
|
|
||||||
|
Sometimes contextual search is not enough. Let's add a filter. To do so, lets add a new metadata field called "group", add it to your `content/my-incredible-dataset/index.md` like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
group: 'Incredible'
|
||||||
|
---
|
||||||
|
|
||||||
|
# My Incredible Dataset
|
||||||
|
|
||||||
|
This is my incredible dataset.
|
||||||
|
|
||||||
|
## Chart
|
||||||
|
|
||||||
|
<LineChart
|
||||||
|
title="US Population By Decade"
|
||||||
|
xAxis="Year"
|
||||||
|
yAxis="Population (mi)"
|
||||||
|
data="my-incredible-data.csv"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Also add it to your `content/my-awesome-dataset/index.md` like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: 'My awesome dataset'
|
||||||
|
group: 'Awesome'
|
||||||
|
---
|
||||||
|
|
||||||
|
# My Awesome Dataset
|
||||||
|
|
||||||
|
Built with PortalJS
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
<Table url="data.csv" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Now on your `content/index.md` you can add a "facet" to the `Catalog` component, like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Welcome to my data portal!
|
||||||
|
|
||||||
|
List of available datasets:
|
||||||
|
|
||||||
|
<Catalog datasets={datasets} facets={['group']}/>
|
||||||
|
```
|
||||||
|
|
||||||
|
You now have a filter in your page with all possible values automatically added to it.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<DocsPagination prev="/docs/creating-new-datasets" next="/docs/showing-metadata" />
|
||||||
36
site/content/docs/showing-metadata.md
Normal file
36
site/content/docs/showing-metadata.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Showing metadata
|
||||||
|
|
||||||
|
If you go now to `http://localhost:3000/my-awesome-dataset`, you will see that we now have two titles on the page. That's because `title` is one of the default metadata fields supported by PortalJS.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Change the content inside `/content/my-awesome-dataset/index.md` to this.
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
title: 'My awesome dataset'
|
||||||
|
author: 'Rufus Pollock'
|
||||||
|
description: 'An awesome dataset displaying some awesome data'
|
||||||
|
modified: '2023-05-04'
|
||||||
|
files: ['data.csv']
|
||||||
|
groups: ['Awesome']
|
||||||
|
---
|
||||||
|
|
||||||
|
Built with PortalJS
|
||||||
|
|
||||||
|
## Table
|
||||||
|
|
||||||
|
<Table url="data.csv" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you refresh the page at `http://localhost:3000/my-awesome-dataset` you should see something like this at the top:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
These are the standard metadata fields that will be shown at the top of the page if you add them.
|
||||||
|
|
||||||
|
- `title` that gets displayed as a big header at the top of the page
|
||||||
|
- `author`, `description`, and `modified` which gets displayed below the title
|
||||||
|
- `files` that get displayed as a table with two columns: `File` which is linked directly to the file, and `Format` which show the file format.
|
||||||
|
|
||||||
|
<DocsPagination prev="/docs/searching-datasets" next="/docs/deploying-your-portaljs-app" />
|
||||||
27
site/content/docs/sidebar.json
Normal file
27
site/content/docs/sidebar.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Getting started",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "Setup",
|
||||||
|
"href": "/docs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Creating new datasets",
|
||||||
|
"href": "/docs/creating-new-datasets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Searching datasets",
|
||||||
|
"href": "/docs/searching-datasets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Showing metadata",
|
||||||
|
"href": "/docs/showing-metadata"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Deploying your PortalJS app",
|
||||||
|
"href": "/docs/deploying-your-portaljs-app"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,19 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "Docs",
|
|
||||||
"href": "/docs",
|
|
||||||
"level": 0,
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"name": "Getting Started",
|
|
||||||
"href": "/docs",
|
|
||||||
"level": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Creating new datasets",
|
|
||||||
"href": "/tutorial-i-creating-new-datasets",
|
|
||||||
"level": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -13,7 +13,7 @@ import { CustomAppProps } from './_app.jsx';
|
|||||||
import computeFields from '@/lib/computeFields';
|
import computeFields from '@/lib/computeFields';
|
||||||
import { getAuthorsDetails } from '@/lib/getAuthorsDetails';
|
import { getAuthorsDetails } from '@/lib/getAuthorsDetails';
|
||||||
|
|
||||||
export default function DRDPage({ source, meta, siteMap }) {
|
export default function Page({ source, meta, sidebarTree }) {
|
||||||
source = JSON.parse(source);
|
source = JSON.parse(source);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -32,7 +32,8 @@ export default function DRDPage({ source, meta, siteMap }) {
|
|||||||
<Layout
|
<Layout
|
||||||
tableOfContents={tableOfContents}
|
tableOfContents={tableOfContents}
|
||||||
title={meta.title}
|
title={meta.title}
|
||||||
siteMap={siteMap}
|
sidebarTree={sidebarTree}
|
||||||
|
urlPath={meta.urlPath}
|
||||||
>
|
>
|
||||||
<MDXPage source={source} frontMatter={meta} />
|
<MDXPage source={source} frontMatter={meta} />
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -61,6 +62,12 @@ export const getStaticProps: GetStaticProps = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temporary, docs pages should present the LHS sidebar
|
||||||
|
if (dbFile.url_path.startsWith('docs')) {
|
||||||
|
frontMatter.showSidebar = true;
|
||||||
|
frontMatter.sidebarTreeFile = 'content/docs/sidebar.json';
|
||||||
|
}
|
||||||
|
|
||||||
const source = fs.readFileSync(filePath, { encoding: 'utf-8' });
|
const source = fs.readFileSync(filePath, { encoding: 'utf-8' });
|
||||||
const { mdxSource } = await parse(source, 'mdx', {});
|
const { mdxSource } = await parse(source, 'mdx', {});
|
||||||
|
|
||||||
@ -77,11 +84,12 @@ export const getStaticProps: GetStaticProps = async ({
|
|||||||
if (frontMatterWithComputedFields?.showSidebar) {
|
if (frontMatterWithComputedFields?.showSidebar) {
|
||||||
let sidebarTreeFile = frontMatterWithComputedFields?.sidebarTreeFile;
|
let sidebarTreeFile = frontMatterWithComputedFields?.sidebarTreeFile;
|
||||||
|
|
||||||
|
// Added this file funcionality so that we can control
|
||||||
|
// which items appear in the sidebar and the order via
|
||||||
|
// a json file
|
||||||
if (sidebarTreeFile) {
|
if (sidebarTreeFile) {
|
||||||
const tree = fs.readFileSync(sidebarTreeFile, { encoding: "utf-8" });
|
const tree = fs.readFileSync(sidebarTreeFile, { encoding: 'utf-8' });
|
||||||
|
|
||||||
sidebarTree = JSON.parse(tree);
|
sidebarTree = JSON.parse(tree);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const allPages = await mddb.getFiles({ extensions: ['md', 'mdx'] });
|
const allPages = await mddb.getFiles({ extensions: ['md', 'mdx'] });
|
||||||
const pages = allPages.filter((p) => !p.metadata?.isDraft);
|
const pages = allPages.filter((p) => !p.metadata?.isDraft);
|
||||||
@ -95,7 +103,7 @@ export const getStaticProps: GetStaticProps = async ({
|
|||||||
props: {
|
props: {
|
||||||
source: JSON.stringify(mdxSource),
|
source: JSON.stringify(mdxSource),
|
||||||
meta: frontMatterWithComputedFields,
|
meta: frontMatterWithComputedFields,
|
||||||
siteMap: sidebarTree,
|
sidebarTree,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user