Compare commits

...

12 Commits

Author SHA1 Message Date
Ola Rubaj
573f938dfe [.changeset][xs]: add changeset file 2023-08-14 15:22:29 +02:00
Ola Rubaj
961a219f61 [remark-wiki-link][m]: parse note embeds as regular wiki links
- temporary solution before we have a proper note embed support
2023-08-11 18:07:02 +02:00
João Demenech
df395e2b70 [site,#957,seo][s]: fix build issue 2023-08-10 11:01:10 -03:00
João Demenech
ea5dade346 [site,#957,seo][s]: improves descriptions for pages 2023-08-10 10:52:23 -03:00
João Demenech
8027026399 [site,#957][s]: improves mobile reponsiveness 2023-08-10 10:35:34 -03:00
João Demenech
af7812f689 Merge pull request #1004 from datopian/custom-404
[site]: Custom 404 page + JSON-LD breadcrumbs
2023-08-09 18:01:04 -03:00
João Demenech
6a36e65b27 [site,seo][m]: add breadcrumbs json-ld to dynamic pages 2023-08-09 17:53:59 -03:00
João Demenech
38aa62fcef [404,seo][xs]: custom 404 page with noindex,nofollow meta tag 2023-08-09 16:57:01 -03:00
Ola Rubaj
ed9b575b4e Core package storybook setup (#1003)
* [/][m]: add storybook to core package

* [/][s]: replace nx with nrwl packages for storybook

* [core][xs]: mv tsconfig.storybook.json to .storybook/tsconfig.json

* [/][s]: configure storybook with tailwind

* [core/tailwind.config.js][xs]: add basic Flowershow tailwind config

* [core][xs]: add example story

* [core][s]: darkmode storybook addon
2023-08-09 14:14:06 -03:00
João Demenech
8327f4efc0 [site,storybook,seo][xs]: fix duplicate user canonical issues -- refs #957 2023-08-07 15:18:12 -03:00
Ola Rubaj
d6a12e3111 Merge pull request #998 from datopian/987-tutorial4-websitey-stuff
Tutorial 4 (overview): Customising your website locally and previewing your changes locally
2023-08-04 14:55:20 +02:00
Ola Rubaj
9fc834c16d Merge pull request #997 from datopian/985-tutorial3-collaboration
Tutorial 3 (overview): Collaborating with others on your website project
2023-08-04 14:53:24 +02:00
37 changed files with 14500 additions and 1321 deletions

View File

@@ -0,0 +1,5 @@
---
'@portaljs/remark-wiki-link': minor
---
Parse note embeds as regular wiki links (until we add proper support for note embeds).

21
nx.json
View File

@@ -5,7 +5,13 @@
"default": {
"runner": "nx/tasks-runners/default",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"]
"cacheableOperations": [
"build",
"lint",
"test",
"e2e",
"build-storybook"
]
}
}
},
@@ -30,6 +36,14 @@
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore"
]
},
"build-storybook": {
"inputs": [
"default",
"^production",
"{projectRoot}/.storybook/**/*",
"{projectRoot}/tsconfig.storybook.json"
]
}
},
"namedInputs": {
@@ -39,7 +53,10 @@
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
"!{projectRoot}/tsconfig.spec.json",
"!{projectRoot}/jest.config.[jt]s",
"!{projectRoot}/.eslintrc.json"
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/**/*.stories.@(js|jsx|ts|tsx|mdx)",
"!{projectRoot}/.storybook/**/*",
"!{projectRoot}/tsconfig.storybook.json"
],
"sharedGlobals": ["{workspaceRoot}/babel.config.json"]
},

