diff --git a/.gitignore b/.gitignore
index 2708d6f8..2e0e79d8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ node_modules
*.launch
.settings/
*.sublime-workspace
+.obsidian
# IDE - VSCode
.vscode/*
diff --git a/site/content/assets/howto/catalog-example.png b/site/content/assets/howto/catalog-example.png
new file mode 100644
index 00000000..f85d1fec
Binary files /dev/null and b/site/content/assets/howto/catalog-example.png differ
diff --git a/site/content/assets/howto/catalog-facets-example.png b/site/content/assets/howto/catalog-facets-example.png
new file mode 100644
index 00000000..38a715cb
Binary files /dev/null and b/site/content/assets/howto/catalog-facets-example.png differ
diff --git a/site/content/assets/howto/comments-example.png b/site/content/assets/howto/comments-example.png
new file mode 100644
index 00000000..d1e4ead3
Binary files /dev/null and b/site/content/assets/howto/comments-example.png differ
diff --git a/site/content/assets/howto/linechart-example.png b/site/content/assets/howto/linechart-example.png
new file mode 100644
index 00000000..ec9a30df
Binary files /dev/null and b/site/content/assets/howto/linechart-example.png differ
diff --git a/site/content/assets/howto/table-example.png b/site/content/assets/howto/table-example.png
new file mode 100644
index 00000000..ffe635d6
Binary files /dev/null and b/site/content/assets/howto/table-example.png differ
diff --git a/site/content/howto/analytics.md b/site/content/howto/analytics.md
new file mode 100644
index 00000000..25fb35e1
--- /dev/null
+++ b/site/content/howto/analytics.md
@@ -0,0 +1,77 @@
+# How to add Google Analytics?
+
+>[!todo] Prerequisites
+>- [Google Analytics account](https://support.google.com/analytics/answer/9304153?hl=en)
+>- [Google tag ID](https://support.google.com/analytics/answer/12270356?hl=en#:~:text=A%20Measurement%20ID%20is%20an,same%20as%20your%20destination%20ID.)
+
+The following piece of code adds pageview tracking functionality to the custom Next.js App in `/pages/_app.tsx`, but you can use it only in specific pages if you want.
+
+```tsx
+// /pages/_app.tsx
+import { useRouter } from "next/router";
+import { useEffect } from "react";
+import Script from "next/script";
+
+// Step 0:
+// Add your Google tag ID to your local .env.local file, e.g. under GA_TRACKING_ID
+
+// Step 1: create the following pageview function
+const pageview = ({
+ url,
+ analyticsID,
+}: {
+ url: string;
+ analyticsID: string;
+}) => {
+ if (typeof window.gtag !== undefined) {
+ window.gtag("config", analyticsID, {
+ page_path: url,
+ });
+ }
+};
+
+export default function MyApp({ Component, pageProps }) {
+ const router = useRouter();
+
+ // ... your code
+
+ // Step 3: Add the following useEffect to trigger pageview on each route change
+ useEffect(() => {
+ const handleRouteChange = (url) => {
+ pageview(url, GA_TRACKING_ID);
+ };
+ router.events.on("routeChangeComplete", handleRouteChange);
+ return () => {
+ router.events.off("routeChangeComplete", handleRouteChange);
+ };
+ }, [router.events]);
+
+ return (
+ {/* Step 4: Install Google Analytics tag */}
+ <>
+
+
+
+ >
+ );
+};
+```
+
+>[!info]
+> For more info on measuring pageviews with Google Analytics see [Google Analytics documentation](https://developers.google.com/analytics/devguides/collection/gtagjs/pages).
\ No newline at end of file
diff --git a/site/content/howto/blog.md b/site/content/howto/blog.md
new file mode 100644
index 00000000..8a4f8d98
--- /dev/null
+++ b/site/content/howto/blog.md
@@ -0,0 +1,75 @@
+# How to add a simple blog?
+
+## Setup
+
+The following example uses components imported from the [`@flowershow/core` package](https://github.com/datopian/flowershow). If you want to follow along install it too:
+
+```sh
+npm i @flowershow/core
+```
+
+## Create home page for your blogs
+
+Add the following code to the Next.js page that is going to be your blog home page, e.g. to `/pages/blog/index.tsx`:
+
+```tsx
+import { BlogsList, SimpleLayout } from "@flowershow/core";
+
+// pass a list of blogs, home page title and home page description, e.g. from `getStaticProps`
+export default function BlogIndex({ blogs, title, description }) {
+ return (
+
+
+
+ );
+}
+```
+
+`BlogsList` component has the following API:
+
+```ts
+interface BlogsListProps {
+ blogs: Blog;
+}
+
+interface Blog {
+ title: string;
+ date: string;
+ urlPath: string;
+ description?: string;
+ authors?: Array;
+ tags?: Array;
+}
+```
+
+## Create blog post pages
+
+Add the following code to your blog pages, e.g. to `/pages/blog/[...slug].tsx`:
+
+```tsx
+import { BlogLayout } from "@flowershow/core";
+
+export default BlogPost({ content, title, date, authors }) {
+ return (
+
+ )
+}
+```
+
+`BlogLayout` component has the following API:
+
+```ts
+interface BlogLayoutProps {
+ title?: string;
+ date?: string;
+ authors?: Array;
+}
+
+interface Author {
+ name: string;
+ avatar: string; // avatar image path
+ urlPath?: string; // author page
+}
+```
diff --git a/site/content/howto/comments.md b/site/content/howto/comments.md
new file mode 100644
index 00000000..5d8743d6
--- /dev/null
+++ b/site/content/howto/comments.md
@@ -0,0 +1,106 @@
+# How to add user comments?
+
+![[comments-example.png]]
+
+## Setup
+
+Page comments can be setup with any one of the following supported providers:
+
+1. [giscus](https://giscus.app/)
+2. [utterances](https://utteranc.es/)
+3. [disqus](https://disqus.com/)
+
+Each provider has it's own configuration options that you should add to your `.env` file.
+
+>[!Info]
+>If you are hosting your website on hosting providers like Netlify, Vercel or Cloudflare, you will also need to add the environment variables there.
+
+### Giscus
+
+[Giscus](https://giscus.app/) uses your github repo's discussions feature to store your comments and display them on your site.
+
+#### Prerequisites
+
+1. You have a public [github repository](https://docs.github.com/en/get-started/quickstart/create-a-repo) with the [discussions](https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/enabling-or-disabling-github-discussions-for-a-repository) feature activated which will be used to store your comments.
+2. The [giscus app](https://github.com/apps/giscus) installed in your repo by following their configuration setup at [https://giscus.app/](https://giscus.app/)
+
+Once the above steps are completed, head over to [https://giscus.app](https://giscus.app/) and follow the steps there by filling out the fields to get your config values.
+
+>[!important]
+> Make sure to choose `pathname` under page discussions mapping.
+
+After filling out the fields, you will be provided with a script tag that contains your config values. Add them to your `.env` file, like so:
+
+```
+NEXT_PUBLIC_GISCUS_REPO=
+NEXT_PUBLIC_GISCUS_REPOSITORY_ID=
+NEXT_PUBLIC_GISCUS_CATEGORY=
+NEXT_PUBLIC_GISCUS_CATEGORY_ID=
+```
+
+When a user visits a web page, Giscus searches for a discussion whose title contains the page's pathname URL component. If there are no matches (i.e., no comments have been made about that page), then a new discussion is created for that page.
+
+By using the pathname for mapping with a page, Giscus ensures that each discussion is unique to a particular page and that comments made on that page are kept together.
+
+More info on how this connection is achieved can be found on their '**how it works**' section in their repo - [https://github.com/giscus/giscus#how-it-works](https://github.com/giscus/giscus#how-it-works) and also in the '**Page Discussions Mapping**' section at [https://giscus.app/](https://giscus.app/)
+
+### Utterances
+
+[Utterances](https://utteranc.es/) uses your github repo's issues to store comments for your pages and display them on your site. To setup comments with utterances, you would need to do the following:
+
+#### Prerequisites
+
+1. You have a public [github repository](https://docs.github.com/en/get-started/quickstart/create-a-repo)
+2. The utterances app installed in your repo by following their configuration setup at [https://utteranc.es/](https://utteranc.es/)
+
+Once installed fill in the required fields and you will see a script tag with your repo value which you can add to your `.env` file, like so:
+
+```
+NEXT_PUBLIC_UTTERANCES_REPO=
+```
+
+### Disqus
+
+[Disqus](https://disqus.com/) is a feature rich provider which can be used to add comments to your site. You can configure flowershow to use disqus by creating an account on their site and following their process. You will be asked to enter a shortname for your site. Once completed, we can use this shortname in our comments configuration as below.
+
+#### Prerequisites
+
+1. Create an account on [Disqus](https://disqus.com/) and follow their setup process.
+
+You will be asked to enter a shortname for your site. Add it to your `.env` file as well, like so:
+
+```
+NEXT_PUBLIC_DISQUS_SHORTNAME=
+```
+
+## Add comments to your page layout
+
+You can use `@flowershow/core` to import the comments section component. Install it with:
+
+```sh
+npm i @flowershow/core
+```
+
+Then, add the following to your custom layout (or directly to your pages):
+
+```tsx
+import Navbar from './navbar';
+import Footer from './footer';
+
+export default function Layout({ children }) {
+ return (
+ <>
+
+ {children}
+
+
+
+
+ >
+ );
+}
+```
+
+## Add user comments
+
+Learn how to add support for user comments in [[comments|this guide]].
\ No newline at end of file
diff --git a/site/content/howto/drd.md b/site/content/howto/drd.md
new file mode 100644
index 00000000..47295d57
--- /dev/null
+++ b/site/content/howto/drd.md
@@ -0,0 +1,334 @@
+# How to create data-rich documents with charts and tables?
+
+> [!info] Prerequisites
+> If you want to enrich your markdown content with charts and tables, you first need to add support for rendering markdown files in your PortalJS app. Follow [[markdown|this guide]] to learn how to do this.
+
+PortalJS comes with a library of components that provides essential pieces for your data portal. The best way to explore the components is to look at our [Storybook](https://storybook.portaljs.org/) that contains all the details on how to use them. Below is an overview of available components.
+
+You can install the library with:
+
+```sh
+npm i @portaljs/components
+```
+
+Now, in order to use these components in your markdown files, we need to pass them to our `MDXRemote` component (see [[markdown#Import, parse and render your markdown files|this section in our markdown guide]]):
+
+```tsx
+// e.g. /blog/[[...slug]].tsx
+import fs from "fs";
+import { LineChart, Table, Catalog, Vega, VegaLite } from "@portaljs/components";
+
+import { MdxRemote } from "next-mdx-remote";
+import clientPromise from "@/lib/mddb.mjs";
+import parse from "@/lib/markdown";
+
+const components = {
+ Table: Table,
+ Vega: Vega,
+ VegaLite: VegaLite,
+ LineChart: LineChart,
+};
+
+
+export default function Page({ source }) {
+ source = JSON.parse(source);
+
+ return (
+ <>
+
+ >
+ );
+}
+
+// Import metadata of a file matching the static path and return its parsed source and frontmatter object
+export const getStaticProps = async ({ params }) => {
+ const urlPath = params?.slug ? (params.slug as string[]).join("/") : "/";
+
+ const mddb = await clientPromise;
+ const dbFile = await mddb.getFileByUrl(urlPath);
+ const filePath = dbFile!.file_path;
+ // const frontMatter = dbFile!.metadata ?? {};
+
+ const source = fs.readFileSync(filePath, { encoding: "utf-8" });
+ const { mdxSource } = await parse(source, "mdx", {});
+
+ return {
+ props: {
+ source: JSON.stringify(mdxSource),
+ // frontMatter
+ },
+ };
+};
+
+```
+
+You can now use these components in your markdown, like so:
+
+```md
+### My Data Rich Note
+
+Some markdown here.
+
+
+
+Some markdown here.
+```
+
+## PortalJS components
+
+### Table
+
+An easy-to-use table component with built-in pagination, search, and sorting. It can be used with raw data, raw CSV strings or with URLs to hosted CSV files.
+
+![[table-example.png]]
+
+Example usage:
+
+```js
+
+```
+
+>[!info]
+> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-table--docs)
+
+### Linechart
+
+A simple component that allows the creation of an opinionated line chart without the need to go deep into charting standards. It can be used with raw data, raw CSV strings or with URLs to hosted CSV files.
+
+![[linechart-example.png]]
+
+Example usage:
+
+```js
+
+```
+
+>[!info]
+> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-linechart--docs)
+
+### Vega chart
+
+A wrapper around the [Vega specification](https://vega.github.io/vega/) that allows you to build pretty much any kind of chart imaginable.
+
+Example usage:
+
+
+```js
+
+```
+
+> [!info]
+> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-vega--docs)
+
+### VegaLite chart
+
+A wrapper around the [Vega Lite specification](https://vega.github.io/vega-lite/) which allows for a more concise grammar than Vega around the building of charts.
+
+Example usage:
+
+```js
+
+```
+
+>[!info]
+> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-vegalite--docs)
+
+### Catalog
+
+A searchable catalog that will index a list of datasets and allow for contextual searching + filters.
+
+![[catalog-example.png]]
+
+Example usage:
+
+```js
+
+```
+
+You can also add facets that are going to act as filters for your metadata.
+
+![[catalog-facets-example.png]]
+
+```js
+
+```
+
+>[!info]
+> More info on the [storybook page](https://storybook.portaljs.org/?path=/docs/components-catalog--docs)
diff --git a/site/content/howto/index.md b/site/content/howto/index.md
new file mode 100644
index 00000000..33ff7721
--- /dev/null
+++ b/site/content/howto/index.md
@@ -0,0 +1,12 @@
+# Guides and tutorials
+
+- [[howto/analytics|How to add web analytics?]]
+- [[howto/seo|How to customize page metadata for SEO?]]
+- [[howto/sitemap|How to build a sitemap?]]
+- [[howto/markdown|How to add markdown-based content pages?]]
+- [[howto/blog|How to add a simple blog?]]
+- [[howto/drd|How to create data-rich documents with charts and tables?]]
+- [[howto/comments|How to add user comments?]]
+
+If you have questions about anything related to PortalJS, you're always welcome to ask our community on [GitHub Discussions](https://github.com/datopian/portaljs/discussions) or on [our chat channel on Discord](https://discord.gg/EeyfGrGu4U).
+
diff --git a/site/content/howto/markdown.md b/site/content/howto/markdown.md
new file mode 100644
index 00000000..4687406f
--- /dev/null
+++ b/site/content/howto/markdown.md
@@ -0,0 +1,169 @@
+# How to add markdown-based content pages?
+
+## Add content layer to your app
+
+Create a folder where you'll keep your markdown files and add some markdown files to it.
+
+```sh
+cd my-portaljs-project
+mkdir content
+# touch content/index.md ...
+```
+
+Install [MarkdownDB](https://github.com/datopian/markdowndb) package:
+
+```
+npm i @flowershow/markdowndb
+```
+
+And add the following to your `package.json`:
+
+```json
+{
+ "scripts": {
+ "mddb": "mddb ",
+ "prebuild": "npm run mddb"
+ },
+}
+```
+
+You can give it a go by running `npm run mddb`. You should see a `markdown.db` file created in the root of your project. You can inspect it with any SQLite viewer or in the command line. In the `files` table you should see all your markdown files from your content folder.
+
+Now, once the data is in the database, you can add the following script to your project (e.g. in `/lib` folder). It will allow you to establish a single connection to the database and use it across your app.
+
+```ts
+// lib/mddb.ts
+import { MarkdownDB } from "@flowershow/markdowndb";
+
+// path to the markdown.db file created by the mddb script
+const dbPath = "markdown.db";
+
+const client = new MarkdownDB({
+ client: "sqlite3",
+ connection: {
+ filename: dbPath,
+ },
+});
+
+const clientPromise = client.init();
+
+export default clientPromise;
+```
+
+Now you can import it across your project to query the database, e.g.:
+
+```ts
+import clientPromise from "@/lib/mddb";
+
+const mddb = await clientPromise;
+const blogs = await mddb.getFiles({
+ folder: "blog",
+ extensions: ["md", "mdx"],
+});
+```
+
+## Write a markdown parser
+
+Install [next-mdx-remote](https://github.com/hashicorp/next-mdx-remote) package, which we'll first use to parse markdown files and then to render them in Next.js app.
+
+```sh
+npm i next-mdx-remote
+```
+
+Create the following basic parser for your markdown files, e.g. in `/lib/markdown.ts`:
+
+```ts
+import matter from "gray-matter";
+import remarkGfm from "remark-gfm";
+import { serialize } from "next-mdx-remote/serialize";
+
+
+const parse = async function (source) {
+ const { content } = matter(source);
+
+ const mdxSource = await serialize(
+ { value: content },
+ {
+ mdxOptions: {
+ remarkPlugins: [
+ remarkGfm,
+ // ... your remark plugins
+ ],
+ rehypePlugins: [
+ // ... your plugins
+ ],
+ format,
+ }
+ }
+ );
+
+ return {
+ mdxSource
+ };
+};
+
+export default parse;
+
+```
+
+## Import, parse and render your markdown files
+
+Create a page in the `/pages` folder that will render your markdown content, e.g. `pages/blog/[[...slug]].tsx`:
+
+```tsx
+import fs from "fs";
+
+import { MdxRemote } from "next-mdx-remote";
+import clientPromise from "@/lib/mddb.mjs";
+import parse from "@/lib/markdown";
+
+
+export default function Page({ source }) {
+ source = JSON.parse(source);
+
+ return (
+ <>
+
+ >
+ );
+}
+
+// Import metadata of a file matching the static path and return its parsed source and frontmatter object
+export const getStaticProps = async ({ params }) => {
+ const urlPath = params?.slug ? (params.slug as string[]).join("/") : "/";
+
+ const mddb = await clientPromise;
+ const dbFile = await mddb.getFileByUrl(urlPath);
+ const filePath = dbFile!.file_path;
+ // const frontMatter = dbFile!.metadata ?? {};
+
+ const source = fs.readFileSync(filePath, { encoding: "utf-8" });
+ const { mdxSource } = await parse(source, "mdx", {});
+
+ return {
+ props: {
+ source: JSON.stringify(mdxSource),
+ // frontMatter
+ },
+ };
+};
+
+
+// Import metadata of your markdown files from MarkdownDB and return a list of static paths
+export const getStaticPaths = async () => {
+ const mddb = await clientPromise;
+ const allDocuments = await mddb.getFiles({ extensions: ["md", "mdx"] });
+ const paths = allDocuments
+ .map((page) => {
+ const url = decodeURI(page.url_path);
+ const parts = url.split("/");
+ return { params: { slug: parts } };
+ });
+
+ return {
+ paths,
+ fallback: false,
+ };
+};
+```
+
diff --git a/site/content/howto/seo.md b/site/content/howto/seo.md
new file mode 100644
index 00000000..42d3c544
--- /dev/null
+++ b/site/content/howto/seo.md
@@ -0,0 +1,79 @@
+# How to customize page metadata for SEO?
+
+>[!info]
+>See [`next-seo` documentation](https://github.com/garmeeh/next-seo) to learn more.
+
+## Setup
+
+Install `next-seo` package:
+
+```sh
+npm i next-seo
+```
+
+## Default SEO configuration
+
+Create `next-seo.config` file (e.g. in the root of your project) and add default meta tags values you want to be set up for your pages. For example:
+
+```ts
+export default {
+ openGraph: {
+ type: 'website',
+ locale: 'en_IE',
+ url: 'https://www.my.portaljs.app/',
+ siteName: 'MyPortalJSApp',
+ },
+ twitter: {
+ handle: '@handle',
+ site: '@site',
+ cardType: 'summary_large_image',
+ },
+};
+```
+
+Then, in your custom Next.js App, add the following:
+
+```tsx
+// /pages/_app.tsx
+import { DefaultSeo } from "next-seo";
+
+// import your default seo configuration
+import SEO from '../next-seo.config';
+
+
+export default function MyApp({ Component, pageProps }) {
+ // ... your code
+ return (
+ <>
+
+
+ >
+ );
+};
+
+```
+
+>[!info]
+>To learn more on default SEO configuration with `next-seo`, see [this docs section](https://github.com/garmeeh/next-seo#default-seo-configuration).
+
+## Per-page SEO configuration
+
+To add page specific meta tag values:
+
+```tsx
+import { NextSeo } from 'next-seo';
+
+export default function Page() (
+ // ...
+ <>
+
+ My Portal JS page
+ >
+);
+```
+
+>[!info]
+> To learn more on per-page SEO configuration with `next-seo`, see [this docs section](https://github.com/garmeeh/next-seo#add-seo-to-page)
\ No newline at end of file
diff --git a/site/content/howto/sitemap.md b/site/content/howto/sitemap.md
new file mode 100644
index 00000000..1f63daa5
--- /dev/null
+++ b/site/content/howto/sitemap.md
@@ -0,0 +1,94 @@
+# How to build a sitemap?
+
+## Setup
+
+Install the following packages:
+
+```sh
+npm i globby
+npm i -D prettier
+```
+
+## Add sitemap script
+
+>[!info]
+>The following example assumes files list for dynamic Next.js routes is imported from the database created with [MarkdownDB](https://github.com/datopian/markdowndb) package. You need to adjust it to your specific use case.
+
+Add the following script to e.g. `/scripts/sitemap.mjs` and adjust it to work with your app:
+
+```js
+// /scripts/sitemap.mjs
+import { writeFileSync } from "fs";
+import { globby } from "globby";
+import prettier from "prettier";
+
+// Change this to import data from your content layer (if needed)
+import clientPromise from "../lib/mddb.mjs";
+
+export default async function sitemap() {
+ const prettierConfig = await prettier.resolveConfig("path-to-your-prettier-config");
+
+ // Change this to create a list of page paths
+ const mddb = await clientPromise;
+ const allFiles = await mddb.getFiles({ extensions: ["mdx", "md"] });
+ const contentPages = allFiles
+ .filter((x) => !x.metadata?.isDraft)
+ .map((x) => `/${x.url_path}`);
+ const pages = await globby([
+ "pages/*.(js|tsx)",
+ "!pages/_*.(js|tsx)",
+ "!pages/api",
+ "!pages/404.(js|tsx)",
+ "!pages/**/\\[\\[*\\]\\].(js|tsx)", // pages/[[...slug]].tsx
+ ]);
+ const allPages = pages.concat(contentPages);
+ // end
+
+ // Replace this with your site domain
+ const siteDomain = "your-site-domain";
+
+ const sitemap = `
+
+
+ ${allPages
+ .map((page) => {
+ const path = page
+ .replace("pages/", "/")
+ .replace("public/", "/")
+ .replace(/\.js|.tsx|.mdx|.md[^\/.]+$/, "")
+ .replace("/feed.xml", "");
+ const route = path === "/index" ? "" : path;
+ return `
+
+ ${siteDomain}${route}
+
+ `;
+ })
+ .join("")}
+
+ `;
+
+ const formatted = prettier.format(sitemap, {
+ ...prettierConfig,
+ parser: "html",
+ });
+
+ if (siteUrl) {
+ writeFileSync("public/sitemap.xml", formatted);
+ console.log("Sitemap generated...");
+ }
+}
+
+await sitemap();
+process.exit();
+```
+
+## Execute script before each build
+
+Add this to the scripts section in your `package.json` to regenerate the sitemap before each build.
+
+```json
+
+"prebuild": "node ./scripts/sitemap.mjs",
+
+```
\ No newline at end of file