[site][lg] - integrate demenech changes into monorepo
This commit is contained in:
parent
fda6c4b827
commit
97c1dc0d59
19
apps/site/README.md
Normal file
19
apps/site/README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
This the Portal.JS website.
|
||||||
|
|
||||||
|
It is built on [Next.js](https://nextjs.org/).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
We currently deploy on Vercel.
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import Link from 'next/link'
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function CustomLink({ as, href, ...otherProps }) {
|
export default function CustomLink({ as, href, ...otherProps }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link as={as} href={href}>
|
<Link legacyBehavior as={as} href={href}>
|
||||||
<a {...otherProps} />
|
<a {...otherProps} />
|
||||||
</Link>
|
</Link>
|
||||||
<style jsx>{`
|
<style jsx>{`
|
||||||
@ -12,5 +12,5 @@ export default function CustomLink({ as, href, ...otherProps }) {
|
|||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
34
apps/site/components/Layout.tsx
Normal file
34
apps/site/components/Layout.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { NextSeo } from "next-seo";
|
||||||
|
|
||||||
|
import Nav from "./Nav";
|
||||||
|
|
||||||
|
export default function Layout({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
children;
|
||||||
|
title?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{title && <NextSeo title={title} />}
|
||||||
|
<Nav />
|
||||||
|
<div className="mx-auto p-6">{children}</div>
|
||||||
|
<footer className="flex items-center justify-center w-full h-24 border-t">
|
||||||
|
<a
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
href="https://datopian.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Built by{" "}
|
||||||
|
<img
|
||||||
|
src="/datopian-logo.png"
|
||||||
|
alt="Datopian Logo"
|
||||||
|
className="h-6 ml-2"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
40
apps/site/components/MDXPage.tsx
Normal file
40
apps/site/components/MDXPage.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { MDXRemote } from "next-mdx-remote";
|
||||||
|
import layouts from "layouts";
|
||||||
|
|
||||||
|
export default function DRD({ source, frontMatter }) {
|
||||||
|
const Layout = ({ children }) => {
|
||||||
|
if (frontMatter.layout) {
|
||||||
|
let LayoutComponent = layouts[frontMatter.layout];
|
||||||
|
return <LayoutComponent {...frontMatter}>{children}</LayoutComponent>;
|
||||||
|
}
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="prose mx-auto">
|
||||||
|
<header>
|
||||||
|
<div className="mb-6">
|
||||||
|
{/* Default layout */}
|
||||||
|
{!frontMatter.layout && (
|
||||||
|
<>
|
||||||
|
<h1>{frontMatter.title}</h1>
|
||||||
|
{frontMatter.author && (
|
||||||
|
<div className="-mt-6">
|
||||||
|
<p className="opacity-60 pl-1">{frontMatter.author}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{frontMatter.description && (
|
||||||
|
<p className="description">{frontMatter.description}</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<Layout>
|
||||||
|
<MDXRemote {...source} />
|
||||||
|
</Layout>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,22 +1,15 @@
|
|||||||
import { Disclosure, Menu, Transition } from '@headlessui/react'
|
import { Fragment } from "react";
|
||||||
|
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||||
|
import { siteConfig } from "config/siteConfig";
|
||||||
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
import Link from 'next/link'
|
import Link from "next/link";
|
||||||
import GitHubButton from 'react-next-github-btn'
|
import GitHubButton from "react-next-github-btn";
|
||||||
|
|
||||||
const navigation = [
|
const navigation = siteConfig.navLinks;
|
||||||
{ name: 'Docs', href: '/docs' },
|
|
||||||
{ name: 'Components', href: '/docs/components' },
|
|
||||||
{ name: 'Learn', href: '/learn' },
|
|
||||||
{ name: 'Gallery', href: '/gallery' },
|
|
||||||
{ name: 'Data Literate', href: '/data-literate/', current: false },
|
|
||||||
{ name: 'DL Demo', href: '/data-literate/demo/', current: false },
|
|
||||||
{ name: 'Excel Viewer', href: '/excel-viewer/', current: false },
|
|
||||||
{ name: 'Github', href: 'https://github.com/datopian/portal.js', current: false },
|
|
||||||
]
|
|
||||||
|
|
||||||
function classNames(...classes) {
|
function classNames(...classes) {
|
||||||
return classes.filter(Boolean).join(' ')
|
return classes.filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Nav() {
|
export default function Nav() {
|
||||||
@ -31,29 +24,31 @@ export default function Nav() {
|
|||||||
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
|
||||||
<span className="sr-only">Open main menu</span>
|
<span className="sr-only">Open main menu</span>
|
||||||
{open ? (
|
{open ? (
|
||||||
<XMarkIcon className="block h-6 w-6" aria-hidden="true" />
|
|
||||||
) : (
|
|
||||||
<Bars3Icon className="block h-6 w-6" aria-hidden="true" />
|
<Bars3Icon className="block h-6 w-6" aria-hidden="true" />
|
||||||
|
) : (
|
||||||
|
<XMarkIcon className="block h-6 w-6" aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
||||||
<div className="flex-shrink-0 flex items-center">
|
<div className="flex-shrink-0 flex items-center">
|
||||||
<Link href="/" className="text-white"
|
<Link href="/" className="text-white">
|
||||||
>
|
Portal.JS
|
||||||
Portal.JS
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:block sm:ml-6">
|
<div className="hidden sm:block sm:ml-6">
|
||||||
<div className="flex space-x-4">
|
<div className="flex space-x-4">
|
||||||
{navigation.map((item) => (
|
{navigation.map((item) => (
|
||||||
<Link href={item.href}
|
<Link
|
||||||
|
href={item.href}
|
||||||
key={item.name}
|
key={item.name}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
item.current
|
||||||
'px-3 py-2 rounded-md text-sm font-medium'
|
? "bg-gray-900 text-white"
|
||||||
|
: "text-gray-300 hover:bg-gray-700 hover:text-white",
|
||||||
|
"px-3 py-2 rounded-md text-sm font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={item.current ? 'page' : undefined}
|
aria-current={item.current ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
@ -62,8 +57,16 @@ export default function Nav() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 justify-end">
|
<div className="mt-2 justify-end">
|
||||||
<GitHubButton href="https://github.com/datopian/portal.js" data-color-scheme="no-preference: light; light: light; dark: dark;" data-size="large" data-show-count="true" aria-label="Star datopian/portal.js on GitHub">Stars</GitHubButton>
|
<GitHubButton
|
||||||
</div>
|
href="https://github.com/datopian/portal.js"
|
||||||
|
data-color-scheme="no-preference: light; light: light; dark: dark;"
|
||||||
|
data-size="large"
|
||||||
|
data-show-count="true"
|
||||||
|
aria-label="Star datopian/portal.js on GitHub"
|
||||||
|
>
|
||||||
|
Stars
|
||||||
|
</GitHubButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -74,10 +77,12 @@ export default function Nav() {
|
|||||||
key={item.name}
|
key={item.name}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
|
item.current
|
||||||
'block px-3 py-2 rounded-md text-base font-medium'
|
? "bg-gray-900 text-white"
|
||||||
|
: "text-gray-300 hover:bg-gray-700 hover:text-white",
|
||||||
|
"block px-3 py-2 rounded-md text-base font-medium"
|
||||||
)}
|
)}
|
||||||
aria-current={item.current ? 'page' : undefined}
|
aria-current={item.current ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</a>
|
</a>
|
||||||
@ -87,5 +92,5 @@ export default function Nav() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
46
apps/site/components/data-literate/DataLiterate.js
Normal file
46
apps/site/components/data-literate/DataLiterate.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import Layout from '../Layout'
|
||||||
|
|
||||||
|
import { MDXRemote } from 'next-mdx-remote'
|
||||||
|
import dynamic from 'next/dynamic'
|
||||||
|
import Head from 'next/head'
|
||||||
|
|
||||||
|
import CustomLink from '../CustomLink'
|
||||||
|
import { Vega, VegaLite } from 'react-vega'
|
||||||
|
|
||||||
|
// Custom components/renderers to pass to MDX.
|
||||||
|
// Since the MDX files aren't loaded by webpack, they have no knowledge of how
|
||||||
|
// to handle import statements. Instead, you must include components in scope
|
||||||
|
// here.
|
||||||
|
const components = {
|
||||||
|
a: CustomLink,
|
||||||
|
Table: dynamic(() => import('./Table')),
|
||||||
|
Excel: dynamic(() => import('./Excel')),
|
||||||
|
// TODO: try and make these dynamic ...
|
||||||
|
Vega: Vega,
|
||||||
|
VegaLite: VegaLite,
|
||||||
|
LineChart: dynamic(() => import('./LineChart')),
|
||||||
|
Head,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DataLiterate({ source, frontMatter }) {
|
||||||
|
return (
|
||||||
|
<Layout title={frontMatter.title}>
|
||||||
|
<div className="prose mx-auto">
|
||||||
|
<header>
|
||||||
|
<div className="mb-6">
|
||||||
|
<h1>{frontMatter.title}</h1>
|
||||||
|
{frontMatter.author && (
|
||||||
|
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
|
||||||
|
)}
|
||||||
|
{frontMatter.description && (
|
||||||
|
<p className="description">{frontMatter.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<MDXRemote {...source} components={components} />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
}
|
||||||
74
apps/site/components/data-literate/Excel.js
Normal file
74
apps/site/components/data-literate/Excel.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import XLSX from 'xlsx'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import Table from './Table'
|
||||||
|
|
||||||
|
export default function Excel ({ src='' }) {
|
||||||
|
const [data, setData] = React.useState([])
|
||||||
|
const [cols, setCols] = React.useState([])
|
||||||
|
const [workbook, setWorkbook] = React.useState(null)
|
||||||
|
const [error, setError] = React.useState('')
|
||||||
|
const [hasMounted, setHasMounted] = React.useState(0)
|
||||||
|
|
||||||
|
// so this is here so we re-render this in the browser
|
||||||
|
// and not just when we build the page statically in nextjs
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasMounted==0) {
|
||||||
|
handleUrl(src)
|
||||||
|
}
|
||||||
|
setHasMounted(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleUrl(url) {
|
||||||
|
// if url is external may have CORS issue so we proxy it ...
|
||||||
|
if (url.startsWith('http')) {
|
||||||
|
const PROXY_URL = window.location.origin + '/api/proxy'
|
||||||
|
url = PROXY_URL + '?url=' + encodeURIComponent(url)
|
||||||
|
}
|
||||||
|
axios.get(url, {
|
||||||
|
responseType: 'arraybuffer'
|
||||||
|
}).then((res) => {
|
||||||
|
let out = new Uint8Array(res.data)
|
||||||
|
let workbook = XLSX.read(out, {type: "array"})
|
||||||
|
// Get first worksheet
|
||||||
|
const wsname = workbook.SheetNames[0]
|
||||||
|
const ws = workbook.Sheets[wsname]
|
||||||
|
// Convert array of arrays
|
||||||
|
const datatmp = XLSX.utils.sheet_to_json(ws, {header:1})
|
||||||
|
const colstmp = make_cols(ws['!ref'])
|
||||||
|
setData(datatmp)
|
||||||
|
setCols(colstmp)
|
||||||
|
setWorkbook(workbook)
|
||||||
|
}).catch((e) => {
|
||||||
|
setError(e.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{error &&
|
||||||
|
<div>
|
||||||
|
There was an error loading the excel file at {src}:
|
||||||
|
<p>{error}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{workbook &&
|
||||||
|
<ul>
|
||||||
|
{workbook.SheetNames.map((value, index) => {
|
||||||
|
return <li key={index}>{value}</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
<Table data={data} cols={cols} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generate an array of column objects */
|
||||||
|
const make_cols = refstr => {
|
||||||
|
let o = [], C = XLSX.utils.decode_range(refstr).e.c + 1
|
||||||
|
for(var i = 0; i < C; ++i) o[i] = {name:XLSX.utils.encode_col(i), key:i}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
33
apps/site/components/data-literate/LineChart.js
Normal file
33
apps/site/components/data-literate/LineChart.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Vega, VegaLite } from 'react-vega'
|
||||||
|
|
||||||
|
export default function LineChart( { data=[] }) {
|
||||||
|
var tmp = data
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
tmp = data.map((r,i) => {
|
||||||
|
return { x: r[0], y: r[1] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const vegaData = { "table": tmp }
|
||||||
|
const spec = {
|
||||||
|
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
|
||||||
|
"mark": "line",
|
||||||
|
"data": {
|
||||||
|
"name": "table"
|
||||||
|
},
|
||||||
|
"encoding": {
|
||||||
|
"x": {
|
||||||
|
"field": "x",
|
||||||
|
"timeUnit": "year",
|
||||||
|
"type": "temporal"
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"field": "y",
|
||||||
|
"type": "quantitative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VegaLite data={ vegaData } spec={ spec } />
|
||||||
|
)
|
||||||
|
}
|
||||||
83
apps/site/components/data-literate/Table.js
Normal file
83
apps/site/components/data-literate/Table.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
const papa = require("papaparse")
|
||||||
|
|
||||||
|
/*
|
||||||
|
Simple HTML Table
|
||||||
|
usage: <OutTable data={data} cols={cols} />
|
||||||
|
data:Array<Array<any> >;
|
||||||
|
cols:Array<{name:string, key:number|string}>;
|
||||||
|
*/
|
||||||
|
export default function Table({ data=[], cols=[], csv='', url='' }) {
|
||||||
|
if (csv) {
|
||||||
|
const out = parseCsv(csv)
|
||||||
|
data = out.rows
|
||||||
|
cols = out.cols
|
||||||
|
}
|
||||||
|
|
||||||
|
const [ourdata, setData] = React.useState(data)
|
||||||
|
const [ourcols, setCols] = React.useState(cols)
|
||||||
|
const [error, setError] = React.useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (url) {
|
||||||
|
loadUrl(url)
|
||||||
|
}
|
||||||
|
}, [url])
|
||||||
|
|
||||||
|
function loadUrl(path) {
|
||||||
|
// HACK: duplicate of Excel code - maybe refactor
|
||||||
|
// if url is external may have CORS issue so we proxy it ...
|
||||||
|
if (url.startsWith('http')) {
|
||||||
|
const PROXY_URL = window.location.origin + '/api/proxy'
|
||||||
|
url = PROXY_URL + '?url=' + encodeURIComponent(url)
|
||||||
|
}
|
||||||
|
axios.get(url).then((res) => {
|
||||||
|
const { rows, fields } = parseCsv(res.data)
|
||||||
|
setData(rows)
|
||||||
|
setCols(fields)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SimpleTable data={ourdata} cols={ourcols} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Simple HTML Table
|
||||||
|
usage: <OutTable data={data} cols={cols} />
|
||||||
|
data:Array<Array<any> >;
|
||||||
|
cols:Array<{name:string, key:number|string}>;
|
||||||
|
*/
|
||||||
|
function SimpleTable({ data=[], cols=[] }) {
|
||||||
|
return (
|
||||||
|
<div className="table-responsive">
|
||||||
|
<table className="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>{cols.map((c) => <th key={c.key}>{c.name}</th>)}</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{data.map((r,i) => <tr key={i}>
|
||||||
|
{cols.map(c => <td key={c.key}>{ r[c.key] }</td>)}
|
||||||
|
</tr>)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseCsv(csv) {
|
||||||
|
csv = csv.trim()
|
||||||
|
const rawdata = papa.parse(csv, {header: true})
|
||||||
|
const cols = rawdata.meta.fields.map((r,i) => {
|
||||||
|
return { key: r, name: r }
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
rows: rawdata.data,
|
||||||
|
fields: cols
|
||||||
|
}
|
||||||
|
}
|
||||||
13
apps/site/config/siteConfig.ts
Normal file
13
apps/site/config/siteConfig.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { defaultConfig } from "@flowershow/core";
|
||||||
|
import userConfig from "../content/config";
|
||||||
|
|
||||||
|
export const siteConfig: any = {
|
||||||
|
...defaultConfig,
|
||||||
|
...userConfig,
|
||||||
|
// prevent theme object overrides for
|
||||||
|
// values not provided in userConfig
|
||||||
|
theme: {
|
||||||
|
...defaultConfig.theme,
|
||||||
|
...userConfig?.theme,
|
||||||
|
},
|
||||||
|
};
|
||||||
51
apps/site/content/config.js
Normal file
51
apps/site/content/config.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const config = {
|
||||||
|
title:
|
||||||
|
"Portal.JS - Rapidly build rich data portals using a modern frontend framework",
|
||||||
|
description:
|
||||||
|
"Portal.JS is a framework for rapidly building rich data portal frontends using a modern frontend approach. portal.js can be used to present a single dataset or build a full-scale data catalog/portal.",
|
||||||
|
theme: {
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
author: "Datopian",
|
||||||
|
authorLogo: "/datopian-logo.png",
|
||||||
|
authorUrl: "https://datopian.com/",
|
||||||
|
navLinks: [
|
||||||
|
{ name: "Docs", href: "/docs" },
|
||||||
|
{ name: "Components", href: "/docs/components" },
|
||||||
|
{ name: "Learn", href: "/learn" },
|
||||||
|
{ name: "Gallery", href: "/gallery" },
|
||||||
|
{ name: "Data Literate", href: "/data-literate" },
|
||||||
|
{ name: "DL Demo", href: "/data-literate/demo" },
|
||||||
|
{ name: "Excel Viewer", href: "/excel-viewer" },
|
||||||
|
{ name: "GitHub", href: "https://github.com/datopian/portal.js" },
|
||||||
|
],
|
||||||
|
footerLinks: [],
|
||||||
|
nextSeo: {
|
||||||
|
openGraph: {
|
||||||
|
type: "website",
|
||||||
|
title:
|
||||||
|
"Portal.JS - Rapidly build rich data portals using a modern frontend framework",
|
||||||
|
description:
|
||||||
|
"Portal.JS is a framework for rapidly building rich data portal frontends using a modern frontend approach. portal.js can be used to present a single dataset or build a full-scale data catalog/portal.",
|
||||||
|
locale: "en_US",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "https://datahub.io/static/img/opendata/product.png", // TODO
|
||||||
|
alt: "Portal.JS - Rapidly build rich data portals using a modern frontend framework",
|
||||||
|
width: 1200,
|
||||||
|
height: 627,
|
||||||
|
type: "image/jpg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
handle: "@datopian",
|
||||||
|
site: "https://datopian.com/",
|
||||||
|
cardType: "summary_large_image",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// tableOfContents: true,
|
||||||
|
// analytics: "xxxxxx",
|
||||||
|
// editLinkShow: true,
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
@ -42,7 +42,7 @@ export default function Home(){
|
|||||||
|
|
||||||
Nav component accepts two properties:
|
Nav component accepts two properties:
|
||||||
* **logo**: A string to an image path. Can be relative or absolute.
|
* **logo**: A string to an image path. Can be relative or absolute.
|
||||||
* **navMenu**: An array of objects with title and path. E.g : [\{ title: 'Blog', path: '/blog' },\{ title: 'Search', path: '/search' }]
|
* **navMenu**: An array of objects with title and path. E.g : {"[{ title: 'Blog', path: '/blog' },{ title: 'Search', path: '/search' }]"}
|
||||||
|
|
||||||
|
|
||||||
#### [Recent Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Recent.js)
|
#### [Recent Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Recent.js)
|
||||||
@ -346,8 +346,8 @@ export default function Home() {
|
|||||||
#### Table Component Prop Types
|
#### Table Component Prop Types
|
||||||
|
|
||||||
Table component accepts two properties:
|
Table component accepts two properties:
|
||||||
* **data**: An array of column names with properties: e.g [\{field: "col1", headerName: "col1"}, \{field: "col2", headerName: "col2"}]
|
* **data**: An array of column names with properties: e.g {'[{field: "col1", headerName: "col1"}, {field: "col2", headerName: "col2"}]'}
|
||||||
* **columns**: An array of data objects e.g. [ \{col1: 1, col2: 2}, \{col1: 5, col2: 7} ]
|
* **columns**: An array of data objects e.g. {'[ {col1: 1, col2: 2}, {col1: 5, col2: 7} ]'}
|
||||||
|
|
||||||
|
|
||||||
### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search)
|
### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search)
|
||||||
|
|||||||
@ -55,7 +55,7 @@ These are the default routes set up in the "starter" app.
|
|||||||
|
|
||||||
You can create new routes in `/pages` directory where each file is associated with a route based on its name. We suggest using [Next.JS docs][] for more detailed information.
|
You can create new routes in `/pages` directory where each file is associated with a route based on its name. We suggest using [Next.JS docs][] for more detailed information.
|
||||||
|
|
||||||
[Next.JS docs]: https://nextjs.org/docs/basic-features/pages
|
[next.js docs]: https://nextjs.org/docs/basic-features/pages
|
||||||
|
|
||||||
### Data fetching
|
### Data fetching
|
||||||
|
|
||||||
@ -205,10 +205,8 @@ export default function Org({ variables }) {
|
|||||||
}
|
}
|
||||||
className="h-5 w-5 mr-2 inline-block"
|
className="h-5 w-5 mr-2 inline-block"
|
||||||
/>
|
/>
|
||||||
<Link href={`/@${organization.name}`}>
|
<Link href={`/@${organization.name}`} className="font-semibold text-primary underline">
|
||||||
<a className="font-semibold text-primary underline">
|
|
||||||
{organization.title || organization.name}
|
{organization.title || organization.name}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -217,4 +215,4 @@ export default function Org({ variables }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
13
apps/site/layouts/index.ts
Normal file
13
apps/site/layouts/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import {
|
||||||
|
SimpleLayout,
|
||||||
|
DocsLayout,
|
||||||
|
UnstyledLayout,
|
||||||
|
BlogLayout,
|
||||||
|
} from "@flowershow/core";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
simple: SimpleLayout,
|
||||||
|
docs: DocsLayout,
|
||||||
|
unstyled: UnstyledLayout,
|
||||||
|
blog: BlogLayout,
|
||||||
|
};
|
||||||
35
apps/site/lib/data-literate/markdown.js
Normal file
35
apps/site/lib/data-literate/markdown.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Used by Data Literate
|
||||||
|
|
||||||
|
import matter from 'gray-matter'
|
||||||
|
import toc from 'remark-toc'
|
||||||
|
import slug from 'remark-slug'
|
||||||
|
import gfm from 'remark-gfm'
|
||||||
|
import footnotes from 'remark-footnotes'
|
||||||
|
|
||||||
|
import { serialize } from 'next-mdx-remote/serialize'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a markdown or MDX file to an MDX source form + front matter data
|
||||||
|
*
|
||||||
|
* @source: the contents of a markdown or mdx file
|
||||||
|
* @returns: { mdxSource: mdxSource, frontMatter: ...}
|
||||||
|
*/
|
||||||
|
const parse = async function(source) {
|
||||||
|
const { content, data } = matter(source)
|
||||||
|
|
||||||
|
const mdxSource = await serialize(content, {
|
||||||
|
// Optionally pass remark/rehype plugins
|
||||||
|
mdxOptions: {
|
||||||
|
remarkPlugins: [gfm, toc, slug, footnotes],
|
||||||
|
rehypePlugins: [],
|
||||||
|
},
|
||||||
|
scope: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
mdxSource: mdxSource,
|
||||||
|
frontMatter: data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parse
|
||||||
26
apps/site/lib/getAuthorsDetails.ts
Normal file
26
apps/site/lib/getAuthorsDetails.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { siteConfig } from "config/siteConfig";
|
||||||
|
import clientPromise from "lib/mddb";
|
||||||
|
|
||||||
|
export const getAuthorsDetails = async (authors: string[]) => {
|
||||||
|
const mddb = await clientPromise;
|
||||||
|
const allPeople = await mddb.getFiles({ folder: "people" });
|
||||||
|
|
||||||
|
// Temporary, flowershow UI component expects contentlayer obj props
|
||||||
|
const allPeopleMetadata = allPeople.map((p) => p.metadata);
|
||||||
|
|
||||||
|
let blogAuthors = [];
|
||||||
|
|
||||||
|
if (authors) {
|
||||||
|
blogAuthors = authors;
|
||||||
|
} else if (siteConfig.defaultAuthor) {
|
||||||
|
blogAuthors = [siteConfig.defaultAuthor];
|
||||||
|
}
|
||||||
|
|
||||||
|
return blogAuthors.map((author) => {
|
||||||
|
const person = allPeopleMetadata.find(
|
||||||
|
({ id, name }) => id === author || name === author
|
||||||
|
);
|
||||||
|
|
||||||
|
return person ?? { name: author, avatar: siteConfig.avatarPlaceholder };
|
||||||
|
});
|
||||||
|
};
|
||||||
99
apps/site/lib/markdown.mjs
Normal file
99
apps/site/lib/markdown.mjs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import matter from "gray-matter";
|
||||||
|
import mdxmermaid from "mdx-mermaid";
|
||||||
|
import { h } from "hastscript";
|
||||||
|
import remarkCallouts from "@flowershow/remark-callouts";
|
||||||
|
import remarkEmbed from "@flowershow/remark-embed";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
import remarkMath from "remark-math";
|
||||||
|
import remarkSmartypants from "remark-smartypants";
|
||||||
|
import remarkToc from "remark-toc";
|
||||||
|
import remarkWikiLink from "@flowershow/remark-wiki-link";
|
||||||
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||||
|
import rehypeKatex from "rehype-katex";
|
||||||
|
import rehypeSlug from "rehype-slug";
|
||||||
|
import rehypePrismPlus from "rehype-prism-plus";
|
||||||
|
|
||||||
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a markdown or MDX file to an MDX source form + front matter data
|
||||||
|
*
|
||||||
|
* @source: the contents of a markdown or mdx file
|
||||||
|
* @format: used to indicate to next-mdx-remote which format to use (md or mdx)
|
||||||
|
* @returns: { mdxSource: mdxSource, frontMatter: ...}
|
||||||
|
*/
|
||||||
|
const parse = async function (source, format, scope) {
|
||||||
|
const { content, data } = matter(source);
|
||||||
|
|
||||||
|
const mdxSource = await serialize(
|
||||||
|
{ value: content, path: format },
|
||||||
|
{
|
||||||
|
// Optionally pass remark/rehype plugins
|
||||||
|
mdxOptions: {
|
||||||
|
remarkPlugins: [
|
||||||
|
remarkEmbed,
|
||||||
|
remarkGfm,
|
||||||
|
[remarkSmartypants, { quotes: false, dashes: "oldschool" }],
|
||||||
|
remarkMath,
|
||||||
|
remarkCallouts,
|
||||||
|
remarkWikiLink,
|
||||||
|
[
|
||||||
|
remarkToc,
|
||||||
|
{
|
||||||
|
heading: "Table of contents",
|
||||||
|
tight: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[mdxmermaid, {}],
|
||||||
|
],
|
||||||
|
rehypePlugins: [
|
||||||
|
rehypeSlug,
|
||||||
|
[
|
||||||
|
rehypeAutolinkHeadings,
|
||||||
|
{
|
||||||
|
properties: { className: "heading-link" },
|
||||||
|
test(element) {
|
||||||
|
return (
|
||||||
|
["h2", "h3", "h4", "h5", "h6"].includes(element.tagName) &&
|
||||||
|
element.properties?.id !== "table-of-contents" &&
|
||||||
|
element.properties?.className !== "blockquote-heading"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
content() {
|
||||||
|
return [
|
||||||
|
h(
|
||||||
|
"svg",
|
||||||
|
{
|
||||||
|
xmlns: "http:www.w3.org/2000/svg",
|
||||||
|
fill: "#ab2b65",
|
||||||
|
viewBox: "0 0 20 20",
|
||||||
|
className: "w-5 h-5",
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h("path", {
|
||||||
|
fillRule: "evenodd",
|
||||||
|
clipRule: "evenodd",
|
||||||
|
d: "M9.493 2.853a.75.75 0 00-1.486-.205L7.545 6H4.198a.75.75 0 000 1.5h3.14l-.69 5H3.302a.75.75 0 000 1.5h3.14l-.435 3.148a.75.75 0 001.486.205L7.955 14h2.986l-.434 3.148a.75.75 0 001.486.205L12.456 14h3.346a.75.75 0 000-1.5h-3.14l.69-5h3.346a.75.75 0 000-1.5h-3.14l.435-3.147a.75.75 0 00-1.486-.205L12.045 6H9.059l.434-3.147zM8.852 7.5l-.69 5h2.986l.69-5H8.852z",
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[rehypeKatex, { output: "mathml" }],
|
||||||
|
[rehypePrismPlus, { ignoreMissing: true }],
|
||||||
|
],
|
||||||
|
format,
|
||||||
|
},
|
||||||
|
scope: { ...scope, ...data },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mdxSource: mdxSource,
|
||||||
|
frontMatter: data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parse;
|
||||||
24
apps/site/lib/mddb.ts
Normal file
24
apps/site/lib/mddb.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { MarkdownDB } from "@flowershow/markdowndb";
|
||||||
|
// import config from "./markdowndb.config.js";
|
||||||
|
|
||||||
|
// TODO get this path from markdowndb.config.js or something
|
||||||
|
const dbPath = "markdown.db";
|
||||||
|
//
|
||||||
|
// if (!config.dbPath)
|
||||||
|
// throw new Error("Invalid/Missing path in markdowndb.config.js");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const dbPath = config.dbPath;
|
||||||
|
// OR
|
||||||
|
// const dbOptions = config.dbOptions;
|
||||||
|
|
||||||
|
const client = new MarkdownDB({
|
||||||
|
client: "sqlite3",
|
||||||
|
connection: {
|
||||||
|
filename: dbPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const clientPromise = client.init();
|
||||||
|
|
||||||
|
export default clientPromise;
|
||||||
@ -3,7 +3,7 @@ import glob from 'glob'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
// POSTS_PATH is useful when you want to get the path to a specific file
|
// POSTS_PATH is useful when you want to get the path to a specific file
|
||||||
export const POSTS_PATH = path.join(process.cwd(), '/apps/site/content')
|
export const POSTS_PATH = path.join(process.cwd(), 'content')
|
||||||
|
|
||||||
const walkSync = (dir, filelist = []) => {
|
const walkSync = (dir, filelist = []) => {
|
||||||
fs.readdirSync(dir).forEach(file => {
|
fs.readdirSync(dir).forEach(file => {
|
||||||
|
|||||||
BIN
apps/site/markdown.db
Normal file
BIN
apps/site/markdown.db
Normal file
Binary file not shown.
@ -1,48 +0,0 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
import parse from '../lib/markdown.js'
|
|
||||||
|
|
||||||
import DataLiterate from '../components/DataLiterate'
|
|
||||||
|
|
||||||
import { postFilePaths, POSTS_PATH } from '../lib/mdxUtils'
|
|
||||||
|
|
||||||
|
|
||||||
export default function PostPage({ source, frontMatter }) {
|
|
||||||
return (
|
|
||||||
<DataLiterate source={source} frontMatter={frontMatter} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStaticProps = async ({ params }) => {
|
|
||||||
const mdxPath = path.join(POSTS_PATH, `${params.slug.join('/')}.mdx`)
|
|
||||||
const postFilePath = fs.existsSync(mdxPath) ? mdxPath : mdxPath.slice(0, -1)
|
|
||||||
const source = fs.readFileSync(postFilePath)
|
|
||||||
|
|
||||||
const { mdxSource, frontMatter } = await parse(source)
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
source: mdxSource,
|
|
||||||
frontMatter: frontMatter,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getStaticPaths = async () => {
|
|
||||||
var paths = postFilePaths
|
|
||||||
// Remove file extensions for page paths
|
|
||||||
.map((path) => path.replace(/\.mdx?$/, ''))
|
|
||||||
|
|
||||||
// Map the path into the static paths object required by Next.js
|
|
||||||
paths = paths.map((slug) => {
|
|
||||||
// /demo => [demo]
|
|
||||||
const parts = slug.slice(1).split('/')
|
|
||||||
return { params: { slug: parts } }
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
paths,
|
|
||||||
fallback: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
76
apps/site/pages/[...slug].tsx
Normal file
76
apps/site/pages/[...slug].tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import parse from "../lib/markdown.mjs";
|
||||||
|
|
||||||
|
import MDXPage from "../components/MDXPage";
|
||||||
|
import clientPromise from "@/lib/mddb";
|
||||||
|
import { getAuthorsDetails } from "lib/getAuthorsDetails";
|
||||||
|
import Layout from "components/Layout";
|
||||||
|
|
||||||
|
export default function DRDPage({ source, frontMatter }) {
|
||||||
|
source = JSON.parse(source);
|
||||||
|
frontMatter = JSON.parse(frontMatter);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout title={frontMatter.title}>
|
||||||
|
<MDXPage source={source} frontMatter={frontMatter} />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps = async ({ params }) => {
|
||||||
|
const urlPath = params.slug ? params.slug.join("/") : "";
|
||||||
|
|
||||||
|
const mddb = await clientPromise;
|
||||||
|
const dbFile = await mddb.getFileByUrl(urlPath);
|
||||||
|
|
||||||
|
const dbBacklinks = await mddb.getLinks({
|
||||||
|
fileId: dbFile._id,
|
||||||
|
direction: "backward",
|
||||||
|
});
|
||||||
|
// TODO temporary solution, we will have a method on MddbFile to get these links
|
||||||
|
const dbBacklinkFilesPromises = dbBacklinks.map((link) =>
|
||||||
|
mddb.getFileById(link.from)
|
||||||
|
);
|
||||||
|
const dbBacklinkFiles = await Promise.all(dbBacklinkFilesPromises);
|
||||||
|
const dbBacklinkUrls = dbBacklinkFiles.map(
|
||||||
|
(file) => file.toObject().url_path
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO we can already get frontmatter from dbFile.metadata
|
||||||
|
// so parse could only return mdxSource
|
||||||
|
const source = fs.readFileSync(dbFile.file_path, { encoding: "utf-8" });
|
||||||
|
const { mdxSource, frontMatter } = await parse(source, "mdx", {
|
||||||
|
backlinks: dbBacklinkUrls,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Temporary, so that blogs work properly
|
||||||
|
if (dbFile.url_path.startsWith("blog/")) {
|
||||||
|
frontMatter.layout = "blog";
|
||||||
|
frontMatter.authorsDetails = await getAuthorsDetails(
|
||||||
|
dbFile.metadata.authors
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
source: JSON.stringify(mdxSource),
|
||||||
|
frontMatter: JSON.stringify(frontMatter),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const mddb = await clientPromise;
|
||||||
|
const allDocuments = await mddb.getFiles({ extensions: ["md", "mdx"] });
|
||||||
|
|
||||||
|
const paths = allDocuments.map((page) => {
|
||||||
|
const parts = page.url_path.split("/");
|
||||||
|
return { params: { slug: parts } };
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paths,
|
||||||
|
fallback: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
import '../styles/globals.css'
|
|
||||||
import '../styles/tailwind.css'
|
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
|
||||||
return <Component {...pageProps} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MyApp
|
|
||||||
64
apps/site/pages/_app.tsx
Normal file
64
apps/site/pages/_app.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import "../styles/globals.css";
|
||||||
|
import "../styles/tailwind.css";
|
||||||
|
|
||||||
|
import Script from "next/script";
|
||||||
|
|
||||||
|
import { DefaultSeo } from "next-seo";
|
||||||
|
|
||||||
|
import { pageview, ThemeProvider } from "@flowershow/core";
|
||||||
|
import { siteConfig } from "../config/siteConfig";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from "next/dist/client/router";
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (siteConfig.analytics) {
|
||||||
|
const handleRouteChange = (url) => {
|
||||||
|
pageview(url);
|
||||||
|
};
|
||||||
|
router.events.on("routeChangeComplete", handleRouteChange);
|
||||||
|
return () => {
|
||||||
|
router.events.off("routeChangeComplete", handleRouteChange);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [router.events]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider
|
||||||
|
disableTransitionOnChange
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme={siteConfig.theme.default}
|
||||||
|
forcedTheme={siteConfig.theme.default ? null : "light"}
|
||||||
|
>
|
||||||
|
<DefaultSeo defaultTitle={siteConfig.title} {...siteConfig.nextSeo} />
|
||||||
|
{/* Global Site Tag (gtag.js) - Google Analytics */}
|
||||||
|
{siteConfig.analytics && (
|
||||||
|
<>
|
||||||
|
<Script
|
||||||
|
strategy="afterInteractive"
|
||||||
|
src={`https://www.googletagmanager.com/gtag/js?id=${siteConfig.analytics}`}
|
||||||
|
/>
|
||||||
|
<Script
|
||||||
|
id="gtag-init"
|
||||||
|
strategy="afterInteractive"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', '${siteConfig.analytics}', {
|
||||||
|
page_path: window.location.pathname,
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MyApp;
|
||||||
23
apps/site/pages/data-literate/demo.tsx
Normal file
23
apps/site/pages/data-literate/demo.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import parse from "../../lib/data-literate/markdown";
|
||||||
|
|
||||||
|
import DataLiterate from "../../components/data-literate/DataLiterate";
|
||||||
|
|
||||||
|
export default function PostPage({ source, frontMatter }) {
|
||||||
|
return <DataLiterate source={source} frontMatter={frontMatter} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStaticProps = async ({ params }) => {
|
||||||
|
const mdxPath = "content/data-literate/demo.mdx";
|
||||||
|
const source = fs.readFileSync(mdxPath);
|
||||||
|
|
||||||
|
const { mdxSource, frontMatter } = await parse(source);
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
source: mdxSource,
|
||||||
|
frontMatter: frontMatter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
@import "@flowershow/remark-callouts/styles.css";
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -14,3 +16,69 @@ a {
|
|||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* mathjax */
|
||||||
|
.math-inline > mjx-container > svg {
|
||||||
|
display: inline;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* smooth scrolling in modern browsers */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tooltip fade-out clip */
|
||||||
|
.tooltip-body::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 3.6rem; /* multiple of $line-height used on the tooltip body (defined in tooltipBodyStyle) */
|
||||||
|
height: 1.2rem; /* ($top + $height)/$line-height is the number of lines we want to clip tooltip text at*/
|
||||||
|
width: 10rem;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
rgba(255, 255, 255, 0),
|
||||||
|
rgba(255, 255, 255, 1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(h2, h3, h4, h5, h6):not(.blogitem-title) {
|
||||||
|
margin-left: -2rem !important;
|
||||||
|
padding-left: 2rem !important;
|
||||||
|
scroll-margin-top: 4.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-link {
|
||||||
|
padding: 1px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin: auto 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #1e293b;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .heading-link {
|
||||||
|
/* border: 1px solid #ab2b65; */
|
||||||
|
/* background: none; */
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(h2, h3, h4, h5, h6):not(.blogitem-title):hover .heading-link {
|
||||||
|
opacity: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-link svg {
|
||||||
|
transform: scale(0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 640px) {
|
||||||
|
.heading-link {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
const defaultTheme = require("tailwindcss/defaultTheme");
|
||||||
const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind');
|
const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind');
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
|
|
||||||
@ -10,8 +11,21 @@ module.exports = {
|
|||||||
),
|
),
|
||||||
...createGlobPatternsForDependencies(__dirname),
|
...createGlobPatternsForDependencies(__dirname),
|
||||||
],
|
],
|
||||||
|
darkMode: false, // or 'media' or 'class'
|
||||||
theme: {
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
mono: ["Inconsolata", ...defaultTheme.fontFamily.mono]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,23 +1,31 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "preserve",
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@flowershow/core": ["node_modules/@flowershow/core/dist/src"],
|
||||||
|
"@flowershow/remark-callouts": [
|
||||||
|
"node_modules/@flowershow/remark-callouts/dist/src"
|
||||||
|
],
|
||||||
|
"@flowershow/remark-embed": [
|
||||||
|
"node_modules/@flowershow/remark-embed/dist/src"
|
||||||
|
],
|
||||||
|
"@/*": ["./*"]
|
||||||
|
},
|
||||||
|
"target": "es2020",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"esModuleInterop": true,
|
"skipLibCheck": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"strict": false,
|
"strict": false,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
"incremental": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"incremental": true,
|
"jsx": "preserve"
|
||||||
"types": ["jest", "node"]
|
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"],
|
"include": ["types.d.ts", "next-env.d.ts", "**/*.tsx", "**/*.ts"],
|
||||||
"exclude": [
|
"exclude": ["node_modules"]
|
||||||
"node_modules",
|
|
||||||
"jest.config.ts",
|
|
||||||
"src/**/*.spec.ts",
|
|
||||||
"src/**/*.test.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
markdown.db
Normal file
BIN
markdown.db
Normal file
Binary file not shown.
8109
package-lock.json
generated
8109
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
@ -7,6 +7,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
|
"@flowershow/core": "^0.4.9",
|
||||||
|
"@flowershow/markdowndb": "^0.1.0",
|
||||||
|
"@flowershow/remark-callouts": "^1.0.0",
|
||||||
|
"@flowershow/remark-embed": "^1.0.0",
|
||||||
|
"@flowershow/remark-wiki-link": "^1.0.1",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@heroicons/react": "^2.0.17",
|
"@heroicons/react": "^2.0.17",
|
||||||
"@material-ui/data-grid": "^4.0.0-alpha.37",
|
"@material-ui/data-grid": "^4.0.0-alpha.37",
|
||||||
@ -14,11 +19,13 @@
|
|||||||
"@mui/material": "^5.11.16",
|
"@mui/material": "^5.11.16",
|
||||||
"@mui/x-data-grid": "^6.1.0",
|
"@mui/x-data-grid": "^6.1.0",
|
||||||
"@nrwl/next": "15.9.2",
|
"@nrwl/next": "15.9.2",
|
||||||
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"filesize": "^10.0.7",
|
"filesize": "^10.0.7",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"html-react-parser": "^3.0.15",
|
"html-react-parser": "^3.0.15",
|
||||||
"next": "13.1.1",
|
"next": "13.1.1",
|
||||||
"next-mdx-remote": "^4.4.1",
|
"next-mdx-remote": "^4.4.1",
|
||||||
|
"next-seo": "^6.0.0",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"plotly.js-basic-dist": "^2.20.0",
|
"plotly.js-basic-dist": "^2.20.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
@ -27,9 +34,15 @@
|
|||||||
"react-next-github-btn": "^1.2.1",
|
"react-next-github-btn": "^1.2.1",
|
||||||
"react-plotlyjs": "^0.4.4",
|
"react-plotlyjs": "^0.4.4",
|
||||||
"react-vega": "^7.6.0",
|
"react-vega": "^7.6.0",
|
||||||
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
|
"rehype-katex": "^6.0.2",
|
||||||
|
"rehype-prism-plus": "^1.5.1",
|
||||||
|
"rehype-slug": "^5.1.0",
|
||||||
"remark-footnotes": "^4.0.1",
|
"remark-footnotes": "^4.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
"remark-math": "^5.1.1",
|
||||||
"remark-slug": "^7.0.1",
|
"remark-slug": "^7.0.1",
|
||||||
|
"remark-smartypants": "^2.0.0",
|
||||||
"remark-toc": "^8.0.1",
|
"remark-toc": "^8.0.1",
|
||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user