@brillout/docpress 0.16.9 → 0.16.11
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/Layout.tsx +1 -2
- package/code-blocks/components/ChoiceGroup.css +6 -7
- package/code-blocks/components/ChoiceGroup.tsx +38 -28
- package/code-blocks/hooks/useMDXComponents.tsx +0 -2
- package/code-blocks/hooks/useSelectedChoice.ts +18 -15
- package/code-blocks/remarkChoiceGroup.ts +22 -27
- package/code-blocks/remarkDetype.ts +33 -34
- package/code-blocks/remarkPkgManager.ts +7 -7
- package/code-blocks/utils/generateChoiceGroupCode.ts +79 -0
- package/dist/code-blocks/remarkChoiceGroup.d.ts +2 -1
- package/dist/code-blocks/remarkChoiceGroup.js +16 -15
- package/dist/code-blocks/remarkDetype.js +31 -33
- package/dist/code-blocks/remarkPkgManager.js +7 -6
- package/dist/code-blocks/utils/generateChoiceGroupCode.d.ts +9 -0
- package/dist/code-blocks/utils/generateChoiceGroupCode.js +63 -0
- package/index.ts +0 -1
- package/package.json +1 -1
- package/code-blocks/components/CodeSnippets.css +0 -75
- package/code-blocks/components/CodeSnippets.tsx +0 -41
- package/code-blocks/hooks/useSelectCodeLang.ts +0 -25
- package/code-blocks/utils/generateChoiceGroup.ts +0 -87
- package/dist/code-blocks/utils/generateChoiceGroup.d.ts +0 -9
- package/dist/code-blocks/utils/generateChoiceGroup.js +0 -66
package/Layout.tsx
CHANGED
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
import { MenuModal } from './MenuModal'
|
|
29
29
|
import { autoScrollNav_SSR } from './autoScrollNav'
|
|
30
30
|
import { initializeChoiceGroup_SSR } from './code-blocks/hooks/useSelectedChoice'
|
|
31
|
-
import { initializeJsToggle_SSR } from './code-blocks/hooks/useSelectCodeLang'
|
|
32
31
|
import { SearchLink } from './docsearch/SearchLink'
|
|
33
32
|
import { navigate } from 'vike/client/router'
|
|
34
33
|
import { css } from './utils/css'
|
|
@@ -101,7 +100,7 @@ function Layout({ children }: { children: React.ReactNode }) {
|
|
|
101
100
|
{content}
|
|
102
101
|
</div>
|
|
103
102
|
{/* Early toggling, to avoid layout jumps */}
|
|
104
|
-
<script dangerouslySetInnerHTML={{ __html: `${initializeChoiceGroup_SSR}
|
|
103
|
+
<script dangerouslySetInnerHTML={{ __html: `${initializeChoiceGroup_SSR}` }}></script>
|
|
105
104
|
<Style>{getStyleLayout()}</Style>
|
|
106
105
|
</div>
|
|
107
106
|
)
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
|
|
4
4
|
&:hover {
|
|
5
5
|
.select-choice,
|
|
6
|
-
.copy-button
|
|
7
|
-
.code-lang-toggle {
|
|
6
|
+
.copy-button {
|
|
8
7
|
opacity: 1;
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
--select-width:
|
|
11
|
+
--select-width: 85px;
|
|
13
12
|
--select-top-position: 10px;
|
|
14
13
|
--select-right-position: 42px;
|
|
15
|
-
--
|
|
14
|
+
--show-js-dropdown-right-position: calc(var(--select-right-position) + var(--select-width) + 2px);
|
|
16
15
|
|
|
17
16
|
.select-choice {
|
|
18
17
|
position: absolute;
|
|
@@ -25,11 +24,11 @@
|
|
|
25
24
|
opacity: 0;
|
|
26
25
|
transition: opacity 0.5s ease-in-out, background-color 0.4s ease-in-out;
|
|
27
26
|
|
|
28
|
-
&.
|
|
29
|
-
--select-right-position: var(--
|
|
27
|
+
&.show-js-dropdown {
|
|
28
|
+
--select-right-position: var(--show-js-dropdown-right-position);
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
&:has(+ .choice .choice-group) {
|
|
31
|
+
&:has(+ .choice > .choice-group:not([data-choice-group='codeLang'])) {
|
|
33
32
|
right: calc(var(--select-width) + var(--select-right-position) + 2px);
|
|
34
33
|
}
|
|
35
34
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { ChoiceGroup }
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useRef } from 'react'
|
|
4
4
|
import { usePageContext } from '../../renderer/usePageContext'
|
|
5
5
|
import { useSelectedChoice } from '../hooks/useSelectedChoice'
|
|
6
6
|
import { useRestoreScroll } from '../hooks/useRestoreScroll'
|
|
@@ -9,31 +9,41 @@ import { cls } from '../../utils/cls'
|
|
|
9
9
|
import type { PageContext } from 'vike/types'
|
|
10
10
|
import './ChoiceGroup.css'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
const CHOICES_BUILT_IN: Record<string, { choices: string[]; default: string }> = {
|
|
13
|
+
codeLang: {
|
|
14
|
+
choices: ['JavaScript', 'TypeScript'],
|
|
15
|
+
default: 'JavaScript',
|
|
16
|
+
},
|
|
17
|
+
pkgManager: {
|
|
18
|
+
choices: ['npm', 'pnpm', 'Bun', 'Yarn'],
|
|
19
|
+
default: 'npm',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ChoiceGroup({
|
|
24
|
+
children,
|
|
25
|
+
choices,
|
|
26
|
+
hide = false,
|
|
27
|
+
}: { children: React.ReactNode; choices: string[]; hide: boolean }) {
|
|
13
28
|
const pageContext = usePageContext()
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const [
|
|
29
|
+
const cleanChoices = choices.map(c => c.split(':')[0])
|
|
30
|
+
const choiceGroup = findChoiceGroup(pageContext, cleanChoices)
|
|
31
|
+
const [selectedChoice, setSelectedChoice] = useSelectedChoice(choiceGroup.name, choiceGroup.default)
|
|
32
|
+
const isJsDropdownVisible = choices.indexOf(`${selectedChoice}:jsDropdown`) !== -1
|
|
17
33
|
const choiceGroupRef = useRef<HTMLDivElement>(null)
|
|
18
34
|
const prevPositionRef = useRestoreScroll([selectedChoice])
|
|
19
|
-
const isHidden = choices.length === 1 || !
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (!choiceGroupRef.current) return
|
|
23
|
-
const selectedChoiceEl = choiceGroupRef.current.querySelector<HTMLDivElement>(`div[id="${selectedChoice}"]`)
|
|
24
|
-
setHasJsToggle(!!selectedChoiceEl?.classList.contains('has-toggle'))
|
|
25
|
-
}, [selectedChoice])
|
|
35
|
+
const isHidden = choices.length === 1 || !cleanChoices.includes(selectedChoice) || hide
|
|
26
36
|
|
|
27
37
|
return (
|
|
28
|
-
<div ref={choiceGroupRef} data-group
|
|
38
|
+
<div ref={choiceGroupRef} data-choice-group={choiceGroup.name} className="choice-group">
|
|
29
39
|
<select
|
|
30
|
-
name={
|
|
40
|
+
name={`choicesFor-${choiceGroup.name}`}
|
|
31
41
|
value={selectedChoice}
|
|
32
42
|
onChange={onChange}
|
|
33
|
-
className={cls(['select-choice',
|
|
43
|
+
className={cls(['select-choice', isJsDropdownVisible && 'show-js-dropdown', isHidden && 'hidden'])}
|
|
34
44
|
>
|
|
35
|
-
{
|
|
36
|
-
<option key={i} value={choice} disabled={!
|
|
45
|
+
{choiceGroup.choices.map((choice, i) => (
|
|
46
|
+
<option key={i} value={choice} disabled={!cleanChoices.includes(choice)}>
|
|
37
47
|
{choice}
|
|
38
48
|
</option>
|
|
39
49
|
))}
|
|
@@ -49,13 +59,13 @@ function ChoiceGroup({ children, choices }: { children: React.ReactNode; choices
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
function
|
|
53
|
-
const { choices:
|
|
54
|
-
|
|
62
|
+
function findChoiceGroup(pageContext: PageContext, choices: string[]) {
|
|
63
|
+
const { choices: choicesConfig } = pageContext.globalContext.config.docpress
|
|
64
|
+
const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig }
|
|
55
65
|
|
|
56
|
-
const groupName = Object.keys(
|
|
57
|
-
// get only the values that exist in both choices and
|
|
58
|
-
const relevantChoices =
|
|
66
|
+
const groupName = Object.keys(choicesAll).find((key) => {
|
|
67
|
+
// get only the values that exist in both choices and choicesAll[key].choices
|
|
68
|
+
const relevantChoices = choicesAll[key].choices.filter((choice) => choices.includes(choice))
|
|
59
69
|
// if nothing exists, skip this key
|
|
60
70
|
if (relevantChoices.length === 0) return false
|
|
61
71
|
|
|
@@ -71,15 +81,15 @@ function findGroup(pageContext: PageContext, choices: string[]) {
|
|
|
71
81
|
|
|
72
82
|
return true
|
|
73
83
|
})
|
|
74
|
-
assertUsage(groupName, `
|
|
84
|
+
assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`)
|
|
75
85
|
|
|
76
|
-
const mergedChoices = [...new Set([...choices, ...
|
|
86
|
+
const mergedChoices = [...new Set([...choices, ...choicesAll[groupName].choices])]
|
|
77
87
|
|
|
78
|
-
const
|
|
88
|
+
const choiceGroup = {
|
|
79
89
|
name: groupName,
|
|
80
|
-
...
|
|
90
|
+
...choicesAll[groupName],
|
|
81
91
|
choices: mergedChoices,
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
return
|
|
94
|
+
return choiceGroup
|
|
85
95
|
}
|
|
@@ -3,13 +3,11 @@ export { useMDXComponents }
|
|
|
3
3
|
import React from 'react'
|
|
4
4
|
import type { UseMdxComponents } from '@mdx-js/mdx'
|
|
5
5
|
import { Pre } from '../components/Pre.js'
|
|
6
|
-
import { CodeSnippets } from '../components/CodeSnippets.js'
|
|
7
6
|
import { ChoiceGroup } from '../components/ChoiceGroup.js'
|
|
8
7
|
|
|
9
8
|
const useMDXComponents: UseMdxComponents = () => {
|
|
10
9
|
return {
|
|
11
10
|
ChoiceGroup,
|
|
12
|
-
CodeSnippets,
|
|
13
11
|
pre: (props) => <Pre {...props} />,
|
|
14
12
|
}
|
|
15
13
|
}
|
|
@@ -1,34 +1,37 @@
|
|
|
1
1
|
export { useSelectedChoice }
|
|
2
2
|
export { initializeChoiceGroup_SSR }
|
|
3
3
|
|
|
4
|
-
import { useState } from 'react'
|
|
5
4
|
import { useLocalStorage } from './useLocalStorage'
|
|
6
5
|
|
|
7
6
|
const keyPrefix = 'docpress'
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Uses `useLocalStorage` if `persistId` is provided, otherwise regular state.
|
|
9
|
+
* Stores and retrieves a selected choice from local storage.
|
|
12
10
|
*
|
|
13
|
-
* @param
|
|
11
|
+
* @param choiceGroupName Group name for the stored choice.
|
|
14
12
|
* @param defaultValue Default choice value.
|
|
15
13
|
* @returns `[selectedChoice, setSelectedChoice]`.
|
|
16
14
|
*/
|
|
17
|
-
function useSelectedChoice(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return useLocalStorage(`${keyPrefix}:${persistId}`, defaultValue)
|
|
15
|
+
function useSelectedChoice(choiceGroupName: string, defaultValue: string) {
|
|
16
|
+
return useLocalStorage(`${keyPrefix}:choice:${choiceGroupName}`, defaultValue)
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
// WARNING: We cannot use
|
|
19
|
+
// WARNING: We cannot use `keyPrefix` here: closures don't work because we serialize the function.
|
|
24
20
|
const initializeChoiceGroup_SSR = `initializeChoiceGroup();${initializeChoiceGroup.toString()};`
|
|
25
21
|
function initializeChoiceGroup() {
|
|
26
|
-
const groupsElements = document.querySelectorAll<HTMLDivElement>('[data-group
|
|
22
|
+
const groupsElements = document.querySelectorAll<HTMLDivElement>('[data-choice-group]')
|
|
27
23
|
for (const groupEl of groupsElements) {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
const choiceGroupName = groupEl.getAttribute('data-choice-group')!
|
|
25
|
+
const storageKey = `docpress:choice:${choiceGroupName}`
|
|
26
|
+
const selectedChoice = localStorage.getItem(storageKey)
|
|
27
|
+
if (selectedChoice) {
|
|
28
|
+
const selectEl = groupEl.querySelector<HTMLSelectElement>(`.select-choice`)!
|
|
29
|
+
const selectedIndex = [...selectEl.options].findIndex((option) => option.value === selectedChoice)
|
|
30
|
+
if (selectedIndex === -1) {
|
|
31
|
+
localStorage.removeItem(storageKey)
|
|
32
|
+
} else {
|
|
33
|
+
selectEl.value = selectedChoice
|
|
34
|
+
}
|
|
35
|
+
}
|
|
33
36
|
}
|
|
34
37
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
export { remarkChoiceGroup }
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { Root } from 'mdast'
|
|
4
4
|
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
5
|
-
import type {
|
|
5
|
+
import type { ChoiceNode } from './utils/generateChoiceGroupCode.js'
|
|
6
6
|
import { visit } from 'unist-util-visit'
|
|
7
7
|
import { parseMetaString } from './rehypeMetaToProps.js'
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
type Node = Code | MdxJsxFlowElement | ContainerDirective
|
|
8
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js'
|
|
11
9
|
|
|
12
10
|
function remarkChoiceGroup() {
|
|
13
11
|
return function (tree: Root) {
|
|
@@ -18,13 +16,13 @@ function remarkChoiceGroup() {
|
|
|
18
16
|
const { choice } = meta.props
|
|
19
17
|
node.meta = meta.rest
|
|
20
18
|
|
|
21
|
-
if (choice) node.data ??= { choice }
|
|
19
|
+
if (choice) node.data ??= { customDataChoice: choice, customDataFilter: `code-${node.lang}` }
|
|
22
20
|
}
|
|
23
21
|
if (node.type === 'containerDirective' && node.name === 'Choice') {
|
|
24
22
|
if (!node.attributes) return
|
|
25
23
|
const { id: choice } = node.attributes
|
|
26
24
|
if (choice) {
|
|
27
|
-
node.data ??= { choice }
|
|
25
|
+
node.data ??= { customDataChoice: choice, customDataFilter: node.type }
|
|
28
26
|
node.attributes = {}
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -40,12 +38,12 @@ function remarkChoiceGroup() {
|
|
|
40
38
|
|
|
41
39
|
const process = () => {
|
|
42
40
|
if (start === -1 || start === end) return
|
|
43
|
-
const nodes = node.children.slice(start, end) as
|
|
44
|
-
const
|
|
41
|
+
const nodes = node.children.slice(start, end) as ChoiceNode['children']
|
|
42
|
+
const choiceNodesFiltered = filterChoices(nodes)
|
|
45
43
|
const replacements: MdxJsxFlowElement[] = []
|
|
46
44
|
|
|
47
|
-
for (const
|
|
48
|
-
const replacement =
|
|
45
|
+
for (const choiceNodes of choiceNodesFiltered) {
|
|
46
|
+
const replacement = generateChoiceGroupCode(choiceNodes)
|
|
49
47
|
|
|
50
48
|
replacements.push(replacement)
|
|
51
49
|
replaced.add(replacement)
|
|
@@ -65,7 +63,7 @@ function remarkChoiceGroup() {
|
|
|
65
63
|
continue
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
if (!child.data?.
|
|
66
|
+
if (!child.data?.customDataChoice) {
|
|
69
67
|
process()
|
|
70
68
|
continue
|
|
71
69
|
}
|
|
@@ -78,35 +76,32 @@ function remarkChoiceGroup() {
|
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function groupByNodeType(nodes: Node[]) {
|
|
87
|
-
const groupedNodes = new Set<NodeGroup[]>()
|
|
88
|
-
const filters = [...new Set(nodes.flat().map((node) => (node.type === 'code' ? node.lang! : node.name)))]
|
|
79
|
+
function filterChoices(nodes: ChoiceNode['children']) {
|
|
80
|
+
const filteredChoices = new Set<ChoiceNode[]>()
|
|
81
|
+
const filters = [...new Set(nodes.flat().map((node) => node.data!.customDataFilter!))]
|
|
89
82
|
|
|
90
83
|
filters.map((filter) => {
|
|
91
|
-
const nodesByChoice = new Map<string,
|
|
84
|
+
const nodesByChoice = new Map<string, ChoiceNode['children']>()
|
|
92
85
|
nodes
|
|
93
|
-
.filter((node) =>
|
|
86
|
+
.filter((node) => node.data!.customDataFilter! === filter)
|
|
94
87
|
.map((node) => {
|
|
95
|
-
const choice = node.data!.
|
|
88
|
+
const choice = node.data!.customDataChoice!
|
|
96
89
|
const nodes = nodesByChoice.get(choice) ?? []
|
|
90
|
+
node.data!.customDataChoice = undefined
|
|
97
91
|
nodes.push(node)
|
|
98
|
-
node.data = {}
|
|
99
92
|
nodesByChoice.set(choice, nodes)
|
|
100
93
|
})
|
|
101
94
|
|
|
102
|
-
|
|
95
|
+
const choiceNodes = [...nodesByChoice].map(([name, nodes]) => ({ choiceValue: name, children: nodes }))
|
|
96
|
+
filteredChoices.add(choiceNodes)
|
|
103
97
|
})
|
|
104
98
|
|
|
105
|
-
return [...
|
|
99
|
+
return [...filteredChoices]
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
declare module 'mdast' {
|
|
109
103
|
export interface Data {
|
|
110
|
-
|
|
104
|
+
customDataChoice?: string
|
|
105
|
+
customDataFilter?: string
|
|
111
106
|
}
|
|
112
107
|
}
|
|
@@ -6,6 +6,7 @@ import type { VFile } from '@mdx-js/mdx/internal-create-format-aware-processors'
|
|
|
6
6
|
import { visit } from 'unist-util-visit'
|
|
7
7
|
import { assertUsage } from '../utils/assert.js'
|
|
8
8
|
import { parseMetaString } from './rehypeMetaToProps.js'
|
|
9
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js'
|
|
9
10
|
import pc from '@brillout/picocolors'
|
|
10
11
|
import module from 'node:module'
|
|
11
12
|
// Cannot use `import { transform } from 'detype'` as it results in errors,
|
|
@@ -57,6 +58,9 @@ function transformYaml(node: CodeNode) {
|
|
|
57
58
|
// Skip wrapping if the YAML code block hasn't changed
|
|
58
59
|
if (codeBlockContentJs === codeBlock.value) return
|
|
59
60
|
|
|
61
|
+
const meta = parseMetaString(codeBlock.meta, ['choice'])
|
|
62
|
+
const { choice } = meta.props
|
|
63
|
+
codeBlock.meta = meta.rest
|
|
60
64
|
const { position, ...rest } = codeBlock
|
|
61
65
|
|
|
62
66
|
// Create a new code node for the JS version based on the modified YAML
|
|
@@ -65,19 +69,16 @@ function transformYaml(node: CodeNode) {
|
|
|
65
69
|
value: codeBlockContentJs,
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
// Wrap both the original YAML and `yamlJsCode` with
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
],
|
|
79
|
-
}
|
|
80
|
-
parent.children.splice(index, 1, yamlContainer)
|
|
72
|
+
// Wrap both the original YAML and `yamlJsCode` with `<ChoiceGroup>`
|
|
73
|
+
const choiceNodes = [
|
|
74
|
+
{ choiceValue: 'JavaScript', children: [yamlJsCode] },
|
|
75
|
+
{ choiceValue: 'TypeScript', children: [codeBlock] },
|
|
76
|
+
]
|
|
77
|
+
const replacement = generateChoiceGroupCode(choiceNodes)
|
|
78
|
+
replacement.attributes.push({ type: 'mdxJsxAttribute', name: 'hide' })
|
|
79
|
+
replacement.data ??= { customDataChoice: choice, customDataFilter: 'codeLang' }
|
|
80
|
+
|
|
81
|
+
parent.children.splice(index, 1, replacement)
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
@@ -87,6 +88,9 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
87
88
|
const { choice } = meta.props
|
|
88
89
|
codeBlock.meta = meta.rest
|
|
89
90
|
|
|
91
|
+
codeBlock.data ??= { customDataChoice: choice, customDataFilter: 'codeLang' }
|
|
92
|
+
if (choice === 'TypeScript') return
|
|
93
|
+
|
|
90
94
|
let codeBlockReplacedJs = replaceFileNameSuffixes(codeBlock.value)
|
|
91
95
|
let codeBlockContentJs = ''
|
|
92
96
|
|
|
@@ -127,9 +131,9 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
127
131
|
// No wrapping needed if JS and TS code are still identical
|
|
128
132
|
if (codeBlockContentJs === codeBlock.value) return
|
|
129
133
|
|
|
130
|
-
const { position, lang, ...rest } = codeBlock
|
|
131
|
-
const attributes: MdxJsxFlowElement['attributes'] = []
|
|
134
|
+
const { position, lang, data, ...rest } = codeBlock
|
|
132
135
|
|
|
136
|
+
const tsCode: Code = { ...rest, lang }
|
|
133
137
|
const jsCode: Code = {
|
|
134
138
|
...rest,
|
|
135
139
|
// The jsCode lang should be js|jsx|vue
|
|
@@ -137,25 +141,20 @@ async function transformTsToJs(node: CodeNode, file: VFile) {
|
|
|
137
141
|
value: codeBlockContentJs,
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
144
|
+
// Wrap both `tsCode` and `jsCode` with `<ChoiceGroup>`
|
|
145
|
+
const choiceNodes = [
|
|
146
|
+
{ choiceValue: 'JavaScript', children: [jsCode] },
|
|
147
|
+
{ choiceValue: 'TypeScript', children: [tsCode] },
|
|
148
|
+
]
|
|
149
|
+
const replacement = generateChoiceGroupCode(choiceNodes)
|
|
147
150
|
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
type: '
|
|
151
|
-
name: 'CodeSnippets',
|
|
152
|
-
children: [jsCode, codeBlock],
|
|
153
|
-
attributes,
|
|
151
|
+
// Add `hide` attribute (prop) to `<ChoiceGroup>` if the only change was replacing `.ts` with `.js`
|
|
152
|
+
if (codeBlockReplacedJs === codeBlockContentJs) {
|
|
153
|
+
replacement.attributes.push({ type: 'mdxJsxAttribute', name: 'hide' })
|
|
154
154
|
}
|
|
155
|
+
replacement.data ??= { ...data }
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
parent.children.splice(index, 1, container)
|
|
157
|
+
parent.children.splice(index, 1, replacement)
|
|
159
158
|
}
|
|
160
159
|
|
|
161
160
|
// Replace all '.ts' extensions with '.js'
|
|
@@ -170,8 +169,8 @@ function cleanUpCode(code: string, isJsCode: boolean = false) {
|
|
|
170
169
|
return processMagicComments(code)
|
|
171
170
|
}
|
|
172
171
|
function processMagicComments(code: string) {
|
|
173
|
-
// @
|
|
174
|
-
const renameCommentRE = /^\s*\/\/\s@
|
|
172
|
+
// @docpress-replace DummyLayout Layout
|
|
173
|
+
const renameCommentRE = /^\s*\/\/\s@docpress-replace\s([^ ]+) ([^ ]+)\n/gm
|
|
175
174
|
const matches = Array.from(code.matchAll(renameCommentRE))
|
|
176
175
|
|
|
177
176
|
if (matches.length) {
|
|
@@ -181,7 +180,7 @@ function processMagicComments(code: string) {
|
|
|
181
180
|
code = code.split(fullMatch).join('').replaceAll(renameFrom, renameTo)
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
|
-
code = code.replaceAll('// @
|
|
183
|
+
code = code.replaceAll('// @docpress-uncomment ', '')
|
|
185
184
|
|
|
186
185
|
return code
|
|
187
186
|
}
|
|
@@ -4,9 +4,9 @@ import type { Code, Root } from 'mdast'
|
|
|
4
4
|
import { visit } from 'unist-util-visit'
|
|
5
5
|
import convert from 'npm-to-yarn'
|
|
6
6
|
import { parseMetaString } from './rehypeMetaToProps.js'
|
|
7
|
-
import {
|
|
7
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js'
|
|
8
8
|
|
|
9
|
-
const PKG_MANAGERS = ['pnpm', '
|
|
9
|
+
const PKG_MANAGERS = ['pnpm', 'Bun', 'Yarn'] as const
|
|
10
10
|
|
|
11
11
|
function remarkPkgManager() {
|
|
12
12
|
return function (tree: Root) {
|
|
@@ -14,7 +14,6 @@ function remarkPkgManager() {
|
|
|
14
14
|
if (!parent || typeof index === 'undefined') return
|
|
15
15
|
if (!['sh', 'shell'].includes(node.lang || '')) return
|
|
16
16
|
if (node.value.indexOf('npm') === -1 && node.value.indexOf('npx') === -1) return
|
|
17
|
-
|
|
18
17
|
let choice: string | undefined = undefined
|
|
19
18
|
const nodes = new Map<string, Code>()
|
|
20
19
|
|
|
@@ -24,6 +23,7 @@ function remarkPkgManager() {
|
|
|
24
23
|
node.meta = meta.rest
|
|
25
24
|
}
|
|
26
25
|
|
|
26
|
+
node.value = node.value.replaceAll('npm i ', 'npm install ')
|
|
27
27
|
nodes.set('npm', node)
|
|
28
28
|
|
|
29
29
|
for (const pm of PKG_MANAGERS) {
|
|
@@ -31,14 +31,14 @@ function remarkPkgManager() {
|
|
|
31
31
|
type: node.type,
|
|
32
32
|
lang: node.lang,
|
|
33
33
|
meta: node.meta,
|
|
34
|
-
value: convert(node.value, pm),
|
|
34
|
+
value: convert(node.value, pm.toLowerCase() as 'pnpm' | 'bun' | 'yarn'),
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const
|
|
39
|
-
const replacement =
|
|
38
|
+
const choiceNodes = [...nodes].map(([name, node]) => ({ choiceValue: name, children: [node] }))
|
|
39
|
+
const replacement = generateChoiceGroupCode(choiceNodes)
|
|
40
40
|
|
|
41
|
-
replacement.data ??= { choice }
|
|
41
|
+
replacement.data ??= { customDataChoice: choice, customDataFilter: replacement.type }
|
|
42
42
|
parent.children.splice(index, 1, replacement)
|
|
43
43
|
})
|
|
44
44
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export { generateChoiceGroupCode }
|
|
2
|
+
export type { ChoiceNode }
|
|
3
|
+
|
|
4
|
+
import type { BlockContent, DefinitionContent } from 'mdast'
|
|
5
|
+
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
6
|
+
|
|
7
|
+
type ChoiceNode = {
|
|
8
|
+
choiceValue: string
|
|
9
|
+
children: (BlockContent | DefinitionContent)[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function generateChoiceGroupCode(choiceNodes: ChoiceNode[]): MdxJsxFlowElement {
|
|
13
|
+
const attributes: MdxJsxAttribute[] = []
|
|
14
|
+
const children: MdxJsxFlowElement[] = []
|
|
15
|
+
|
|
16
|
+
const elements = choiceNodes.map((choiceNode) => ({
|
|
17
|
+
type: 'Literal',
|
|
18
|
+
value: findVisibleJsDropdown(choiceNode.children[0])
|
|
19
|
+
? `${choiceNode.choiceValue}:jsDropdown`
|
|
20
|
+
: choiceNode.choiceValue,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
attributes.push({
|
|
24
|
+
type: 'mdxJsxAttribute',
|
|
25
|
+
name: 'choices',
|
|
26
|
+
value: {
|
|
27
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
28
|
+
value: '',
|
|
29
|
+
data: {
|
|
30
|
+
estree: {
|
|
31
|
+
type: 'Program',
|
|
32
|
+
sourceType: 'module',
|
|
33
|
+
comments: [],
|
|
34
|
+
body: [
|
|
35
|
+
{
|
|
36
|
+
type: 'ExpressionStatement',
|
|
37
|
+
expression: {
|
|
38
|
+
type: 'ArrayExpression',
|
|
39
|
+
// @ts-ignore: Missing properties in type definition
|
|
40
|
+
elements,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
for (const choiceNode of choiceNodes) {
|
|
50
|
+
children.push({
|
|
51
|
+
type: 'mdxJsxFlowElement',
|
|
52
|
+
name: 'div',
|
|
53
|
+
attributes: [
|
|
54
|
+
{ type: 'mdxJsxAttribute', name: 'data-choice-value', value: choiceNode.choiceValue },
|
|
55
|
+
{ type: 'mdxJsxAttribute', name: 'className', value: 'choice' },
|
|
56
|
+
],
|
|
57
|
+
children: choiceNode.children.every((node) => node.type === 'containerDirective')
|
|
58
|
+
? choiceNode.children.flatMap((node) => [...node.children])
|
|
59
|
+
: choiceNode.children,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
type: 'mdxJsxFlowElement',
|
|
65
|
+
name: 'ChoiceGroup',
|
|
66
|
+
attributes,
|
|
67
|
+
children,
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function findVisibleJsDropdown(node: BlockContent | DefinitionContent) {
|
|
72
|
+
let currentNode = node
|
|
73
|
+
if (node.type === 'containerDirective' && node.name === 'Choice') currentNode = node.children[0]
|
|
74
|
+
return (
|
|
75
|
+
currentNode.type === 'mdxJsxFlowElement' &&
|
|
76
|
+
currentNode.data?.customDataFilter === 'codeLang' &&
|
|
77
|
+
currentNode.attributes.every((attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hide')
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { remarkChoiceGroup };
|
|
2
2
|
import { visit } from 'unist-util-visit';
|
|
3
3
|
import { parseMetaString } from './rehypeMetaToProps.js';
|
|
4
|
-
import {
|
|
4
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js';
|
|
5
5
|
function remarkChoiceGroup() {
|
|
6
6
|
return function (tree) {
|
|
7
7
|
visit(tree, (node) => {
|
|
@@ -12,14 +12,14 @@ function remarkChoiceGroup() {
|
|
|
12
12
|
const { choice } = meta.props;
|
|
13
13
|
node.meta = meta.rest;
|
|
14
14
|
if (choice)
|
|
15
|
-
node.data ??= { choice };
|
|
15
|
+
node.data ??= { customDataChoice: choice, customDataFilter: `code-${node.lang}` };
|
|
16
16
|
}
|
|
17
17
|
if (node.type === 'containerDirective' && node.name === 'Choice') {
|
|
18
18
|
if (!node.attributes)
|
|
19
19
|
return;
|
|
20
20
|
const { id: choice } = node.attributes;
|
|
21
21
|
if (choice) {
|
|
22
|
-
node.data ??= { choice };
|
|
22
|
+
node.data ??= { customDataChoice: choice, customDataFilter: node.type };
|
|
23
23
|
node.attributes = {};
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -36,10 +36,10 @@ function remarkChoiceGroup() {
|
|
|
36
36
|
if (start === -1 || start === end)
|
|
37
37
|
return;
|
|
38
38
|
const nodes = node.children.slice(start, end);
|
|
39
|
-
const
|
|
39
|
+
const choiceNodesFiltered = filterChoices(nodes);
|
|
40
40
|
const replacements = [];
|
|
41
|
-
for (const
|
|
42
|
-
const replacement =
|
|
41
|
+
for (const choiceNodes of choiceNodesFiltered) {
|
|
42
|
+
const replacement = generateChoiceGroupCode(choiceNodes);
|
|
43
43
|
replacements.push(replacement);
|
|
44
44
|
replaced.add(replacement);
|
|
45
45
|
}
|
|
@@ -53,7 +53,7 @@ function remarkChoiceGroup() {
|
|
|
53
53
|
process();
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
if (!child.data?.
|
|
56
|
+
if (!child.data?.customDataChoice) {
|
|
57
57
|
process();
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
@@ -64,21 +64,22 @@ function remarkChoiceGroup() {
|
|
|
64
64
|
});
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
-
function
|
|
68
|
-
const
|
|
69
|
-
const filters = [...new Set(nodes.flat().map((node) =>
|
|
67
|
+
function filterChoices(nodes) {
|
|
68
|
+
const filteredChoices = new Set();
|
|
69
|
+
const filters = [...new Set(nodes.flat().map((node) => node.data.customDataFilter))];
|
|
70
70
|
filters.map((filter) => {
|
|
71
71
|
const nodesByChoice = new Map();
|
|
72
72
|
nodes
|
|
73
|
-
.filter((node) =>
|
|
73
|
+
.filter((node) => node.data.customDataFilter === filter)
|
|
74
74
|
.map((node) => {
|
|
75
|
-
const choice = node.data.
|
|
75
|
+
const choice = node.data.customDataChoice;
|
|
76
76
|
const nodes = nodesByChoice.get(choice) ?? [];
|
|
77
|
+
node.data.customDataChoice = undefined;
|
|
77
78
|
nodes.push(node);
|
|
78
|
-
node.data = {};
|
|
79
79
|
nodesByChoice.set(choice, nodes);
|
|
80
80
|
});
|
|
81
|
-
|
|
81
|
+
const choiceNodes = [...nodesByChoice].map(([name, nodes]) => ({ choiceValue: name, children: nodes }));
|
|
82
|
+
filteredChoices.add(choiceNodes);
|
|
82
83
|
});
|
|
83
|
-
return [...
|
|
84
|
+
return [...filteredChoices];
|
|
84
85
|
}
|
|
@@ -2,6 +2,7 @@ export { remarkDetype };
|
|
|
2
2
|
import { visit } from 'unist-util-visit';
|
|
3
3
|
import { assertUsage } from '../utils/assert.js';
|
|
4
4
|
import { parseMetaString } from './rehypeMetaToProps.js';
|
|
5
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js';
|
|
5
6
|
import pc from '@brillout/picocolors';
|
|
6
7
|
import module from 'node:module';
|
|
7
8
|
// Cannot use `import { transform } from 'detype'` as it results in errors,
|
|
@@ -43,25 +44,24 @@ function transformYaml(node) {
|
|
|
43
44
|
// Skip wrapping if the YAML code block hasn't changed
|
|
44
45
|
if (codeBlockContentJs === codeBlock.value)
|
|
45
46
|
return;
|
|
47
|
+
const meta = parseMetaString(codeBlock.meta, ['choice']);
|
|
48
|
+
const { choice } = meta.props;
|
|
49
|
+
codeBlock.meta = meta.rest;
|
|
46
50
|
const { position, ...rest } = codeBlock;
|
|
47
51
|
// Create a new code node for the JS version based on the modified YAML
|
|
48
52
|
const yamlJsCode = {
|
|
49
53
|
...rest,
|
|
50
54
|
value: codeBlockContentJs,
|
|
51
55
|
};
|
|
52
|
-
// Wrap both the original YAML and `yamlJsCode` with
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
};
|
|
64
|
-
parent.children.splice(index, 1, yamlContainer);
|
|
56
|
+
// Wrap both the original YAML and `yamlJsCode` with `<ChoiceGroup>`
|
|
57
|
+
const choiceNodes = [
|
|
58
|
+
{ choiceValue: 'JavaScript', children: [yamlJsCode] },
|
|
59
|
+
{ choiceValue: 'TypeScript', children: [codeBlock] },
|
|
60
|
+
];
|
|
61
|
+
const replacement = generateChoiceGroupCode(choiceNodes);
|
|
62
|
+
replacement.attributes.push({ type: 'mdxJsxAttribute', name: 'hide' });
|
|
63
|
+
replacement.data ??= { customDataChoice: choice, customDataFilter: 'codeLang' };
|
|
64
|
+
parent.children.splice(index, 1, replacement);
|
|
65
65
|
}
|
|
66
66
|
async function transformTsToJs(node, file) {
|
|
67
67
|
const { codeBlock, index, parent } = node;
|
|
@@ -69,6 +69,9 @@ async function transformTsToJs(node, file) {
|
|
|
69
69
|
const maxWidth = Number(meta.props['max-width']);
|
|
70
70
|
const { choice } = meta.props;
|
|
71
71
|
codeBlock.meta = meta.rest;
|
|
72
|
+
codeBlock.data ??= { customDataChoice: choice, customDataFilter: 'codeLang' };
|
|
73
|
+
if (choice === 'TypeScript')
|
|
74
|
+
return;
|
|
72
75
|
let codeBlockReplacedJs = replaceFileNameSuffixes(codeBlock.value);
|
|
73
76
|
let codeBlockContentJs = '';
|
|
74
77
|
// Remove TypeScript from the TS/TSX/Vue code node
|
|
@@ -105,31 +108,26 @@ async function transformTsToJs(node, file) {
|
|
|
105
108
|
// No wrapping needed if JS and TS code are still identical
|
|
106
109
|
if (codeBlockContentJs === codeBlock.value)
|
|
107
110
|
return;
|
|
108
|
-
const { position, lang, ...rest } = codeBlock;
|
|
109
|
-
const
|
|
111
|
+
const { position, lang, data, ...rest } = codeBlock;
|
|
112
|
+
const tsCode = { ...rest, lang };
|
|
110
113
|
const jsCode = {
|
|
111
114
|
...rest,
|
|
112
115
|
// The jsCode lang should be js|jsx|vue
|
|
113
116
|
lang: lang.replace('t', 'j'),
|
|
114
117
|
value: codeBlockContentJs,
|
|
115
118
|
};
|
|
116
|
-
//
|
|
119
|
+
// Wrap both `tsCode` and `jsCode` with `<ChoiceGroup>`
|
|
120
|
+
const choiceNodes = [
|
|
121
|
+
{ choiceValue: 'JavaScript', children: [jsCode] },
|
|
122
|
+
{ choiceValue: 'TypeScript', children: [tsCode] },
|
|
123
|
+
];
|
|
124
|
+
const replacement = generateChoiceGroupCode(choiceNodes);
|
|
125
|
+
// Add `hide` attribute (prop) to `<ChoiceGroup>` if the only change was replacing `.ts` with `.js`
|
|
117
126
|
if (codeBlockReplacedJs === codeBlockContentJs) {
|
|
118
|
-
attributes.push({
|
|
119
|
-
type: 'mdxJsxAttribute',
|
|
120
|
-
name: 'hideToggle',
|
|
121
|
-
});
|
|
127
|
+
replacement.attributes.push({ type: 'mdxJsxAttribute', name: 'hide' });
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
type: 'mdxJsxFlowElement',
|
|
126
|
-
name: 'CodeSnippets',
|
|
127
|
-
children: [jsCode, codeBlock],
|
|
128
|
-
attributes,
|
|
129
|
-
};
|
|
130
|
-
if (choice)
|
|
131
|
-
container.data ??= { choice };
|
|
132
|
-
parent.children.splice(index, 1, container);
|
|
129
|
+
replacement.data ??= { ...data };
|
|
130
|
+
parent.children.splice(index, 1, replacement);
|
|
133
131
|
}
|
|
134
132
|
// Replace all '.ts' extensions with '.js'
|
|
135
133
|
function replaceFileNameSuffixes(codeBlockValue) {
|
|
@@ -142,8 +140,8 @@ function cleanUpCode(code, isJsCode = false) {
|
|
|
142
140
|
return processMagicComments(code);
|
|
143
141
|
}
|
|
144
142
|
function processMagicComments(code) {
|
|
145
|
-
// @
|
|
146
|
-
const renameCommentRE = /^\s*\/\/\s@
|
|
143
|
+
// @docpress-replace DummyLayout Layout
|
|
144
|
+
const renameCommentRE = /^\s*\/\/\s@docpress-replace\s([^ ]+) ([^ ]+)\n/gm;
|
|
147
145
|
const matches = Array.from(code.matchAll(renameCommentRE));
|
|
148
146
|
if (matches.length) {
|
|
149
147
|
for (let i = matches.length - 1; i >= 0; i--) {
|
|
@@ -152,7 +150,7 @@ function processMagicComments(code) {
|
|
|
152
150
|
code = code.split(fullMatch).join('').replaceAll(renameFrom, renameTo);
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
|
-
code = code.replaceAll('// @
|
|
153
|
+
code = code.replaceAll('// @docpress-uncomment ', '');
|
|
156
154
|
return code;
|
|
157
155
|
}
|
|
158
156
|
/**
|
|
@@ -2,8 +2,8 @@ export { remarkPkgManager };
|
|
|
2
2
|
import { visit } from 'unist-util-visit';
|
|
3
3
|
import convert from 'npm-to-yarn';
|
|
4
4
|
import { parseMetaString } from './rehypeMetaToProps.js';
|
|
5
|
-
import {
|
|
6
|
-
const PKG_MANAGERS = ['pnpm', '
|
|
5
|
+
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js';
|
|
6
|
+
const PKG_MANAGERS = ['pnpm', 'Bun', 'Yarn'];
|
|
7
7
|
function remarkPkgManager() {
|
|
8
8
|
return function (tree) {
|
|
9
9
|
visit(tree, 'code', (node, index, parent) => {
|
|
@@ -20,18 +20,19 @@ function remarkPkgManager() {
|
|
|
20
20
|
choice = meta.props['choice'];
|
|
21
21
|
node.meta = meta.rest;
|
|
22
22
|
}
|
|
23
|
+
node.value = node.value.replaceAll('npm i ', 'npm install ');
|
|
23
24
|
nodes.set('npm', node);
|
|
24
25
|
for (const pm of PKG_MANAGERS) {
|
|
25
26
|
nodes.set(pm, {
|
|
26
27
|
type: node.type,
|
|
27
28
|
lang: node.lang,
|
|
28
29
|
meta: node.meta,
|
|
29
|
-
value: convert(node.value, pm),
|
|
30
|
+
value: convert(node.value, pm.toLowerCase()),
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
|
-
const
|
|
33
|
-
const replacement =
|
|
34
|
-
replacement.data ??= { choice };
|
|
33
|
+
const choiceNodes = [...nodes].map(([name, node]) => ({ choiceValue: name, children: [node] }));
|
|
34
|
+
const replacement = generateChoiceGroupCode(choiceNodes);
|
|
35
|
+
replacement.data ??= { customDataChoice: choice, customDataFilter: replacement.type };
|
|
35
36
|
parent.children.splice(index, 1, replacement);
|
|
36
37
|
});
|
|
37
38
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { generateChoiceGroupCode };
|
|
2
|
+
export type { ChoiceNode };
|
|
3
|
+
import type { BlockContent, DefinitionContent } from 'mdast';
|
|
4
|
+
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
|
5
|
+
type ChoiceNode = {
|
|
6
|
+
choiceValue: string;
|
|
7
|
+
children: (BlockContent | DefinitionContent)[];
|
|
8
|
+
};
|
|
9
|
+
declare function generateChoiceGroupCode(choiceNodes: ChoiceNode[]): MdxJsxFlowElement;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export { generateChoiceGroupCode };
|
|
2
|
+
function generateChoiceGroupCode(choiceNodes) {
|
|
3
|
+
const attributes = [];
|
|
4
|
+
const children = [];
|
|
5
|
+
const elements = choiceNodes.map((choiceNode) => ({
|
|
6
|
+
type: 'Literal',
|
|
7
|
+
value: findVisibleJsDropdown(choiceNode.children[0])
|
|
8
|
+
? `${choiceNode.choiceValue}:jsDropdown`
|
|
9
|
+
: choiceNode.choiceValue,
|
|
10
|
+
}));
|
|
11
|
+
attributes.push({
|
|
12
|
+
type: 'mdxJsxAttribute',
|
|
13
|
+
name: 'choices',
|
|
14
|
+
value: {
|
|
15
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
16
|
+
value: '',
|
|
17
|
+
data: {
|
|
18
|
+
estree: {
|
|
19
|
+
type: 'Program',
|
|
20
|
+
sourceType: 'module',
|
|
21
|
+
comments: [],
|
|
22
|
+
body: [
|
|
23
|
+
{
|
|
24
|
+
type: 'ExpressionStatement',
|
|
25
|
+
expression: {
|
|
26
|
+
type: 'ArrayExpression',
|
|
27
|
+
// @ts-ignore: Missing properties in type definition
|
|
28
|
+
elements,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
for (const choiceNode of choiceNodes) {
|
|
37
|
+
children.push({
|
|
38
|
+
type: 'mdxJsxFlowElement',
|
|
39
|
+
name: 'div',
|
|
40
|
+
attributes: [
|
|
41
|
+
{ type: 'mdxJsxAttribute', name: 'data-choice-value', value: choiceNode.choiceValue },
|
|
42
|
+
{ type: 'mdxJsxAttribute', name: 'className', value: 'choice' },
|
|
43
|
+
],
|
|
44
|
+
children: choiceNode.children.every((node) => node.type === 'containerDirective')
|
|
45
|
+
? choiceNode.children.flatMap((node) => [...node.children])
|
|
46
|
+
: choiceNode.children,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
type: 'mdxJsxFlowElement',
|
|
51
|
+
name: 'ChoiceGroup',
|
|
52
|
+
attributes,
|
|
53
|
+
children,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function findVisibleJsDropdown(node) {
|
|
57
|
+
let currentNode = node;
|
|
58
|
+
if (node.type === 'containerDirective' && node.name === 'Choice')
|
|
59
|
+
currentNode = node.children[0];
|
|
60
|
+
return (currentNode.type === 'mdxJsxFlowElement' &&
|
|
61
|
+
currentNode.data?.customDataFilter === 'codeLang' &&
|
|
62
|
+
currentNode.attributes.every((attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hide'));
|
|
63
|
+
}
|
package/index.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/* PUBLIC */
|
|
3
3
|
/**********/
|
|
4
4
|
export { CodeBlockTransformer, Link, RepoLink, FileAdded, FileRemoved, ImportMeta, Emoji } from './components'
|
|
5
|
-
export { TypescriptOnly } from './code-blocks/components/CodeSnippets'
|
|
6
5
|
export { MenuToggle } from './Layout'
|
|
7
6
|
export * from './components/Note'
|
|
8
7
|
export * from './icons/index'
|
package/package.json
CHANGED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
.code-snippets {
|
|
2
|
-
position: relative;
|
|
3
|
-
|
|
4
|
-
&:hover {
|
|
5
|
-
.copy-button,
|
|
6
|
-
.code-lang-toggle {
|
|
7
|
-
opacity: 1;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/* Language toggle styles */
|
|
12
|
-
.code-lang-toggle {
|
|
13
|
-
position: absolute !important;
|
|
14
|
-
top: 10px;
|
|
15
|
-
right: 42px;
|
|
16
|
-
z-index: 3;
|
|
17
|
-
|
|
18
|
-
/* Checkbox appearance reset */
|
|
19
|
-
appearance: none;
|
|
20
|
-
-webkit-appearance: none;
|
|
21
|
-
-moz-appearance: none;
|
|
22
|
-
|
|
23
|
-
margin: 0;
|
|
24
|
-
padding: 0 4px;
|
|
25
|
-
height: 25px;
|
|
26
|
-
width: 59px;
|
|
27
|
-
display: flex;
|
|
28
|
-
background-color: #f7f7f7;
|
|
29
|
-
opacity: 0;
|
|
30
|
-
transition: opacity 0.5s ease-in-out, background-color 0.4s ease-in-out;
|
|
31
|
-
|
|
32
|
-
&:not(:hover) {
|
|
33
|
-
background-color: #eee;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* Toggle Labels */
|
|
37
|
-
&::before,
|
|
38
|
-
&::after {
|
|
39
|
-
font-family: 'Inter', sans-serif;
|
|
40
|
-
width: 24px;
|
|
41
|
-
display: flex;
|
|
42
|
-
justify-content: center;
|
|
43
|
-
align-items: center;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
&::before {
|
|
47
|
-
content: 'JS';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
&::after {
|
|
51
|
-
content: 'TS';
|
|
52
|
-
border-left: none;
|
|
53
|
-
opacity: 0.3;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
&:checked {
|
|
57
|
-
&::before {
|
|
58
|
-
opacity: 0.3;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
&::after {
|
|
62
|
-
opacity: 1;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/* Code block visibility based on toggle */
|
|
68
|
-
&:has(.code-lang-toggle:checked) figure:first-of-type {
|
|
69
|
-
display: none;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
&:has(.code-lang-toggle:not(:checked)) figure:last-of-type {
|
|
73
|
-
display: none;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
// Public
|
|
2
|
-
export { TypescriptOnly }
|
|
3
|
-
|
|
4
|
-
// Internal
|
|
5
|
-
export { CodeSnippets }
|
|
6
|
-
|
|
7
|
-
import React from 'react'
|
|
8
|
-
import { useSelectCodeLang } from '../hooks/useSelectCodeLang'
|
|
9
|
-
import { useRestoreScroll } from '../hooks/useRestoreScroll'
|
|
10
|
-
import './CodeSnippets.css'
|
|
11
|
-
|
|
12
|
-
/** Only show if TypeScript is selected */
|
|
13
|
-
function TypescriptOnly({ children }: { children: React.ReactNode }) {
|
|
14
|
-
const [codeLangSelected] = useSelectCodeLang()
|
|
15
|
-
return <div style={{ display: codeLangSelected === 'ts' ? 'block' : 'none' }}>{children}</div>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function CodeSnippets({ children, hideToggle = false }: { children: React.ReactNode; hideToggle: boolean }) {
|
|
19
|
-
const [codeLangSelected, selectCodeLang] = useSelectCodeLang()
|
|
20
|
-
const prevPositionRef = useRestoreScroll([codeLangSelected])
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div className="code-snippets">
|
|
24
|
-
<input
|
|
25
|
-
type="checkbox"
|
|
26
|
-
name="code-lang-toggle"
|
|
27
|
-
className="code-lang-toggle raised"
|
|
28
|
-
style={{ display: hideToggle ? 'none' : undefined }}
|
|
29
|
-
checked={codeLangSelected === 'ts'}
|
|
30
|
-
onChange={onChange}
|
|
31
|
-
title="Toggle language"
|
|
32
|
-
/>
|
|
33
|
-
{children}
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
37
|
-
const element = e.target
|
|
38
|
-
prevPositionRef.current = { top: element.getBoundingClientRect().top, el: element }
|
|
39
|
-
selectCodeLang(element.checked ? 'ts' : 'js')
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export { useSelectCodeLang }
|
|
2
|
-
export { initializeJsToggle_SSR }
|
|
3
|
-
|
|
4
|
-
import { useLocalStorage } from './useLocalStorage'
|
|
5
|
-
|
|
6
|
-
const storageKey = 'docpress:code-lang'
|
|
7
|
-
const codeLangDefaultSsr = 'ts'
|
|
8
|
-
const codeLangDefaultClient = 'js'
|
|
9
|
-
|
|
10
|
-
function useSelectCodeLang() {
|
|
11
|
-
return useLocalStorage(storageKey, codeLangDefaultClient, codeLangDefaultSsr)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// WARNING: We cannot use the variables storageKey nor codeLangDefaultClient here: closures
|
|
15
|
-
// don't work because we serialize the function.
|
|
16
|
-
// WARNING: We cannot use TypeScript here, for the same reason.
|
|
17
|
-
const initializeJsToggle_SSR = `initializeJsToggle();${initializeJsToggle.toString()};`
|
|
18
|
-
function initializeJsToggle() {
|
|
19
|
-
const codeLangSelected = localStorage.getItem('docpress:code-lang') ?? 'js'
|
|
20
|
-
if (codeLangSelected === 'js') {
|
|
21
|
-
const inputs = document.querySelectorAll('.code-lang-toggle')
|
|
22
|
-
// @ts-ignore
|
|
23
|
-
for (const input of inputs) input.checked = false
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export { generateChoiceGroup }
|
|
2
|
-
export type { CodeChoice }
|
|
3
|
-
|
|
4
|
-
import type { BlockContent, DefinitionContent } from 'mdast'
|
|
5
|
-
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
6
|
-
|
|
7
|
-
type CodeChoice = {
|
|
8
|
-
value: string
|
|
9
|
-
children: (BlockContent | DefinitionContent)[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function generateChoiceGroup(codeChoices: CodeChoice[]): MdxJsxFlowElement {
|
|
13
|
-
const attributes: MdxJsxAttribute[] = []
|
|
14
|
-
const children: MdxJsxFlowElement[] = []
|
|
15
|
-
|
|
16
|
-
attributes.push({
|
|
17
|
-
type: 'mdxJsxAttribute',
|
|
18
|
-
name: 'choices',
|
|
19
|
-
value: {
|
|
20
|
-
type: 'mdxJsxAttributeValueExpression',
|
|
21
|
-
value: '',
|
|
22
|
-
data: {
|
|
23
|
-
estree: {
|
|
24
|
-
type: 'Program',
|
|
25
|
-
sourceType: 'module',
|
|
26
|
-
comments: [],
|
|
27
|
-
body: [
|
|
28
|
-
{
|
|
29
|
-
type: 'ExpressionStatement',
|
|
30
|
-
expression: {
|
|
31
|
-
type: 'ArrayExpression',
|
|
32
|
-
// @ts-ignore: Missing properties in type definition
|
|
33
|
-
elements: codeChoices.map((choice) => ({
|
|
34
|
-
type: 'Literal',
|
|
35
|
-
value: choice.value,
|
|
36
|
-
})),
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
for (const codeChoice of codeChoices) {
|
|
46
|
-
const classNames = ['choice']
|
|
47
|
-
if (findHasJsToggle(codeChoice.children[0])) {
|
|
48
|
-
classNames.push('has-toggle')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
children.push({
|
|
52
|
-
type: 'mdxJsxFlowElement',
|
|
53
|
-
name: 'div',
|
|
54
|
-
attributes: [
|
|
55
|
-
{ type: 'mdxJsxAttribute', name: 'id', value: codeChoice.value },
|
|
56
|
-
{ type: 'mdxJsxAttribute', name: 'className', value: classNames.join(' ') },
|
|
57
|
-
],
|
|
58
|
-
children: codeChoice.children.every((node) => node.type === 'containerDirective')
|
|
59
|
-
? codeChoice.children.flatMap((node) => [...node.children])
|
|
60
|
-
: codeChoice.children,
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
type: 'mdxJsxFlowElement',
|
|
66
|
-
name: 'ChoiceGroup',
|
|
67
|
-
attributes,
|
|
68
|
-
children,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function findHasJsToggle(node: BlockContent | DefinitionContent) {
|
|
73
|
-
if (node.type === 'containerDirective' && node.name === 'Choice') {
|
|
74
|
-
return (
|
|
75
|
-
node.children[0].type === 'mdxJsxFlowElement' &&
|
|
76
|
-
node.children[0].name === 'CodeSnippets' &&
|
|
77
|
-
node.children[0].attributes.every(
|
|
78
|
-
(attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hideToggle',
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
return (
|
|
83
|
-
node.type === 'mdxJsxFlowElement' &&
|
|
84
|
-
node.name === 'CodeSnippets' &&
|
|
85
|
-
node.attributes.every((attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hideToggle')
|
|
86
|
-
)
|
|
87
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { generateChoiceGroup };
|
|
2
|
-
export type { CodeChoice };
|
|
3
|
-
import type { BlockContent, DefinitionContent } from 'mdast';
|
|
4
|
-
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
|
5
|
-
type CodeChoice = {
|
|
6
|
-
value: string;
|
|
7
|
-
children: (BlockContent | DefinitionContent)[];
|
|
8
|
-
};
|
|
9
|
-
declare function generateChoiceGroup(codeChoices: CodeChoice[]): MdxJsxFlowElement;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export { generateChoiceGroup };
|
|
2
|
-
function generateChoiceGroup(codeChoices) {
|
|
3
|
-
const attributes = [];
|
|
4
|
-
const children = [];
|
|
5
|
-
attributes.push({
|
|
6
|
-
type: 'mdxJsxAttribute',
|
|
7
|
-
name: 'choices',
|
|
8
|
-
value: {
|
|
9
|
-
type: 'mdxJsxAttributeValueExpression',
|
|
10
|
-
value: '',
|
|
11
|
-
data: {
|
|
12
|
-
estree: {
|
|
13
|
-
type: 'Program',
|
|
14
|
-
sourceType: 'module',
|
|
15
|
-
comments: [],
|
|
16
|
-
body: [
|
|
17
|
-
{
|
|
18
|
-
type: 'ExpressionStatement',
|
|
19
|
-
expression: {
|
|
20
|
-
type: 'ArrayExpression',
|
|
21
|
-
// @ts-ignore: Missing properties in type definition
|
|
22
|
-
elements: codeChoices.map((choice) => ({
|
|
23
|
-
type: 'Literal',
|
|
24
|
-
value: choice.value,
|
|
25
|
-
})),
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
for (const codeChoice of codeChoices) {
|
|
34
|
-
const classNames = ['choice'];
|
|
35
|
-
if (findHasJsToggle(codeChoice.children[0])) {
|
|
36
|
-
classNames.push('has-toggle');
|
|
37
|
-
}
|
|
38
|
-
children.push({
|
|
39
|
-
type: 'mdxJsxFlowElement',
|
|
40
|
-
name: 'div',
|
|
41
|
-
attributes: [
|
|
42
|
-
{ type: 'mdxJsxAttribute', name: 'id', value: codeChoice.value },
|
|
43
|
-
{ type: 'mdxJsxAttribute', name: 'className', value: classNames.join(' ') },
|
|
44
|
-
],
|
|
45
|
-
children: codeChoice.children.every((node) => node.type === 'containerDirective')
|
|
46
|
-
? codeChoice.children.flatMap((node) => [...node.children])
|
|
47
|
-
: codeChoice.children,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
type: 'mdxJsxFlowElement',
|
|
52
|
-
name: 'ChoiceGroup',
|
|
53
|
-
attributes,
|
|
54
|
-
children,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
function findHasJsToggle(node) {
|
|
58
|
-
if (node.type === 'containerDirective' && node.name === 'Choice') {
|
|
59
|
-
return (node.children[0].type === 'mdxJsxFlowElement' &&
|
|
60
|
-
node.children[0].name === 'CodeSnippets' &&
|
|
61
|
-
node.children[0].attributes.every((attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hideToggle'));
|
|
62
|
-
}
|
|
63
|
-
return (node.type === 'mdxJsxFlowElement' &&
|
|
64
|
-
node.name === 'CodeSnippets' &&
|
|
65
|
-
node.attributes.every((attribute) => attribute.type !== 'mdxJsxAttribute' || attribute.name !== 'hideToggle'));
|
|
66
|
-
}
|