diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 803b73d6..00000000 --- a/.babelrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presets": [ - ["@babel/preset-react", { - "runtime": "automatic" - }] - ] -} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..6e87a003 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..06cc47d9 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "root": true, + "ignorePatterns": ["**/*"], + "plugins": ["@nrwl/nx"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@nrwl/nx/enforce-module-boundaries": [ + "error", + { + "enforceBuildableLibDependency": true, + "allow": [], + "depConstraints": [ + { + "sourceTag": "*", + "onlyDependOnLibsWithTags": ["*"] + } + ] + } + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "extends": ["plugin:@nrwl/nx/typescript"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "extends": ["plugin:@nrwl/nx/javascript"], + "rules": {} + } + ] +} diff --git a/.github/workflows/examples-catalog-cypress.yml b/.github/workflows/examples-catalog-cypress.yml deleted file mode 100644 index e8acc33b..00000000 --- a/.github/workflows/examples-catalog-cypress.yml +++ /dev/null @@ -1,17 +0,0 @@ -# name: Cypress Integration Tests -# on: [push] -# jobs: -# cypress-run: -# runs-on: ubuntu-16.04 -# steps: -# - name: Checkout -# uses: actions/checkout@v1 - -# - name: Cypress run -# uses: cypress-io/github-action@v2 -# with: -# working-directory: examples/catalog -# browser: chrome -# build: yarn run build -# start: yarn start -# wait-on: "http://localhost:3000" diff --git a/.github/workflows/examples-catalog-test.yml b/.github/workflows/examples-catalog-test.yml deleted file mode 100644 index 572939e0..00000000 --- a/.github/workflows/examples-catalog-test.yml +++ /dev/null @@ -1,30 +0,0 @@ -# name: Portal-Catalog-Tests -# on: push -# jobs: -# build: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/checkout@v2 -# - name: Install modules -# run: | -# cd examples/catalog -# yarn -# - name: Run tests -# run: | -# cd examples/catalog -# yarn test -u - -# cypress-run: -# runs-on: ubuntu-16.04 -# steps: -# - name: Checkout -# uses: actions/checkout@v1 - -# - name: Cypress run -# uses: cypress-io/github-action@v2 -# with: -# working-directory: examples/catalog -# browser: chrome -# build: yarn run build -# start: yarn start -# wait-on: "http://localhost:3000" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index f689d632..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,39 +0,0 @@ -# name: github pages - -# on: -# push: -# branches: -# - main - -# jobs: -# deploy: -# runs-on: ubuntu-18.04 -# steps: -# - uses: actions/checkout@v2 - -# - name: Setup Node -# uses: actions/setup-node@v2.1.2 -# with: -# node-version: '12.x' - -# - name: Get yarn cache -# id: yarn-cache -# run: echo "::set-output name=dir::$(yarn cache dir)" - -# - name: Cache dependencies -# uses: actions/cache@v2 -# with: -# path: ${{ steps.yarn-cache.outputs.dir }} -# key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} -# restore-keys: | -# ${{ runner.os }}-yarn- - -# - run: yarn install --frozen-lockfile -# - run: yarn build -# - run: yarn export - -# - name: Deploy -# uses: peaceiris/actions-gh-pages@v3 -# with: -# github_token: ${{ secrets.GITHUB_TOKEN }} -# publish_dir: ./out \ No newline at end of file diff --git a/.gitignore b/.gitignore index b5ccbcc0..794fab1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +dist +tmp +/out-tsc + # dependencies -node_modules/ -.pnp -.pnp.js +node_modules -# testing -coverage -cypress/videos +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace -# next.js -.next/ -/out/ - -# production -/build +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json # misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files .DS_Store +Thumbs.db -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env -.env.local -.env.development.local -.env.test.local -.env.production.local +# Next.js +.next \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..9481e77e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Add files here to ignore them from prettier formatting +/dist +/coverage \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..64553b17 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "nrwl.angular-console", + "esbenp.prettier-vscode", + "firsttris.vscode-jest-runner", + "dbaeumer.vscode-eslint" + ] +} diff --git a/DESIGN.md b/DESIGN.md deleted file mode 100644 index 007513de..00000000 --- a/DESIGN.md +++ /dev/null @@ -1,83 +0,0 @@ -# Design Notes - -## Roadmap - -General comment: let's do "README" (docs) driven development here. - -* [x] [show] Local functionality for Frictionless datasets with CSV #528 - * [x] Move in new work (portal-experiment) into portal.js and refactor https://github.com/datopian/portal.js.bak/issues/59 -* [ ] [show] Uber Epic covering all functionality **See below** - * [ ] [show] README only + data datasets (don’t have to be frictionless) - * (?) Graphs direct in README with say visdown … - * [ ] [show] SQL interface to the data (alasql or sql.js … https://github.com/agershun/alasql/wiki/Performance-Tests) - * [ ] file/resource subpages ... (for datasets with lots of resources) -* [ ] Docs **80% analysed** # -* [ ] Create portal components and library i.e. have a Table, Graph, Dataset component - * [ ] publish to @datopian/portal - * [ ] Examples -* [ ] Catalog functionality **20% analysed** - -## [uber][epic] Show functionality for single datasets - -### Features - -* Elegant -* Description (README/Description) -* Data preview and exploration (for tablular) - * Basic: some sample data shown - * Data exploration v1: filterable - * Data Exploration v2: can do sql etc ... -* Graphs / visualization -* Validation: this row does not match schema in column X -* Summarization e.g. this columns has this range of values, this average value, this number of nulls - -### Dataset structure support (in rough order of priority / like implementation) - -* Frictionless -* Plain README (with frontmatter) -* README (no frontmatter) and LICENSE file (?) - -Data has roughly two dimensions that are relevant - -* Format - * CSV - * xlsx - * JSON - * ... -* Size - * Small: < 5mb (can just load inline ...) - * Medium < 100mb - * Large < 5Gb - * xlarge > 5Gb - -* TODO: How does show/build work with remote files e.g. a resource ... - - ``` - path: abc.csv - remote_storage_url: s3://.../.../.../ - ``` - - Options: - - * We clone the data into path locally ... - * Possible problem if data is big ... - * Load data direct from remote_storage_url (as long as supports CORs) - - - - -## Architecture - -Portal.js is a React and NextJS based framework for building dataset/resources pages and catalogs. It consists of: - -* React components for data portal functionality e.g. data tables, graphs, dataset pages etc -* Tooling to load data (based on Frictionless) -* Template sites you can reuse using `create-next-app` - * Single dataset micro-site - * Github backed catalog - * CKAN backed catalog - * ... -* Local development environment -* Deployment integration with DataHub.io - -In summary, technically PortalJS is: NextJS + data specific react components + data loading glue (mostly using frictionless-js). diff --git a/README.md b/README.md index 6df994c1..2f057f93 100644 --- a/README.md +++ b/README.md @@ -1,830 +1,21 @@ -

-🌀 Portal.JS -
-Create a gateway to your data -

+# Portaljs -* [What is Portal.JS ?](#What-is-Portal.JS) - * [Features](#Features) - * [For developers](#For-developers) -* [Installation and setup](#Installation-and-setup) -* [Getting Started](#Getting-Started) -* [Tutorial](#Tutorial) - * [Build a single Frictionless dataset portal](#Build-a-single-Frictionless-dataset-portal) - * [Build a CKAN powered dataset portal](#Build-a-CKAN-powered-dataset-portal) -* [Architecture / Reference](#Architecture--Reference) - * [Component List](#Component-List) - * [UI Components](#UI-Components) - * [Dataset Components](#Dataset-Components) - * [View Components](#View-Components) - * [Search Components](#Search-Components) - * [Blog Components](#Blog-Components) - * [Misc Components](#Misc-Components) - * [Concepts and Terms](#Concepts-and-Terms) - * [Dataset](#Dataset) - * [Resource](#Resource) - * [View Spec](#view-spec) -* [Appendix](#Appendix) - * [What happened to Recline?](#What-happened-to-Recline?) + -# What is Portal.JS -🌀 `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. +✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨ -`portal.js` is built in Javascript and React on top of the popular [Next.js](https://nextjs.com/) framework. `portal` assumes a "decoupled" approach where the frontend is a separate service from the backend and interacts with backend(s) via an API. It can be used with any backend and has out of the box support for [CKAN](https://ckan.org/). +## Development server -## Features +Run `nx serve portaljs` for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files. -- 🗺️ Unified sites: present data and content in one seamless site, pulling datasets from a DMS (e.g. CKAN) and content from a CMS (e.g. wordpress) with a common internal API. -- 👩‍💻 Developer friendly: built with familiar frontend tech Javascript, React etc -- 🔋 Batteries included: Full set of portal components out of the box e.g. catalog search, dataset showcase, blog etc. -- 🎨 Easy to theme and customize: installable themes, use standard CSS and React+CSS tooling. Add new routes quickly. -- 🧱 Extensible: quickly extend and develop/import your own React components -- 📝 Well documented: full set of documentation plus the documentation of NextJS and Apollo. +## Understand this workspace -### For developers +Run `nx graph` to see a diagram of the dependencies of the projects. -- 🏗 Build with modern, familiar frontend tech such as Javascript and React. -- 🚀 NextJS framework: so everything in NextJS for free React, SSR, static site generation, huge number of examples and integrations etc. - - SSR => unlimited number of pages, SEO etc whilst still using React. - - Static Site Generation (SSG) (good for small sites) => ultra-simple deployment, great performance and lighthouse scores etc +## Remote caching -# Installation and setup -Before installation, ensure your system satisfies the following requirements: +Run `npx nx connect-to-nx-cloud` to enable [remote caching](https://nx.app) and make CI faster. -- Node.js 10.13 or later -- Nextjs 10.0.3 -- MacOS, Windows (including WSL), and Linux are supported +## Further help -> Note: We also recommend instead of npm using `yarn` instead of `npm`. -> -Portal.js is built with React on top of Nextjs framework, so for a quick setup, you can bootstrap a Nextjs app and install portal.js as demonstrated in the code below: - -```bash= -## Create a react app -npx create-next-app -# or -yarn create next-app -``` -After the installation is complete, follow the instructions to start the development server. Try editing pages/index.js and see the result on your browser. - -> For more information on how to use create-next-app, you can review the [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) documentation. - -Once you have Nextjs created, you can install portal.js: - -```bash= -yarn add https://github.com/datopian/portal.js.git -``` - -You're now ready to use portal.js in your next app. To test portal.js, open your `index.js` file in the pages folder. By default you should have some autogenerated code in the `index.js` file: - - -Which outputs a page with the following content: - -![](https://i.imgur.com/GVh0P6p.png) - -Now, we are going to do some clean up and add a table component. In the `index.js` file, import a [Table]() component from portal as shown below: - -```javascript -import Head from 'next/head' -import { Table } from 'portal' //import Table component -import styles from '../styles/Home.module.css' - -export default function Home() { - - const columns = [ - { field: 'id', headerName: 'ID' }, - { field: 'firstName', headerName: 'First name' }, - { field: 'lastName', headerName: 'Last name' }, - { field: 'age', headerName: 'Age' } - ]; - - const rows = [ - { id: 1, lastName: 'Snow', firstName: 'Jon', age: 35 }, - { id: 2, lastName: 'Lannister', firstName: 'Cersei', age: 42 }, - { id: 3, lastName: 'Lannister', firstName: 'Jaime', age: 45 }, - { id: 4, lastName: 'Stark', firstName: 'Arya', age: 16 }, - { id: 7, lastName: 'Clifford', firstName: 'Ferrara', age: 44 }, - { id: 8, lastName: 'Frances', firstName: 'Rossini', age: 36 }, - { id: 9, lastName: 'Roxie', firstName: 'Harvey', age: 65 }, - ]; - - return ( -
- - Create Portal App - - - -

- Welcome to Portal.JS -

- - {/* Use table component */} - - - - ) -} -``` - -Now, your page should look like the following: - -![](https://i.imgur.com/n0vSjY4.png) - -> **Note**: You can learn more about individual portal components, as well as their prop types in the [components reference](#Component-List). - -___ - -# Getting Started - -If you're new to Portal.js we recommend that you start with the step-by-step guide below. You can also check out the following examples of projects built with portal.js. - -* [A portal for a single Frictionless dataset](#Build-a-single-Frictionless-dataset-portal) -* [A portal with a CKAN backend](#Build-a-CKAN-powered-dataset-portal) - -> The [`examples` directory](https://github.com/datopian/portal.js/tree/main/examples) is regularly updated with different portal examples. - -If you have questions about anything related to Portal.js, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portal.js/discussions). -___ - -# Tutorial - -## Build a single Frictionless dataset portal -This tutorial will guide you through building a portal for a single Frictionless dataset. - -[Here’s](https://portal-js.vercel.app/) an example of the final result. - -### Setup -The dataset should be a Frictionless Dataset i.e. it should have a [datapackage.json](https://specs.frictionlessdata.io/data-package/). - -Create a frictionless dataset portal app from the template: -``` -npx create-next-app -e https://github.com/datopian/portal.js/tree/main/examples/dataset-frictionless -#choose a name for your portal when prompted e.g. your-portal -``` -Go into your portal's directory and set the path to your dataset directory that contains the `datapackage.json`: -``` -cd -export PORTAL_DATASET_PATH= -``` -Start the server: -``` -yarn dev -``` -Visit the Page to view your dataset portal. - -## Build a CKAN powered dataset portal - -See [the CKAN Portal.JS example](./examples/ckan). - -___ - - -# Architecture / Reference - -## Component List - -Portal.js supports many components that can help you build amazing data portals similar to [this](https://catalog-portal-js.vercel.app/) and [this](https://portal-js.vercel.app/). - -In this section, we'll cover all supported components in depth, and help you understand their use as well as the expected properties. - -Components are grouped under the following sections: -* [UI](https://github.com/datopian/portal.js/tree/main/src/components/ui): Components like Nav bar, Footer, e.t.c -* [Dataset](https://github.com/datopian/portal.js/tree/main/src/components/dataset): Components used for displaying a Frictionless dataset and resources -* [Search](https://github.com/datopian/portal.js/tree/main/src/components/search): Components used for building a search interface for datasets -* [Blog](https://github.com/datopian/portal.js/tree/main/src/components/blog): Components for building a simple blog for datasets -* [Views](https://github.com/datopian/portal.js/tree/main/src/components/views): Components like charts, tables, maps for generating data views -* [Misc](https://github.com/datopian/portal.js/tree/main/src/components/misc): Miscellaneous components like errors, custom links, etc used for extra design. - -### UI Components - -In the UI we group all components that can be used for building generic page sections. These are components for building sections like the Navigation bar, Footer, Side pane, Recent datasets, e.t.c. - -#### [Nav Component](https://github.com/datopian/portal.js/blob/main/src/components/ui/Nav.js) - -To build a navigation bar, you can use the `Nav` component as demonstrated below: - -```javascript -import { Nav } from 'portal' - -export default function Home(){ - - const navMenu = [{ title: 'Blog', path: '/blog' }, - { title: 'Search', path: '/search' }] - - return ( - <> -
- ) -} - -``` -> Note: Under the hood, Table component uses the [DataGrid Material UI table](https://material-ui.com/components/data-grid/), and as such all supported params in data and columns are supported. - - -#### Table Component Prop Types - -Table component accepts two properties: -* **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} ] - - -### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search) - -Search components groups together components that can be used for creating a search interface. This includes search forms, search item as well as search result list. - -#### [Form Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Form.js) - -The search`Form` component is a simple search input and submit button. See example of a search form [here](https://catalog-portal-js.vercel.app/search). - -The search `form` requires a submit handler (`handleSubmit`). This handler function receives the search term, and handles actual search. - -In the example below, we demonstrate how to use the `Form` component. - -```javascript -import { Form } from 'portal' - -export default function Home() { - - const handleSearchSubmit = (searchQuery) => { - // Write your custom code to perform search in db - console.log(searchQuery); - } - - return ( - - ) -} -``` - -#### Form Component Prop Types - -The `Form` component accepts a single property: -* **handleSubmit**: A function that receives the search text, and can be customize to perform the actual search. - -#### [Item Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js) - -The search`Item` component can be used to display a single search result. - -In the example below, we demonstrate how to use the `Item` component. - -```javascript -import { Item } from 'portal' - -export default function Home() { - const datapackage = { - "name": "finance-vix", - "title": "VIX - CBOE Volatility Index", - "homepage": "http://www.cboe.com/micro/VIX/", - "version": "0.1.0", - "description": "This is a test organization description", - "resources": [ - { - "name": "vix-daily", - "path": "vix-daily.csv", - "format": "csv", - "size": 20982, - "mediatype": "text/csv", - } - ] - } - - return ( - - ) -} -``` - -#### Item Component Prop Types - -The `Item` component accepts a single property: -* **dataset**: A [Frictionless data package descriptor](https://specs.frictionlessdata.io/data-package/#descriptor) - - -#### [ItemTotal Component](https://github.com/datopian/portal.js/blob/main/src/components/search/Item.js) - -The search`ItemTotal` is a simple component for displaying the total search result - -In the example below, we demonstrate how to use the `ItemTotal` component. - -```javascript -import { ItemTotal } from 'portal' - -export default function Home() { - //do some custom search to get results - const search = (text) => { - return [{ name: "data1" }, { name: "data2" }] - } - //get the total result count - const searchTotal = search("some text").length - - return ( - - ) -} -``` - -#### ItemTotal Component Prop Types - -The `ItemTotal` component accepts a single property: -* **count**: An integer of the total number of results. - - -### [Blog Components](https://github.com/datopian/portal.js/tree/main/src/components/blog) - -These are group of components for building a portal blog. See example of portal blog [here](https://catalog-portal-js.vercel.app/blog) - -#### [PostList Components](https://github.com/datopian/portal.js/tree/main/src/components/misc) - -The `PostList` component is used to display a list of blog posts with the title and a short excerpts from the content. - -In the example below, we demonstrate how to use the `PostList` component. - -```javascript -import { PostList } from 'portal' - -export default function Home() { - - const posts = [ - { title: "Blog post 1", excerpt: "This is the first blog excerpts in this list." }, - { title: "Blog post 2", excerpt: "This is the second blog excerpts in this list." }, - { title: "Blog post 3", excerpt: "This is the third blog excerpts in this list." }, - ] - return ( - - ) -} -``` - -#### PostList Component Prop Types - -The `PostList` component accepts a single property: -* **posts**: An array of post list objects with the following properties: - ```javascript - [ - { - title: "The title of the blog post", - excerpt: "A short excerpt from the post content", - }, - ] - ``` - -#### [Post Components](https://github.com/datopian/portal.js/tree/main/src/components/misc) - -The `Post` component is used to display a blog post. See an example of a blog post [here](https://catalog-portal-js.vercel.app/blog/nyt-pa-platformen-opdateringsfrekvens-og-andres-data) - -In the example below, we demonstrate how to use the `Post` component. - -```javascript -import { Post } from 'portal' -import * as dayjs from 'dayjs' //For converting UTC time to relative format -import relativeTime from 'dayjs/plugin/relativeTime' - -dayjs.extend(relativeTime) - -export default function Home() { - - const post = { - title: "This is a sample blog post", - content: `

A simple header

- The PostList component is used to display a list of blog posts - with the title and a short excerpts from the content. - In the example below, we demonstrate how to use the PostList component.`, - createdAt: dayjs().to(dayjs(1620649596902)), - featuredImage: "https://pixabay.com/get/ge9a766d1f7b5fe0eccbf0f439501a2cf2b191997290e7ab15e6a402574acc2fdba48a82d278dca3547030e0202b7906d_640.jpg" - } - - return ( - - ) -} -``` - -#### Post Component Prop Types - -The `Post` component accepts a single property: -* **post**: An object with the following properties: -```javascript - { - title: - content: - createdAt: - featuredImage: < Url/relative url to post cover image> - } -``` - - -### [Misc Components](https://github.com/datopian/portal.js/tree/main/src/components/misc) - -These are group of miscellaneous/extra components for extending your portal. They include components like Errors, custom links, etc. - -#### [Error Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js) - -The `Error` component is used to display a custom error message. - -In the example below, we demonstrate how to use the `Error` component. - -```javascript -import { Error } from 'portal' - -export default function Home() { - - return ( - - ) -} -``` - -#### Error Component Prop Types - -The `Error` component accepts a single property: -* **message**: A string with the error message to display. - - -#### [Custom Component](https://github.com/datopian/portal.js/blob/main/src/components/misc/Error.js) - -The `CustomLink` component is used to create a link with a consistent style to other portal components. - -In the example below, we demonstrate how to use the `CustomLink` component. - -```javascript -import { CustomLink } from 'portal' - -export default function Home() { - - return ( - - ) -} -``` - -#### CustomLink Component Prop Types - -The `CustomLink` component accepts the following properties: - -* **url**: A string. The relative or absolute url of the link. -* **title**: A string. The title of the link - - -___ - -## Concepts and Terms -In this section, we explain some of the terms and concepts used throughtout the portal.js documentation. -> Some of these concepts are part of official specs, and when appropriate, we'll link to the sources where you can get more details. -### Dataset -A dataset extends the [Frictionless data package](https://specs.frictionlessdata.io/data-package/#metadata) to add an extra organization property. The organization property describes the organization the dataset belongs to, and it should have the following properties: -```javascript -organization = { - name: "some org name", - title: "Some optional org title", - description: "A description of the organization" -} -``` -An example of dataset with organization properties is given below: -```javascript -datasets = [{ - organization: { - name: "some org name", - title: "Some optional org title", - description: "A description of the organization" - }, - title: "Data package title", - name: "Data package name", - description: "description of data package", - resources: [...], - licences: [...], - sources: [...] - } -] -``` - -### Resource -TODO - -### view spec - ---- - -## Deploying portal build to github pages - -[Deploying single frictionless dataset to Github](https://portaljs.org/publish) - -## Showcases - -### Single Dataset with Default Theme - -![Single Dataset Example](./examples/dataset-frictionless/assets/demo.gif) - ---- - -# Appendix - -## What happened to Recline? - -Portal.JS used to be Recline(JS). If you are looking for the old Recline codebase it still exists: see the [`recline` branch](https://github.com/datopian/portal.js/tree/recline). If you want context for the rename see [this issue](https://github.com/datopian/portal.js/issues/520). +Visit the [Nx Documentation](https://nx.dev) to learn more. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index b4bc1380..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -Please report security issues to `security@datopian.com` \ No newline at end of file diff --git a/apps/.gitkeep b/apps/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/data-literate-e2e/.eslintrc.json b/apps/data-literate-e2e/.eslintrc.json new file mode 100644 index 00000000..696cb8b1 --- /dev/null +++ b/apps/data-literate-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/data-literate-e2e/cypress.config.ts b/apps/data-literate-e2e/cypress.config.ts new file mode 100644 index 00000000..22f7c84e --- /dev/null +++ b/apps/data-literate-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/data-literate-e2e/project.json b/apps/data-literate-e2e/project.json new file mode 100644 index 00000000..214c992e --- /dev/null +++ b/apps/data-literate-e2e/project.json @@ -0,0 +1,30 @@ +{ + "name": "data-literate-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/data-literate-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/data-literate-e2e/cypress.config.ts", + "devServerTarget": "data-literate:serve:development", + "testingType": "e2e" + }, + "configurations": { + "production": { + "devServerTarget": "data-literate:serve:production" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/data-literate-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["data-literate"] +} diff --git a/apps/data-literate-e2e/src/e2e/app.cy.ts b/apps/data-literate-e2e/src/e2e/app.cy.ts new file mode 100644 index 00000000..3fcabc19 --- /dev/null +++ b/apps/data-literate-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('data-literate', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome data-literate'); + }); +}); diff --git a/apps/data-literate-e2e/src/fixtures/example.json b/apps/data-literate-e2e/src/fixtures/example.json new file mode 100644 index 00000000..294cbed6 --- /dev/null +++ b/apps/data-literate-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/data-literate-e2e/src/support/app.po.ts b/apps/data-literate-e2e/src/support/app.po.ts new file mode 100644 index 00000000..32934246 --- /dev/null +++ b/apps/data-literate-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/examples/ckan/cypress/support/commands.js b/apps/data-literate-e2e/src/support/commands.ts similarity index 67% rename from examples/ckan/cypress/support/commands.js rename to apps/data-literate-e2e/src/support/commands.ts index ca4d256f..310f1fa0 100644 --- a/examples/ckan/cypress/support/commands.js +++ b/apps/data-literate-e2e/src/support/commands.ts @@ -7,11 +7,19 @@ // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** -// + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} // // -- This is a parent command -- -// Cypress.Commands.add("login", (email, password) => { ... }) -// +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) diff --git a/examples/ckan/cypress/support/index.js b/apps/data-literate-e2e/src/support/e2e.ts similarity index 89% rename from examples/ckan/cypress/support/index.js rename to apps/data-literate-e2e/src/support/e2e.ts index 37a498fb..3d469a6b 100644 --- a/examples/ckan/cypress/support/index.js +++ b/apps/data-literate-e2e/src/support/e2e.ts @@ -15,6 +15,3 @@ // Import commands.js using ES2015 syntax: import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/apps/data-literate-e2e/tsconfig.json b/apps/data-literate-e2e/tsconfig.json new file mode 100644 index 00000000..cc509a73 --- /dev/null +++ b/apps/data-literate-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/data-literate/.eslintrc.json b/apps/data-literate/.eslintrc.json new file mode 100644 index 00000000..82b9aa29 --- /dev/null +++ b/apps/data-literate/.eslintrc.json @@ -0,0 +1,34 @@ +{ + "extends": [ + "plugin:@nrwl/nx/react-typescript", + "next", + "next/core-web-vitals", + "../../.eslintrc.json" + ], + "ignorePatterns": ["!**/*", ".next/**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@next/next/no-html-link-for-pages": [ + "error", + "apps/data-literate/pages" + ] + } + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ], + "rules": { + "@next/next/no-html-link-for-pages": "off" + }, + "env": { + "jest": true + } +} diff --git a/examples/data-literate/README.md b/apps/data-literate/README.md similarity index 100% rename from examples/data-literate/README.md rename to apps/data-literate/README.md diff --git a/examples/data-literate/components/DataLiterate.js b/apps/data-literate/components/DataLiterate.js similarity index 100% rename from examples/data-literate/components/DataLiterate.js rename to apps/data-literate/components/DataLiterate.js diff --git a/examples/data-literate/components/Excel.js b/apps/data-literate/components/Excel.js similarity index 100% rename from examples/data-literate/components/Excel.js rename to apps/data-literate/components/Excel.js diff --git a/examples/data-literate/components/Layout.js b/apps/data-literate/components/Layout.js similarity index 100% rename from examples/data-literate/components/Layout.js rename to apps/data-literate/components/Layout.js diff --git a/examples/data-literate/components/LineChart.js b/apps/data-literate/components/LineChart.js similarity index 100% rename from examples/data-literate/components/LineChart.js rename to apps/data-literate/components/LineChart.js diff --git a/examples/data-literate/components/Table.js b/apps/data-literate/components/Table.js similarity index 100% rename from examples/data-literate/components/Table.js rename to apps/data-literate/components/Table.js diff --git a/examples/data-literate/components/TableGrid.js b/apps/data-literate/components/TableGrid.js similarity index 97% rename from examples/data-literate/components/TableGrid.js rename to apps/data-literate/components/TableGrid.js index be4eaa73..fb844fca 100644 --- a/examples/data-literate/components/TableGrid.js +++ b/apps/data-literate/components/TableGrid.js @@ -1,6 +1,6 @@ import axios from 'axios' import React, { useEffect } from 'react' -import { Table } from 'portal' +import { Table } from '@portaljs/components' const papa = require("papaparse") diff --git a/examples/data-literate/content/demo.mdx b/apps/data-literate/content/demo.mdx similarity index 100% rename from examples/data-literate/content/demo.mdx rename to apps/data-literate/content/demo.mdx diff --git a/examples/data-literate/datahub-portal-local-cli.js b/apps/data-literate/datahub-portal-local-cli.js similarity index 100% rename from examples/data-literate/datahub-portal-local-cli.js rename to apps/data-literate/datahub-portal-local-cli.js diff --git a/apps/data-literate/index.d.ts b/apps/data-literate/index.d.ts new file mode 100644 index 00000000..7ba08fa1 --- /dev/null +++ b/apps/data-literate/index.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module '*.svg' { + const content: any; + export const ReactComponent: any; + export default content; +} diff --git a/apps/data-literate/jest.config.ts b/apps/data-literate/jest.config.ts new file mode 100644 index 00000000..f1d7ee0e --- /dev/null +++ b/apps/data-literate/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'data-literate', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/data-literate', +}; diff --git a/examples/data-literate/lib/markdown.js b/apps/data-literate/lib/markdown.js similarity index 100% rename from examples/data-literate/lib/markdown.js rename to apps/data-literate/lib/markdown.js diff --git a/site/lib/mdxUtils.js b/apps/data-literate/lib/mdxUtils.js similarity index 89% rename from site/lib/mdxUtils.js rename to apps/data-literate/lib/mdxUtils.js index d89b09f4..34c69a48 100644 --- a/site/lib/mdxUtils.js +++ b/apps/data-literate/lib/mdxUtils.js @@ -3,7 +3,7 @@ import glob from 'glob' import path from 'path' // POSTS_PATH is useful when you want to get the path to a specific file -export const POSTS_PATH = path.join(process.cwd(), 'content') +export const POSTS_PATH = path.join(process.cwd(), '/apps/data-literate/content') const walkSync = (dir, filelist = []) => { fs.readdirSync(dir).forEach(file => { diff --git a/examples/ckan/next-env.d.ts b/apps/data-literate/next-env.d.ts similarity index 82% rename from examples/ckan/next-env.d.ts rename to apps/data-literate/next-env.d.ts index 9bc3dd46..4f11a03d 100644 --- a/examples/ckan/next-env.d.ts +++ b/apps/data-literate/next-env.d.ts @@ -1,5 +1,4 @@ /// -/// /// // NOTE: This file should not be edited diff --git a/apps/data-literate/next.config.js b/apps/data-literate/next.config.js new file mode 100644 index 00000000..90460201 --- /dev/null +++ b/apps/data-literate/next.config.js @@ -0,0 +1,17 @@ +//@ts-check + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { withNx } = require('@nrwl/next/plugins/with-nx'); + +/** + * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + **/ +const nextConfig = { + nx: { + // Set this to true if you would like to use SVGR + // See: https://github.com/gregberge/svgr + svgr: false, + }, +}; + +module.exports = withNx(nextConfig); diff --git a/examples/data-literate/pages/[...slug].js b/apps/data-literate/pages/[...slug].js similarity index 100% rename from examples/data-literate/pages/[...slug].js rename to apps/data-literate/pages/[...slug].js diff --git a/examples/data-literate/pages/_app.js b/apps/data-literate/pages/_app.js similarity index 100% rename from examples/data-literate/pages/_app.js rename to apps/data-literate/pages/_app.js diff --git a/examples/data-literate/pages/api/proxy.js b/apps/data-literate/pages/api/proxy.js similarity index 100% rename from examples/data-literate/pages/api/proxy.js rename to apps/data-literate/pages/api/proxy.js diff --git a/apps/data-literate/postcss.config.js b/apps/data-literate/postcss.config.js new file mode 100644 index 00000000..c72626d6 --- /dev/null +++ b/apps/data-literate/postcss.config.js @@ -0,0 +1,15 @@ +const { join } = require('path'); + +// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build +// option from your application's configuration (i.e. project.json). +// +// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/apps/data-literate/project.json b/apps/data-literate/project.json new file mode 100644 index 00000000..5002a4e0 --- /dev/null +++ b/apps/data-literate/project.json @@ -0,0 +1,69 @@ +{ + "name": "data-literate", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/data-literate", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/next:build", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "root": "apps/data-literate", + "outputPath": "dist/apps/data-literate" + }, + "configurations": { + "development": { + "outputPath": "apps/data-literate" + }, + "production": {} + } + }, + "serve": { + "executor": "@nrwl/next:server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "data-literate:build", + "dev": true + }, + "configurations": { + "development": { + "buildTarget": "data-literate:build:development", + "dev": true + }, + "production": { + "buildTarget": "data-literate:build:production", + "dev": false + } + } + }, + "export": { + "executor": "@nrwl/next:export", + "options": { + "buildTarget": "data-literate:build:production" + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/data-literate/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/data-literate/**/*.{ts,tsx,js,jsx}"] + } + } + }, + "tags": [] +} diff --git a/apps/data-literate/public/.gitkeep b/apps/data-literate/public/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/examples/data-literate/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv b/apps/data-literate/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv similarity index 100% rename from examples/data-literate/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv rename to apps/data-literate/public/_files/HadCRUT.5.0.1.0.analysis.summary_series.global.annual.csv diff --git a/examples/data-literate/public/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx b/apps/data-literate/public/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx similarity index 100% rename from examples/data-literate/public/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx rename to apps/data-literate/public/_files/eight-centuries-of-global-real-interest-rates-r-g-and-the-suprasecular-decline-1311-2018-data.xlsx diff --git a/examples/data-literate/public/datopian-logo.png b/apps/data-literate/public/datopian-logo.png similarity index 100% rename from examples/data-literate/public/datopian-logo.png rename to apps/data-literate/public/datopian-logo.png diff --git a/examples/data-literate/public/favicon.ico b/apps/data-literate/public/favicon.ico similarity index 100% rename from examples/data-literate/public/favicon.ico rename to apps/data-literate/public/favicon.ico diff --git a/apps/data-literate/specs/index.spec.tsx b/apps/data-literate/specs/index.spec.tsx new file mode 100644 index 00000000..42c94022 --- /dev/null +++ b/apps/data-literate/specs/index.spec.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import Index from '../pages/index'; + +describe('Index', () => { + it('should render successfully', () => { + const { baseElement } = render(); + expect(baseElement).toBeTruthy(); + }); +}); diff --git a/examples/data-literate/styles/globals.css b/apps/data-literate/styles/globals.css similarity index 100% rename from examples/data-literate/styles/globals.css rename to apps/data-literate/styles/globals.css diff --git a/examples/data-literate/styles/tailwind.css b/apps/data-literate/styles/tailwind.css similarity index 100% rename from examples/data-literate/styles/tailwind.css rename to apps/data-literate/styles/tailwind.css diff --git a/apps/data-literate/tailwind.config.js b/apps/data-literate/tailwind.config.js new file mode 100644 index 00000000..9a942e21 --- /dev/null +++ b/apps/data-literate/tailwind.config.js @@ -0,0 +1,17 @@ +const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind'); +const { join } = require('path'); + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + join( + __dirname, + '{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html,js,jsx}' + ), + ...createGlobPatternsForDependencies(__dirname), + ], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/apps/data-literate/tsconfig.json b/apps/data-literate/tsconfig.json new file mode 100644 index 00000000..7a9f3ecf --- /dev/null +++ b/apps/data-literate/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "preserve", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": false, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "types": ["jest", "node"] + }, + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], + "exclude": [ + "node_modules", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts" + ] +} diff --git a/apps/data-literate/tsconfig.spec.json b/apps/data-literate/tsconfig.spec.json new file mode 100644 index 00000000..214b2cc2 --- /dev/null +++ b/apps/data-literate/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "jsx": "react" + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/apps/site-e2e/.eslintrc.json b/apps/site-e2e/.eslintrc.json new file mode 100644 index 00000000..696cb8b1 --- /dev/null +++ b/apps/site-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/site-e2e/cypress.config.ts b/apps/site-e2e/cypress.config.ts new file mode 100644 index 00000000..22f7c84e --- /dev/null +++ b/apps/site-e2e/cypress.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'cypress'; +import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; + +export default defineConfig({ + e2e: nxE2EPreset(__dirname), +}); diff --git a/apps/site-e2e/project.json b/apps/site-e2e/project.json new file mode 100644 index 00000000..b6cd0d87 --- /dev/null +++ b/apps/site-e2e/project.json @@ -0,0 +1,30 @@ +{ + "name": "site-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/site-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/site-e2e/cypress.config.ts", + "devServerTarget": "site:serve:development", + "testingType": "e2e" + }, + "configurations": { + "production": { + "devServerTarget": "site:serve:production" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/site-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["site"] +} diff --git a/apps/site-e2e/src/e2e/app.cy.ts b/apps/site-e2e/src/e2e/app.cy.ts new file mode 100644 index 00000000..3a752dd4 --- /dev/null +++ b/apps/site-e2e/src/e2e/app.cy.ts @@ -0,0 +1,13 @@ +import { getGreeting } from '../support/app.po'; + +describe('site', () => { + beforeEach(() => cy.visit('/')); + + it('should display welcome message', () => { + // Custom command example, see `../support/commands.ts` file + cy.login('my-email@something.com', 'myPassword'); + + // Function helper example, see `../support/app.po.ts` file + getGreeting().contains('Welcome site'); + }); +}); diff --git a/apps/site-e2e/src/fixtures/example.json b/apps/site-e2e/src/fixtures/example.json new file mode 100644 index 00000000..294cbed6 --- /dev/null +++ b/apps/site-e2e/src/fixtures/example.json @@ -0,0 +1,4 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io" +} diff --git a/apps/site-e2e/src/support/app.po.ts b/apps/site-e2e/src/support/app.po.ts new file mode 100644 index 00000000..32934246 --- /dev/null +++ b/apps/site-e2e/src/support/app.po.ts @@ -0,0 +1 @@ +export const getGreeting = () => cy.get('h1'); diff --git a/apps/site-e2e/src/support/commands.ts b/apps/site-e2e/src/support/commands.ts new file mode 100644 index 00000000..310f1fa0 --- /dev/null +++ b/apps/site-e2e/src/support/commands.ts @@ -0,0 +1,33 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} +// +// -- This is a parent command -- +Cypress.Commands.add('login', (email, password) => { + console.log('Custom command example: Login', email, password); +}); +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/apps/site-e2e/src/support/e2e.ts b/apps/site-e2e/src/support/e2e.ts new file mode 100644 index 00000000..3d469a6b --- /dev/null +++ b/apps/site-e2e/src/support/e2e.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/site-e2e/tsconfig.json b/apps/site-e2e/tsconfig.json new file mode 100644 index 00000000..cc509a73 --- /dev/null +++ b/apps/site-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] +} diff --git a/apps/site/.eslintrc.json b/apps/site/.eslintrc.json new file mode 100644 index 00000000..12e8399a --- /dev/null +++ b/apps/site/.eslintrc.json @@ -0,0 +1,31 @@ +{ + "extends": [ + "plugin:@nrwl/nx/react-typescript", + "next", + "next/core-web-vitals", + "../../.eslintrc.json" + ], + "ignorePatterns": ["!**/*", ".next/**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": { + "@next/next/no-html-link-for-pages": ["error", "apps/site/pages"] + } + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ], + "rules": { + "@next/next/no-html-link-for-pages": "off" + }, + "env": { + "jest": true + } +} diff --git a/site/README.md b/apps/site/README.md similarity index 100% rename from site/README.md rename to apps/site/README.md diff --git a/site/components/CustomLink.js b/apps/site/components/CustomLink.js similarity index 72% rename from site/components/CustomLink.js rename to apps/site/components/CustomLink.js index f5902307..c311089d 100644 --- a/site/components/CustomLink.js +++ b/apps/site/components/CustomLink.js @@ -1,9 +1,9 @@ -import Link from 'next/link' +import Link from "next/link"; export default function CustomLink({ as, href, ...otherProps }) { return ( <> - + - ) + ); } diff --git a/site/components/DataLiterate.js b/apps/site/components/DataLiterate.js similarity index 100% rename from site/components/DataLiterate.js rename to apps/site/components/DataLiterate.js diff --git a/apps/site/components/Excel.js b/apps/site/components/Excel.js new file mode 100644 index 00000000..1f69d950 --- /dev/null +++ b/apps/site/components/Excel.js @@ -0,0 +1,74 @@ +import axios from 'axios' +import * as 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 && +
+ There was an error loading the excel file at {src}: +

{error}

+
+ } + {workbook && +
    + {workbook.SheetNames.map((value, index) => { + return
  • {value}
  • + })} +
+ } +
+ + ) +} + +/* 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 +} + diff --git a/site/components/ExcelViewerApp.js b/apps/site/components/ExcelViewerApp.js similarity index 100% rename from site/components/ExcelViewerApp.js rename to apps/site/components/ExcelViewerApp.js diff --git a/site/components/Layout.js b/apps/site/components/Layout.js similarity index 100% rename from site/components/Layout.js rename to apps/site/components/Layout.js diff --git a/apps/site/components/Layout.tsx b/apps/site/components/Layout.tsx new file mode 100644 index 00000000..626cd44a --- /dev/null +++ b/apps/site/components/Layout.tsx @@ -0,0 +1,34 @@ +import { NextSeo } from "next-seo"; + +import Nav from "./Nav"; + +export default function Layout({ + children, + title, +}: { + children; + title?: string; +}) { + return ( + <> + {title && } +
+ + {cols.map((c) => )} + + + {data.map((r,i) => + {cols.map(c => )} + )} + +
{c.name}
{ r[c.key] }
+
+ ) +} + +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 + } +} diff --git a/apps/site/config/siteConfig.ts b/apps/site/config/siteConfig.ts new file mode 100644 index 00000000..53485fd2 --- /dev/null +++ b/apps/site/config/siteConfig.ts @@ -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, + }, +}; diff --git a/apps/site/content/config.js b/apps/site/content/config.js new file mode 100644 index 00000000..ad787c76 --- /dev/null +++ b/apps/site/content/config.js @@ -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; diff --git a/site/content/data-literate.md b/apps/site/content/data-literate.md similarity index 100% rename from site/content/data-literate.md rename to apps/site/content/data-literate.md diff --git a/site/content/data-literate/demo.mdx b/apps/site/content/data-literate/demo.mdx similarity index 100% rename from site/content/data-literate/demo.mdx rename to apps/site/content/data-literate/demo.mdx diff --git a/site/content/docs.md b/apps/site/content/docs.md similarity index 100% rename from site/content/docs.md rename to apps/site/content/docs.md diff --git a/site/content/docs/components.md b/apps/site/content/docs/components.md similarity index 98% rename from site/content/docs/components.md rename to apps/site/content/docs/components.md index 234ba6e9..ba142e7a 100644 --- a/site/content/docs/components.md +++ b/apps/site/content/docs/components.md @@ -42,7 +42,7 @@ export default function Home(){ Nav component accepts two properties: * **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) @@ -346,8 +346,8 @@ export default function Home() { #### Table Component Prop Types Table component accepts two properties: -* **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} ] +* **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} ]'} ### [Search Components](https://github.com/datopian/portal.js/tree/main/src/components/search) diff --git a/site/content/docs/references.md b/apps/site/content/docs/references.md similarity index 100% rename from site/content/docs/references.md rename to apps/site/content/docs/references.md diff --git a/site/content/gallery.md b/apps/site/content/gallery.md similarity index 100% rename from site/content/gallery.md rename to apps/site/content/gallery.md diff --git a/site/content/learn.md b/apps/site/content/learn.md similarity index 100% rename from site/content/learn.md rename to apps/site/content/learn.md diff --git a/site/content/learn/ckan.md b/apps/site/content/learn/ckan.md similarity index 96% rename from site/content/learn/ckan.md rename to apps/site/content/learn/ckan.md index 828fbb12..39df75ac 100644 --- a/site/content/learn/ckan.md +++ b/apps/site/content/learn/ckan.md @@ -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. -[Next.JS docs]: https://nextjs.org/docs/basic-features/pages +[next.js docs]: https://nextjs.org/docs/basic-features/pages ### Data fetching @@ -205,10 +205,8 @@ export default function Org({ variables }) { } className="h-5 w-5 mr-2 inline-block" /> - - + {organization.title || organization.name} - ) : ( @@ -217,4 +215,4 @@ export default function Org({ variables }) { ); } -``` \ No newline at end of file +``` diff --git a/site/content/learn/deploy-to-gh-pages.md b/apps/site/content/learn/deploy-to-gh-pages.md similarity index 100% rename from site/content/learn/deploy-to-gh-pages.md rename to apps/site/content/learn/deploy-to-gh-pages.md diff --git a/site/content/learn/single-frictionless-dataset.md b/apps/site/content/learn/single-frictionless-dataset.md similarity index 100% rename from site/content/learn/single-frictionless-dataset.md rename to apps/site/content/learn/single-frictionless-dataset.md diff --git a/apps/site/index.d.ts b/apps/site/index.d.ts new file mode 100644 index 00000000..7ba08fa1 --- /dev/null +++ b/apps/site/index.d.ts @@ -0,0 +1,6 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module '*.svg' { + const content: any; + export const ReactComponent: any; + export default content; +} diff --git a/apps/site/jest.config.ts b/apps/site/jest.config.ts new file mode 100644 index 00000000..5e8864ad --- /dev/null +++ b/apps/site/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'site', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/site', +}; diff --git a/apps/site/layouts/index.ts b/apps/site/layouts/index.ts new file mode 100644 index 00000000..535b5a01 --- /dev/null +++ b/apps/site/layouts/index.ts @@ -0,0 +1,13 @@ +import { + SimpleLayout, + DocsLayout, + UnstyledLayout, + BlogLayout, +} from "@flowershow/core"; + +export default { + simple: SimpleLayout, + docs: DocsLayout, + unstyled: UnstyledLayout, + blog: BlogLayout, +}; diff --git a/examples/nextjs-tailwind-mdx/lib/mdx.js b/apps/site/lib/data-literate/markdown.js similarity index 78% rename from examples/nextjs-tailwind-mdx/lib/mdx.js rename to apps/site/lib/data-literate/markdown.js index 0902164c..98b05562 100644 --- a/examples/nextjs-tailwind-mdx/lib/mdx.js +++ b/apps/site/lib/data-literate/markdown.js @@ -1,16 +1,18 @@ +// Used by Data Literate + import matter from 'gray-matter' -import gfm from 'remark-gfm' import toc from 'remark-toc' import slug from 'remark-slug' -import smartypants from '@silvenon/remark-smartypants' -import { serialize } from 'next-mdx-remote/serialize' +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: object } + * @returns: { mdxSource: mdxSource, frontMatter: ...} */ const parse = async function(source) { const { content, data } = matter(source) @@ -18,7 +20,7 @@ const parse = async function(source) { const mdxSource = await serialize(content, { // Optionally pass remark/rehype plugins mdxOptions: { - remarkPlugins: [gfm, toc, slug, smartypants], + remarkPlugins: [gfm, toc, slug, footnotes], rehypePlugins: [], }, scope: data, diff --git a/apps/site/lib/getAuthorsDetails.ts b/apps/site/lib/getAuthorsDetails.ts new file mode 100644 index 00000000..792819e7 --- /dev/null +++ b/apps/site/lib/getAuthorsDetails.ts @@ -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 }; + }); +}; diff --git a/site/lib/markdown.js b/apps/site/lib/markdown.js similarity index 100% rename from site/lib/markdown.js rename to apps/site/lib/markdown.js diff --git a/apps/site/lib/markdown.mjs b/apps/site/lib/markdown.mjs new file mode 100644 index 00000000..4a571c24 --- /dev/null +++ b/apps/site/lib/markdown.mjs @@ -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; diff --git a/apps/site/lib/mddb.ts b/apps/site/lib/mddb.ts new file mode 100644 index 00000000..1e4086ff --- /dev/null +++ b/apps/site/lib/mddb.ts @@ -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; diff --git a/examples/data-literate/lib/mdxUtils.js b/apps/site/lib/mdxUtils.js similarity index 100% rename from examples/data-literate/lib/mdxUtils.js rename to apps/site/lib/mdxUtils.js diff --git a/site/lib/utils.js b/apps/site/lib/utils.js similarity index 100% rename from site/lib/utils.js rename to apps/site/lib/utils.js diff --git a/apps/site/markdown.db b/apps/site/markdown.db new file mode 100644 index 00000000..4d3ec2ac Binary files /dev/null and b/apps/site/markdown.db differ diff --git a/apps/site/next-env.d.ts b/apps/site/next-env.d.ts new file mode 100644 index 00000000..4f11a03d --- /dev/null +++ b/apps/site/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/site/next.config.js b/apps/site/next.config.js new file mode 100644 index 00000000..90460201 --- /dev/null +++ b/apps/site/next.config.js @@ -0,0 +1,17 @@ +//@ts-check + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { withNx } = require('@nrwl/next/plugins/with-nx'); + +/** + * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} + **/ +const nextConfig = { + nx: { + // Set this to true if you would like to use SVGR + // See: https://github.com/gregberge/svgr + svgr: false, + }, +}; + +module.exports = withNx(nextConfig); diff --git a/apps/site/pages/[...slug].tsx b/apps/site/pages/[...slug].tsx new file mode 100644 index 00000000..a7610bcd --- /dev/null +++ b/apps/site/pages/[...slug].tsx @@ -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 ( + + + + ); +} + +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, + }; +} diff --git a/apps/site/pages/_app.tsx b/apps/site/pages/_app.tsx new file mode 100644 index 00000000..25ed0543 --- /dev/null +++ b/apps/site/pages/_app.tsx @@ -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 ( + + + {/* Global Site Tag (gtag.js) - Google Analytics */} + {siteConfig.analytics && ( + <> +