Compare commits
15 Commits
@portaljs/
...
feature/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d367deaea3 | ||
|
|
3e9eadcc69 | ||
|
|
da226ef205 | ||
|
|
a37a31f89a | ||
|
|
03c27df800 | ||
|
|
d198130038 | ||
|
|
06209877ea | ||
|
|
822a3ce5ec | ||
|
|
1f06c67d13 | ||
|
|
9dea140859 | ||
|
|
d5899b22ab | ||
|
|
dc895ed277 | ||
|
|
7315df8a86 | ||
|
|
349f5bea66 | ||
|
|
e908cb9344 |
2
package-lock.json
generated
@@ -40048,7 +40048,7 @@
|
|||||||
},
|
},
|
||||||
"packages/components": {
|
"packages/components": {
|
||||||
"name": "@portaljs/components",
|
"name": "@portaljs/components",
|
||||||
"version": "0.2.0",
|
"version": "0.3.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@githubocto/flat-ui": "^0.14.1",
|
"@githubocto/flat-ui": "^0.14.1",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "portaljs",
|
"name": "portaljs",
|
||||||
"workspaces": ["./packages/*"],
|
"workspaces": [
|
||||||
|
"./packages/*"
|
||||||
|
],
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
5
site/.gitignore
vendored
@@ -35,3 +35,8 @@ yarn-error.log*
|
|||||||
|
|
||||||
# markdowndb
|
# markdowndb
|
||||||
markdown.db
|
markdown.db
|
||||||
|
|
||||||
|
# seo
|
||||||
|
robots.txt
|
||||||
|
sitemap-0.xml
|
||||||
|
sitemap.xml
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function Features() {
|
|||||||
>
|
>
|
||||||
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.blue.300),theme(colors.blue.400),theme(colors.blue.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)]" />
|
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.blue.300),theme(colors.blue.400),theme(colors.blue.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)]" />
|
||||||
<div className="relative overflow-hidden rounded-xl p-6">
|
<div className="relative overflow-hidden rounded-xl p-6">
|
||||||
<img src={feature.icon} alt="" className="h-24 w-auto" />
|
<img src={feature.icon} alt={feature.title} className="h-24 w-auto" />
|
||||||
<h2 className="mt-4 font-display text-base text-primary dark:text-primary-dark">
|
<h2 className="mt-4 font-display text-base text-primary dark:text-primary-dark">
|
||||||
<span className="absolute -inset-px rounded-xl" />
|
<span className="absolute -inset-px rounded-xl" />
|
||||||
{feature.title}
|
{feature.title}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useRef } from 'react';
|
|||||||
import ButtonLink from './ButtonLink';
|
import ButtonLink from './ButtonLink';
|
||||||
import NewsletterForm from './NewsletterForm';
|
import NewsletterForm from './NewsletterForm';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import DatahubExampleImg from "@/public/images/showcases/datahub.png"
|
import DatahubExampleImg from '@/public/images/showcases/datahub.webp';
|
||||||
|
|
||||||
const codeLanguage = 'javascript';
|
const codeLanguage = 'javascript';
|
||||||
const code = `export default {
|
const code = `export default {
|
||||||
@@ -41,7 +41,7 @@ export function Hero() {
|
|||||||
{/* Commented code on line 37, 39 and 113 will reenable the two columns hero */}
|
{/* Commented code on line 37, 39 and 113 will reenable the two columns hero */}
|
||||||
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-y-16 gap-x-8 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
|
<div className="mx-auto grid max-w-2xl grid-cols-1 gap-y-16 gap-x-8 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
|
||||||
<div className="relative mb-10 lg:mb-0 md:text-center lg:text-left">
|
<div className="relative mb-10 lg:mb-0 md:text-center lg:text-left">
|
||||||
<div role="heading">
|
<div>
|
||||||
<h1 className="inline bg-gradient-to-r from-blue-500 via-blue-300 to-blue-500 bg-clip-text text-5xl tracking-tight text-transparent">
|
<h1 className="inline bg-gradient-to-r from-blue-500 via-blue-300 to-blue-500 bg-clip-text text-5xl tracking-tight text-transparent">
|
||||||
The JavaScript framework for data portals
|
The JavaScript framework for data portals
|
||||||
</h1>
|
</h1>
|
||||||
@@ -72,9 +72,11 @@ export function Hero() {
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
src="/images/datopian_logo.png"
|
src="/images/datopian_logo.png"
|
||||||
alt="Datopian"
|
alt="Datopian"
|
||||||
|
width={24}
|
||||||
|
height={20}
|
||||||
className="mx-2 mb-1 h-6 inline bg-black rounded-full"
|
className="mx-2 mb-1 h-6 inline bg-black rounded-full"
|
||||||
/>
|
/>
|
||||||
<span>Datopian</span>
|
<span>Datopian</span>
|
||||||
@@ -85,7 +87,12 @@ export function Hero() {
|
|||||||
<div className="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur">
|
<div className="relative rounded-2xl bg-[#0A101F]/80 ring-1 ring-white/10 backdrop-blur">
|
||||||
<div className="absolute -top-px left-20 right-11 h-px bg-gradient-to-r from-sky-300/0 via-sky-300/70 to-sky-300/0" />
|
<div className="absolute -top-px left-20 right-11 h-px bg-gradient-to-r from-sky-300/0 via-sky-300/70 to-sky-300/0" />
|
||||||
<div className="absolute -bottom-px left-11 right-20 h-px bg-gradient-to-r from-blue-400/0 via-blue-400 to-blue-400/0" />
|
<div className="absolute -bottom-px left-11 right-20 h-px bg-gradient-to-r from-blue-400/0 via-blue-400 to-blue-400/0" />
|
||||||
<Image src={DatahubExampleImg} alt="opendata.datahub.io" />
|
<Image
|
||||||
|
height={400}
|
||||||
|
width={600}
|
||||||
|
src={DatahubExampleImg}
|
||||||
|
alt="opendata.datahub.io"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
65
site/components/JSONLD.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { ArticleJsonLd } from 'next-seo';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
export default function JSONLD({
|
||||||
|
meta,
|
||||||
|
source,
|
||||||
|
}: {
|
||||||
|
meta: any;
|
||||||
|
source: string;
|
||||||
|
}): JSX.Element {
|
||||||
|
if (!source) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://portaljs.org';
|
||||||
|
const pageUrl = `${baseUrl}/${meta.urlPath}`;
|
||||||
|
|
||||||
|
const imageMatches = source.match(
|
||||||
|
/(?<=src: ")(.*)\.((png)|(jpg)|(jpeg))(?=")/g
|
||||||
|
);
|
||||||
|
let images = [];
|
||||||
|
if (imageMatches) {
|
||||||
|
images = [...imageMatches];
|
||||||
|
images = images.map((img) =>
|
||||||
|
img.startsWith('http')
|
||||||
|
? img
|
||||||
|
: `${baseUrl}${img.startsWith('/') ? '' : '/'}${img}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Component: JSX.Element;
|
||||||
|
|
||||||
|
const isBlog: boolean =
|
||||||
|
/^blog\/.*/.test(meta.urlPath) || meta.filetype === 'blog';
|
||||||
|
const isDoc: boolean = /^((docs)|(howtos\/)|(guide\/)).*/.test(meta.urlPath);
|
||||||
|
|
||||||
|
if (isBlog) {
|
||||||
|
Component = (
|
||||||
|
<ArticleJsonLd
|
||||||
|
type="BlogPosting"
|
||||||
|
url={pageUrl}
|
||||||
|
title={meta.title}
|
||||||
|
datePublished={meta.date}
|
||||||
|
dateModified={meta.date}
|
||||||
|
authorName={meta.authors.length ? meta.authors[0].name : 'PortalJS'}
|
||||||
|
description={meta.description}
|
||||||
|
images={images}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (isDoc) {
|
||||||
|
Component = (
|
||||||
|
<ArticleJsonLd
|
||||||
|
url={pageUrl}
|
||||||
|
title={meta.title}
|
||||||
|
images={images}
|
||||||
|
datePublished={meta.date}
|
||||||
|
dateModified={meta.date}
|
||||||
|
authorName={meta.authors.length ? meta.authors[0].name : 'PortalJS'}
|
||||||
|
description={meta.description}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Component;
|
||||||
|
}
|
||||||
@@ -54,12 +54,14 @@ function useTableOfContents(tableOfContents) {
|
|||||||
export default function Layout({
|
export default function Layout({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
|
description,
|
||||||
tableOfContents = [],
|
tableOfContents = [],
|
||||||
isHomePage = false,
|
isHomePage = false,
|
||||||
sidebarTree = [],
|
sidebarTree = [],
|
||||||
}: {
|
}: {
|
||||||
children;
|
children;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
tableOfContents?;
|
tableOfContents?;
|
||||||
urlPath?: string;
|
urlPath?: string;
|
||||||
sidebarTree?: [];
|
sidebarTree?: [];
|
||||||
@@ -82,7 +84,7 @@ export default function Layout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{title && <NextSeo title={title} />}
|
{title && <NextSeo title={title} description={description} />}
|
||||||
<Nav />
|
<Nav />
|
||||||
<div className="mx-auto p-6 bg-background dark:bg-background-dark">
|
<div className="mx-auto p-6 bg-background dark:bg-background-dark">
|
||||||
{isHomePage && <Hero />}
|
{isHomePage && <Hero />}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export default function MDXPage({ source, frontMatter }) {
|
|||||||
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<MDXRemote {...source} components={{ DocsPagination, NextSeo, Hero }} />
|
<MDXRemote {...source} components={{ DocsPagination, NextSeo, Hero }} />
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default function NavItem({ item }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative">
|
<Menu as="div" role="menu" className="relative">
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{Object.prototype.hasOwnProperty.call(item, 'href') ? (
|
{Object.prototype.hasOwnProperty.call(item, 'href') ? (
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ import Script from 'next/script';
|
|||||||
export default function NewsletterForm() {
|
export default function NewsletterForm() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://sibforms.com/forms/end-form/build/sib-styles.css"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
id="sib-form-container"
|
id="sib-form-container"
|
||||||
className="mt-8 sm:mx-auto sm:text-center lg:text-left lg:mx-0"
|
className="mt-8 sm:mx-auto sm:text-center lg:text-left lg:mx-0"
|
||||||
@@ -119,6 +115,7 @@ export default function NewsletterForm() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Script
|
<Script
|
||||||
|
strategy="worker"
|
||||||
id="newsletter-submit-form"
|
id="newsletter-submit-form"
|
||||||
src="https://sibforms.com/forms/end-form/build/main.js"
|
src="https://sibforms.com/forms/end-form/build/main.js"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ const items = [
|
|||||||
{
|
{
|
||||||
title: 'Open Data Northern Ireland',
|
title: 'Open Data Northern Ireland',
|
||||||
href: 'https://www.opendatani.gov.uk/',
|
href: 'https://www.opendatani.gov.uk/',
|
||||||
image: '/images/showcases/odni.png',
|
image: '/images/showcases/odni.webp',
|
||||||
description: 'Government Open Data Portal',
|
description: 'Government Open Data Portal',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Birmingham City Observatory',
|
title: 'Birmingham City Observatory',
|
||||||
href: 'https://www.cityobservatory.birmingham.gov.uk/',
|
href: 'https://www.cityobservatory.birmingham.gov.uk/',
|
||||||
image: '/images/showcases/birmingham.png',
|
image: '/images/showcases/birmingham.webp',
|
||||||
description: 'Government Open Data Portal',
|
description: 'Government Open Data Portal',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'UAE Open Data',
|
title: 'UAE Open Data',
|
||||||
href: 'https://opendata.fcsc.gov.ae/',
|
href: 'https://opendata.fcsc.gov.ae/',
|
||||||
image: '/images/showcases/uae.png',
|
image: '/images/showcases/uae.webp',
|
||||||
description: 'Government Open Data Portal',
|
description: 'Government Open Data Portal',
|
||||||
sourceUrl: 'https://github.com/FCSCOpendata/frontend',
|
sourceUrl: 'https://github.com/FCSCOpendata/frontend',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Datahub Open Data',
|
title: 'Datahub Open Data',
|
||||||
href: 'https://opendata.datahub.io/',
|
href: 'https://opendata.datahub.io/',
|
||||||
image: '/images/showcases/datahub.png',
|
image: '/images/showcases/datahub.webp',
|
||||||
description: 'Demo Data Portal by DataHub',
|
description: 'Demo Data Portal by DataHub',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
BIN
site/content/assets/blog/2023-07-18-map-polygons-and-points.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
site/content/assets/blog/2023-07-18-map-polygons-on-region.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
site/content/assets/blog/2023-07-18-map-tooltips.png
Normal file
|
After Width: | Height: | Size: 467 KiB |
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: 'Enhancing Geospatial Data Visualization with PortalJS'
|
||||||
|
date: 2023-07-18
|
||||||
|
authors: ['João Demenech', 'Luccas Mateus', 'Yoana Popova']
|
||||||
|
filetype: 'blog'
|
||||||
|
---
|
||||||
|
|
||||||
|
Are you keen on building rich and interactive data portals? Do you find value in the power and flexibility of JavaScript, Nextjs, and React? In that case, allow us to introduce you to [PortalJS](https://portaljs.org/), a state-of-the-art framework leveraging these technologies to help you build amazing data portals.
|
||||||
|
|
||||||
|
Perhaps you already understand that the effective data visualization lies in the adept utilization of various data components. Within [PortalJS](https://portaljs.org/), we take data visualization a step further. It's not just about displaying data - it's about telling a captivating story through the strategic orchestration of a diverse array of data components.
|
||||||
|
|
||||||
|
We are now eager to share our latest enhancement to [PortalJS](https://portaljs.org/): maps, a powerful tool for visualizing geospatial data. In this post, we will to take you on a tour of our experiments and progress in enhancing map functionalities on [PortalJS](https://portaljs.org/). Our journey into this innovative feature is still in its early stages, with new facets being unveiled and refined as we perfect our API. Still, this exciting development opens a new avenue for visualizing data, enhancing your ability to convey complex geospatial information with clarity and precision.
|
||||||
|
|
||||||
|
## Exploring Map Formats
|
||||||
|
|
||||||
|
Maps play a crucial role in geospatial data visualization. Several formats exist for storing and sharing this type of data, with GeoJSON, KML, and shapefiles being among the most popular. As a prominent figure in the field of open-source data portal platforms, [PortalJS](https://portaljs.org/) strives to support as many map formats as possible.
|
||||||
|
|
||||||
|
Taking inspiration from the ckanext-geoview extension, we currently support KML and GeoJSON formats in [PortalJS](https://portaljs.org/). This remarkable extension is a plugin for CKAN, the world’s leading open source data management system, that enables users to visualize geospatial data in diverse formats on an interactive map. Apart from KML and GeoJSON formats support, our roadmap entails extending compatibility to encompass all other formats supported by ckanext-geoview. Rest assured, we are committed to empowering users with a wide array of map format options in the future.
|
||||||
|
|
||||||
|
So, what makes these formats special?
|
||||||
|
|
||||||
|
- **GeoJSON**: This format uses JSON to depict simple geographic features and their associated attributes. It's often hailed as the most popular choice in the field.
|
||||||
|
- **KML**: An XML-based format, KML can store details like placemarks, paths, polygons, and styles.
|
||||||
|
- **Shapefiles**: These are file collections that store vector data—points, lines, and polygons—and their attributes.
|
||||||
|
|
||||||
|
## Unveiling the Power of Leaflet and OpenLayers
|
||||||
|
|
||||||
|
To display maps in [PortalJS](https://portaljs.org/), we utilize two powerful JavaScript libraries for creating interactive maps based on different layers: Leaflet and OpenLayers. Each offers distinct advantages (and disadvantages), inspiring us to integrate both and give users the flexibility to choose.
|
||||||
|
|
||||||
|
Leaflet is the leading open-source JavaScript library known for its mobile-friendly, interactive maps. With its compact size (just 42 KB of JS), it provides all the map features most developers need. Leaflet is designed with simplicity, performance and usability in mind. It works efficiently across all major desktop and mobile platforms.
|
||||||
|
|
||||||
|
OpenLayers is a high-performance library packed with features for creating interactive web maps. OpenLayers can display map tiles, vector data, and markers sourced from anywhere on any webpage. It's an excellent tool for leveraging geographic information of all kinds.
|
||||||
|
|
||||||
|
## Introducing Map Feature
|
||||||
|
|
||||||
|
Both components have some similar features and props, such as:
|
||||||
|
|
||||||
|
### Polygons and points
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Our initial version enables the use of both vectors and points to display information. Points are simpler and faster to render than vectors, but they have less detail and interactivity. For example, if you have data that is represented by coordinates or addresses, you can use points to show them on the map. This way, you can avoid time-consuming loading and rendering complex vector shapes that may slow down your map.
|
||||||
|
|
||||||
|
### Tooltips
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
We have implemented an exciting feature that enhances the usability of our map component: tooltips. When you hover over a polygon or point on the map, a small pop-up window, known as a tooltip, appears. This tooltip provides relevant details about the feature under your cursor, according to what features the map creator wants to highlight. For example, when exploring countries, you can effortlessly discover their name, population, and area by hovering over them. Similarly, hovering over cities reveals useful information like their name, temperature, and elevation. To enable this handy tooltip functionality on our map, simply include a tooltip prop when using the map component.
|
||||||
|
|
||||||
|
### Focus
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Users can also choose a region of focus, which will depend on the data, by setting initial center coordinates and zoom level. This is especially helpful for maps that are not global, such as the ones that use data from a specific country, city or region.
|
||||||
|
|
||||||
|
## Mapping the Future with PortalJS
|
||||||
|
|
||||||
|
Through our ongoing enhancements to the [PortalJS library](https://storybook.portaljs.org/), we aim to empower users to create engaging and informative data portals featuring diverse map formats and data components.
|
||||||
|
|
||||||
|
Why not give [PortalJS](https://portaljs.org/) a try today and discover the possibilities for your own data portals? To get started, check out our comprehensive documentation here: [PortalJS Documentation](https://portaljs.org/docs).
|
||||||
|
|
||||||
|
Have questions or comments about using [PortalJS](https://portaljs.org/) for your data portals? Feel free to share your thoughts on our [Discord channel](https://discord.com/invite/EeyfGrGu4U). We're here to help you make the most of your data.
|
||||||
|
|
||||||
|
Stay tuned for more exciting developments as we continue to enhance [PortalJS](https://portaljs.org/)!
|
||||||
@@ -264,7 +264,7 @@ The above script will output the following to the terminal:
|
|||||||
|
|
||||||
## Done!
|
## Done!
|
||||||
|
|
||||||
That's it! We've just created a simple catalog of our GitHub projects using markdown files and the MarkdownDB package. You can find the full code for this tutorial [here](https://github.com/datopian/markdowndb/tree/main/examples/basic-example).
|
That's it! We've just created a simple catalog of our GitHub projects using markdown files and the MarkdownDB package. You can find the [full code for this tutorial here](https://github.com/datopian/markdowndb/tree/main/examples/basic-example).
|
||||||
|
|
||||||
We look forward to seeing the amazing applications you'll build with this tool!
|
We look forward to seeing the amazing applications you'll build with this tool!
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +1,72 @@
|
|||||||
const config = {
|
const config = {
|
||||||
title:
|
title: 'PortalJS - The JavaScript framework for data portals.',
|
||||||
"PortalJS",
|
|
||||||
description:
|
description:
|
||||||
"PortalJS is a framework for rapidly building rich data portal frontends using a modern frontend approach. PortalJS can be used to present a single dataset or build a full-scale data catalog/portal.",
|
'PortalJS is a JavaScript framework for rapidly building rich data portal frontends using a modern frontend approach.',
|
||||||
theme: {
|
theme: {
|
||||||
default: "dark",
|
default: 'dark',
|
||||||
toggleIcon: "/images/theme-button.svg",
|
toggleIcon: '/images/theme-button.svg',
|
||||||
},
|
},
|
||||||
author: "Datopian",
|
author: 'Datopian',
|
||||||
authorLogo: "/datopian-logo.png",
|
authorLogo: '/datopian-logo.webp',
|
||||||
authorUrl: "https://datopian.com/",
|
authorUrl: 'https://datopian.com/',
|
||||||
navbarTitle: {
|
navbarTitle: {
|
||||||
// logo: "/images/logo.svg",
|
// logo: "/images/logo.svg",
|
||||||
text: "🌀 PortalJS",
|
text: '🌀 PortalJS',
|
||||||
// version: "Alpha",
|
// version: "Alpha",
|
||||||
},
|
},
|
||||||
navLinks: [
|
navLinks: [
|
||||||
{ name: "Docs", href: "/docs" },
|
{ name: 'Docs', href: '/docs' },
|
||||||
// { name: "Components", href: "/docs/components" },
|
// { name: "Components", href: "/docs/components" },
|
||||||
{ name: "Blog", href: "/blog" },
|
{ name: 'Blog', href: '/blog' },
|
||||||
{ name: "Showcases", href: "/#showcases" },
|
{ name: 'Showcases', href: '/#showcases' },
|
||||||
{ name: "Howtos", href: "/howtos" },
|
{ name: 'Howtos', href: '/howtos' },
|
||||||
{ name: "Guide", href: "/guide" },
|
{ name: 'Guide', href: '/guide' },
|
||||||
{ name: "Examples", href: "https://github.com/datopian/portaljs/tree/main/examples", target: "_blank" },
|
{
|
||||||
{ name: "Components", href: "https://storybook.portaljs.org", target: "_blank" },
|
name: 'Examples',
|
||||||
|
href: 'https://github.com/datopian/portaljs/tree/main/examples',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Components',
|
||||||
|
href: 'https://storybook.portaljs.org',
|
||||||
|
target: '_blank',
|
||||||
|
},
|
||||||
// { name: "DL Demo", href: "/data-literate/demo" },
|
// { name: "DL Demo", href: "/data-literate/demo" },
|
||||||
// { name: "Excel Viewer", href: "/excel-viewer" },
|
// { name: "Excel Viewer", href: "/excel-viewer" },
|
||||||
],
|
],
|
||||||
footerLinks: [],
|
footerLinks: [],
|
||||||
nextSeo: {
|
nextSeo: {
|
||||||
|
additionalLinkTags: [
|
||||||
|
{ rel: 'icon', href: '/favicon.ico' },
|
||||||
|
{ rel: 'apple-touch-icon', href: '/icon.png', sizes: '120x120' },
|
||||||
|
],
|
||||||
openGraph: {
|
openGraph: {
|
||||||
type: "website",
|
type: 'website',
|
||||||
title:
|
title:
|
||||||
"PortalJS - rapidly build rich data portals using a modern frontend framework.",
|
'PortalJS - rapidly build rich data portals using a modern frontend framework.',
|
||||||
description:
|
description:
|
||||||
"PortalJS is a framework for rapidly building rich data portal frontends using a modern frontend approach. PortalJS can be used to present a single dataset or build a full-scale data catalog and portal.",
|
'PortalJS is a framework for rapidly building rich data portal frontends using a modern frontend approach. PortalJS can be used to present a single dataset or build a full-scale data catalog and portal.',
|
||||||
locale: "en_US",
|
locale: 'en_US',
|
||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: "/homepage-screenshot.png", // TODO
|
url: '/homepage-screenshot.png', // TODO
|
||||||
alt: "PortalJS - rapidly build rich data portals using a modern frontend framework.",
|
alt: 'PortalJS - rapidly build rich data portals using a modern frontend framework.',
|
||||||
width: 1280,
|
width: 1280,
|
||||||
height: 720,
|
height: 720,
|
||||||
type: "image/jpg",
|
type: 'image/jpg',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
handle: "@datopian",
|
handle: '@datopian',
|
||||||
site: "https://datopian.com/",
|
site: 'https://datopian.com/',
|
||||||
cardType: "summary_large_image",
|
cardType: 'summary_large_image',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
github: "https://github.com/datopian/portaljs",
|
github: 'https://github.com/datopian/portaljs',
|
||||||
discord: "https://discord.gg/EeyfGrGu4U",
|
discord: 'https://discord.gg/EeyfGrGu4U',
|
||||||
tableOfContents: true,
|
tableOfContents: true,
|
||||||
analytics: "G-96GWZHMH57",
|
analytics: 'G-96GWZHMH57',
|
||||||
// editLinkShow: true,
|
// editLinkShow: true,
|
||||||
};
|
};
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<NextSeo title="Creating new datasets - PortalJS" />
|
---
|
||||||
|
title: 'Creating new datasets'
|
||||||
# Creating new datasets
|
description: 'PortalJS Tutorial II - Learn how to create new datasets on a data portal'
|
||||||
|
---
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ Year,Population (mi)
|
|||||||
|
|
||||||
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:
|
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" />
|
<img src="/assets/docs/my-incredible-dataset.png" alt="Page of a new dataset created on a PortalJS data portal" />
|
||||||
|
|
||||||
> [!tip]
|
> [!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.
|
> 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.
|
||||||
@@ -58,12 +59,11 @@ List of available datasets:
|
|||||||
|
|
||||||
- [My Awesome Dataset](/my-awesome-dataset)
|
- [My Awesome Dataset](/my-awesome-dataset)
|
||||||
- [My Incredible Dataset](/my-incredible-dataset)
|
- [My Incredible Dataset](/my-incredible-dataset)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
From the browser, access http://localhost:3000. You should see the following:
|
From the browser, access http://localhost:3000. You should see the following:
|
||||||
|
|
||||||
<img src="/assets/docs/datasets-index-page.png" />
|
<img src="/assets/docs/datasets-index-page.png" alt="PortalJS data portal with multiple datasets" />
|
||||||
|
|
||||||
At this point, the app has multiple datasets, and users can find and navigate to any dataset they want. In the next lesson, you are going to learn how to improve this experience with search.
|
At this point, the app has multiple datasets, and users can find and navigate to any dataset they want. In the next lesson, you are going to learn how to improve this experience with search.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<NextSeo title="Getting Started - PortalJS" />
|
---
|
||||||
|
title: Getting Started
|
||||||
# Getting Started
|
description: 'Getting started guide and tutorial about data portal-building with PortalJS'
|
||||||
|
---
|
||||||
|
|
||||||
Welcome to the PortalJS documentation!
|
Welcome to the PortalJS documentation!
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ Let's check it's working and what we have! Open http://localhost:3000 from your
|
|||||||
|
|
||||||
You should see a page like this when you access http://localhost:3000. This is the starter template page which shows the most simple data portal you could have: a simple README plus csv file.
|
You should see a page like this when you access http://localhost:3000. This is the starter template page which shows the most simple data portal you could have: a simple README plus csv file.
|
||||||
|
|
||||||
<img src="/assets/examples/basic-example.png" />
|
<img src="/assets/examples/basic-example.png" alt="Initial state of the PortalJS tutorial project" />
|
||||||
|
|
||||||
### Editing the Page
|
### Editing the Page
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ Let’s try editing the starter page.
|
|||||||
|
|
||||||
After refreshing the page, you should see the new text:
|
After refreshing the page, you should see the new text:
|
||||||
|
|
||||||
<img src="/assets/docs/editing-the-page-1.png" />
|
<img src="/assets/docs/editing-the-page-1.png" alt="PortalJS tutorial project after a simple change is made by a user" />
|
||||||
|
|
||||||
Congratulations! The app is up and running and you learned how to edit a page. In the next lesson, you are going to learn how to create new datasets.
|
Congratulations! The app is up and running and you learned how to edit a page. In the next lesson, you are going to learn how to create new datasets.
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ This example makes use of the [markdowndb](https://github.com/datopian/markdownd
|
|||||||
|
|
||||||
From the browser, access http://localhost:3000. You should see the following, you now have a searchable automatic list of your datasets:
|
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:
|
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:
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ Built with PortalJS
|
|||||||
|
|
||||||
Rerun `npm run mddb` and, from the browser, access http://localhost:3000. You should see the title appearing instead of the folder name:
|
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.
|
Any frontmatter attribute that you add will automatically get indexed and be usable in the search box.
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ List of available datasets:
|
|||||||
|
|
||||||
You now have a filter in your page with all possible values automatically added to it.
|
You now have a filter in your page with all possible values automatically added to it.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
In the next lesson, you are going to learn how to display metadata on the dataset page.
|
In the next lesson, you are going to learn how to display metadata on the dataset page.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
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.
|
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.
|
Change the content inside `/content/my-awesome-dataset/index.md` to this.
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ Built with PortalJS
|
|||||||
|
|
||||||
Once you refresh the page at `http://localhost:3000/my-awesome-dataset` you should see something like this at the top:
|
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.
|
These are the standard metadata fields that will be shown at the top of the page if you add them.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
---
|
---
|
||||||
showToc: false
|
showToc: false
|
||||||
showSidebar: false
|
showSidebar: false
|
||||||
|
title: "Markdown-based Websites Guide"
|
||||||
|
disableTitle: true
|
||||||
|
description: Create markdown-based websites and data portals, update it, add collaborators and discover markdown superpowers with Flowershow and PortalJS
|
||||||
---
|
---
|
||||||
|
|
||||||
<Hero title="Markdown-based Websites" subtitle="Create markdown-based website, update it, add collaborators and discover markdown superpowers" />
|
<Hero title="Markdown-based Websites" subtitle="Create markdown-based website, update it, add collaborators and discover markdown superpowers" />
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to add Google Analytics?
|
---
|
||||||
|
title: How to add Google Analytics?
|
||||||
|
description: Learn to implement Google Analytics on PortalJS data portals
|
||||||
|
---
|
||||||
|
|
||||||
>[!todo] Prerequisites
|
>[!todo] Prerequisites
|
||||||
>- [Google Analytics account](https://support.google.com/analytics/answer/9304153?hl=en)
|
>- [Google Analytics account](https://support.google.com/analytics/answer/9304153?hl=en)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to add a simple blog?
|
---
|
||||||
|
title: How to add a simple blog?
|
||||||
|
description: How to add a simple blog on a PortalJS data portal
|
||||||
|
---
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to add user comments?
|
---
|
||||||
|
title: How to add user comments?
|
||||||
|
description: Learn how to add user comments on a PortalJS data portal
|
||||||
|
---
|
||||||
|
|
||||||
![[comments-example.png]]
|
![[comments-example.png]]
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to create data-rich documents with charts and tables?
|
---
|
||||||
|
title: How to create data-rich documents with charts and tables?
|
||||||
|
description: Learn how to create a data-rich document with charts and tables on a PortalJS data portal
|
||||||
|
---
|
||||||
|
|
||||||
> [!info] Prerequisites
|
> [!info] Prerequisites
|
||||||
> If you want to enrich your markdown content with charts and tables, you first need to add support for rendering markdown files in your PortalJS app. Follow [[markdown|this guide]] to learn how to do this.
|
> If you want to enrich your markdown content with charts and tables, you first need to add support for rendering markdown files in your PortalJS app. Follow [[markdown|this guide]] to learn how to do this.
|
||||||
@@ -15,12 +18,12 @@ Now, in order to use these components in your markdown files, we need to pass th
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// e.g. /blog/[[...slug]].tsx
|
// e.g. /blog/[[...slug]].tsx
|
||||||
import fs from "fs";
|
import fs from 'fs';
|
||||||
import { LineChart, Table, Catalog, Vega, VegaLite } from "@portaljs/components";
|
import { LineChart, Table, Catalog, Vega, VegaLite } from '@portaljs/components';
|
||||||
|
|
||||||
import { MdxRemote } from "next-mdx-remote";
|
import { MdxRemote } from 'next-mdx-remote';
|
||||||
import clientPromise from "@/lib/mddb.mjs";
|
import clientPromise from '@/lib/mddb.mjs';
|
||||||
import parse from "@/lib/markdown";
|
import parse from '@/lib/markdown';
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
Table: Table,
|
Table: Table,
|
||||||
@@ -29,7 +32,6 @@ const components = {
|
|||||||
LineChart: LineChart,
|
LineChart: LineChart,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function Page({ source }) {
|
export default function Page({ source }) {
|
||||||
source = JSON.parse(source);
|
source = JSON.parse(source);
|
||||||
|
|
||||||
@@ -42,15 +44,15 @@ export default function Page({ source }) {
|
|||||||
|
|
||||||
// Import metadata of a file matching the static path and return its parsed source and frontmatter object
|
// Import metadata of a file matching the static path and return its parsed source and frontmatter object
|
||||||
export const getStaticProps = async ({ params }) => {
|
export const getStaticProps = async ({ params }) => {
|
||||||
const urlPath = params?.slug ? (params.slug as string[]).join("/") : "/";
|
const urlPath = params?.slug ? (params.slug as string[]).join('/') : '/';
|
||||||
|
|
||||||
const mddb = await clientPromise;
|
const mddb = await clientPromise;
|
||||||
const dbFile = await mddb.getFileByUrl(urlPath);
|
const dbFile = await mddb.getFileByUrl(urlPath);
|
||||||
const filePath = dbFile!.file_path;
|
const filePath = dbFile!.file_path;
|
||||||
// const frontMatter = dbFile!.metadata ?? {};
|
// const frontMatter = dbFile!.metadata ?? {};
|
||||||
|
|
||||||
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', {});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
@@ -59,7 +61,6 @@ export const getStaticProps = async ({ params }) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now use these components in your markdown, like so:
|
You can now use these components in your markdown, like so:
|
||||||
@@ -93,39 +94,39 @@ Example usage:
|
|||||||
cols={[
|
cols={[
|
||||||
{
|
{
|
||||||
key: 'id',
|
key: 'id',
|
||||||
name: 'ID'
|
name: 'ID',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'firstName',
|
key: 'firstName',
|
||||||
name: 'First name'
|
name: 'First name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'lastName',
|
key: 'lastName',
|
||||||
name: 'Last name'
|
name: 'Last name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'age',
|
key: 'age',
|
||||||
name: 'Age'
|
name: 'Age',
|
||||||
}
|
},
|
||||||
]}
|
]}
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
age: 35,
|
age: 35,
|
||||||
firstName: 'Jon',
|
firstName: 'Jon',
|
||||||
id: 1,
|
id: 1,
|
||||||
lastName: 'Snow'
|
lastName: 'Snow',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
age: 42,
|
age: 42,
|
||||||
firstName: 'Cersei',
|
firstName: 'Cersei',
|
||||||
id: 2,
|
id: 2,
|
||||||
lastName: 'Lannister'
|
lastName: 'Lannister',
|
||||||
}
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
>[!info]
|
> [!info]
|
||||||
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-table--docs)
|
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-table--docs)
|
||||||
|
|
||||||
### Linechart
|
### Linechart
|
||||||
@@ -139,31 +140,16 @@ Example usage:
|
|||||||
```js
|
```js
|
||||||
<LineChart
|
<LineChart
|
||||||
data={[
|
data={[
|
||||||
[
|
['1850', -0.41765878],
|
||||||
'1850',
|
['1851', -0.2333498],
|
||||||
-0.41765878
|
['1852', -0.22939907],
|
||||||
],
|
['1853', -0.27035445],
|
||||||
[
|
['1854', -0.29163003],
|
||||||
'1851',
|
|
||||||
-0.2333498
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'1852',
|
|
||||||
-0.22939907
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'1853',
|
|
||||||
-0.27035445
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'1854',
|
|
||||||
-0.29163003
|
|
||||||
]
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
>[!info]
|
> [!info]
|
||||||
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-linechart--docs)
|
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-linechart--docs)
|
||||||
|
|
||||||
### Vega chart
|
### Vega chart
|
||||||
@@ -179,30 +165,30 @@ Example usage:
|
|||||||
table: [
|
table: [
|
||||||
{
|
{
|
||||||
x: 1850,
|
x: 1850,
|
||||||
y: -0.418
|
y: -0.418,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
x: 2020,
|
x: 2020,
|
||||||
y: 0.923
|
y: 0.923,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}}
|
}}
|
||||||
spec={{
|
spec={{
|
||||||
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
|
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
|
||||||
data: {
|
data: {
|
||||||
name: 'table'
|
name: 'table',
|
||||||
},
|
},
|
||||||
encoding: {
|
encoding: {
|
||||||
x: {
|
x: {
|
||||||
field: 'x',
|
field: 'x',
|
||||||
type: 'ordinal'
|
type: 'ordinal',
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
field: 'y',
|
field: 'y',
|
||||||
type: 'quantitative'
|
type: 'quantitative',
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mark: 'bar'
|
},
|
||||||
|
mark: 'bar',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
@@ -222,35 +208,35 @@ Example usage:
|
|||||||
table: [
|
table: [
|
||||||
{
|
{
|
||||||
x: 1850,
|
x: 1850,
|
||||||
y: -0.418
|
y: -0.418,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
x: 2020,
|
x: 2020,
|
||||||
y: 0.923
|
y: 0.923,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}}
|
}}
|
||||||
spec={{
|
spec={{
|
||||||
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
|
$schema: 'https://vega.github.io/schema/vega-lite/v4.json',
|
||||||
data: {
|
data: {
|
||||||
name: 'table'
|
name: 'table',
|
||||||
},
|
},
|
||||||
encoding: {
|
encoding: {
|
||||||
x: {
|
x: {
|
||||||
field: 'x',
|
field: 'x',
|
||||||
type: 'ordinal'
|
type: 'ordinal',
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
field: 'y',
|
field: 'y',
|
||||||
type: 'quantitative'
|
type: 'quantitative',
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mark: 'bar'
|
},
|
||||||
|
mark: 'bar',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
>[!info]
|
> [!info]
|
||||||
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-vegalite--docs)
|
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-vegalite--docs)
|
||||||
|
|
||||||
### Catalog
|
### Catalog
|
||||||
@@ -269,28 +255,21 @@ Example usage:
|
|||||||
metadata: {
|
metadata: {
|
||||||
'details-of-task': 'Detect and categorise abusive language in social media data',
|
'details-of-task': 'Detect and categorise abusive language in social media data',
|
||||||
language: 'Albanian',
|
language: 'Albanian',
|
||||||
'level-of-annotation': [
|
'level-of-annotation': ['Posts'],
|
||||||
'Posts'
|
|
||||||
],
|
|
||||||
'link-to-data': 'https://doi.org/10.6084/m9.figshare.19333298.v1',
|
'link-to-data': 'https://doi.org/10.6084/m9.figshare.19333298.v1',
|
||||||
'link-to-publication': 'https://arxiv.org/abs/2107.13592',
|
'link-to-publication': 'https://arxiv.org/abs/2107.13592',
|
||||||
medium: [
|
medium: ['Text'],
|
||||||
'Text'
|
|
||||||
],
|
|
||||||
'percentage-abusive': 13.2,
|
'percentage-abusive': 13.2,
|
||||||
platform: [
|
platform: ['Instagram', 'Youtube'],
|
||||||
'Instagram',
|
|
||||||
'Youtube'
|
|
||||||
],
|
|
||||||
reference: 'Nurce, E., Keci, J., Derczynski, L., 2021. Detecting Abusive Albanian. arXiv:2107.13592',
|
reference: 'Nurce, E., Keci, J., Derczynski, L., 2021. Detecting Abusive Albanian. arXiv:2107.13592',
|
||||||
'size-of-dataset': 11874,
|
'size-of-dataset': 11874,
|
||||||
'task-description': 'Hierarchical (offensive/not; untargeted/targeted; person/group/other)',
|
'task-description': 'Hierarchical (offensive/not; untargeted/targeted; person/group/other)',
|
||||||
title: 'Detecting Abusive Albanian'
|
title: 'Detecting Abusive Albanian',
|
||||||
},
|
},
|
||||||
url_path: 'dataset-4'
|
url_path: 'dataset-4',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also add facets that are going to act as filters for your metadata.
|
You can also add facets that are going to act as filters for your metadata.
|
||||||
@@ -305,30 +284,23 @@ You can also add facets that are going to act as filters for your metadata.
|
|||||||
metadata: {
|
metadata: {
|
||||||
'details-of-task': 'Detect and categorise abusive language in social media data',
|
'details-of-task': 'Detect and categorise abusive language in social media data',
|
||||||
language: 'Albanian',
|
language: 'Albanian',
|
||||||
'level-of-annotation': [
|
'level-of-annotation': ['Posts'],
|
||||||
'Posts'
|
|
||||||
],
|
|
||||||
'link-to-data': 'https://doi.org/10.6084/m9.figshare.19333298.v1',
|
'link-to-data': 'https://doi.org/10.6084/m9.figshare.19333298.v1',
|
||||||
'link-to-publication': 'https://arxiv.org/abs/2107.13592',
|
'link-to-publication': 'https://arxiv.org/abs/2107.13592',
|
||||||
medium: [
|
medium: ['Text'],
|
||||||
'Text'
|
|
||||||
],
|
|
||||||
'percentage-abusive': 13.2,
|
'percentage-abusive': 13.2,
|
||||||
platform: [
|
platform: ['Instagram', 'Youtube'],
|
||||||
'Instagram',
|
|
||||||
'Youtube'
|
|
||||||
],
|
|
||||||
reference: 'Nurce, E., Keci, J., Derczynski, L., 2021. Detecting Abusive Albanian. arXiv:2107.13592',
|
reference: 'Nurce, E., Keci, J., Derczynski, L., 2021. Detecting Abusive Albanian. arXiv:2107.13592',
|
||||||
'size-of-dataset': 11874,
|
'size-of-dataset': 11874,
|
||||||
'task-description': 'Hierarchical (offensive/not; untargeted/targeted; person/group/other)',
|
'task-description': 'Hierarchical (offensive/not; untargeted/targeted; person/group/other)',
|
||||||
title: 'Detecting Abusive Albanian'
|
title: 'Detecting Abusive Albanian',
|
||||||
},
|
},
|
||||||
url_path: 'dataset-4'
|
url_path: 'dataset-4',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
facets={['platform', 'language']}
|
facets={['platform', 'language']}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
>[!info]
|
> [!info]
|
||||||
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-catalog--docs)
|
> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-catalog--docs)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# Guides and tutorials
|
---
|
||||||
|
title: Guides and Tutorials
|
||||||
|
description: Learn more about how you can achieve different data portal features with PortalJS
|
||||||
|
---
|
||||||
|
|
||||||
- [[howtos/analytics|How to add web analytics?]]
|
- [[howtos/analytics|How to add web analytics?]]
|
||||||
- [[howtos/seo|How to customize page metadata for SEO?]]
|
- [[howtos/seo|How to customize page metadata for SEO?]]
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to add markdown-based content pages?
|
---
|
||||||
|
title: How to add markdown-based content pages?
|
||||||
|
description: Learn how to add markdown-based content pages on PortalJS data portals
|
||||||
|
---
|
||||||
|
|
||||||
## Add content layer to your app
|
## Add content layer to your app
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to customize page metadata for SEO?
|
---
|
||||||
|
title: How to customize page metadata for SEO?
|
||||||
|
description: Learn to customize page metadata for SEO on data portals with PortalJS
|
||||||
|
---
|
||||||
|
|
||||||
>[!info]
|
>[!info]
|
||||||
>See [`next-seo` documentation](https://github.com/garmeeh/next-seo) to learn more.
|
>See [`next-seo` documentation](https://github.com/garmeeh/next-seo) to learn more.
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
# How to build a sitemap?
|
---
|
||||||
|
title: How to build a sitemap?
|
||||||
|
description: Learn how to build a sitemap for a data portal with PortalJS
|
||||||
|
---
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default function DefaultLayout({ children, ...frontMatter }) {
|
|||||||
{/* Default layout */}
|
{/* Default layout */}
|
||||||
{!frontMatter.layout && (
|
{!frontMatter.layout && (
|
||||||
<>
|
<>
|
||||||
<h1>{frontMatter.title}</h1>
|
{!frontMatter.disableTitle && <h1>{frontMatter.title}</h1>}
|
||||||
{frontMatter.author && (
|
{frontMatter.author && (
|
||||||
<div className="-mt-6">
|
<div className="-mt-6">
|
||||||
<p className="opacity-60 pl-1">{frontMatter.author}</p>
|
<p className="opacity-60 pl-1">{frontMatter.author}</p>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const DocsLayout: React.FC<any> = ({ children, ...frontMatter }) => {
|
|||||||
<time dateTime={created}>{formatDate(created)}</time>
|
<time dateTime={created}>{formatDate(created)}</time>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{title && <h1>{title}</h1>}
|
{!frontMatter.disableTitle && title && <h1>{title}</h1>}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<section>{children}</section>
|
<section>{children}</section>
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { h } from "hastscript";
|
import { h } from 'hastscript';
|
||||||
import matter from "gray-matter";
|
import matter from 'gray-matter';
|
||||||
import mdxmermaid from "mdx-mermaid";
|
import mdxmermaid from 'mdx-mermaid';
|
||||||
import remarkCallouts from "@portaljs/remark-callouts";
|
import remarkCallouts from '@portaljs/remark-callouts';
|
||||||
import remarkEmbed from "@portaljs/remark-embed";
|
import remarkEmbed from '@portaljs/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, { getPermalinks } from "@portaljs/remark-wiki-link";
|
import remarkWikiLink, { getPermalinks } from '@portaljs/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';
|
||||||
import rehypePrismPlus from "rehype-prism-plus";
|
import rehypePrismPlus from 'rehype-prism-plus';
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
import { serialize } from 'next-mdx-remote/serialize';
|
||||||
|
|
||||||
import * as tw from "../tailwind.config";
|
import * as tw from '../tailwind.config';
|
||||||
import { siteConfig } from "../config/siteConfig";
|
import { siteConfig } from '../config/siteConfig';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a markdown or MDX file to an MDX source form + front matter data
|
* Parse a markdown or MDX file to an MDX source form + front matter data
|
||||||
@@ -36,14 +36,14 @@ const parse = async function (source, format, scope) {
|
|||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
remarkEmbed,
|
remarkEmbed,
|
||||||
remarkGfm,
|
remarkGfm,
|
||||||
[remarkSmartypants, { quotes: false, dashes: "oldschool" }],
|
[remarkSmartypants, { quotes: false, dashes: 'oldschool' }],
|
||||||
remarkMath,
|
remarkMath,
|
||||||
remarkCallouts,
|
remarkCallouts,
|
||||||
[remarkWikiLink, { permalinks, pathFormat: "obsidian-short" }],
|
[remarkWikiLink, { permalinks, pathFormat: 'obsidian-short' }],
|
||||||
[
|
[
|
||||||
remarkToc,
|
remarkToc,
|
||||||
{
|
{
|
||||||
heading: "Table of contents",
|
heading: 'Table of contents',
|
||||||
tight: true,
|
tight: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -54,29 +54,36 @@ const parse = async function (source, format, scope) {
|
|||||||
[
|
[
|
||||||
rehypeAutolinkHeadings,
|
rehypeAutolinkHeadings,
|
||||||
{
|
{
|
||||||
properties: { className: "heading-link" },
|
properties: { className: 'heading-link' },
|
||||||
test(element) {
|
test(element) {
|
||||||
return (
|
return (
|
||||||
["h2", "h3", "h4", "h5", "h6"].includes(element.tagName) &&
|
['h2', 'h3', 'h4', 'h5', 'h6'].includes(element.tagName) &&
|
||||||
element.properties?.id !== "table-of-contents" &&
|
element.properties?.id !== 'table-of-contents' &&
|
||||||
element.properties?.className !== "blockquote-heading"
|
element.properties?.className !== 'blockquote-heading'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
content() {
|
content(node) {
|
||||||
return [
|
return [
|
||||||
h(
|
h(
|
||||||
"svg",
|
'span.invisible.block.h-0.w-0',
|
||||||
|
{ ariaLabel: node.properties.id },
|
||||||
|
'Read the “',
|
||||||
|
node.properties.id,
|
||||||
|
'” section'
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'svg',
|
||||||
{
|
{
|
||||||
xmlns: "http:www.w3.org/2000/svg",
|
xmlns: 'http:www.w3.org/2000/svg',
|
||||||
fill: tw.theme.extend.colors.secondary.DEFAULT,
|
fill: tw.theme.extend.colors.secondary.DEFAULT,
|
||||||
viewBox: "0 0 20 20",
|
viewBox: '0 0 20 20',
|
||||||
className: "w-5 h-5",
|
className: 'w-5 h-5',
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
h("path", {
|
h('path', {
|
||||||
fillRule: "evenodd",
|
fillRule: 'evenodd',
|
||||||
clipRule: "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",
|
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',
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@@ -84,7 +91,7 @@ const parse = async function (source, format, scope) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[rehypeKatex, { output: "mathml" }],
|
[rehypeKatex, { output: 'mathml' }],
|
||||||
[rehypePrismPlus, { ignoreMissing: true }],
|
[rehypePrismPlus, { ignoreMissing: true }],
|
||||||
],
|
],
|
||||||
format,
|
format,
|
||||||
|
|||||||
5
site/next-sitemap.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/** @type {import('next-sitemap').IConfig} */
|
||||||
|
module.exports = {
|
||||||
|
siteUrl: process.env.SITE_URL || 'https://portaljs.org',
|
||||||
|
generateRobotsTxt: true,
|
||||||
|
}
|
||||||
49
site/package-lock.json
generated
@@ -50,6 +50,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"next-sitemap": "^4.1.8",
|
||||||
"postcss": "^8.4.22",
|
"postcss": "^8.4.22",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"remark": "^14.0.2",
|
"remark": "^14.0.2",
|
||||||
@@ -229,6 +230,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz",
|
||||||
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
|
"integrity": "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@corex/deepmerge": {
|
||||||
|
"version": "4.0.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz",
|
||||||
|
"integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@docsearch/css": {
|
"node_modules/@docsearch/css": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.0.tgz",
|
||||||
@@ -7087,6 +7094,15 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minipass": {
|
"node_modules/minipass": {
|
||||||
"version": "3.3.6",
|
"version": "3.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||||
@@ -7330,6 +7346,39 @@
|
|||||||
"react-dom": ">=16.0.0"
|
"react-dom": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-sitemap": {
|
||||||
|
"version": "4.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.1.8.tgz",
|
||||||
|
"integrity": "sha512-XAXpBHX4o89JfMgvrm0zimlZwpu2iBPXHpimJMUrqOZSc4C2oB1Lv89mxuVON9IE8HOezaM+w4GjJxcYCuGPTQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/iamvishnusankar/next-sitemap.git"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@corex/deepmerge": "^4.0.43",
|
||||||
|
"@next/env": "^13.4.3",
|
||||||
|
"fast-glob": "^3.2.12",
|
||||||
|
"minimist": "^1.2.8"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"next-sitemap": "bin/next-sitemap.mjs",
|
||||||
|
"next-sitemap-cjs": "bin/next-sitemap.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-sitemap/node_modules/@next/env": {
|
||||||
|
"version": "13.4.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.10.tgz",
|
||||||
|
"integrity": "sha512-3G1yD/XKTSLdihyDSa8JEsaWOELY+OWe08o0LUYzfuHp1zHDA8SObQlzKt+v+wrkkPcnPweoLH1ImZeUa0A1NQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/next-themes": {
|
"node_modules/next-themes": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"dev": "npm run mddb && next dev",
|
"dev": "npm run mddb && next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"prebuild": "npm run mddb && node ./scripts/fix-symlinks.mjs",
|
"prebuild": "npm run mddb && node ./scripts/fix-symlinks.mjs",
|
||||||
|
"postbuild": "next-sitemap",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"mddb": "mddb content"
|
"mddb": "mddb content"
|
||||||
},
|
},
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"next-sitemap": "^4.1.8",
|
||||||
"postcss": "^8.4.22",
|
"postcss": "^8.4.22",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"remark": "^14.0.2",
|
"remark": "^14.0.2",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { GetStaticProps, GetStaticPropsResult } from 'next';
|
|||||||
import { CustomAppProps } from './_app.jsx';
|
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';
|
||||||
|
import JSONLD from '@/components/JSONLD';
|
||||||
|
|
||||||
export default function Page({ source, meta, sidebarTree }) {
|
export default function Page({ source, meta, sidebarTree }) {
|
||||||
source = JSON.parse(source);
|
source = JSON.parse(source);
|
||||||
@@ -29,14 +30,18 @@ export default function Page({ source, meta, sidebarTree }) {
|
|||||||
}, [router.asPath]); // update table of contents on route change with next/link
|
}, [router.asPath]); // update table of contents on route change with next/link
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<JSONLD meta={meta} source={source.compiledSource} />
|
||||||
<Layout
|
<Layout
|
||||||
tableOfContents={tableOfContents}
|
tableOfContents={tableOfContents}
|
||||||
title={meta.title}
|
title={meta.title}
|
||||||
|
description={meta.description}
|
||||||
sidebarTree={sidebarTree}
|
sidebarTree={sidebarTree}
|
||||||
urlPath={meta.urlPath}
|
urlPath={meta.urlPath}
|
||||||
>
|
>
|
||||||
<MDXPage source={source} frontMatter={meta} />
|
<MDXPage source={source} frontMatter={meta} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import "../styles/globals.css";
|
import '../styles/globals.css';
|
||||||
import "../styles/tailwind.css";
|
import '../styles/tailwind.css';
|
||||||
|
import '../styles/sib-form.css';
|
||||||
|
|
||||||
import Script from "next/script";
|
import Script from 'next/script';
|
||||||
|
|
||||||
import { DefaultSeo } from "next-seo";
|
import { DefaultSeo } from 'next-seo';
|
||||||
|
|
||||||
import { NavGroup, NavItem, pageview, ThemeProvider } from "@portaljs/core";
|
import { NavGroup, NavItem, pageview, ThemeProvider } from '@portaljs/core';
|
||||||
import { siteConfig } from "../config/siteConfig";
|
import { siteConfig } from '../config/siteConfig';
|
||||||
import { useEffect } from "react";
|
import { useEffect } from 'react';
|
||||||
import { useRouter } from "next/dist/client/router";
|
import { useRouter } from 'next/dist/client/router';
|
||||||
|
|
||||||
export interface CustomAppProps {
|
export interface CustomAppProps {
|
||||||
meta: {
|
meta: {
|
||||||
@@ -32,9 +33,9 @@ function MyApp({ Component, pageProps }) {
|
|||||||
const handleRouteChange = (url) => {
|
const handleRouteChange = (url) => {
|
||||||
pageview(url);
|
pageview(url);
|
||||||
};
|
};
|
||||||
router.events.on("routeChangeComplete", handleRouteChange);
|
router.events.on('routeChangeComplete', handleRouteChange);
|
||||||
return () => {
|
return () => {
|
||||||
router.events.off("routeChangeComplete", handleRouteChange);
|
router.events.off('routeChangeComplete', handleRouteChange);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [router.events]);
|
}, [router.events]);
|
||||||
@@ -44,9 +45,14 @@ function MyApp({ Component, pageProps }) {
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme={siteConfig.theme.default}
|
defaultTheme={siteConfig.theme.default}
|
||||||
forcedTheme={siteConfig.theme.default ? null : "light"}
|
forcedTheme={siteConfig.theme.default ? null : 'light'}
|
||||||
>
|
>
|
||||||
<DefaultSeo defaultTitle={siteConfig.title} {...siteConfig.nextSeo} />
|
<DefaultSeo
|
||||||
|
defaultTitle={siteConfig.title}
|
||||||
|
description={siteConfig.description}
|
||||||
|
titleTemplate="PortalJS - %s"
|
||||||
|
{...siteConfig.nextSeo}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Global Site Tag (gtag.js) - Google Analytics */}
|
{/* Global Site Tag (gtag.js) - Google Analytics */}
|
||||||
{siteConfig.analytics && (
|
{siteConfig.analytics && (
|
||||||
@@ -57,7 +63,7 @@ function MyApp({ Component, pageProps }) {
|
|||||||
/>
|
/>
|
||||||
<Script
|
<Script
|
||||||
id="gtag-init"
|
id="gtag-init"
|
||||||
strategy="afterInteractive"
|
strategy="lazyOnload"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: `
|
__html: `
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
@@ -73,7 +79,12 @@ function MyApp({ Component, pageProps }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Umami Analytics */}
|
{/* Umami Analytics */}
|
||||||
<Script async defer data-website-id="061e14c1-6157-4a93-820c-777c7a937c12" src="https://analytics.datopian.com/umami.js" />
|
<Script
|
||||||
|
async
|
||||||
|
defer
|
||||||
|
data-website-id="061e14c1-6157-4a93-820c-777c7a937c12"
|
||||||
|
src="https://analytics.datopian.com/umami.js"
|
||||||
|
/>
|
||||||
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
13
site/pages/_document.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Html, Head, Main, NextScript } from 'next/document';
|
||||||
|
|
||||||
|
export default function Document() {
|
||||||
|
return (
|
||||||
|
<Html lang="en">
|
||||||
|
<Head />
|
||||||
|
<body>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,10 +3,15 @@ import computeFields from '@/lib/computeFields';
|
|||||||
import clientPromise from '@/lib/mddb';
|
import clientPromise from '@/lib/mddb';
|
||||||
import { BlogsList, SimpleLayout } from '@portaljs/core';
|
import { BlogsList, SimpleLayout } from '@portaljs/core';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import { NextSeo } from 'next-seo';
|
||||||
|
|
||||||
export default function Blog({ blogs }) {
|
export default function Blog({ blogs }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<NextSeo
|
||||||
|
title="Blog posts"
|
||||||
|
description="Find news and more information about rapidly building rich data portals using a modern frontend framework in the PortalJS blog"
|
||||||
|
/>
|
||||||
<Layout>
|
<Layout>
|
||||||
<SimpleLayout title="Blog posts">
|
<SimpleLayout title="Blog posts">
|
||||||
<BlogsList blogs={blogs} />
|
<BlogsList blogs={blogs} />
|
||||||
@@ -48,12 +53,9 @@ export async function getStaticProps() {
|
|||||||
const blogList = await Promise.all(blogsWithComputedFields);
|
const blogList = await Promise.all(blogsWithComputedFields);
|
||||||
|
|
||||||
const blogsSorted = blogList.sort(
|
const blogsSorted = blogList.sort(
|
||||||
(a, b) =>
|
(a, b) => new Date(b?.date).getTime() - new Date(a?.date).getTime()
|
||||||
new Date(b?.date).getTime() -
|
|
||||||
new Date(a?.date).getTime()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
blogs: blogsSorted,
|
blogs: blogsSorted,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import Layout from '../components/Layout';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { collectHeadings } from '@portaljs/core';
|
import { collectHeadings } from '@portaljs/core';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import { LogoJsonLd } from 'next-seo';
|
||||||
|
|
||||||
export default function Home({ sidebarTree }) {
|
export default function Home({ sidebarTree }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -23,7 +25,15 @@ export default function Home({ sidebarTree }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout isHomePage={true} tableOfContents={tableOfContents} sidebarTree={sidebarTree} >
|
<LogoJsonLd
|
||||||
|
url="https://portaljs.org"
|
||||||
|
logo="https://portaljs.org/icon.png"
|
||||||
|
/>
|
||||||
|
<Layout
|
||||||
|
isHomePage={true}
|
||||||
|
tableOfContents={tableOfContents}
|
||||||
|
sidebarTree={sidebarTree}
|
||||||
|
>
|
||||||
<Features />
|
<Features />
|
||||||
<Showcases />
|
<Showcases />
|
||||||
<Community />
|
<Community />
|
||||||
|
|||||||
BIN
site/public/datopian-logo.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
site/public/icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
site/public/images/showcases/birmingham.webp
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
site/public/images/showcases/brazil.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
site/public/images/showcases/datahub.webp
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
site/public/images/showcases/example-ckan.webp
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
site/public/images/showcases/example-simple-catalog.webp
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
site/public/images/showcases/odni.webp
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
site/public/images/showcases/uae.webp
Normal file
|
After Width: | Height: | Size: 45 KiB |
@@ -1,4 +1,4 @@
|
|||||||
@import "@portaljs/remark-callouts/styles.css";
|
@import '@portaljs/remark-callouts/styles.css';
|
||||||
@import './prism.css';
|
@import './prism.css';
|
||||||
|
|
||||||
html,
|
html,
|
||||||
@@ -31,7 +31,7 @@ html {
|
|||||||
|
|
||||||
/* tooltip fade-out clip */
|
/* tooltip fade-out clip */
|
||||||
.tooltip-body::after {
|
.tooltip-body::after {
|
||||||
content: "";
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
|
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
|
||||||
|
|||||||