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