From 4baa386bc06853502dc006a74afa65788af21a05 Mon Sep 17 00:00:00 2001
From: atanasster
Date: Sat, 10 Oct 2020 01:28:09 -0400
Subject: [PATCH] feat: base url siteRoot configuration option
---
core/core/README.md | 26 ++--
core/core/src/configuration.ts | 6 +-
core/core/src/document-utils.ts | 76 ++++++----
core/core/test/paths.test.ts | 133 ++++++++++++++++++
core/store/src/create-pages/pages-paths.ts | 10 +-
core/store/src/state/context/document.tsx | 6 +-
core/store/src/state/recoil/document.ts | 5 +-
.../tests/__snapshots__/draft.test.ts.snap | 1 +
.../example-stories.test.ts.snap | 1 +
.../react-typescript.test.ts.snap | 1 +
examples/nextjs/pages/_document.js | 6 +-
examples/starter/.config/buildtime.js | 1 +
examples/starter/pages/_document.js | 6 +-
.../starter/pages/{index.tsx => root.tsx} | 0
.../starter/pages/{ => root}/[doctype].tsx | 0
.../pages/{ => root}/[doctype]/[...docid].tsx | 0
examples/starter/src/docs/first-blog.mdx | 8 ++
.../src/tutorial/configuration/buildtime.mdx | 1 +
.../tutorial/getting-started/ssg/nextjs.mdx | 6 +-
.../src/components/GatsbyLink.tsx | 8 +-
.../gatsby-theme-stories/src/gatsby-node.ts | 4 +-
.../nextjs-plugin/src/components/NextLink.tsx | 25 ++--
integrations/nextjs-plugin/src/page-links.ts | 18 ++-
ui/app/src/AppContext/mdxComponents.tsx | 20 ++-
ui/app/src/Header/Header.tsx | 16 ++-
ui/app/src/Sidebar/Sidebar.tsx | 1 +
ui/blocks/src/PageContainer/PageContainer.tsx | 2 +-
ui/components/README.md | 5 +
ui/components/src/Link/Link.tsx | 9 +-
ui/components/src/Link/index.ts | 1 +
ui/components/src/Link/useIsLocalLink.tsx | 7 +
.../tests/__snapshots__/stories.test.js.snap | 2 +
32 files changed, 329 insertions(+), 82 deletions(-)
create mode 100644 core/core/test/paths.test.ts
rename examples/starter/pages/{index.tsx => root.tsx} (100%)
rename examples/starter/pages/{ => root}/[doctype].tsx (100%)
rename examples/starter/pages/{ => root}/[doctype]/[...docid].tsx (100%)
create mode 100644 examples/starter/src/docs/first-blog.mdx
create mode 100644 ui/components/src/Link/useIsLocalLink.tsx
diff --git a/core/core/README.md b/core/core/README.md
index d73a1c351..94e369ba4 100644
--- a/core/core/README.md
+++ b/core/core/README.md
@@ -1067,6 +1067,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
| `ignore` | string\[] | files to ignore. by default \['readme.md', 'changelog.md', 'code_of_conduct.md', 'contributing.md', 'license.md'] |
| `instrument` | any | instrumentation configuration |
| `pages` | [PagesOnlyRoutes](#pagesonlyroutes) | base url path for API documentation pages. Default is "docs/" |
+| `siteRoot` | string | the site base url, by default the site starts at / |
| `stories` | string \| string\[] | wild card search string for the stories internally using \`glob\` for the search: https://www.npmjs.com/package/glob example: "./stories/ |
| `webpack` | [WebpackConfig](#webpackconfig) | custom webpack configuration setup. One or the other will be used |
@@ -1074,7 +1075,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
configuration options for the controls module
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L186)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L189)_
@@ -1105,7 +1106,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
global configuration used at build time
stored in a file named main.js/main.ts
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L198)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L201)_
@@ -1167,7 +1168,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
## ToolbarConfig
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L166)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L169)_
@@ -1259,7 +1260,7 @@ Record<
## RunConfiguration
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L304)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L307)_
### properties
@@ -1272,13 +1273,13 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
static menu items
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L181)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L184)_
string | **menu**: [StaticMenuItem](#staticmenuitem)\[]**name**: string
## StaticMenuItems
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L182)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L185)_
[StaticMenuItem](#staticmenuitem)\[]
@@ -1304,7 +1305,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
## convertConfig
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L344)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L347)_
**function** convertConfig(`config`\*: [RunConfiguration](#runconfiguration)): [RunConfiguration](#runconfiguration);
@@ -1317,7 +1318,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
## defaultBuildConfig
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L353)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L356)_
@@ -1327,11 +1328,12 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
| ------------- | --------- | ----------- |
| `categories*` | string\[] | |
| `ignore*` | string\[] | |
+| `siteRoot*` | string | |
| `pages*` | object | |
## defaultRunConfig
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L307)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L310)_
@@ -1397,7 +1399,7 @@ Record<string,
## RunConfiguration
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L304)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L307)_
### properties
@@ -1559,7 +1561,7 @@ Record<
## StaticMenuItems
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L182)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L185)_
[StaticMenuItem](#staticmenuitem)\[]
@@ -1627,7 +1629,7 @@ _defined in [@component-controls/core/src/configuration.ts](https://github.com/c
static menu items
-_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L181)_
+_defined in [@component-controls/core/src/configuration.ts](https://github.com/ccontrols/component-controls/tree/master/core/core/src/configuration.ts#L184)_
string | **menu**: [StaticMenuItem](#staticmenuitem)\[]**name**: string
diff --git a/core/core/src/configuration.ts b/core/core/src/configuration.ts
index 0036b59be..cc1b203e4 100644
--- a/core/core/src/configuration.ts
+++ b/core/core/src/configuration.ts
@@ -136,7 +136,10 @@ export interface BuildConfiguration {
* alternative naming for docz compatibility
*/
files?: string | string[];
-
+ /**
+ * the site base url, by default the site starts at /
+ */
+ siteRoot?: string;
/**
* files to ignore. by default ['readme.md', 'changelog.md', 'code_of_conduct.md', 'contributing.md', 'license.md']
*/
@@ -351,6 +354,7 @@ export const convertConfig = (config: RunConfiguration): RunConfiguration => {
};
export const defaultBuildConfig: BuildConfiguration = {
+ siteRoot: '/',
categories: ['author', 'tags'],
ignore: [
'readme.md',
diff --git a/core/core/src/document-utils.ts b/core/core/src/document-utils.ts
index 82525c79a..b86d917ff 100644
--- a/core/core/src/document-utils.ts
+++ b/core/core/src/document-utils.ts
@@ -2,7 +2,12 @@ import {
toId,
storyNameFromExport as csfStoryNameFromExport,
} from '@storybook/csf';
-import { PagesOnlyRoutes, DocType, PageConfiguration } from './configuration';
+import {
+ PagesOnlyRoutes,
+ DocType,
+ PageConfiguration,
+ BuildConfiguration,
+} from './configuration';
import { Document, Story, defDocType, Store } from './document';
export const storyNameFromExport = csfStoryNameFromExport;
@@ -14,8 +19,21 @@ export const ensureTrailingSlash = (route: string) =>
export const ensureStartingSlash = (route: string) =>
route.startsWith('/') ? route : '/' + route;
-export const removeTrailingSlash = (route: string) =>
- route.endsWith('/') ? route.substr(0, route.length - 1) : route;
+export const removeTrailingSlash = (route: string) => {
+ let result = route;
+ while (result.length > 1 && result.endsWith('/')) {
+ result = result.substr(0, result.length - 1);
+ }
+ return result;
+};
+
+export const removeStartingSlash = (route: string) => {
+ let result = route;
+ while (result.length > 1 && result.startsWith('/')) {
+ result = result.substr(1);
+ }
+ return result;
+};
export const getDocPath = (
docType: DocType,
@@ -27,6 +45,8 @@ export const getDocPath = (
const pagesConfig: PagesOnlyRoutes | undefined = store
? (store.config.pages as PagesOnlyRoutes)
: undefined;
+ const { siteRoot = '/' } = (store?.config as BuildConfiguration) || {};
+
const { basePath = '', sideNav = {} } = pagesConfig?.[docType] || {};
const { storyPaths } = sideNav;
const activeTab = doc?.MDXPage ? undefined : tab;
@@ -35,17 +55,13 @@ export const getDocPath = (
}
const route = doc
? doc.route ||
- `${ensureStartingSlash(
- ensureTrailingSlash(basePath),
- )}${ensureTrailingSlash(strToId(doc.title))}${
- activeTab ? ensureTrailingSlash(activeTab) : ''
+ `${ensureTrailingSlash(basePath)}${strToId(doc.title)}${
+ activeTab ? ensureStartingSlash(activeTab) : ''
}`
- : `${ensureStartingSlash(
- ensureTrailingSlash(basePath),
- )}${ensureTrailingSlash(strToId(name))}${
- activeTab ? ensureTrailingSlash(activeTab) : ''
+ : `${ensureTrailingSlash(basePath)}${strToId(name)}${
+ activeTab ? ensureStartingSlash(activeTab) : ''
}`;
- return removeTrailingSlash(route);
+ return encodeURI(`${siteRoot}${removeStartingSlash(route)}`);
};
export const getStoryPath = (
@@ -57,32 +73,44 @@ export const getStoryPath = (
const pagesConfig: PagesOnlyRoutes | undefined = store
? (store.config.pages as PagesOnlyRoutes)
: undefined;
-
+ const { siteRoot = '/' } = (store?.config as BuildConfiguration) || {};
const docType = doc?.type || defDocType;
const activeTab = doc?.MDXPage ? undefined : tab;
if (!storyId) {
return getDocPath(docType, doc, store, undefined, activeTab);
}
const { basePath = '' } = pagesConfig?.[docType] || {};
- const docRoute = `${
- doc?.route
- ? ensureStartingSlash(ensureTrailingSlash(doc?.route))
- : ensureStartingSlash(ensureTrailingSlash(basePath))
- }`;
+ const docRoute = removeTrailingSlash(doc?.route || basePath);
const story = store?.stories[storyId];
const { dynamicId, name } = story || {};
const id = dynamicId || storyId;
- const route = `${docRoute}${id ? ensureTrailingSlash(id) : ''}${
- activeTab ? ensureTrailingSlash(activeTab) : ''
+ const route = `${docRoute}${id ? ensureStartingSlash(id) : ''}${
+ activeTab ? ensureStartingSlash(activeTab) : ''
}${dynamicId ? `?story=${name}` : ''}`;
- return encodeURI(removeTrailingSlash(route));
+ return encodeURI(`${siteRoot}${route}`);
};
-export const getDocTypePath = (type: PageConfiguration) =>
- type.basePath
- ? removeTrailingSlash(ensureStartingSlash(type.basePath))
+export const getDocTypePath = (store: Store, type: PageConfiguration) => {
+ const { siteRoot = '/' } = (store?.config as BuildConfiguration) || {};
+ return type.basePath
+ ? `${siteRoot}${removeTrailingSlash(type.basePath)}`
: undefined;
+};
+
+export const getHomePath = (store: Store) => {
+ const { siteRoot = '/' } = (store?.config as BuildConfiguration) || {};
+ return siteRoot.length > 1 ? removeTrailingSlash(siteRoot) : siteRoot;
+};
+export const getRoutePath = (store: Store, route?: string) => {
+ const { siteRoot = '/' } = (store?.config as BuildConfiguration) || {};
+
+ return route
+ ? removeTrailingSlash(
+ `${ensureTrailingSlash(siteRoot)}${removeStartingSlash(route)}`,
+ )
+ : undefined;
+};
export const docStoryToId = (docId: string, storyId: string) =>
toId(docId, storyNameFromExport(storyId));
diff --git a/core/core/test/paths.test.ts b/core/core/test/paths.test.ts
new file mode 100644
index 000000000..11b7581fe
--- /dev/null
+++ b/core/core/test/paths.test.ts
@@ -0,0 +1,133 @@
+import {
+ getHomePath,
+ getRoutePath,
+ getDocPath,
+ getStoryPath,
+} from '../src/document-utils';
+import { getDefaultStore, Store } from '../src/document';
+import { defaultBuildConfig } from '../src/configuration';
+import { deepMerge } from '../src';
+describe('paths', () => {
+ const storeDefault: Store = deepMerge(getDefaultStore(), {
+ config: defaultBuildConfig,
+ stories: {
+ 'api-introducetion--story-mame': {
+ name: 'StoryName',
+ },
+ 'api-introducetion--dynamic': {
+ name: 'Dynamic',
+ dynamicId: 'api-introducetion--story-mame',
+ },
+ },
+ });
+ const storeSiteRoot1: Store = deepMerge(storeDefault, {
+ config: {
+ siteRoot: '/root',
+ },
+ });
+ const storeSiteRoot2: Store = deepMerge(storeDefault, {
+ config: {
+ siteRoot: '/root/',
+ },
+ });
+
+ it('home path "/"', () => {
+ expect(getHomePath(storeDefault)).toEqual('/');
+ });
+ it('home path "/root"', () => {
+ expect(getHomePath(storeSiteRoot1)).toEqual('/root');
+ });
+ it('home path "/root/"', () => {
+ expect(getHomePath(storeSiteRoot2)).toEqual('/root');
+ });
+
+ it('route home page "/"', () => {
+ expect(getRoutePath(storeDefault, '/')).toEqual('/');
+ });
+ it('route home page "/root"', () => {
+ expect(getRoutePath(storeSiteRoot1, '/')).toEqual('/root');
+ });
+ it('route home page "/root/"', () => {
+ expect(getRoutePath(storeSiteRoot2, '/')).toEqual('/root');
+ });
+
+ it('route "/custom/route" with root "/"', () => {
+ expect(getRoutePath(storeDefault, '/custom/route')).toEqual(
+ '/custom/route',
+ );
+ });
+ it('route "/custom/route" with root "/root"', () => {
+ expect(getRoutePath(storeSiteRoot1, '/custom/route')).toEqual(
+ '/root/custom/route',
+ );
+ });
+ it('route "/custom/route" with root "/root/"', () => {
+ expect(getRoutePath(storeSiteRoot2, '/custom/route')).toEqual(
+ '/root/custom/route',
+ );
+ });
+
+ it('document route with title with root "/"', () => {
+ expect(
+ getDocPath(
+ 'story',
+ { title: 'API/Introducetion', componentsLookup: {} },
+ storeDefault,
+ 'doc',
+ 'test',
+ ),
+ ).toEqual('/docs/api-introducetion/test');
+ });
+ it('document route with title with root "/root/"', () => {
+ expect(
+ getDocPath(
+ 'story',
+ { title: 'API/Introducetion', componentsLookup: {} },
+ storeSiteRoot2,
+ 'doc',
+ 'test',
+ ),
+ ).toEqual('/root/docs/api-introducetion/test');
+ });
+ it('document route with name with root "/"', () => {
+ expect(getDocPath('story', undefined, storeDefault, 'Doc name')).toEqual(
+ '/docs/doc-name',
+ );
+ });
+ it('document route with name with root "/root/"', () => {
+ expect(getDocPath('story', undefined, storeSiteRoot2, 'Doc-name')).toEqual(
+ '/root/docs/doc-name',
+ );
+ });
+
+ it('story route with title with root "/"', () => {
+ expect(
+ getStoryPath(
+ 'api-introducetion--story-mame',
+ undefined,
+ storeDefault,
+ 'test',
+ ),
+ ).toEqual('/docs/api-introducetion--story-mame/test');
+ });
+ it('story route with title with root "/root/"', () => {
+ expect(
+ getStoryPath(
+ 'api-introducetion--story-mame',
+ undefined,
+ storeSiteRoot2,
+ 'test',
+ ),
+ ).toEqual('/root/docs/api-introducetion--story-mame/test');
+ });
+ it('story route with dynmic id with root "/root/"', () => {
+ expect(
+ getStoryPath(
+ 'api-introducetion--dynamic',
+ undefined,
+ storeSiteRoot2,
+ 'test',
+ ),
+ ).toEqual('/root/docs/api-introducetion--story-mame/test?story=Dynamic');
+ });
+});
diff --git a/core/store/src/create-pages/pages-paths.ts b/core/store/src/create-pages/pages-paths.ts
index bb92d57b3..bad008ed1 100644
--- a/core/store/src/create-pages/pages-paths.ts
+++ b/core/store/src/create-pages/pages-paths.ts
@@ -3,6 +3,8 @@ import {
defDocType,
DocType,
getDocTypePath,
+ getHomePath,
+ getRoutePath,
removeTrailingSlash,
ensureStartingSlash,
Pages,
@@ -15,9 +17,10 @@ import { HomePageInfo } from '../types';
export const getIndexPage = (store: Store): HomePageInfo => {
const docs = Object.keys(store.docs);
+ const homePath = getHomePath(store);
const homePageId = docs.find(key => {
const doc = store.docs[key];
- return doc.route === '/';
+ return getRoutePath(store, doc.route) === homePath;
});
const homePage = homePageId
? store.docs[homePageId]
@@ -50,7 +53,7 @@ export const getHomePages = (store: Store): DocHomePagesPath[] => {
const paths: DocHomePagesPath[] = Object.keys(pages)
.map((type: DocType) => {
const page = pages[type];
- const path = getDocTypePath(page) as string;
+ const path = getDocTypePath(store, page) as string;
const docId = page.indexHome
? undefined
@@ -133,6 +136,7 @@ export interface DocPagesPath {
export const getDocPages = (store: Store): DocPagesPath[] => {
const { pages = {}, categories = [] } = store?.config || {};
const docPaths: DocPagesPath[] = [];
+ const homePath = getHomePath(store);
Object.keys(pages).forEach(type => {
if (!categories.includes(type as DocType)) {
const page = pages[type as DocType];
@@ -147,7 +151,7 @@ export const getDocPages = (store: Store): DocPagesPath[] => {
? tab.route || (tab.title ? tab.title.toLowerCase() : '')
: undefined;
docs.forEach(doc => {
- if (doc.route !== '/') {
+ if (getRoutePath(store, doc.route) !== homePath) {
const stories =
page.sideNav?.storyPaths && doc.stories?.length
? doc.stories
diff --git a/core/store/src/state/context/document.tsx b/core/store/src/state/context/document.tsx
index 7ab8e8542..741fdcb12 100644
--- a/core/store/src/state/context/document.tsx
+++ b/core/store/src/state/context/document.tsx
@@ -1,4 +1,4 @@
-import React, { FC, createContext, useContext, useState } from 'react';
+import React, { FC, createContext, useContext, useState, useMemo } from 'react';
import {
Document,
Documents,
@@ -7,6 +7,7 @@ import {
DocType,
defDocType,
getDocPath,
+ getHomePath,
getComponentName,
} from '@component-controls/core';
import { useStore, useActiveTab } from './store';
@@ -165,12 +166,13 @@ export type DocCountType = Record;
*/
export const useDocTypeCount = (): DocCountType => {
const store = useStore();
+ const homePath = useMemo(() => getHomePath(store), [store]);
const getByDocType = useGetDocByType();
const { pages = {} } = store?.config || {};
return Object.keys(pages).reduce((acc: DocCountType, type: DocType) => {
const docs = getByDocType(type);
const home = docs.length
- ? docs.find(doc => doc.route === '/') || docs[0]
+ ? docs.find(doc => doc.route === homePath) || docs[0]
: undefined;
return {
...acc,
diff --git a/core/store/src/state/recoil/document.ts b/core/store/src/state/recoil/document.ts
index 9bc5981fb..edee3a799 100644
--- a/core/store/src/state/recoil/document.ts
+++ b/core/store/src/state/recoil/document.ts
@@ -1,3 +1,4 @@
+import { useMemo } from 'react';
import {
atom,
atomFamily,
@@ -14,6 +15,7 @@ import {
DocType,
defDocType,
getDocPath,
+ getHomePath,
getComponentName,
} from '@component-controls/core';
import { storeState, useStore, activeTabState } from './store';
@@ -176,10 +178,11 @@ const docTypeCountState = selector({
get: ({ get }) => {
const store = get(storeState);
const { pages = {} } = store?.config || {};
+ const homePath = useMemo(() => getHomePath(store), [store]);
return Object.keys(pages).reduce((acc: DocCountType, type: DocType) => {
const docs = get(docsByTypeState(type));
const home = docs.length
- ? docs.find(doc => doc.route === '/') || docs[0]
+ ? docs.find(doc => doc.route === homePath) || docs[0]
: undefined;
return {
...acc,
diff --git a/core/webpack-compile/tests/__snapshots__/draft.test.ts.snap b/core/webpack-compile/tests/__snapshots__/draft.test.ts.snap
index b919c2ddb..8353302a3 100644
--- a/core/webpack-compile/tests/__snapshots__/draft.test.ts.snap
+++ b/core/webpack-compile/tests/__snapshots__/draft.test.ts.snap
@@ -40,6 +40,7 @@ Object {
"basePath": "tags/",
},
},
+ "siteRoot": "/",
"stories": Array [
"./*.stories.(js|jsx|tsx|mdx)",
],
diff --git a/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap b/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap
index 0fdfead80..0cccab5f3 100644
--- a/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap
+++ b/core/webpack-compile/tests/__snapshots__/example-stories.test.ts.snap
@@ -40,6 +40,7 @@ Object {
"basePath": "tags/",
},
},
+ "siteRoot": "/",
"stories": Array [
"../../../../../examples/stories/src/**/*.stories.(js|jsx|tsx|mdx)",
],
diff --git a/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap b/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap
index 4143ff056..76cebc674 100644
--- a/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap
+++ b/core/webpack-compile/tests/__snapshots__/react-typescript.test.ts.snap
@@ -40,6 +40,7 @@ Object {
"basePath": "tags/",
},
},
+ "siteRoot": "/",
"stories": Array [
"../../../../ui/components/src/ActionBar/*.stories.(js|jsx|tsx|mdx)",
],
diff --git a/examples/nextjs/pages/_document.js b/examples/nextjs/pages/_document.js
index 12c08b388..c49858045 100644
--- a/examples/nextjs/pages/_document.js
+++ b/examples/nextjs/pages/_document.js
@@ -1,5 +1,5 @@
import React from 'react';
-import Document, { Head, Main, NextScript } from 'next/document';
+import Document, { Html, Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';
export default class MyDocument extends Document {
@@ -22,13 +22,13 @@ export default class MyDocument extends Document {
render() {
return (
-
+
-
+
);
}
}
diff --git a/examples/starter/.config/buildtime.js b/examples/starter/.config/buildtime.js
index 8bd3e04f4..cc38a3c46 100644
--- a/examples/starter/.config/buildtime.js
+++ b/examples/starter/.config/buildtime.js
@@ -3,6 +3,7 @@ const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPl
const { defaultBuildConfig } = require('@component-controls/core');
module.exports = {
+ siteRoot: '/root/',
stories: [
'../src/docs/*.@(mdx|tsx)',
],
diff --git a/examples/starter/pages/_document.js b/examples/starter/pages/_document.js
index 12c08b388..c49858045 100644
--- a/examples/starter/pages/_document.js
+++ b/examples/starter/pages/_document.js
@@ -1,5 +1,5 @@
import React from 'react';
-import Document, { Head, Main, NextScript } from 'next/document';
+import Document, { Html, Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';
export default class MyDocument extends Document {
@@ -22,13 +22,13 @@ export default class MyDocument extends Document {
render() {
return (
-
+
-
+
);
}
}
diff --git a/examples/starter/pages/index.tsx b/examples/starter/pages/root.tsx
similarity index 100%
rename from examples/starter/pages/index.tsx
rename to examples/starter/pages/root.tsx
diff --git a/examples/starter/pages/[doctype].tsx b/examples/starter/pages/root/[doctype].tsx
similarity index 100%
rename from examples/starter/pages/[doctype].tsx
rename to examples/starter/pages/root/[doctype].tsx
diff --git a/examples/starter/pages/[doctype]/[...docid].tsx b/examples/starter/pages/root/[doctype]/[...docid].tsx
similarity index 100%
rename from examples/starter/pages/[doctype]/[...docid].tsx
rename to examples/starter/pages/root/[doctype]/[...docid].tsx
diff --git a/examples/starter/src/docs/first-blog.mdx b/examples/starter/src/docs/first-blog.mdx
new file mode 100644
index 000000000..0cf527972
--- /dev/null
+++ b/examples/starter/src/docs/first-blog.mdx
@@ -0,0 +1,8 @@
+---
+title: First blog
+type: blog
+---
+
+# My first blog post
+
+some text
\ No newline at end of file
diff --git a/examples/stories/src/tutorial/configuration/buildtime.mdx b/examples/stories/src/tutorial/configuration/buildtime.mdx
index fa2d7f4c4..9e354072e 100644
--- a/examples/stories/src/tutorial/configuration/buildtime.mdx
+++ b/examples/stories/src/tutorial/configuration/buildtime.mdx
@@ -25,5 +25,6 @@ Possible names for the file:
| `stories` or `files` | string | string[] | wild card search string for the stories internally using [glob](https://www.npmjs.com/package/glob)|
| `ignore` | string[] | files to ignore while building the site, in lowercase. Defaults to ['readme.md', 'changelog.md', 'code_of_conduct.md', 'contributing.md', 'license.md',]|
| `webpack` or `finalWebpack` | [WebpackConfig](/tutorial/reference/configuration#webpackconfig) | custom webpack configuration setup. One or the other will be used |
+| `siteRoot` | string | the site base url, by default the site starts at /|
diff --git a/examples/stories/src/tutorial/getting-started/ssg/nextjs.mdx b/examples/stories/src/tutorial/getting-started/ssg/nextjs.mdx
index 0f62ad375..c33ac1fdf 100644
--- a/examples/stories/src/tutorial/getting-started/ssg/nextjs.mdx
+++ b/examples/stories/src/tutorial/getting-started/ssg/nextjs.mdx
@@ -124,7 +124,7 @@ Create a new or edit `_document.js` file in the pages folder:
```js:title=pages/_document.js
import React from 'react';
-import Document, { Head, Main, NextScript } from 'next/document';
+import Document, { Html, Head, Main, NextScript } from 'next/document';
import { extractCritical } from 'emotion-server';
export default class MyDocument extends Document {
@@ -147,13 +147,13 @@ export default class MyDocument extends Document {
render() {
return (
-
+
-
+
);
}
}
diff --git a/integrations/gatsby-theme-stories/src/components/GatsbyLink.tsx b/integrations/gatsby-theme-stories/src/components/GatsbyLink.tsx
index af313ec32..6d099d05b 100644
--- a/integrations/gatsby-theme-stories/src/components/GatsbyLink.tsx
+++ b/integrations/gatsby-theme-stories/src/components/GatsbyLink.tsx
@@ -1,14 +1,18 @@
/** @jsx jsx */
-import { FC } from 'react';
+import { FC, useMemo } from 'react';
import { jsx, LinkProps } from 'theme-ui';
import { Link } from 'gatsby';
+import { getHomePath } from '@component-controls/core';
+import { useStore } from '@component-controls/store';
export const GatsbyLink: FC = ({
href,
to,
...props
}) => {
- const link = href || to || '/';
+ const store = useStore();
+ const homePath = useMemo(() => getHomePath(store), [store]);
+ const link = href || to || homePath;
//@ts-ignore
return link ? : null;
};
diff --git a/integrations/gatsby-theme-stories/src/gatsby-node.ts b/integrations/gatsby-theme-stories/src/gatsby-node.ts
index a4d88b8e5..c0031b07b 100644
--- a/integrations/gatsby-theme-stories/src/gatsby-node.ts
+++ b/integrations/gatsby-theme-stories/src/gatsby-node.ts
@@ -14,7 +14,7 @@ import {
PluginCallback,
Page,
} from 'gatsby';
-import { Store } from '@component-controls/core';
+import { Store, getHomePath } from '@component-controls/core';
import {
getIndexPage,
getHomePages,
@@ -56,7 +56,7 @@ export const createPagesStatefully = async (
const { docId = null, type = null, storyId = null } =
getIndexPage(store) || {};
createGatsbyPage({
- path: `/`,
+ path: getHomePath(store),
component: require.resolve(`../src/templates/DocPage.tsx`),
context: {
docId,
diff --git a/integrations/nextjs-plugin/src/components/NextLink.tsx b/integrations/nextjs-plugin/src/components/NextLink.tsx
index f172c878f..579c46442 100644
--- a/integrations/nextjs-plugin/src/components/NextLink.tsx
+++ b/integrations/nextjs-plugin/src/components/NextLink.tsx
@@ -1,13 +1,18 @@
/** @jsx jsx */
-import { FC } from 'react';
+import { FC, useMemo } from 'react';
import { jsx, LinkProps } from 'theme-ui';
import Link from 'next/link';
+import { getHomePath, ensureTrailingSlash } from '@component-controls/core';
+import { useStore } from '@component-controls/store';
export const NextLink: FC = ({
href = '',
children,
...props
}) => {
+ const store = useStore();
+ const homePath = useMemo(() => getHomePath(store), [store]);
+ const homePathParts = homePath === '/' ? 1 : homePath.split('/').length;
if (href.startsWith('#')) {
return (
@@ -17,18 +22,20 @@ export const NextLink: FC = ({
}
const urlparts = href.split('/');
- const isHomePage =
- urlparts.length === 2 && urlparts[0] === '' && urlparts[1] === '';
+ const isHomePage = href === homePath;
const isDocTypeHome =
- (!isHomePage && urlparts.length === 2) ||
- (urlparts.length === 3 && urlparts[2] === '');
+ (!isHomePage && urlparts.length === 1 + homePathParts) ||
+ (urlparts.length === 2 + homePathParts &&
+ urlparts[1 + homePathParts] === '');
+
+ const homePagePrefix = ensureTrailingSlash(homePath);
const dynamicHref = isHomePage
- ? '/'
+ ? homePath
: isDocTypeHome
- ? '/[doctype]'
- : '/[doctype]/[...docid]';
+ ? `${homePagePrefix}[doctype]`
+ : `${homePagePrefix}[doctype]/[...docid]`;
return (
-
+
{children}
);
diff --git a/integrations/nextjs-plugin/src/page-links.ts b/integrations/nextjs-plugin/src/page-links.ts
index 1ac5e1280..6d68a8322 100644
--- a/integrations/nextjs-plugin/src/page-links.ts
+++ b/integrations/nextjs-plugin/src/page-links.ts
@@ -5,7 +5,13 @@ import {
getDocPages,
DocPagesPath,
} from '@component-controls/store';
-import { Store, DocType } from '@component-controls/core';
+import {
+ Store,
+ DocType,
+ getHomePath,
+ getRoutePath,
+ ensureTrailingSlash,
+} from '@component-controls/core';
export { getIndexPage } from '@component-controls/store';
export const getHomePagesPaths = (store: Store): string[] => {
@@ -18,7 +24,9 @@ export const getDocHomePage = (
path: string,
): DocHomePagesPath | undefined => {
const pages = getHomePages(store);
- return pages.find(page => page.path === `/${path}`);
+ const resolvedPath = getRoutePath(store, path);
+ const page = pages.find(page => page.path === resolvedPath);
+ return page;
};
export const getDocPagesPaths = (store: Store): string[] => {
@@ -31,7 +39,9 @@ export const getDocPage = (
docTyoe: DocType,
docId: string[],
): DocPagesPath | undefined => {
- const path = `/${docTyoe}/${docId.join('/')}`;
+ const homePath = getHomePath(store);
+ const path = `${ensureTrailingSlash(homePath)}${docTyoe}/${docId.join('/')}`;
const pages = getDocPages(store);
- return pages.find(page => page.path === path);
+ const page = pages.find(page => page.path === path);
+ return page;
};
diff --git a/ui/app/src/AppContext/mdxComponents.tsx b/ui/app/src/AppContext/mdxComponents.tsx
index fe89b1d89..d08ea71a1 100644
--- a/ui/app/src/AppContext/mdxComponents.tsx
+++ b/ui/app/src/AppContext/mdxComponents.tsx
@@ -1,5 +1,19 @@
-import { MarkdownComponentType } from '@component-controls/components';
-import { Link } from '@component-controls/components';
+/* eslint-disable react/display-name */
+import React from 'react';
+import { getRoutePath } from '@component-controls/core';
+import {
+ MarkdownComponentType,
+ Link,
+ useIsLocalLink,
+} from '@component-controls/components';
+import { useStore } from '@component-controls/store';
+
export const mdxComponents: MarkdownComponentType = {
- a: Link,
+ a: props => {
+ const { href, ...rest } = props;
+ const isLocal = useIsLocalLink(href);
+ const store = useStore();
+ const link = isLocal ? getRoutePath(store, href) : href;
+ return ;
+ },
};
diff --git a/ui/app/src/Header/Header.tsx b/ui/app/src/Header/Header.tsx
index 34eb7dd94..b20149732 100644
--- a/ui/app/src/Header/Header.tsx
+++ b/ui/app/src/Header/Header.tsx
@@ -1,7 +1,7 @@
/** @jsx jsx */
import { FC, useContext, useMemo } from 'react';
import { jsx, Box, Heading } from 'theme-ui';
-import { DocType, getDocTypePath } from '@component-controls/core';
+import { DocType, getDocTypePath, getHomePath } from '@component-controls/core';
import { ActionBar, ActionItems, Link } from '@component-controls/components';
import {
ColorMode,
@@ -30,13 +30,19 @@ export const Header: FC = ({ toolbar = {} }) => {
const { SidebarToggle, collapsed, responsive } = useContext(SidebarContext);
const store = useStore();
const homePage = useMemo(() => getIndexPage(store), [store]);
+ const homePath = getHomePath(store);
const docCounts = useDocTypeCount();
const config = useConfig();
const doc = useCurrentDocument();
const { pages, siteTitle } = config || {};
const leftActions: ActionItems = useMemo(() => {
const actions: ActionItems = [
- { node: 'Home', href: '/', 'aria-label': 'go to home page', id: 'home' },
+ {
+ node: 'Home',
+ href: homePath,
+ 'aria-label': 'go to home page',
+ id: 'home',
+ },
];
if (pages) {
const pageItems = Object.keys(pages)
@@ -56,21 +62,21 @@ export const Header: FC = ({ toolbar = {} }) => {
.map(({ page }) => ({
id: page.label?.toLowerCase(),
'aria-label': `go to page ${page.label}`,
- href: getDocTypePath(page),
+ href: getDocTypePath(store, page),
node: page.label,
}));
if (pageItems.length) {
Array.prototype.push.apply(actions, pageItems);
} else {
actions[0].node = (
-
+
{siteTitle}
);
}
}
return toolbar.left ? [...actions, ...toolbar.left] : actions;
- }, [pages, toolbar.left, docCounts, homePage, siteTitle]);
+ }, [pages, toolbar.left, docCounts, homePath, store, homePage, siteTitle]);
const rightActions: ActionItems = useMemo(() => {
const actions: ActionItems = [
diff --git a/ui/app/src/Sidebar/Sidebar.tsx b/ui/app/src/Sidebar/Sidebar.tsx
index 423c7e1b6..90436347a 100644
--- a/ui/app/src/Sidebar/Sidebar.tsx
+++ b/ui/app/src/Sidebar/Sidebar.tsx
@@ -100,6 +100,7 @@ const createMenuItem = (
};
});
} else {
+ newItem.id = doc.stories[0];
newItem.icon = ;
//only one story -direct link to it
newItem.href = storyPath(doc.stories[0], activeTab);
diff --git a/ui/blocks/src/PageContainer/PageContainer.tsx b/ui/blocks/src/PageContainer/PageContainer.tsx
index 39163fa10..de5117396 100644
--- a/ui/blocks/src/PageContainer/PageContainer.tsx
+++ b/ui/blocks/src/PageContainer/PageContainer.tsx
@@ -11,8 +11,8 @@ import {
import { jsx, Box, BoxProps } from 'theme-ui';
import { get } from '@theme-ui/css';
import { useTheme } from '@component-controls/components';
-import { Container } from '../Container/Container';
import { useCurrentDocument } from '@component-controls/store';
+import { Container } from '../Container/Container';
export interface PageContainerOwnProps {
/**
diff --git a/ui/components/README.md b/ui/components/README.md
index a0581ac86..7b16bfc87 100644
--- a/ui/components/README.md
+++ b/ui/components/README.md
@@ -16,6 +16,7 @@
- [Keyboard](#inskeyboardins)
- [Link](#inslinkins)
- [LinkContextProvider](#inslinkcontextproviderins)
+ - [useIsLocalLink](#insuseislocallinkins)
- [LinkHeading](#inslinkheadingins)
- [Markdown](#insmarkdownins)
- [Multiselect](#insmultiselectins)
@@ -257,6 +258,10 @@ _LinkContextProvider [source code](https://github.com/ccontrols/component-contro
| ------------ | ----- | ----------- |
| `linkClass*` | _any_ | |
+## useIsLocalLink
+
+_useIsLocalLink [source code](https://github.com/ccontrols/component-controls/tree/master/ui/components/src/Link/useIsLocalLink.tsx)_
+
## LinkHeading
h1-h6 heading component that generates automatically a github-style anchor to navigate to a section
diff --git a/ui/components/src/Link/Link.tsx b/ui/components/src/Link/Link.tsx
index 6c051bf7e..e506913c8 100644
--- a/ui/components/src/Link/Link.tsx
+++ b/ui/components/src/Link/Link.tsx
@@ -1,14 +1,15 @@
-import React, { FC, useMemo } from 'react';
+import React, { FC } from 'react';
import { LinkProps } from 'theme-ui';
import { ExternalLink } from '../ExternalLink';
import { useGetLinkClass } from './LinkContext';
+import { useIsLocalLink } from './useIsLocalLink';
export const Link: FC = props => {
const { href } = props;
- //https://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative/10687158
- const r = useMemo(() => new RegExp('^(?:[a-z]+:)?//', 'i'), []);
+ const isLocal = useIsLocalLink(href);
+
const LinkClass = useGetLinkClass();
- if (typeof href === 'string' && r.test(href)) {
+ if (!isLocal) {
//@ts-ignore
return ;
}
diff --git a/ui/components/src/Link/index.ts b/ui/components/src/Link/index.ts
index 0afee592c..21caabb17 100644
--- a/ui/components/src/Link/index.ts
+++ b/ui/components/src/Link/index.ts
@@ -1,2 +1,3 @@
export * from './Link';
export * from './LinkContext';
+export * from './useIsLocalLink';
diff --git a/ui/components/src/Link/useIsLocalLink.tsx b/ui/components/src/Link/useIsLocalLink.tsx
new file mode 100644
index 000000000..f352f3943
--- /dev/null
+++ b/ui/components/src/Link/useIsLocalLink.tsx
@@ -0,0 +1,7 @@
+import { useMemo } from 'react';
+
+export const useIsLocalLink = (link?: string): boolean => {
+ //https://stackoverflow.com/questions/10687099/how-to-test-if-a-url-string-is-absolute-or-relative/10687158
+ const r = useMemo(() => new RegExp('^(?:[a-z]+:)?//', 'i'), []);
+ return typeof link === 'string' && !r.test(link);
+};
diff --git a/ui/components/tests/__snapshots__/stories.test.js.snap b/ui/components/tests/__snapshots__/stories.test.js.snap
index bca847d62..99d2f913d 100644
--- a/ui/components/tests/__snapshots__/stories.test.js.snap
+++ b/ui/components/tests/__snapshots__/stories.test.js.snap
@@ -1965,6 +1965,8 @@ exports[`Components/Navmenu Overview 1`] = `
>