Skip to content

Commit 2b99ba6

Browse files
authored
Fix basePath issues in Doctocat UI components (#46)
* prepend basePath to index card hrefs * fix basePath resolution inside Doctocat UI * remove filtered element and apply to all elements with src * apply index card basePaths only to frontmatter, not default placeholder ones that are impoted through webpack instead
1 parent 1b15bdf commit 2b99ba6

File tree

9 files changed

+61
-4
lines changed

9 files changed

+61
-4
lines changed

.changeset/warm-teachers-repeat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/doctocat-nextjs': patch
3+
---
4+
5+
Prepend `basePath` from `next.config.js` to paths in Doctocat UI components, where it would previously not resolve correctly.

packages/site/content/content-example/tabbed-component/react.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@ You can place an icon inside the Button in either the leading or the trailing po
9898
</AnimationProvider>
9999
```
100100

101+
### Images
102+
103+
```
104+
<>
105+
<img src="/images/avatar-mona.png" />
106+
<Avatar src="/images/avatar-mona.png" />
107+
</>
108+
```
109+
101110
### Polymorphism
102111

103112
The `Button` component can render as a `button` or `a` HTML element. By default, it will render as a `button`.
39.8 KB
Loading

packages/theme/components/context/useConfig.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type ConfigContextLink = {
1010
export type ConfigContextValue = {
1111
headerLinks: ConfigContextLink[]
1212
sidebarLinks: ConfigContextLink[]
13+
basePath: string
1314
}
1415

1516
export const ConfigContext = createContext<ConfigContextValue | null>(null)

packages/theme/components/layout/code-block/ReactCodeBlock.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import React, {PropsWithChildren, useCallback, useState} from 'react'
33
import clsx from 'clsx'
44
import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'
55
import {useColorMode} from '../../context/color-modes/useColorMode'
6+
import {useConfig} from '../../context/useConfig'
67
import styles from './ReactCodeBlock.module.css'
78
import {ActionBar, Button, ThemeProvider} from '@primer/react'
89
import {CopyIcon, MoonIcon, SunIcon, UndoIcon} from '@primer/octicons-react'
910
import {colorModes} from '../../context/color-modes/context'
1011

1112
import {lightTheme, darkTheme} from './syntax-highlighting-themes'
13+
import {codeTransformer} from './code-transformer'
1214

1315
type ReactCodeBlockProps = {
1416
'data-language': string
@@ -18,10 +20,19 @@ type ReactCodeBlockProps = {
1820

1921
export function ReactCodeBlock(props: ReactCodeBlockProps) {
2022
const {colorMode, setColorMode} = useColorMode()
23+
const {basePath} = useConfig()
2124
const initialCode = getCodeFromChildren(props.children)
2225
const [code, setCode] = useState(initialCode)
2326
const shouldShowPreview = ['tsx', 'jsx'].includes(props['data-language'])
2427

28+
/**
29+
* Transforms code to prepend basePath to img src attributes
30+
*/
31+
const transformCodeWithBasePath = useCallback(
32+
(sourceCode: string) => codeTransformer(sourceCode, basePath),
33+
[basePath],
34+
)
35+
2536
const handleReset = useCallback(() => {
2637
setCode(initialCode)
2738
}, [initialCode, setCode])
@@ -34,7 +45,7 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
3445

3546
return (
3647
<>
37-
<LiveProvider code={code} scope={props.jsxScope} noInline={noInline}>
48+
<LiveProvider transformCode={transformCodeWithBasePath} code={code} scope={props.jsxScope} noInline={noInline}>
3849
<div className={clsx(styles.CodeBlock, 'custom-component')}>
3950
{shouldShowPreview && (
4051
<div>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export const codeTransformer = (sourceCode: string, basePath: string): string => {
2+
if (!basePath) return sourceCode
3+
4+
// to skip external URLs and other irrelevant paths
5+
const shouldTransform = (src: string) => !src.startsWith('http') && !src.startsWith('//') && !src.startsWith(basePath)
6+
7+
// normalise for absolute (/path) and relative (path) values
8+
const transformSrc = (src: string) => (src.startsWith('/') ? `${basePath}${src}` : `${basePath}/${src}`)
9+
10+
// Assumes all elements with a src attribute are trying to point at Next.js public folder
11+
return sourceCode.replace(
12+
/<(\w+)\s+([^>]*\s+)?src=["']([^"']+)["']([^>]*)/g,
13+
(match, tagName, before = '', src, after) => {
14+
if (!shouldTransform(src)) return match
15+
16+
return `<${tagName} ${before}src="${transformSrc(src)}"${after}`
17+
},
18+
)
19+
}

packages/theme/components/layout/index-cards/IndexCards.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import placeholderLightFiveThumb from './images/light-5.png'
1818
import placeholderLightSixThumb from './images/light-6.png'
1919

2020
import styles from './IndexCards.module.css'
21+
import {useConfig} from '../../context/useConfig'
2122

2223
type IndexCardsProps = {
2324
route: string
@@ -43,6 +44,7 @@ const lightModePlaceholderThumbs = [
4344
] as unknown as StaticImageData[]
4445

4546
export function IndexCards({route, folderData}: IndexCardsProps) {
47+
const {basePath} = useConfig()
4648
const lastPlaceholderIndexRef = useRef<number>(-1)
4749
// We don't want to show children of these pages. E.g. tabbed pages
4850
const onlyDirectChildren = folderData.filter(item => {
@@ -86,8 +88,10 @@ export function IndexCards({route, folderData}: IndexCardsProps) {
8688

8789
const thumbnailUrl =
8890
colorMode === 'dark'
89-
? item.frontMatter.thumbnail_darkMode || getNextPlaceholderIndex(darkModePlaceholderThumbs).src
90-
: item.frontMatter.thumbnail || getNextPlaceholderIndex(lightModePlaceholderThumbs).src
91+
? `${basePath ? basePath : ''}${item.frontMatter.thumbnail_darkMode}` ||
92+
getNextPlaceholderIndex(darkModePlaceholderThumbs).src
93+
: `${basePath ? basePath : ''}${item.frontMatter.thumbnail}` ||
94+
getNextPlaceholderIndex(lightModePlaceholderThumbs).src
9195

9296
return (
9397
<Grid.Column span={{xsmall: 12, small: 12, medium: 12, large: 6, xlarge: 4}} key={item.frontMatter.title}>

packages/theme/components/layout/root-layout/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function Shell({children, headerLinks = [], sidebarLinks = [], ..
2525
value={{
2626
headerLinks,
2727
sidebarLinks,
28+
basePath: process.env.NEXT_PUBLIC_DOCTOCAT_BASE_PATH || '',
2829
}}
2930
>
3031
<Theme {...rest}>{children}</Theme>

packages/theme/doctocat.config.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ const withNextra = nextra({
1010
* Relies on `transpilePackages: ['@primer/doctocat-nextjs'],` being set in the next.config
1111
*/
1212
function withDoctocat(config = {}) {
13-
return {
13+
const finalConfig = {
1414
...withNextra(),
1515
images: {
1616
unoptimized: true,
1717
},
1818

1919
...config,
2020
}
21+
22+
// Enables basePath in next.config.js to be used inside Doctocat
23+
if (finalConfig.basePath) {
24+
process.env.NEXT_PUBLIC_DOCTOCAT_BASE_PATH = finalConfig.basePath
25+
}
26+
27+
return finalConfig
2128
}
2229

2330
export default withDoctocat

0 commit comments

Comments
 (0)