15337
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,10 +20,20 @@
"@nrwl/js": "15.9.2",
"@nrwl/linter": "15.9.2",
"@nrwl/next": "15.9.2",
"@nrwl/react": "15.9.2",
"@nrwl/react": "^15.9.2",
"@nrwl/rollup": "15.9.2",
"@nrwl/storybook": "15.9.2",
"@nrwl/webpack": "15.9.2",
"@nrwl/workspace": "15.9.2",
"@nx/js": "16.6.0",
"@rollup/plugin-url": "^7.0.0",
"@storybook/addon-essentials": "7.0.18",
"@storybook/addon-interactions": "7.0.18",
"@storybook/core-server": "7.0.18",
"@storybook/jest": "~0.1.0",
"@storybook/react-webpack5": "^7.0.18",
"@storybook/test-runner": "^0.11.0",
"@storybook/testing-library": "~0.2.0",
"@svgr/rollup": "^6.1.2",
"@swc/core": "^1.2.173",
"@swc/helpers": "~0.5.0",
@@ -39,6 +49,7 @@
"@typescript-eslint/parser": "^5.36.1",
"babel-jest": "^29.4.1",
"chai": "^4.3.7",
"core-js": "^3.6.5",
"cypress": "^12.2.0",
"eslint": "~8.15.0",
"eslint-config-next": "13.1.1",
@@ -58,11 +69,16 @@
"react-test-renderer": "18.2.0",
"rehype-stringify": "^9.0.3",
"remark": "^14.0.3",
"storybook-tailwind-dark-mode": "^1.0.22",
"swc-loader": "0.1.15",
"ts-jest": "^29.0.5",
"ts-node": "10.9.1",
"typescript": "~4.9.5",
"unist-util-select": "^4.0.3",
"unist-util-visit": "^4.1.2"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

View File

@@ -0,0 +1,3 @@
User-agent: *
Allow: /$
Disallow: /

View File

@@ -16,7 +16,7 @@
"build": "tsc && vite build && npm run build-tailwind && npm run fix-leaflet",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"build-storybook": "storybook build && cp ./.storybook/robots.txt ./storybook-static",
"build-tailwind": "NODE_ENV=production npx tailwindcss --postcss -c tailwind.config.js -i src/index.css -o ./dist/style.css --minify",
"prepare": "npm run build",
"fix-leaflet": "node ./scripts/fix-leaflet.cjs"

View File

@@ -0,0 +1,21 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@nrwl/react/plugins/storybook',
'storybook-tailwind-dark-mode'
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
};
export default config;
// To customize your webpack configuration you can use the webpackFinal field.
// Check https://storybook.js.org/docs/react/builders/webpack#extending-storybooks-webpack-config
// and https://nx.dev/packages/storybook/documents/custom-builder-configs

View File

@@ -0,0 +1,14 @@
import './tailwind-imports.css';
const preview = {
globalTypes: {
darkMode: {
defaultValue: false, // Enable dark mode by default on all stories
},
className: {
defaultValue: 'dark', // Set your custom dark mode class name
},
},
};
export default preview;

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,31 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"emitDecoratorMetadata": true,
"outDir": ""
},
"files": [
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
"../../node_modules/@nx/react/typings/image.d.ts"
],
"exclude": [
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.jsx",
"src/**/*.test.js"
],
"include": [
"src/**/*.stories.ts",
"src/**/*.stories.js",
"src/**/*.stories.jsx",
"src/**/*.stories.tsx",
"src/**/*.stories.mdx",
".storybook/*.js",
".storybook/*.ts"
]
}

View File

@@ -0,0 +1,11 @@
// libs/shared/ui/postcss.config.js
const { join } = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@@ -34,6 +34,37 @@
"jestConfig": "packages/core/jest.config.ts",
"passWithNoTests": true
}
},
"storybook": {
"executor": "@nrwl/storybook:storybook",
"options": {
"port": 4400,
"configDir": "packages/core/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@nrwl/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"outputDir": "dist/storybook/core",
"configDir": "packages/core/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"test-storybook": {
"executor": "nx:run-commands",
"options": {
"command": "test-storybook -c packages/core/.storybook --url=http://localhost:4400"
}
}
}
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Card } from './Card';
const meta: Meta<typeof Card> = {
component: Card,
};
export default meta;
type Story = StoryObj<typeof Card>;
/*
*👇 Render functions are a framework specific feature to allow you control on how the component renders.
* See https://storybook.js.org/docs/react/api/csf
* to learn how to use render functions.
*/
const blog = {
urlPath: "#",
title: "Card title goes here",
date: "2021-01-01",
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed euismod, diam quis accumsan maximus, quam libero porttitor nisl, vita",
}
export const Primary: Story = {
render: () => (
<Card className="md:col-span-3">
<Card.Title href={`${blog.urlPath}`}>
{blog.title}
</Card.Title>
<Card.Eyebrow
as="time"
dateTime={blog.date}
className="md:hidden"
decorate
>
{blog.date}
</Card.Eyebrow>
{blog.description && (
<Card.Description>
{blog.description}
</Card.Description>
)}
<Card.Cta>Read article</Card.Cta>
</Card>
),
};

