@brillout/docpress 0.15.11 → 0.15.13-commit-ffe283f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ExternalLinks.tsx +32 -10
- package/Layout.tsx +193 -160
- package/MenuModal/NavigationWithColumnLayout.tsx +18 -11
- package/MenuModal/toggleMenuModal.ts +2 -2
- package/MenuModal.tsx +4 -4
- package/NavItemComponent.tsx +15 -1
- package/autoScrollNav.ts +3 -3
- package/{components → code-blocks/components}/CodeSnippets.css +1 -5
- package/{components → code-blocks/components}/CodeSnippets.tsx +3 -2
- package/{components → code-blocks/components}/Pre.tsx +1 -3
- package/{components → code-blocks/hooks}/useMDXComponents.tsx +2 -2
- package/{components/CodeSnippets → code-blocks/hooks}/useSelectCodeLang.ts +14 -0
- package/{rehypeMetaToProps.ts → code-blocks/rehypeMetaToProps.ts} +2 -2
- package/{remarkDetype.ts → code-blocks/remarkDetype.ts} +31 -11
- package/code-blocks/shikiTransformerAutoLinks.ts +61 -0
- package/components/index.ts +0 -1
- package/css/index.css +0 -1
- package/determineNavItemsColumnLayout.spec.ts +518 -0
- package/determineNavItemsColumnLayout.ts +11 -10
- package/dist/NavItemComponent.d.ts +24 -2
- package/dist/code-blocks/rehypeMetaToProps.d.ts +35 -0
- package/dist/{rehypeMetaToProps.js → code-blocks/rehypeMetaToProps.js} +2 -2
- package/dist/{remarkDetype.js → code-blocks/remarkDetype.js} +25 -7
- package/dist/code-blocks/shikiTransformerAutoLinks.d.ts +8 -0
- package/dist/code-blocks/shikiTransformerAutoLinks.js +51 -0
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.js +0 -1
- package/dist/determineNavItemsColumnLayout.js +10 -9
- package/dist/types/Config.d.ts +1 -0
- package/dist/vite.config.js +8 -3
- package/docsearch/SearchLink.tsx +5 -1
- package/icons/coin.svg +38 -0
- package/icons/index.ts +2 -0
- package/icons/loudspeaker.svg +1 -0
- package/index.ts +3 -11
- package/package.json +3 -2
- package/types/Config.ts +1 -0
- package/vite.config.ts +8 -3
- package/dist/components/CodeSnippets/useSelectCodeLang.d.ts +0 -7
- package/dist/components/CodeSnippets/useSelectCodeLang.js +0 -50
- package/dist/components/CodeSnippets.d.ts +0 -11
- package/dist/components/CodeSnippets.js +0 -35
- package/dist/rehypeMetaToProps.d.ts +0 -19
- /package/{components → code-blocks/components}/Pre.css +0 -0
- /package/dist/{remarkDetype.d.ts → code-blocks/remarkDetype.d.ts} +0 -0
|
@@ -3,7 +3,7 @@ export { NavigationWithColumnLayout }
|
|
|
3
3
|
import React, { useEffect, useState } from 'react'
|
|
4
4
|
import { assert } from '../utils/server'
|
|
5
5
|
import { getViewportWidth } from '../utils/getViewportWidth'
|
|
6
|
-
import {
|
|
6
|
+
import { viewTablet, navLeftWidthMax, navLeftWidthMin } from '../Layout'
|
|
7
7
|
import { throttle } from '../utils/throttle'
|
|
8
8
|
import { Collapsible } from './Collapsible'
|
|
9
9
|
import { ColumnMap, getNavItemsWithComputed, NavItem, NavItemComponent, NavItemComputed } from '../NavItemComponent'
|
|
@@ -23,6 +23,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
|
23
23
|
window.addEventListener('resize', throttle(updateviewportwidth, 300), { passive: true })
|
|
24
24
|
})
|
|
25
25
|
const navItemsByColumnLayouts = getNavItemsByColumnLayouts(navItemsWithComputed, viewportWidth)
|
|
26
|
+
const maxColumns = Math.max(...navItemsByColumnLayouts.map((layout) => layout.columns.length), 1)
|
|
26
27
|
return (
|
|
27
28
|
<>
|
|
28
29
|
<Style>{getStyle()}</Style>
|
|
@@ -40,10 +41,10 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
|
40
41
|
>
|
|
41
42
|
{columnLayout.isFullWidthCategory ? (
|
|
42
43
|
<div style={{ marginTop: 0 }}>
|
|
43
|
-
<ColumnsWrapper numberOfColumns={
|
|
44
|
+
<ColumnsWrapper numberOfColumns={maxColumns}>
|
|
44
45
|
<Collapsible
|
|
45
46
|
head={(onClick) => <NavItemComponent navItem={columnLayout.navItemLevel1} onClick={onClick} />}
|
|
46
|
-
disabled={
|
|
47
|
+
disabled={maxColumns > 1}
|
|
47
48
|
collapsedInit={!columnLayout.navItemLevel1.isRelevant}
|
|
48
49
|
marginBottomOnExpand={marginBottomOnExpand}
|
|
49
50
|
>
|
|
@@ -61,7 +62,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
|
61
62
|
</ColumnsWrapper>
|
|
62
63
|
</div>
|
|
63
64
|
) : (
|
|
64
|
-
<ColumnsWrapper numberOfColumns={
|
|
65
|
+
<ColumnsWrapper numberOfColumns={maxColumns}>
|
|
65
66
|
<ColumnsLayout>
|
|
66
67
|
{columnLayout.columns.map((column, j) => (
|
|
67
68
|
<Column key={j}>
|
|
@@ -69,7 +70,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
|
69
70
|
<div key={k} style={{ marginBottom: 0 }}>
|
|
70
71
|
<Collapsible
|
|
71
72
|
head={(onClick) => <NavItemComponent navItem={category.navItemLevel1} onClick={onClick} />}
|
|
72
|
-
disabled={
|
|
73
|
+
disabled={maxColumns > 1}
|
|
73
74
|
collapsedInit={!category.navItemLevel1.isRelevant}
|
|
74
75
|
marginBottomOnExpand={marginBottomOnExpand}
|
|
75
76
|
>
|
|
@@ -93,7 +94,7 @@ function NavigationWithColumnLayout(props: { navItems: NavItem[] }) {
|
|
|
93
94
|
|
|
94
95
|
function getStyle() {
|
|
95
96
|
const style = css`
|
|
96
|
-
@media(min-width: ${
|
|
97
|
+
@media(min-width: ${viewTablet + 1}px) {
|
|
97
98
|
.menu-navigation-content {
|
|
98
99
|
position: absolute;
|
|
99
100
|
width: 100%;
|
|
@@ -269,17 +270,23 @@ function getNavItemsByColumnEntries(navItems: NavItemComputed[]): NavItemsByColu
|
|
|
269
270
|
let isFullWidthCategory: boolean | undefined
|
|
270
271
|
navItems.forEach((navItem) => {
|
|
271
272
|
if (navItem.level === 1) {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
if (isFullWidthCategory) {
|
|
274
|
+
assert(navItem.menuModalFullWidth)
|
|
275
|
+
}
|
|
276
|
+
const isFullWidthCategoryPrevious = !!isFullWidthCategory
|
|
277
|
+
if (navItem.menuModalFullWidth) {
|
|
278
|
+
isFullWidthCategory = true
|
|
279
|
+
// Flush
|
|
275
280
|
navItemsByColumnEntries.push({ columnEntries, isFullWidthCategory: isFullWidthCategoryPrevious })
|
|
276
281
|
columnEntries = []
|
|
282
|
+
} else {
|
|
283
|
+
isFullWidthCategory = false
|
|
277
284
|
}
|
|
278
285
|
}
|
|
279
286
|
assert(isFullWidthCategory !== undefined)
|
|
280
|
-
if (navItem.
|
|
287
|
+
if (navItem.isPotentialColumn) {
|
|
281
288
|
assert(navItem.level === 1 || navItem.level === 4)
|
|
282
|
-
columnEntry = { navItems: [navItem], columnMap: navItem.
|
|
289
|
+
columnEntry = { navItems: [navItem], columnMap: navItem.isPotentialColumn }
|
|
283
290
|
columnEntries.push(columnEntry)
|
|
284
291
|
} else {
|
|
285
292
|
assert(navItem.level !== 1)
|
|
@@ -4,7 +4,7 @@ export { keepMenuModalOpen }
|
|
|
4
4
|
export { closeMenuModal }
|
|
5
5
|
export { coseMenuModalOnMouseLeave }
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { viewTablet } from '../Layout'
|
|
8
8
|
import { getHydrationPromise } from '../renderer/getHydrationPromise'
|
|
9
9
|
import { getViewportWidth } from '../utils/getViewportWidth'
|
|
10
10
|
import { isBrowser } from '../utils/isBrowser'
|
|
@@ -111,7 +111,7 @@ function toggleMenuModal(menuId: number) {
|
|
|
111
111
|
closeMenuModal()
|
|
112
112
|
} else {
|
|
113
113
|
openMenuModal(menuId)
|
|
114
|
-
if (getViewportWidth() <
|
|
114
|
+
if (getViewportWidth() < viewTablet) autoScroll()
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
package/MenuModal.tsx
CHANGED
|
@@ -3,7 +3,7 @@ export { MenuModal }
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import { usePageContext } from './renderer/usePageContext'
|
|
5
5
|
import { css } from './utils/css'
|
|
6
|
-
import {
|
|
6
|
+
import { viewDesktop, viewTablet } from './Layout'
|
|
7
7
|
import { ExternalLinks } from './ExternalLinks'
|
|
8
8
|
import { Style } from './utils/Style'
|
|
9
9
|
import { NavigationWithColumnLayout } from './MenuModal/NavigationWithColumnLayout'
|
|
@@ -79,7 +79,7 @@ function Nav() {
|
|
|
79
79
|
|
|
80
80
|
function getStyle() {
|
|
81
81
|
return css`
|
|
82
|
-
@media(min-width: ${
|
|
82
|
+
@media(min-width: ${viewTablet + 1}px) {
|
|
83
83
|
#menu-modal-scroll-container {
|
|
84
84
|
max-height: calc(100vh - var(--nav-head-height) - var(--block-margin));
|
|
85
85
|
${/* https://github.com/brillout/docpress/issues/23 */ ''}
|
|
@@ -99,7 +99,7 @@ function getStyle() {
|
|
|
99
99
|
display: none !important;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
@media(max-width: ${
|
|
102
|
+
@media(max-width: ${viewTablet}px) {
|
|
103
103
|
#menu-modal-scroll-container {
|
|
104
104
|
${/* Fallback for Firefox: it doesn't support `dvh` yet: https://caniuse.com/?search=dvh */ ''}
|
|
105
105
|
${/* Let's always and systematically use `dvh` instead of `vh` once Firefox supports it */ ''}
|
|
@@ -135,7 +135,7 @@ function getStyle() {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
${/* Hide same-page headings navigation */ ''}
|
|
138
|
-
@container container-viewport (min-width: ${
|
|
138
|
+
@container container-viewport (min-width: ${viewDesktop}px) {
|
|
139
139
|
#menu-modal-wrapper .nav-item-level-3 {
|
|
140
140
|
display: none;
|
|
141
141
|
}
|
package/NavItemComponent.tsx
CHANGED
|
@@ -31,8 +31,22 @@ type NavItem = {
|
|
|
31
31
|
title: string
|
|
32
32
|
titleInNav: string
|
|
33
33
|
menuModalFullWidth?: true
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Maps viewport column counts to column indices.
|
|
36
|
+
* Indicates this nav item is a "column entry" (a level-1 or level-4 heading that starts a new column section).
|
|
37
|
+
* Example: `{ 1: 0, 2: 1, 3: 0 }` means:
|
|
38
|
+
* - When there's 1 column, put this item in column 0
|
|
39
|
+
* - When there are 2 columns, put it in column 1
|
|
40
|
+
* - When there are 3 columns, put it in column 0
|
|
41
|
+
*/
|
|
42
|
+
isPotentialColumn?: ColumnMap
|
|
35
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* A mapping of viewport column counts to column indices.
|
|
46
|
+
* Used to determine which column a nav item should be placed in for different viewport widths.
|
|
47
|
+
* Key: number of columns in the viewport
|
|
48
|
+
* Value: the column index (0-based) where this item should be placed
|
|
49
|
+
*/
|
|
36
50
|
type ColumnMap = Record<number, number>
|
|
37
51
|
|
|
38
52
|
type PropsNavItem = PropsAnchor & PropsSpan
|
package/autoScrollNav.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { autoScrollNav }
|
|
2
|
-
export
|
|
2
|
+
export { autoScrollNav_SSR }
|
|
3
3
|
|
|
4
|
-
// - We cannot use TypeScript
|
|
4
|
+
// - WARNING: We cannot use TypeScript here because we serialize the function.
|
|
5
5
|
// - We have to save & restore `document.documentElement.scrollTop` because scrollIntoView() scrolls the main view. (I don't know why).
|
|
6
6
|
// - Failed alternatives:
|
|
7
7
|
// - scrollIntoViewIfNeeded() (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) would work (it doesn't scroll the main view) but Firefox doesn't support it.
|
|
@@ -17,7 +17,7 @@ export const autoScrollNav_SSR = `autoScrollNav();${autoScrollNav.toString()}`
|
|
|
17
17
|
// // ...
|
|
18
18
|
// document.documentElement.style.overflow = overflowOriginal
|
|
19
19
|
// ```
|
|
20
|
-
|
|
20
|
+
const autoScrollNav_SSR = `autoScrollNav();${autoScrollNav.toString()}`
|
|
21
21
|
function autoScrollNav() {
|
|
22
22
|
const nav = document.querySelector('#nav-left .navigation-content')
|
|
23
23
|
if (!nav) return
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/* Hide language toggle for YAML */
|
|
12
|
-
&:has(code[data-language='yaml']) .code-lang-toggle {
|
|
13
|
-
display: none !important;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
/* Language toggle styles */
|
|
17
12
|
.code-lang-toggle {
|
|
18
13
|
position: absolute !important;
|
|
@@ -28,6 +23,7 @@
|
|
|
28
23
|
margin: 0;
|
|
29
24
|
padding: 0 4px;
|
|
30
25
|
height: 25px;
|
|
26
|
+
width: 59px;
|
|
31
27
|
display: flex;
|
|
32
28
|
background-color: #f7f7f7;
|
|
33
29
|
opacity: 0;
|
|
@@ -5,7 +5,7 @@ export { TypescriptOnly }
|
|
|
5
5
|
export { CodeSnippets }
|
|
6
6
|
|
|
7
7
|
import React, { useEffect, useRef } from 'react'
|
|
8
|
-
import { useSelectCodeLang } from '
|
|
8
|
+
import { useSelectCodeLang } from '../hooks/useSelectCodeLang'
|
|
9
9
|
import './CodeSnippets.css'
|
|
10
10
|
|
|
11
11
|
/** Only show if TypeScript is selected */
|
|
@@ -14,7 +14,7 @@ function TypescriptOnly({ children }: { children: React.ReactNode }) {
|
|
|
14
14
|
return <div style={{ display: codeLangSelected === 'ts' ? 'block' : 'none' }}>{children}</div>
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function CodeSnippets({ children }: { children: React.ReactNode }) {
|
|
17
|
+
function CodeSnippets({ children, hideToggle = false }: { children: React.ReactNode; hideToggle: boolean }) {
|
|
18
18
|
const [codeLangSelected, selectCodeLang] = useSelectCodeLang()
|
|
19
19
|
const prevPositionRef = useRef<null | { top: number; el: Element }>(null)
|
|
20
20
|
|
|
@@ -35,6 +35,7 @@ function CodeSnippets({ children }: { children: React.ReactNode }) {
|
|
|
35
35
|
type="checkbox"
|
|
36
36
|
name="code-lang-toggle"
|
|
37
37
|
className="code-lang-toggle raised"
|
|
38
|
+
style={{ display: hideToggle ? 'none' : undefined }}
|
|
38
39
|
checked={codeLangSelected === 'ts'}
|
|
39
40
|
onChange={onChange}
|
|
40
41
|
title="Toggle language"
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export { Pre }
|
|
2
2
|
|
|
3
3
|
import React, { ComponentPropsWithoutRef, useState } from 'react'
|
|
4
|
-
/* Importing it here chokes the tests. I don't know why.
|
|
5
4
|
import './Pre.css'
|
|
6
|
-
//*/
|
|
7
5
|
|
|
8
6
|
function Pre({ children, ...props }: ComponentPropsWithoutRef<'pre'> & { 'hide-menu'?: string }) {
|
|
9
7
|
return (
|
|
@@ -26,7 +24,7 @@ function CopyButton() {
|
|
|
26
24
|
const icon =
|
|
27
25
|
isSuccess === null ? (
|
|
28
26
|
// Copy icon
|
|
29
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="
|
|
27
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke="black" strokeWidth="2">
|
|
30
28
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
31
29
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
|
32
30
|
</svg>
|
|
@@ -2,8 +2,8 @@ export { useMDXComponents }
|
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import type { UseMdxComponents } from '@mdx-js/mdx'
|
|
5
|
-
import { Pre } from '
|
|
6
|
-
import { CodeSnippets } from '
|
|
5
|
+
import { Pre } from '../components/Pre.js'
|
|
6
|
+
import { CodeSnippets } from '../components/CodeSnippets.js'
|
|
7
7
|
|
|
8
8
|
const useMDXComponents: UseMdxComponents = () => {
|
|
9
9
|
return {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { useSelectCodeLang }
|
|
2
|
+
export { initializeJsToggle_SSR }
|
|
2
3
|
|
|
3
4
|
import { useState, useEffect, useCallback } from 'react'
|
|
4
5
|
import { assertWarning } from '../../utils/assert'
|
|
@@ -53,6 +54,19 @@ function useSelectCodeLang() {
|
|
|
53
54
|
return [codeLangSelected, selectCodeLang] as const
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// WARNING: We cannot use the variables storageKey nor codeLangDefaultClient here: closures
|
|
58
|
+
// don't work because we serialize the function.
|
|
59
|
+
// WARNING: We cannot use TypeScript here, for the same reason.
|
|
60
|
+
const initializeJsToggle_SSR = `initializeJsToggle();${initializeJsToggle.toString()};`
|
|
61
|
+
function initializeJsToggle() {
|
|
62
|
+
const codeLangSelected = localStorage.getItem('docpress:code-lang') ?? 'js'
|
|
63
|
+
if (codeLangSelected === 'js') {
|
|
64
|
+
const inputs = document.querySelectorAll('.code-lang-toggle')
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
for (const input of inputs) input.checked = false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
declare global {
|
|
57
71
|
interface WindowEventMap {
|
|
58
72
|
'code-lang-storage': CustomEvent
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { rehypeMetaToProps }
|
|
1
|
+
export { rehypeMetaToProps, parseMetaString }
|
|
2
2
|
|
|
3
3
|
import { visit } from 'unist-util-visit'
|
|
4
4
|
import type { ElementData, Root } from 'hast'
|
|
@@ -51,7 +51,7 @@ function parseMetaString(metaString: ElementData['meta']): Record<string, string
|
|
|
51
51
|
|
|
52
52
|
const props: Record<string, string> = {}
|
|
53
53
|
|
|
54
|
-
const keyValuePairRE = /([a-zA-Z_-]+)(?:=
|
|
54
|
+
const keyValuePairRE = /([a-zA-Z_-]+)(?:=([^"'\s]+))?(?=\s|$)/g
|
|
55
55
|
for (const match of metaString.matchAll(keyValuePairRE)) {
|
|
56
56
|
let [_, key, value] = match
|
|
57
57
|
props[kebabCase(key)] = value || 'true'
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { remarkDetype }
|
|
2
2
|
|
|
3
3
|
import type { Root, Parent, Code } from 'mdast'
|
|
4
|
+
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
4
5
|
import type { VFile } from '@mdx-js/mdx/internal-create-format-aware-processors'
|
|
5
6
|
import { visit } from 'unist-util-visit'
|
|
6
|
-
import { assertUsage } from '
|
|
7
|
+
import { assertUsage } from '../utils/assert.js'
|
|
8
|
+
import { parseMetaString } from './rehypeMetaToProps.js'
|
|
7
9
|
import pc from '@brillout/picocolors'
|
|
8
10
|
import module from 'node:module'
|
|
9
11
|
// Cannot use `import { transform } from 'detype'` as it results in errors,
|
|
@@ -13,7 +15,6 @@ const { transform: detype } = module.createRequire(import.meta.url)('detype') as
|
|
|
13
15
|
const prettierOptions: NonNullable<Parameters<typeof detype>[2]>['prettierOptions'] = {
|
|
14
16
|
semi: false,
|
|
15
17
|
singleQuote: true,
|
|
16
|
-
printWidth: 100,
|
|
17
18
|
trailingComma: 'none',
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -65,22 +66,29 @@ function transformYaml(node: CodeNode) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
// Wrap both the original YAML and `yamlJsCode` with <CodeSnippets>
|
|
68
|
-
const yamlContainer = {
|
|
69
|
-
type: 'mdxJsxFlowElement'
|
|
69
|
+
const yamlContainer: MdxJsxFlowElement = {
|
|
70
|
+
type: 'mdxJsxFlowElement',
|
|
70
71
|
name: 'CodeSnippets',
|
|
71
72
|
children: [yamlJsCode, codeBlock],
|
|
72
|
-
attributes: [
|
|
73
|
+
attributes: [
|
|
74
|
+
{
|
|
75
|
+
name: 'hideToggle',
|
|
76
|
+
type: 'mdxJsxAttribute',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
73
79
|
}
|
|
74
80
|
parent.children.splice(index, 1, yamlContainer)
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
78
84
|
const { codeBlock, index, parent } = node
|
|
79
|
-
|
|
85
|
+
const maxWidth = Number(parseMetaString(codeBlock.meta)['max-width'])
|
|
86
|
+
let codeBlockReplacedJs = replaceFileNameSuffixes(codeBlock.value)
|
|
87
|
+
let codeBlockContentJs = ''
|
|
80
88
|
|
|
81
89
|
// Remove TypeScript from the TS/TSX/Vue code node
|
|
82
90
|
try {
|
|
83
|
-
codeBlockContentJs = await detype(
|
|
91
|
+
codeBlockContentJs = await detype(codeBlockReplacedJs, `some-dummy-filename.${codeBlock.lang}`, {
|
|
84
92
|
customizeBabelConfig(config) {
|
|
85
93
|
// Add `onlyRemoveTypeImports: true` to the internal `@babel/preset-typescript` config
|
|
86
94
|
// https://github.com/cyco130/detype/blob/46ec867e9efd31d31a312a215ca169bd6bff4726/src/transform.ts#L206
|
|
@@ -88,7 +96,10 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
88
96
|
config.presets = [[config.presets[0], { onlyRemoveTypeImports: true }]]
|
|
89
97
|
},
|
|
90
98
|
removeTsComments: true,
|
|
91
|
-
prettierOptions
|
|
99
|
+
prettierOptions: {
|
|
100
|
+
...prettierOptions,
|
|
101
|
+
printWidth: maxWidth ? maxWidth : 99,
|
|
102
|
+
},
|
|
92
103
|
})
|
|
93
104
|
} catch (error) {
|
|
94
105
|
// Log errors and return original content instead of throwing
|
|
@@ -113,6 +124,7 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
113
124
|
if (codeBlockContentJs === codeBlock.value) return
|
|
114
125
|
|
|
115
126
|
const { position, lang, ...rest } = codeBlock
|
|
127
|
+
const attributes: MdxJsxFlowElement['attributes'] = []
|
|
116
128
|
|
|
117
129
|
const jsCode: Code = {
|
|
118
130
|
...rest,
|
|
@@ -121,12 +133,20 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
121
133
|
value: codeBlockContentJs,
|
|
122
134
|
}
|
|
123
135
|
|
|
136
|
+
// Add `hideToggle` attribute (prop) to `CodeSnippets` if the only change was replacing `.ts` with `.js`
|
|
137
|
+
if (codeBlockReplacedJs === codeBlockContentJs) {
|
|
138
|
+
attributes.push({
|
|
139
|
+
name: 'hideToggle',
|
|
140
|
+
type: 'mdxJsxAttribute',
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
124
144
|
// Wrap both the original `codeBlock` and `jsCode` with <CodeSnippets>
|
|
125
|
-
const container = {
|
|
126
|
-
type: 'mdxJsxFlowElement'
|
|
145
|
+
const container: MdxJsxFlowElement = {
|
|
146
|
+
type: 'mdxJsxFlowElement',
|
|
127
147
|
name: 'CodeSnippets',
|
|
128
148
|
children: [jsCode, codeBlock],
|
|
129
|
-
attributes
|
|
149
|
+
attributes,
|
|
130
150
|
}
|
|
131
151
|
parent.children.splice(index, 1, container)
|
|
132
152
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export { shikiTransformerAutoLinks }
|
|
2
|
+
|
|
3
|
+
import type { ShikiTransformer } from 'shiki'
|
|
4
|
+
|
|
5
|
+
const linkRE = /https:\/\/[^\s]*[^.,\s"'`]/g
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A Shiki transformer that converts plain HTTPS URLs in code blocks into clickable `<a>` links.
|
|
9
|
+
*
|
|
10
|
+
* Inspired by `@jcayzac/shiki-transformer-autolinks`, but tailored for a narrower use case.
|
|
11
|
+
*/
|
|
12
|
+
function shikiTransformerAutoLinks(): ShikiTransformer {
|
|
13
|
+
return {
|
|
14
|
+
name: 'docpress-shiki-autolinks',
|
|
15
|
+
span(span) {
|
|
16
|
+
if (span.children.length !== 1) return
|
|
17
|
+
let child = span.children[0]
|
|
18
|
+
if (child.type !== 'text') return
|
|
19
|
+
|
|
20
|
+
const links: { href: string; index: number }[] = []
|
|
21
|
+
const matches = Array.from(child.value.matchAll(linkRE))
|
|
22
|
+
|
|
23
|
+
// Filter out URLs that contain `${...}`. e.g. `https://star-wars.brillout.com/api/films/${id}.json`.
|
|
24
|
+
const filtered = matches.filter(([href]) => !href.includes('${'))
|
|
25
|
+
if (filtered.length === 0) return
|
|
26
|
+
|
|
27
|
+
for (const match of filtered) {
|
|
28
|
+
const [href] = match
|
|
29
|
+
links.unshift({ href, index: match.index })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const newChildren: typeof span.children = []
|
|
33
|
+
for (const { href, index } of links) {
|
|
34
|
+
const postIndex = index + href.length
|
|
35
|
+
const postValue = child.value.slice(postIndex)
|
|
36
|
+
|
|
37
|
+
if (postValue.length > 0) {
|
|
38
|
+
newChildren.unshift({ type: 'text', value: postValue })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
newChildren.unshift({
|
|
42
|
+
type: 'element',
|
|
43
|
+
tagName: 'a',
|
|
44
|
+
properties: { href },
|
|
45
|
+
children: [{ type: 'text', value: href }],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
child = {
|
|
49
|
+
type: 'text',
|
|
50
|
+
value: child.value.slice(0, index),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (child.value.length > 0) {
|
|
55
|
+
newChildren.unshift(child)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
span.children = newChildren
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
}
|
package/components/index.ts
CHANGED