[site][lg] - integrate demenech changes into monorepo

This commit is contained in:
Luccas Mateus de Medeiros Gomes 2023-04-11 08:19:58 -03:00
parent fda6c4b827
commit 97c1dc0d59
32 changed files with 8815 additions and 319 deletions

19
apps/site/README.md Normal file
View 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.

View File

@ -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>
</> </>
) );
} }

View 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>
</>
);
}

View 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>
);
}

View File

@ -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>
) );
} }

View 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>
)
}

View 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
}

View 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 } />
)
}

View 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
}
}

View 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,
},
};

View 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;

View File

@ -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)

View File

@ -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>
</> </>
) : ( ) : (

View File

@ -0,0 +1,13 @@
import {
SimpleLayout,
DocsLayout,
UnstyledLayout,
BlogLayout,
} from "@flowershow/core";
export default {
simple: SimpleLayout,
docs: DocsLayout,
unstyled: UnstyledLayout,
blog: BlogLayout,
};

View 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

View 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 };
});
};

View 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
View 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;

View File

@ -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

Binary file not shown.

View File

@ -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,
}
}

View 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,
};
}

View File

@ -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
View 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;

View 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,
},
};
};

View File

@ -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;
}
}

View File

@ -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'),
],
}; };

View File

@ -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

Binary file not shown.

8109
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",