View File

@@ -0,0 +1,44 @@
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
const { join } = require('path');
// const defaultTheme = require("tailwindcss/defaultTheme");
const colors = require("tailwindcss/colors");
module.exports = {
content: [
join(__dirname, './src/**/*.{js,ts,jsx,tsx}'),
...createGlobPatternsForDependencies(__dirname),
],
darkMode: "class",
theme: {
extend: {
// support wider width for large screens >1440px eg. in hero
maxWidth: {
"8xl": "88rem",
},
// fontFamily: {
// sans: ["ui-sans-serif", ...defaultTheme.fontFamily.sans],
// serif: ["ui-serif", ...defaultTheme.fontFamily.serif],
// mono: ["ui-monospace", ...defaultTheme.fontFamily.mono],
// headings: ["-apple-system", ...defaultTheme.fontFamily.sans],
// },
colors: {
background: {
DEFAULT: colors.white,
dark: colors.slate[900],
},
primary: {
DEFAULT: colors.gray[700],
dark: colors.gray[300],
},
secondary: {
DEFAULT: colors.sky[400],
dark: colors.sky[400],
},
},
},
},
/* eslint global-require: off */
// plugins: [
// require("@tailwindcss/typography")
// ],
};

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "react-jsx",
"module": "es2020",
"moduleResolution": "node",
@@ -22,6 +23,9 @@
},
{
"path": "./tsconfig.spec.json"
},
{
"path": "./.storybook/tsconfig.json"
}
]
}

View File

@@ -17,7 +17,11 @@
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
"**/*.test.jsx",
"**/*.stories.ts",
"**/*.stories.js",
"**/*.stories.jsx",
"**/*.stories.tsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

View File

