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`] = ` >