@@ -125,6 +125,16 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
if (isEmbed) {
const [isSupportedFormat, format] = isSupportedFileFormat(target);
if (!isSupportedFormat) {
// Temporarily render note transclusion as a regular wiki link
if (!format) {
wikiLink.data.hName = "a";
wikiLink.data.hProperties = {
className: classNames + " " + "transclusion",
href: hrefTemplate(link) + headingId,
};
wikiLink.data.hChildren = [{ type: "text", value: displayName }];
} else {
wikiLink.data.hName = "p";
wikiLink.data.hChildren = [
{
@@ -132,6 +142,7 @@ function fromMarkdown(opts: FromMarkdownOptions = {}) {
value: `![[${target}]]`,
},
];
}
} else if (format === "pdf") {
wikiLink.data.hName = "iframe";
wikiLink.data.hProperties = {

View File

@@ -108,7 +108,16 @@ function html(opts: HtmlOptions = {}) {
if (isEmbed) {
const [isSupportedFormat, format] = isSupportedFileFormat(target);
if (!isSupportedFormat) {
// Temporarily render note transclusion as a regular wiki link
if (!format) {
this.tag(
`<a href="${hrefTemplate(link + headingId)}" class="${classNames} transclusion">`
);
this.raw(displayName);
this.tag("</a>");
} else {
this.raw(`![[${target}]]`);
}
} else if (format === "pdf") {
this.tag(
`<iframe width="100%" src="${hrefTemplate(

View File

@@ -309,4 +309,16 @@ describe("micromark-extension-wiki-link", () => {
expect(serialized).toBe('<p><a href="/" class="internal">/index</a></p>');
});
});
describe("transclusions", () => {
test("parsers a transclusion as a regular wiki link", () => {
const serialized = micromark("![[Some Page]]", "ascii", {
extensions: [syntax()],
htmlExtensions: [html() as any], // TODO type fix
});
expect(serialized).toBe(
'<p><a href="Some Page" class="internal new transclusion">Some Page</a></p>'
);
});
});
});

View File

@@ -535,4 +535,28 @@ describe("remark-wiki-link", () => {
});
});
});
describe("transclusions", () => {
test("replaces a transclusion with a regular wiki link", () => {
const processor = unified().use(markdown).use(wikiLinkPlugin);
let ast = processor.parse("![[Some Page]]");
ast = processor.runSync(ast);
expect(select("wikiLink", ast)).not.toEqual(null);
visit(ast, "wikiLink", (node: Node) => {
expect(node.data?.isEmbed).toEqual(true);
expect(node.data?.exists).toEqual(false);
expect(node.data?.permalink).toEqual("Some Page");
expect(node.data?.alias).toEqual(null);
expect(node.data?.hName).toEqual("a");
expect((node.data?.hProperties as any).className).toEqual(
"internal new transclusion"
);
expect((node.data?.hProperties as any).href).toEqual("Some Page");
expect((node.data?.hChildren as any)[0].value).toEqual("Some Page");
});
});
});
});

View File

@@ -10,7 +10,7 @@ import { useEffect, useState } from 'react';
const Stat = ({ title, value, ...props }) => {
return (
<div {...props}>
<span className="text-6xl font-bold text-secondary">{value}</span>
<span className="text-4xl sm:text-6xl font-bold text-secondary">{value}</span>
<p className="text-lg font-medium">{title}</p>
</div>
);

View File

@@ -86,7 +86,7 @@ export default function Layout({
<>
{title && <NextSeo title={title} description={description} />}
<Nav />
<div className="mx-auto p-6 bg-background dark:bg-background-dark">
<div className="mx-auto p-2 sm:p-6 bg-background dark:bg-background-dark">
{isHomePage && <Hero />}
<div className="relative mx-auto flex max-w-8xl justify-center sm:px-2 lg:px-8 xl:px-12">

View File

@@ -92,7 +92,7 @@ export default function MobileNavigation({ navigation }) {
>
{/* <Logomark className="h-9 w-9" /> */}
<div className="font-extrabold text-slate-900 dark:text-white text-2xl ml-6">
{siteConfig.title}
PortalJS
</div>
</Link>
</div>

View File

@@ -95,7 +95,10 @@ export default function Nav() {
<MobileNavigation navigation={siteConfig.navLinks} />
</div>
<div className="flex flex-none items-center">
<div className="hidden sm:block">
<NavbarTitle />
</div>
<div className="hidden lg:flex ml-8 mr-6 sm:mr-8 md:mr-0">
{siteConfig.navLinks.map((item) => (
<NavItem item={item} key={item.name} />
@@ -123,9 +126,8 @@ export default function Nav() {
)}
{siteConfig.github && (
<div className="mt-1">
<
// @ts-ignore
GitHubButton
{/* @ts-ignore */}
<GitHubButton
href={siteConfig.github}
data-color-scheme="no-preference: light; light: light; dark: dark;"
data-size="large"

View File

@@ -1,6 +1,6 @@
---
title: 'Creating new datasets'
description: 'PortalJS Tutorial II - Learn how to create new datasets on a data portal'
description: 'Learn how to create new datasets and an index for all datasets on a data portal built with PortalJS'
---
So far, the PortalJS app we created only has a single page displaying a dataset. Data catalogs and data portals generally showcase many different datasets.

View File

@@ -1,6 +1,7 @@
<NextSeo title="Deploying your PortalJS app - PortalJS" />
# Deploying your PortalJS app
---
title: Deploying your PortalJS app
description: 'Learn how to deploy PortalJS apps to Vercel and Cloudflare'
---
Finally, let's learn how to deploy PortalJS apps to Vercel or Cloudflare Pages.

View File

@@ -1,6 +1,6 @@
---
title: Getting Started
description: 'Getting started guide and tutorial about data portal-building with PortalJS'
description: 'Getting started guide and tutorial about data portal-building with PortalJS!'
---
Welcome to the PortalJS documentation!

View File

@@ -1,6 +1,7 @@
<NextSeo title="Searching datasets - PortalJS" />
# Searching datasets
---
title: Searching datasets
description: 'Learn how to create a searchable datasets index with facets on a PortalJs data portal'
---
Typing out every link in the index page will get cumbersome eventually, and as the portal grows, finding the datasets you are looking for on the index page will become harder and harder, for that we will need search functionality.

View File

@@ -1,6 +1,7 @@
<NextSeo title="Showing metadata - PortalJS" />
# Showing metadata
---
title: Showing metadata
description: "Learn how to display metadata on the dataset page of a data portal built with PortalJS"
---
If you go now to `http://localhost:3000/my-awesome-dataset`, you will see that we now have two titles on the page. That's because `title` is one of the default metadata fields supported by PortalJS.

View File

@@ -1,6 +1,6 @@
---
title: How to add Google Analytics?
description: Learn to implement Google Analytics on PortalJS data portals
description: Learn how to implement Google Analytics on PortalJS data portals
---
>[!todo] Prerequisites

View File

@@ -1,6 +1,6 @@
---
title: How to add a simple blog?
description: How to add a simple blog on a PortalJS data portal
description: Learn how to add a simple blog on a PortalJS data portal
---
## Setup
@@ -16,7 +16,7 @@ npm i @portaljs/core
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 "@portaljs/core";
import { BlogsList, SimpleLayout } from '@portaljs/core';
// pass a list of blogs, home page title and home page description, e.g. from `getStaticProps`
export default function BlogIndex({ blogs, title, description }) {

View File

@@ -1,6 +1,6 @@
---
title: How to customize page metadata for SEO?
description: Learn to customize page metadata for SEO on data portals with PortalJS
description: Learn how to customize page metadata for SEO on data portals built with PortalJS
---
>[!info]

View File

@@ -1,6 +1,6 @@
---
title: How to build a sitemap?
description: Learn how to build a sitemap for a data portal with PortalJS
description: Learn how to build a sitemap for a data portal built with PortalJS
---
## Setup

View File

@@ -2,4 +2,10 @@
module.exports = {
siteUrl: process.env.SITE_URL || 'https://portaljs.org',
generateRobotsTxt: true,
}
robotsTxtOptions: {
policies: [
{ userAgent: '*', disallow: '/people' },
{ userAgent: '*', disallow: '/?amp=1' },
],
},
};

24
site/pages/404.tsx Normal file
View File

@@ -0,0 +1,24 @@
import Layout from '@/components/Layout';
import { NextSeo } from 'next-seo';
import Link from 'next/link';
export default function () {
return (
<>
<NextSeo noindex={true} nofollow={true} />
<Layout>
<div className="flex items-center justify-center h-full">
<div className="text-center">
<h1 className="text-2xl">404 - Page not found</h1>
<p className="text-lg mt-5">
It seems like you are looking for a page that doesn't exist.
</p>
<Link className="underline" href="/">
Go back to home
</Link>
</div>
</div>
</Layout>
</>
);
}

View File

@@ -13,6 +13,7 @@ import { CustomAppProps } from './_app.jsx';
import computeFields from '@/lib/computeFields';
import { getAuthorsDetails } from '@/lib/getAuthorsDetails';
import JSONLD from '@/components/JSONLD';
import { BreadcrumbJsonLd } from 'next-seo';
export default function Page({ source, meta, sidebarTree }) {
source = JSON.parse(source);
@@ -29,9 +30,19 @@ export default function Page({ source, meta, sidebarTree }) {
setTableOfContents(toc ?? []);
}, [router.asPath]); // update table of contents on route change with next/link
const urlSegments = meta.urlPath.split('/');
const breadcrumbs = urlSegments.map((segment, i) => {
return {
position: i + 1,
name: i == urlSegments.length - 1 ? meta.title || segment : segment,
item: '/' + urlSegments.slice(0, i + 1).join('/'),
};
});
return (
<>
<JSONLD meta={meta} source={source.compiledSource} />
<BreadcrumbJsonLd itemListElements={breadcrumbs} />
<Layout
tableOfContents={tableOfContents}
title={meta.title}

View File

@@ -15,10 +15,10 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"@portaljs/core": ["packages/core/src/index.ts"],
"@portaljs/portaljs-components": [
"packages/portaljs-components/src/index.ts"
],
"@portaljs/core": ["packages/core/src/index.ts"]
]
}
},
"exclude": ["node_modules", "tmp"]