@brillout/docpress 0.16.39 → 0.16.41
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/code-blocks/components/ChoiceGroup.css +22 -0
- package/code-blocks/components/ChoiceGroup.tsx +45 -118
- package/code-blocks/components/Tabs.css +12 -0
- package/code-blocks/components/Tabs.tsx +16 -14
- package/code-blocks/remarkChoiceGroup.ts +56 -6
- package/code-blocks/types.ts +13 -0
- package/code-blocks/utils/generateChoiceGroupCode.ts +78 -31
- package/dist/code-blocks/components/Tabs.js +11 -9
- package/dist/code-blocks/remarkChoiceGroup.d.ts +5 -4
- package/dist/code-blocks/remarkChoiceGroup.js +44 -4
- package/dist/code-blocks/types.d.ts +18 -0
- package/dist/code-blocks/types.js +1 -0
- package/dist/code-blocks/utils/generateChoiceGroupCode.d.ts +3 -2
- package/dist/code-blocks/utils/generateChoiceGroupCode.js +69 -27
- package/dist/types/Config.d.ts +5 -1
- package/dist/vite.config.js +1 -3
- package/package.json +2 -1
- package/types/Config.ts +1 -1
- package/vite.config.ts +1 -3
|
@@ -49,6 +49,28 @@
|
|
|
49
49
|
transition: background 120ms ease;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
.choice-select__option-content {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: 2px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.choice-select__option img {
|
|
59
|
+
width: 14px;
|
|
60
|
+
height: 14px;
|
|
61
|
+
|
|
62
|
+
filter: grayscale(100%);
|
|
63
|
+
opacity: 0.8;
|
|
64
|
+
--transition: 500ms ease;
|
|
65
|
+
transition: filter var(--transition), opacity var(--transition);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.choice-select:hover .choice-select__option img,
|
|
69
|
+
.choice-select[aria-expanded='true'] .choice-select__option img {
|
|
70
|
+
filter: grayscale(0%);
|
|
71
|
+
opacity: 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
.choice-select[aria-expanded='false'] {
|
|
53
75
|
overflow: hidden;
|
|
54
76
|
border-width: 1px 2px 2px 1px;
|
|
@@ -1,33 +1,14 @@
|
|
|
1
1
|
export { ChoiceGroup, CustomSelectsContainer }
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import type { ChoiceGroup as TChoiceGroup, ChoiceGroupWithParent } from '../types.js'
|
|
4
|
+
import React, { createContext, useContext, useState } from 'react'
|
|
5
|
+
import { usePageContext } from '../../renderer/usePageContext.js'
|
|
4
6
|
import { useCurrentSelection } from '../hooks/useCurrentSelection.js'
|
|
5
7
|
import { useRestoreScroll } from '../hooks/useRestoreScroll.js'
|
|
6
8
|
import { cls } from '../../utils/cls.js'
|
|
7
9
|
import './ChoiceGroup.css'
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
name: string
|
|
11
|
-
choices: string[]
|
|
12
|
-
emptyChoices: string[]
|
|
13
|
-
default: string
|
|
14
|
-
hidden: boolean
|
|
15
|
-
lvl: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type ParentChoiceGroup = {
|
|
19
|
-
name: string
|
|
20
|
-
default: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type ChoiceGroupWithParent = TChoiceGroup & { parentChoiceGroup?: ParentChoiceGroup & { choices: string[] } }
|
|
24
|
-
|
|
25
|
-
type ContextType = {
|
|
26
|
-
choiceGroupAll: ChoiceGroupWithParent[]
|
|
27
|
-
registerChoiceGroup: (choiceGroup: TChoiceGroup, parentChoiceGroup?: ParentChoiceGroup & { choice: string }) => void
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const CustomSelectsContainerContext = createContext<ContextType | undefined>(undefined)
|
|
11
|
+
const CustomSelectsContainerContext = createContext<{ choiceGroupAll: ChoiceGroupWithParent[] } | undefined>(undefined)
|
|
31
12
|
|
|
32
13
|
function useCustomSelectsContext() {
|
|
33
14
|
const ctx = useContext(CustomSelectsContainerContext)
|
|
@@ -35,75 +16,24 @@ function useCustomSelectsContext() {
|
|
|
35
16
|
return ctx
|
|
36
17
|
}
|
|
37
18
|
|
|
38
|
-
function CustomSelectsContainer({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const index = prev.findIndex((g) => g.name === choiceGroup.name)
|
|
44
|
-
const existing = prev[index]
|
|
45
|
-
|
|
46
|
-
if (!existing) {
|
|
47
|
-
return [
|
|
48
|
-
...prev,
|
|
49
|
-
{
|
|
50
|
-
...choiceGroup,
|
|
51
|
-
...(parentChoiceGroup && {
|
|
52
|
-
parentChoiceGroup: {
|
|
53
|
-
...parentChoiceGroup,
|
|
54
|
-
choices: !choiceGroup.hidden ? [parentChoiceGroup.choice] : [],
|
|
55
|
-
},
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!parentChoiceGroup || !existing.parentChoiceGroup) return prev
|
|
62
|
-
|
|
63
|
-
const existingChoices = existing.parentChoiceGroup.choices
|
|
64
|
-
if (!choiceGroup.hidden) existing.parentChoiceGroup.choices.push(parentChoiceGroup.choice)
|
|
65
|
-
|
|
66
|
-
const mergedChoices = new Set([...existing.parentChoiceGroup.choices])
|
|
67
|
-
if (mergedChoices.size === existingChoices.length) return prev
|
|
68
|
-
|
|
69
|
-
const next = [...prev]
|
|
70
|
-
next[index] = {
|
|
71
|
-
...existing,
|
|
72
|
-
parentChoiceGroup: {
|
|
73
|
-
...existing.parentChoiceGroup,
|
|
74
|
-
choices: [...mergedChoices],
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
return next
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
<CustomSelectsContainerContext.Provider value={{ choiceGroupAll, registerChoiceGroup }}>
|
|
83
|
-
{children}
|
|
84
|
-
</CustomSelectsContainerContext.Provider>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
type ChoiceGroupProps = {
|
|
89
|
-
children: React.ReactNode
|
|
90
|
-
choiceGroup: TChoiceGroup
|
|
91
|
-
parentChoiceGroup?: ParentChoiceGroup & { choice: string }
|
|
19
|
+
function CustomSelectsContainer({
|
|
20
|
+
children,
|
|
21
|
+
choiceGroupAll,
|
|
22
|
+
}: { children: React.ReactNode; choiceGroupAll: ChoiceGroupWithParent[] }) {
|
|
23
|
+
return <CustomSelectsContainerContext value={{ choiceGroupAll }}>{children}</CustomSelectsContainerContext>
|
|
92
24
|
}
|
|
93
25
|
|
|
94
|
-
function ChoiceGroup({ children, choiceGroup
|
|
26
|
+
function ChoiceGroup({ children, choiceGroup }: { children: React.ReactNode; choiceGroup: TChoiceGroup }) {
|
|
95
27
|
const { name: groupName, choices, default: defaultChoice, lvl } = choiceGroup
|
|
96
28
|
const [selectedChoice] = useCurrentSelection(groupName, defaultChoice)
|
|
97
|
-
const { choiceGroupAll
|
|
98
|
-
|
|
99
|
-
useEffect(() => registerChoiceGroup(choiceGroup, parentChoiceGroup), [])
|
|
29
|
+
const { choiceGroupAll } = useCustomSelectsContext()
|
|
100
30
|
|
|
101
31
|
return (
|
|
102
32
|
<div data-choice-group={groupName} data-lvl={lvl} className="choice-group">
|
|
103
33
|
{/* Hidden select used to control choice visibility via CSS */}
|
|
104
34
|
<select name={`choicesFor-${groupName}`} value={selectedChoice} hidden disabled>
|
|
105
|
-
{choices.map((choice
|
|
106
|
-
<option key={
|
|
35
|
+
{choices.map(({ name: choice }) => (
|
|
36
|
+
<option key={choice} value={choice}>
|
|
107
37
|
{choice}
|
|
108
38
|
</option>
|
|
109
39
|
))}
|
|
@@ -111,49 +41,38 @@ function ChoiceGroup({ children, choiceGroup, parentChoiceGroup }: ChoiceGroupPr
|
|
|
111
41
|
{children}
|
|
112
42
|
{lvl === 0 && !choiceGroup.hidden && (
|
|
113
43
|
<div className={`choice-group__selects`}>
|
|
114
|
-
{choiceGroupAll
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
.map((choiceGroup, i) => (
|
|
118
|
-
<CustomSelect key={i} choiceGroup={choiceGroup} />
|
|
119
|
-
))}
|
|
44
|
+
{choiceGroupAll.map((choiceGroup) => (
|
|
45
|
+
<CustomSelect key={choiceGroup.name} choiceGroup={choiceGroup} />
|
|
46
|
+
))}
|
|
120
47
|
</div>
|
|
121
48
|
)}
|
|
122
49
|
</div>
|
|
123
50
|
)
|
|
124
51
|
}
|
|
125
52
|
|
|
53
|
+
const OPTION_HEIGHT = 25
|
|
126
54
|
function CustomSelect({ choiceGroup }: { choiceGroup: ChoiceGroupWithParent }) {
|
|
127
|
-
const
|
|
55
|
+
const choicesAll = usePageContext().config.docpress.choices
|
|
56
|
+
const { name: groupName, emptyChoices, default: defaultChoice, hidden, parentChoiceGroup, isBuiltIn } = choiceGroup
|
|
128
57
|
const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
|
|
129
|
-
const setPrevPosition = useRestoreScroll([selectedChoice])
|
|
130
58
|
const [expanded, setExpanded] = useState(false)
|
|
59
|
+
const [parentSelectedChoice] = useCurrentSelection(parentChoiceGroup?.name || '', parentChoiceGroup?.default || '')
|
|
60
|
+
const setPrevPosition = useRestoreScroll([selectedChoice])
|
|
131
61
|
|
|
62
|
+
const { choices } = isBuiltIn ? choiceGroup : choicesAll![groupName]!
|
|
63
|
+
const isHidden = parentChoiceGroup ? !parentChoiceGroup.choices.includes(parentSelectedChoice) : hidden
|
|
132
64
|
const isEmptyChoice = (choice: string) => emptyChoices.includes(choice)
|
|
133
|
-
const filteredChoices = choices.filter((choice) => !isEmptyChoice(choice))
|
|
134
|
-
const selectedIndex = filteredChoices.
|
|
135
|
-
const
|
|
136
|
-
const rectTop = -selectedIndex * height
|
|
137
|
-
|
|
138
|
-
function next() {
|
|
139
|
-
const nextIndex = (selectedIndex + 1) % filteredChoices.length
|
|
140
|
-
setSelectedChoice(filteredChoices[nextIndex]!)
|
|
141
|
-
}
|
|
142
|
-
function isHidden() {
|
|
143
|
-
if (parentChoiceGroup) {
|
|
144
|
-
const [parentSelectedChoice] = useCurrentSelection(parentChoiceGroup.name, parentChoiceGroup.default)
|
|
145
|
-
return !parentChoiceGroup.choices.includes(parentSelectedChoice)
|
|
146
|
-
}
|
|
147
|
-
return hidden
|
|
148
|
-
}
|
|
65
|
+
const filteredChoices = choices.filter((choice) => !isEmptyChoice(choice.name))
|
|
66
|
+
const selectedIndex = filteredChoices.findIndex((choice) => choice.name === selectedChoice)
|
|
67
|
+
const rectTop = -selectedIndex * OPTION_HEIGHT
|
|
149
68
|
|
|
150
69
|
return (
|
|
151
70
|
<div
|
|
152
71
|
id={`choicesFor-${groupName}`}
|
|
153
72
|
aria-haspopup="listbox"
|
|
154
73
|
aria-expanded={expanded}
|
|
155
|
-
className={cls(['choice-select', (isHidden
|
|
156
|
-
style={{ height }}
|
|
74
|
+
className={cls(['choice-select', (isHidden || isEmptyChoice(selectedChoice)) && 'hidden'])}
|
|
75
|
+
style={{ height: OPTION_HEIGHT }}
|
|
157
76
|
onMouseEnter={() => setExpanded(true)}
|
|
158
77
|
onMouseLeave={() => setExpanded(false)}
|
|
159
78
|
onClick={() => {
|
|
@@ -164,32 +83,40 @@ function CustomSelect({ choiceGroup }: { choiceGroup: ChoiceGroupWithParent }) {
|
|
|
164
83
|
aria-activedescendant={`choice-${selectedChoice}`}
|
|
165
84
|
role="listbox"
|
|
166
85
|
className="choice-select__list"
|
|
167
|
-
style={{ top: rectTop, height: filteredChoices.length *
|
|
86
|
+
style={{ top: rectTop, height: filteredChoices.length * OPTION_HEIGHT }}
|
|
168
87
|
>
|
|
169
|
-
{filteredChoices.map((choice, i) => (
|
|
88
|
+
{filteredChoices.map(({ name: choice, icon, iconStyle }, i) => (
|
|
170
89
|
<div
|
|
171
|
-
id={choice}
|
|
172
|
-
key={
|
|
90
|
+
id={`choice-${choice}`}
|
|
91
|
+
key={choice}
|
|
173
92
|
aria-selected={i === selectedIndex}
|
|
174
93
|
role="option"
|
|
175
94
|
className="choice-select__option"
|
|
176
|
-
style={{ height }}
|
|
177
|
-
onClick={handleOnClick}
|
|
95
|
+
style={{ height: OPTION_HEIGHT }}
|
|
96
|
+
onClick={(e) => handleOnClick(e, choice)}
|
|
178
97
|
>
|
|
179
|
-
|
|
98
|
+
<span className="choice-select__option-content">
|
|
99
|
+
<img src={icon} alt="" aria-hidden="true" style={iconStyle} />
|
|
100
|
+
{choice}
|
|
101
|
+
</span>
|
|
180
102
|
</div>
|
|
181
103
|
))}
|
|
182
104
|
</div>
|
|
183
105
|
</div>
|
|
184
106
|
)
|
|
185
|
-
|
|
107
|
+
|
|
108
|
+
function next() {
|
|
109
|
+
const nextIndex = (selectedIndex + 1) % filteredChoices.length
|
|
110
|
+
setSelectedChoice(filteredChoices[nextIndex]!.name)
|
|
111
|
+
}
|
|
112
|
+
function handleOnClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>, choice: string) {
|
|
186
113
|
e.stopPropagation()
|
|
187
114
|
const el = e.currentTarget
|
|
188
115
|
setPrevPosition(el)
|
|
189
116
|
if (el.getAttribute('aria-selected') === 'true') {
|
|
190
117
|
next()
|
|
191
118
|
} else {
|
|
192
|
-
setSelectedChoice(
|
|
119
|
+
setSelectedChoice(choice)
|
|
193
120
|
}
|
|
194
121
|
}
|
|
195
122
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
select:has(option:nth-of-type(7):checked) ~ ul[role='tablist'] li:nth-of-type(7) {
|
|
12
12
|
border-bottom: 2px solid #aaa;
|
|
13
13
|
color: var(--color-text);
|
|
14
|
+
font-weight: 600;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -32,6 +33,17 @@
|
|
|
32
33
|
color: color-mix(in srgb, var(--color-text) 80%, white 20%);
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
.choice-tabs__tab-content {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 2px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.choice-tabs__tab img {
|
|
43
|
+
width: 14px;
|
|
44
|
+
height: 14px;
|
|
45
|
+
}
|
|
46
|
+
|
|
35
47
|
@media screen and (max-width: 400px) {
|
|
36
48
|
.choice-tabs__tab {
|
|
37
49
|
padding: 6px 4px;
|
|
@@ -17,43 +17,45 @@ function Tabs({ choice, hide = [] }: { choice: string; hide: string[] }) {
|
|
|
17
17
|
const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
|
|
18
18
|
const setPrevPosition = useRestoreScroll([selectedChoice])
|
|
19
19
|
const isHidden = (choice: string) => hide.includes(choice)
|
|
20
|
-
const filteredChoices = choices.filter((choice) => !isHidden(choice))
|
|
21
|
-
const selectedIndex = filteredChoices.
|
|
20
|
+
const filteredChoices = choices.filter((choice) => !isHidden(choice.name))
|
|
21
|
+
const selectedIndex = filteredChoices.findIndex((choice) => choice.name === selectedChoice)
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div className="choice-tabs" data-choice-group={groupName}>
|
|
25
25
|
{/* Hidden select used to control tablist styling via CSS. */}
|
|
26
26
|
<select name={`choicesFor-${groupName}`} value={selectedChoice} hidden disabled>
|
|
27
|
-
{choices.map((choice
|
|
28
|
-
<option key={
|
|
27
|
+
{choices.map(({ name: choice }) => (
|
|
28
|
+
<option key={choice} value={choice}>
|
|
29
29
|
{choice}
|
|
30
30
|
</option>
|
|
31
31
|
))}
|
|
32
32
|
</select>
|
|
33
33
|
<ul id={`choicesFor-${groupName}`} className="choice-tabs__tab-list" role="tablist">
|
|
34
|
-
{choices.map((choice, i) => (
|
|
34
|
+
{choices.map(({ name: choice, icon, iconStyle }, i) => (
|
|
35
35
|
<li
|
|
36
|
-
key={
|
|
37
|
-
id={choice}
|
|
36
|
+
key={choice}
|
|
37
|
+
id={`tab-${choice}`}
|
|
38
38
|
style={{ display: isHidden(choice) ? 'none' : undefined }}
|
|
39
39
|
className="choice-tabs__tab"
|
|
40
40
|
role="tab"
|
|
41
41
|
aria-selected={i === selectedIndex}
|
|
42
42
|
tabIndex={i === selectedIndex ? 0 : -1}
|
|
43
|
-
onClick={(e) => handleOnClick(e,
|
|
43
|
+
onClick={(e) => handleOnClick(e, choice)}
|
|
44
44
|
onKeyDown={handleOnKeyDown}
|
|
45
45
|
>
|
|
46
|
-
|
|
46
|
+
<span className="choice-tabs__tab-content">
|
|
47
|
+
<img src={icon} alt="" aria-hidden="true" style={iconStyle} />
|
|
48
|
+
{choice}
|
|
49
|
+
</span>
|
|
47
50
|
</li>
|
|
48
51
|
))}
|
|
49
52
|
</ul>
|
|
50
53
|
</div>
|
|
51
54
|
)
|
|
52
55
|
|
|
53
|
-
function handleOnClick(e: React.MouseEvent<HTMLLIElement, MouseEvent>,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setSelectedChoice(choices[index]!)
|
|
56
|
+
function handleOnClick(e: React.MouseEvent<HTMLLIElement, MouseEvent>, choice: string) {
|
|
57
|
+
setPrevPosition(e.currentTarget)
|
|
58
|
+
setSelectedChoice(choice)
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
function handleOnKeyDown(e: React.KeyboardEvent<HTMLLIElement>) {
|
|
@@ -80,7 +82,7 @@ function Tabs({ choice, hide = [] }: { choice: string; hide: string[] }) {
|
|
|
80
82
|
e.preventDefault()
|
|
81
83
|
setPrevPosition(el)
|
|
82
84
|
const nextChoice = filteredChoices[nextIndex]!
|
|
83
|
-
setSelectedChoice(nextChoice)
|
|
85
|
+
setSelectedChoice(nextChoice.name)
|
|
84
86
|
const tabEl = el.parentElement?.parentElement as HTMLDivElement
|
|
85
87
|
|
|
86
88
|
if (!isInViewport(tabEl)) tabEl.scrollIntoView({ block: 'start', behavior: 'smooth' })
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
export { remarkChoiceGroup }
|
|
2
2
|
|
|
3
3
|
import type { Root } from 'mdast'
|
|
4
|
+
import type { Plugin, Transformer } from 'unified'
|
|
4
5
|
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
5
6
|
import type { ChoiceNode } from './utils/generateChoiceGroupCode.js'
|
|
7
|
+
import type { ChoiceGroup, ChoiceGroupWithParent, ParentChoiceGroup } from './types.js'
|
|
6
8
|
import { visit } from 'unist-util-visit'
|
|
7
9
|
import { parseMetaString } from './rehypeMetaToProps.js'
|
|
8
|
-
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js'
|
|
10
|
+
import { generateChoiceGroupCode, expressionToAttribute } from './utils/generateChoiceGroupCode.js'
|
|
11
|
+
import { remarkPkgManager } from './remarkPkgManager.js'
|
|
12
|
+
import { remarkDetype } from './remarkDetype.js'
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
return
|
|
14
|
+
const remarkChoiceGroup: Plugin<[], Root> = (): Transformer<Root> => {
|
|
15
|
+
return async (tree, file) => {
|
|
12
16
|
visit(tree, (node) => {
|
|
13
17
|
if (node.type === 'code') {
|
|
14
18
|
if (!node.meta) return
|
|
@@ -74,6 +78,53 @@ function remarkChoiceGroup() {
|
|
|
74
78
|
|
|
75
79
|
process()
|
|
76
80
|
})
|
|
81
|
+
|
|
82
|
+
await remarkDetype.call(this)(tree, file)
|
|
83
|
+
remarkPkgManager.call(this)(tree, file)
|
|
84
|
+
|
|
85
|
+
visit(tree, 'mdxJsxFlowElement', (node) => {
|
|
86
|
+
if (node.name !== 'CustomSelectsContainer') return 'skip'
|
|
87
|
+
|
|
88
|
+
const choiceGroupAll: ChoiceGroupWithParent[] = []
|
|
89
|
+
|
|
90
|
+
visit(node, 'mdxJsxFlowElement', (child) => {
|
|
91
|
+
if (child.name !== 'ChoiceGroup') return
|
|
92
|
+
|
|
93
|
+
const choiceGroup = child.data?.customDataChoiceGroup
|
|
94
|
+
const parentChoiceGroup = child.data?.customDataParentChoiceGroup
|
|
95
|
+
|
|
96
|
+
if (!choiceGroup) return
|
|
97
|
+
|
|
98
|
+
const existing = choiceGroupAll.find((g) => g.name === choiceGroup.name)
|
|
99
|
+
|
|
100
|
+
// first occurrence
|
|
101
|
+
if (!existing) {
|
|
102
|
+
choiceGroupAll.push({
|
|
103
|
+
...choiceGroup,
|
|
104
|
+
...(parentChoiceGroup && {
|
|
105
|
+
parentChoiceGroup: {
|
|
106
|
+
name: parentChoiceGroup.name,
|
|
107
|
+
default: parentChoiceGroup.default,
|
|
108
|
+
choices: !choiceGroup.hidden ? [parentChoiceGroup.choice] : [],
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// merge parent choices
|
|
117
|
+
if (parentChoiceGroup && existing.parentChoiceGroup && !choiceGroup.hidden) {
|
|
118
|
+
existing.parentChoiceGroup.choices = [
|
|
119
|
+
...new Set([...existing.parentChoiceGroup.choices, parentChoiceGroup.choice]),
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
node.attributes.push(expressionToAttribute('choiceGroupAll', choiceGroupAll))
|
|
125
|
+
|
|
126
|
+
return 'skip'
|
|
127
|
+
})
|
|
77
128
|
}
|
|
78
129
|
}
|
|
79
130
|
|
|
@@ -104,10 +155,9 @@ declare module 'mdast' {
|
|
|
104
155
|
customDataIsVisited?: boolean
|
|
105
156
|
customDataChoice?: string
|
|
106
157
|
customDataFilter?: string
|
|
107
|
-
|
|
108
|
-
|
|
158
|
+
customDataChoiceGroup?: ChoiceGroup
|
|
159
|
+
customDataParentChoiceGroup?: ParentChoiceGroup & {
|
|
109
160
|
choice: string
|
|
110
|
-
default: string
|
|
111
161
|
lvl: number
|
|
112
162
|
}
|
|
113
163
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { ChoiceGroup, ChoiceGroupWithParent, ParentChoiceGroup }
|
|
2
|
+
|
|
3
|
+
import type { Config } from '../types/Config.js'
|
|
4
|
+
|
|
5
|
+
type ChoiceGroup = NonNullable<Config['choices']>[string] & {
|
|
6
|
+
name: string
|
|
7
|
+
emptyChoices: string[]
|
|
8
|
+
hidden: boolean
|
|
9
|
+
lvl: number
|
|
10
|
+
isBuiltIn?: boolean
|
|
11
|
+
}
|
|
12
|
+
type ParentChoiceGroup = { name: string; default: string }
|
|
13
|
+
type ChoiceGroupWithParent = ChoiceGroup & { parentChoiceGroup?: ParentChoiceGroup & { choices: string[] } }
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export { generateChoiceGroupCode }
|
|
1
|
+
export { generateChoiceGroupCode, expressionToAttribute }
|
|
2
2
|
export type { ChoiceNode }
|
|
3
3
|
|
|
4
|
+
import type { Config } from '../../types/Config.js'
|
|
5
|
+
import type { ChoiceGroup } from '../types.js'
|
|
4
6
|
import type { BlockContent, DefinitionContent, Parent } from 'mdast'
|
|
5
|
-
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
|
|
7
|
+
import type { MdxJsxAttribute, MdxJsxFlowElement, MdxJsxFlowElementData } from 'mdast-util-mdx-jsx'
|
|
6
8
|
import { getVikeConfig } from 'vike/plugin'
|
|
7
9
|
import { assertUsage } from '../../utils/assert.js'
|
|
8
10
|
import { valueToEstree } from 'estree-util-value-to-estree'
|
|
@@ -12,13 +14,39 @@ type ChoiceNode = {
|
|
|
12
14
|
children: (BlockContent | DefinitionContent)[]
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
// TODO: determine icon representation for CHOICES_BUILT_IN given lack of SVG/file import support
|
|
18
|
+
// use SVG URLs for now
|
|
19
|
+
const CHOICES_BUILT_IN: NonNullable<Config['choices']> = {
|
|
16
20
|
codeLang: {
|
|
17
|
-
choices: [
|
|
21
|
+
choices: [
|
|
22
|
+
{
|
|
23
|
+
name: 'JavaScript',
|
|
24
|
+
icon: 'https://www.svgrepo.com/show/452045/js.svg',
|
|
25
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'TypeScript',
|
|
29
|
+
icon: 'https://www.svgrepo.com/show/349540/typescript.svg',
|
|
30
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
18
33
|
default: 'JavaScript',
|
|
19
34
|
},
|
|
20
35
|
pkgManager: {
|
|
21
|
-
choices: [
|
|
36
|
+
choices: [
|
|
37
|
+
{
|
|
38
|
+
name: 'npm',
|
|
39
|
+
icon: 'https://www.svgrepo.com/show/452077/npm.svg',
|
|
40
|
+
iconStyle: { position: 'relative', top: 1.5 },
|
|
41
|
+
},
|
|
42
|
+
{ name: 'pnpm', icon: 'https://www.svgrepo.com/show/373778/light-pnpm.svg' },
|
|
43
|
+
{ name: 'Bun', icon: 'https://bun.com/logo.svg' },
|
|
44
|
+
{
|
|
45
|
+
name: 'Yarn',
|
|
46
|
+
icon: 'https://www.svgrepo.com/show/354588/yarn.svg',
|
|
47
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
48
|
+
},
|
|
49
|
+
],
|
|
22
50
|
default: 'npm',
|
|
23
51
|
},
|
|
24
52
|
}
|
|
@@ -33,6 +61,22 @@ function generateChoiceGroupCode(choiceNodes: ChoiceNode[], parent: Parent, hide
|
|
|
33
61
|
const { choiceGroup, mergedChoiceNodes } = resolveChoiceGroupNodes(choiceNodes)
|
|
34
62
|
const attributes: MdxJsxAttribute[] = []
|
|
35
63
|
const children: MdxJsxFlowElement[] = []
|
|
64
|
+
let data: MdxJsxFlowElementData = {}
|
|
65
|
+
|
|
66
|
+
if (parent.data?.customDataParentChoiceGroup) {
|
|
67
|
+
const { lvl: parentLvl } = parent.data.customDataParentChoiceGroup
|
|
68
|
+
lvl = parentLvl + 1
|
|
69
|
+
|
|
70
|
+
data.customDataParentChoiceGroup = parent.data.customDataParentChoiceGroup
|
|
71
|
+
/*
|
|
72
|
+
// Keep the marker on the parent: a single choice can contain several toggleable code blocks
|
|
73
|
+
// (e.g. two TypeScript blocks, or a TypeScript block + an `npm` command). Each of them spawns
|
|
74
|
+
// its own nested choice group and must inherit the same parent level. Clearing the marker here
|
|
75
|
+
// would make every group after the first resolve to `lvl: 0`, wrapping it in its own
|
|
76
|
+
// `CustomSelectsContainer` — which then renders without `choiceGroupAll` and crashes.
|
|
77
|
+
parent.data = undefined
|
|
78
|
+
*/
|
|
79
|
+
}
|
|
36
80
|
|
|
37
81
|
for (const choiceNode of mergedChoiceNodes) {
|
|
38
82
|
const choiceChildren: (BlockContent | DefinitionContent)[] = []
|
|
@@ -51,34 +95,36 @@ function generateChoiceGroupCode(choiceNodes: ChoiceNode[], parent: Parent, hide
|
|
|
51
95
|
],
|
|
52
96
|
children: choiceChildren,
|
|
53
97
|
data: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
...(!Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name) && {
|
|
99
|
+
customDataParentChoiceGroup: {
|
|
100
|
+
name: choiceGroup.name,
|
|
101
|
+
choice: choiceNode.choiceValue,
|
|
102
|
+
default: choiceGroup.default,
|
|
103
|
+
lvl,
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
60
106
|
},
|
|
61
107
|
})
|
|
62
108
|
}
|
|
63
109
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lvl = parentLvl + 1
|
|
70
|
-
parent.data.customDataParentChoiceGroup = undefined
|
|
110
|
+
const choiceGroupAttr: ChoiceGroup = {
|
|
111
|
+
...choiceGroup,
|
|
112
|
+
hidden: choiceNodes.length === 1 || hidden,
|
|
113
|
+
lvl,
|
|
114
|
+
isBuiltIn: Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name),
|
|
71
115
|
}
|
|
72
116
|
|
|
73
|
-
attributes.push(
|
|
74
|
-
expressionToAttribute('choiceGroup', { ...choiceGroup, hidden: choiceNodes.length === 1 || hidden, lvl }),
|
|
75
|
-
)
|
|
117
|
+
attributes.push(expressionToAttribute('choiceGroup', choiceGroupAttr))
|
|
76
118
|
|
|
77
119
|
const choiceGroupNode: MdxJsxFlowElement = {
|
|
78
120
|
type: 'mdxJsxFlowElement',
|
|
79
121
|
name: 'ChoiceGroup',
|
|
80
122
|
attributes,
|
|
81
123
|
children,
|
|
124
|
+
data: {
|
|
125
|
+
...data,
|
|
126
|
+
customDataChoiceGroup: choiceGroupAttr,
|
|
127
|
+
},
|
|
82
128
|
}
|
|
83
129
|
|
|
84
130
|
if (lvl === 0) {
|
|
@@ -99,16 +145,17 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
|
|
|
99
145
|
const { choices: choicesConfig } = vikeConfig.config.docpress
|
|
100
146
|
const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig }
|
|
101
147
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})
|
|
148
|
+
// Resolve to the group that defines ALL of the block's values. Matching a group that merely
|
|
149
|
+
// shares ANY value would mis-resolve a custom group that collides with a built-in on a single
|
|
150
|
+
// value — e.g. a `runtime` group [Node, Bun, Deno, Cloudflare] sharing `Bun` with `pkgManager`.
|
|
151
|
+
const groupName = Object.keys(choicesAll).find((key) =>
|
|
152
|
+
choices.every((choice) => choicesAll[key]!.choices.some(({ name }) => name === choice)),
|
|
153
|
+
)
|
|
109
154
|
assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`)
|
|
110
155
|
|
|
111
|
-
const emptyChoices = choicesAll[groupName]!.choices.filter((choice) => !choices.includes(choice))
|
|
156
|
+
const emptyChoices = choicesAll[groupName]!.choices.filter((choice) => !choices.includes(choice.name)).map(
|
|
157
|
+
(choice) => choice.name,
|
|
158
|
+
)
|
|
112
159
|
|
|
113
160
|
const choiceGroup = {
|
|
114
161
|
name: groupName,
|
|
@@ -117,10 +164,10 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
|
|
|
117
164
|
}
|
|
118
165
|
|
|
119
166
|
const mergedChoiceNodes: ChoiceNode[] = choiceGroup.choices.map((choice) => {
|
|
120
|
-
const node = choiceNodes.find((node) => node.choiceValue === choice)
|
|
167
|
+
const node = choiceNodes.find((node) => node.choiceValue === choice.name)
|
|
121
168
|
|
|
122
169
|
return {
|
|
123
|
-
choiceValue: choice,
|
|
170
|
+
choiceValue: choice.name,
|
|
124
171
|
children: node?.children ?? [],
|
|
125
172
|
}
|
|
126
173
|
})
|
|
@@ -14,15 +14,17 @@ function Tabs({ choice, hide = [] }) {
|
|
|
14
14
|
const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice);
|
|
15
15
|
const setPrevPosition = useRestoreScroll([selectedChoice]);
|
|
16
16
|
const isHidden = (choice) => hide.includes(choice);
|
|
17
|
-
const filteredChoices = choices.filter((choice) => !isHidden(choice));
|
|
18
|
-
const selectedIndex = filteredChoices.
|
|
17
|
+
const filteredChoices = choices.filter((choice) => !isHidden(choice.name));
|
|
18
|
+
const selectedIndex = filteredChoices.findIndex((choice) => choice.name === selectedChoice);
|
|
19
19
|
return (React.createElement("div", { className: "choice-tabs", "data-choice-group": groupName },
|
|
20
|
-
React.createElement("select", { name: `choicesFor-${groupName}`, value: selectedChoice, hidden: true, disabled: true }, choices.map((choice
|
|
21
|
-
React.createElement("ul", { id: `choicesFor-${groupName}`, className: "choice-tabs__tab-list", role: "tablist" }, choices.map((choice, i) => (React.createElement("li", { key:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
React.createElement("select", { name: `choicesFor-${groupName}`, value: selectedChoice, hidden: true, disabled: true }, choices.map(({ name: choice }) => (React.createElement("option", { key: choice, value: choice }, choice)))),
|
|
21
|
+
React.createElement("ul", { id: `choicesFor-${groupName}`, className: "choice-tabs__tab-list", role: "tablist" }, choices.map(({ name: choice, icon, iconStyle }, i) => (React.createElement("li", { key: choice, id: `tab-${choice}`, style: { display: isHidden(choice) ? 'none' : undefined }, className: "choice-tabs__tab", role: "tab", "aria-selected": i === selectedIndex, tabIndex: i === selectedIndex ? 0 : -1, onClick: (e) => handleOnClick(e, choice), onKeyDown: handleOnKeyDown },
|
|
22
|
+
React.createElement("span", { className: "choice-tabs__tab-content" },
|
|
23
|
+
React.createElement("img", { src: icon, alt: "", "aria-hidden": "true", style: iconStyle }),
|
|
24
|
+
choice)))))));
|
|
25
|
+
function handleOnClick(e, choice) {
|
|
26
|
+
setPrevPosition(e.currentTarget);
|
|
27
|
+
setSelectedChoice(choice);
|
|
26
28
|
}
|
|
27
29
|
function handleOnKeyDown(e) {
|
|
28
30
|
const el = e.currentTarget;
|
|
@@ -46,7 +48,7 @@ function Tabs({ choice, hide = [] }) {
|
|
|
46
48
|
e.preventDefault();
|
|
47
49
|
setPrevPosition(el);
|
|
48
50
|
const nextChoice = filteredChoices[nextIndex];
|
|
49
|
-
setSelectedChoice(nextChoice);
|
|
51
|
+
setSelectedChoice(nextChoice.name);
|
|
50
52
|
const tabEl = el.parentElement?.parentElement;
|
|
51
53
|
if (!isInViewport(tabEl))
|
|
52
54
|
tabEl.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export { remarkChoiceGroup };
|
|
2
2
|
import type { Root } from 'mdast';
|
|
3
|
-
|
|
3
|
+
import type { Plugin } from 'unified';
|
|
4
|
+
import type { ChoiceGroup, ParentChoiceGroup } from './types.js';
|
|
5
|
+
declare const remarkChoiceGroup: Plugin<[], Root>;
|
|
4
6
|
declare module 'mdast' {
|
|
5
7
|
interface Data {
|
|
6
8
|
customDataIsVisited?: boolean;
|
|
7
9
|
customDataChoice?: string;
|
|
8
10
|
customDataFilter?: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
customDataChoiceGroup?: ChoiceGroup;
|
|
12
|
+
customDataParentChoiceGroup?: ParentChoiceGroup & {
|
|
11
13
|
choice: string;
|
|
12
|
-
default: string;
|
|
13
14
|
lvl: number;
|
|
14
15
|
};
|
|
15
16
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
export { remarkChoiceGroup };
|
|
2
2
|
import { visit } from 'unist-util-visit';
|
|
3
3
|
import { parseMetaString } from './rehypeMetaToProps.js';
|
|
4
|
-
import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js';
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { generateChoiceGroupCode, expressionToAttribute } from './utils/generateChoiceGroupCode.js';
|
|
5
|
+
import { remarkPkgManager } from './remarkPkgManager.js';
|
|
6
|
+
import { remarkDetype } from './remarkDetype.js';
|
|
7
|
+
const remarkChoiceGroup = () => {
|
|
8
|
+
return async (tree, file) => {
|
|
7
9
|
visit(tree, (node) => {
|
|
8
10
|
if (node.type === 'code') {
|
|
9
11
|
if (!node.meta)
|
|
@@ -62,8 +64,46 @@ function remarkChoiceGroup() {
|
|
|
62
64
|
}
|
|
63
65
|
process();
|
|
64
66
|
});
|
|
67
|
+
await remarkDetype.call(this)(tree, file);
|
|
68
|
+
remarkPkgManager.call(this)(tree, file);
|
|
69
|
+
visit(tree, 'mdxJsxFlowElement', (node) => {
|
|
70
|
+
if (node.name !== 'CustomSelectsContainer')
|
|
71
|
+
return 'skip';
|
|
72
|
+
const choiceGroupAll = [];
|
|
73
|
+
visit(node, 'mdxJsxFlowElement', (child) => {
|
|
74
|
+
if (child.name !== 'ChoiceGroup')
|
|
75
|
+
return;
|
|
76
|
+
const choiceGroup = child.data?.customDataChoiceGroup;
|
|
77
|
+
const parentChoiceGroup = child.data?.customDataParentChoiceGroup;
|
|
78
|
+
if (!choiceGroup)
|
|
79
|
+
return;
|
|
80
|
+
const existing = choiceGroupAll.find((g) => g.name === choiceGroup.name);
|
|
81
|
+
// first occurrence
|
|
82
|
+
if (!existing) {
|
|
83
|
+
choiceGroupAll.push({
|
|
84
|
+
...choiceGroup,
|
|
85
|
+
...(parentChoiceGroup && {
|
|
86
|
+
parentChoiceGroup: {
|
|
87
|
+
name: parentChoiceGroup.name,
|
|
88
|
+
default: parentChoiceGroup.default,
|
|
89
|
+
choices: !choiceGroup.hidden ? [parentChoiceGroup.choice] : [],
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// merge parent choices
|
|
96
|
+
if (parentChoiceGroup && existing.parentChoiceGroup && !choiceGroup.hidden) {
|
|
97
|
+
existing.parentChoiceGroup.choices = [
|
|
98
|
+
...new Set([...existing.parentChoiceGroup.choices, parentChoiceGroup.choice]),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
node.attributes.push(expressionToAttribute('choiceGroupAll', choiceGroupAll));
|
|
103
|
+
return 'skip';
|
|
104
|
+
});
|
|
65
105
|
};
|
|
66
|
-
}
|
|
106
|
+
};
|
|
67
107
|
function filterChoices(nodes) {
|
|
68
108
|
const filteredChoices = new Set();
|
|
69
109
|
const filters = [...new Set(nodes.flat().map((node) => node.data.customDataFilter))];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type { ChoiceGroup, ChoiceGroupWithParent, ParentChoiceGroup };
|
|
2
|
+
import type { Config } from '../types/Config.js';
|
|
3
|
+
type ChoiceGroup = NonNullable<Config['choices']>[string] & {
|
|
4
|
+
name: string;
|
|
5
|
+
emptyChoices: string[];
|
|
6
|
+
hidden: boolean;
|
|
7
|
+
lvl: number;
|
|
8
|
+
isBuiltIn?: boolean;
|
|
9
|
+
};
|
|
10
|
+
type ParentChoiceGroup = {
|
|
11
|
+
name: string;
|
|
12
|
+
default: string;
|
|
13
|
+
};
|
|
14
|
+
type ChoiceGroupWithParent = ChoiceGroup & {
|
|
15
|
+
parentChoiceGroup?: ParentChoiceGroup & {
|
|
16
|
+
choices: string[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
export { generateChoiceGroupCode };
|
|
1
|
+
export { generateChoiceGroupCode, expressionToAttribute };
|
|
2
2
|
export type { ChoiceNode };
|
|
3
3
|
import type { BlockContent, DefinitionContent, Parent } from 'mdast';
|
|
4
|
-
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
|
4
|
+
import type { MdxJsxAttribute, MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
|
5
5
|
type ChoiceNode = {
|
|
6
6
|
choiceValue: string;
|
|
7
7
|
children: (BlockContent | DefinitionContent)[];
|
|
8
8
|
};
|
|
9
9
|
declare function generateChoiceGroupCode(choiceNodes: ChoiceNode[], parent: Parent, hide?: boolean): MdxJsxFlowElement;
|
|
10
|
+
declare function expressionToAttribute(name: string, value: unknown): MdxJsxAttribute;
|
|
@@ -1,14 +1,40 @@
|
|
|
1
|
-
export { generateChoiceGroupCode };
|
|
1
|
+
export { generateChoiceGroupCode, expressionToAttribute };
|
|
2
2
|
import { getVikeConfig } from 'vike/plugin';
|
|
3
3
|
import { assertUsage } from '../../utils/assert.js';
|
|
4
4
|
import { valueToEstree } from 'estree-util-value-to-estree';
|
|
5
|
+
// TODO: determine icon representation for CHOICES_BUILT_IN given lack of SVG/file import support
|
|
6
|
+
// use SVG URLs for now
|
|
5
7
|
const CHOICES_BUILT_IN = {
|
|
6
8
|
codeLang: {
|
|
7
|
-
choices: [
|
|
9
|
+
choices: [
|
|
10
|
+
{
|
|
11
|
+
name: 'JavaScript',
|
|
12
|
+
icon: 'https://www.svgrepo.com/show/452045/js.svg',
|
|
13
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'TypeScript',
|
|
17
|
+
icon: 'https://www.svgrepo.com/show/349540/typescript.svg',
|
|
18
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
19
|
+
},
|
|
20
|
+
],
|
|
8
21
|
default: 'JavaScript',
|
|
9
22
|
},
|
|
10
23
|
pkgManager: {
|
|
11
|
-
choices: [
|
|
24
|
+
choices: [
|
|
25
|
+
{
|
|
26
|
+
name: 'npm',
|
|
27
|
+
icon: 'https://www.svgrepo.com/show/452077/npm.svg',
|
|
28
|
+
iconStyle: { position: 'relative', top: 1.5 },
|
|
29
|
+
},
|
|
30
|
+
{ name: 'pnpm', icon: 'https://www.svgrepo.com/show/373778/light-pnpm.svg' },
|
|
31
|
+
{ name: 'Bun', icon: 'https://bun.com/logo.svg' },
|
|
32
|
+
{
|
|
33
|
+
name: 'Yarn',
|
|
34
|
+
icon: 'https://www.svgrepo.com/show/354588/yarn.svg',
|
|
35
|
+
iconStyle: { position: 'relative', top: -0.5 },
|
|
36
|
+
},
|
|
37
|
+
],
|
|
12
38
|
default: 'npm',
|
|
13
39
|
},
|
|
14
40
|
};
|
|
@@ -19,6 +45,20 @@ function generateChoiceGroupCode(choiceNodes, parent, hide = false) {
|
|
|
19
45
|
const { choiceGroup, mergedChoiceNodes } = resolveChoiceGroupNodes(choiceNodes);
|
|
20
46
|
const attributes = [];
|
|
21
47
|
const children = [];
|
|
48
|
+
let data = {};
|
|
49
|
+
if (parent.data?.customDataParentChoiceGroup) {
|
|
50
|
+
const { lvl: parentLvl } = parent.data.customDataParentChoiceGroup;
|
|
51
|
+
lvl = parentLvl + 1;
|
|
52
|
+
data.customDataParentChoiceGroup = parent.data.customDataParentChoiceGroup;
|
|
53
|
+
/*
|
|
54
|
+
// Keep the marker on the parent: a single choice can contain several toggleable code blocks
|
|
55
|
+
// (e.g. two TypeScript blocks, or a TypeScript block + an `npm` command). Each of them spawns
|
|
56
|
+
// its own nested choice group and must inherit the same parent level. Clearing the marker here
|
|
57
|
+
// would make every group after the first resolve to `lvl: 0`, wrapping it in its own
|
|
58
|
+
// `CustomSelectsContainer` — which then renders without `choiceGroupAll` and crashes.
|
|
59
|
+
parent.data = undefined
|
|
60
|
+
*/
|
|
61
|
+
}
|
|
22
62
|
for (const choiceNode of mergedChoiceNodes) {
|
|
23
63
|
const choiceChildren = [];
|
|
24
64
|
if (choiceNode.children.every((node) => node.type === 'containerDirective')) {
|
|
@@ -36,27 +76,33 @@ function generateChoiceGroupCode(choiceNodes, parent, hide = false) {
|
|
|
36
76
|
],
|
|
37
77
|
children: choiceChildren,
|
|
38
78
|
data: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
79
|
+
...(!Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name) && {
|
|
80
|
+
customDataParentChoiceGroup: {
|
|
81
|
+
name: choiceGroup.name,
|
|
82
|
+
choice: choiceNode.choiceValue,
|
|
83
|
+
default: choiceGroup.default,
|
|
84
|
+
lvl,
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
45
87
|
},
|
|
46
88
|
});
|
|
47
89
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lvl
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
attributes.push(expressionToAttribute('choiceGroup',
|
|
90
|
+
const choiceGroupAttr = {
|
|
91
|
+
...choiceGroup,
|
|
92
|
+
hidden: choiceNodes.length === 1 || hidden,
|
|
93
|
+
lvl,
|
|
94
|
+
isBuiltIn: Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name),
|
|
95
|
+
};
|
|
96
|
+
attributes.push(expressionToAttribute('choiceGroup', choiceGroupAttr));
|
|
55
97
|
const choiceGroupNode = {
|
|
56
98
|
type: 'mdxJsxFlowElement',
|
|
57
99
|
name: 'ChoiceGroup',
|
|
58
100
|
attributes,
|
|
59
101
|
children,
|
|
102
|
+
data: {
|
|
103
|
+
...data,
|
|
104
|
+
customDataChoiceGroup: choiceGroupAttr,
|
|
105
|
+
},
|
|
60
106
|
};
|
|
61
107
|
if (lvl === 0) {
|
|
62
108
|
return {
|
|
@@ -73,25 +119,21 @@ function resolveChoiceGroupNodes(choiceNodes) {
|
|
|
73
119
|
const choices = choiceNodes.map((choiceNode) => choiceNode.choiceValue);
|
|
74
120
|
const { choices: choicesConfig } = vikeConfig.config.docpress;
|
|
75
121
|
const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig };
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (existsChoices.length === 0)
|
|
81
|
-
return false;
|
|
82
|
-
return true;
|
|
83
|
-
});
|
|
122
|
+
// Resolve to the group that defines ALL of the block's values. Matching a group that merely
|
|
123
|
+
// shares ANY value would mis-resolve a custom group that collides with a built-in on a single
|
|
124
|
+
// value — e.g. a `runtime` group [Node, Bun, Deno, Cloudflare] sharing `Bun` with `pkgManager`.
|
|
125
|
+
const groupName = Object.keys(choicesAll).find((key) => choices.every((choice) => choicesAll[key].choices.some(({ name }) => name === choice)));
|
|
84
126
|
assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`);
|
|
85
|
-
const emptyChoices = choicesAll[groupName].choices.filter((choice) => !choices.includes(choice));
|
|
127
|
+
const emptyChoices = choicesAll[groupName].choices.filter((choice) => !choices.includes(choice.name)).map((choice) => choice.name);
|
|
86
128
|
const choiceGroup = {
|
|
87
129
|
name: groupName,
|
|
88
130
|
...choicesAll[groupName],
|
|
89
131
|
emptyChoices,
|
|
90
132
|
};
|
|
91
133
|
const mergedChoiceNodes = choiceGroup.choices.map((choice) => {
|
|
92
|
-
const node = choiceNodes.find((node) => node.choiceValue === choice);
|
|
134
|
+
const node = choiceNodes.find((node) => node.choiceValue === choice.name);
|
|
93
135
|
return {
|
|
94
|
-
choiceValue: choice,
|
|
136
|
+
choiceValue: choice.name,
|
|
95
137
|
children: node?.children ?? [],
|
|
96
138
|
};
|
|
97
139
|
});
|
package/dist/types/Config.d.ts
CHANGED
package/dist/vite.config.js
CHANGED
|
@@ -12,9 +12,7 @@ import { transformerNotationDiff, transformerNotationWordHighlight } from '@shik
|
|
|
12
12
|
// https://github.com/shikijs/shiki/compare/main...brillout:shiki:brillout/highlight-color-param
|
|
13
13
|
import { transformerNotationHighlight } from '@brillout/shiki-transformers';
|
|
14
14
|
import { rehypeMetaToProps } from './code-blocks/rehypeMetaToProps.js';
|
|
15
|
-
import { remarkDetype } from './code-blocks/remarkDetype.js';
|
|
16
15
|
import { shikiTransformerAutoLinks } from './code-blocks/shikiTransformerAutoLinks.js';
|
|
17
|
-
import { remarkPkgManager } from './code-blocks/remarkPkgManager.js';
|
|
18
16
|
import { remarkChoiceGroup } from './code-blocks/remarkChoiceGroup.js';
|
|
19
17
|
const root = process.cwd();
|
|
20
18
|
const prettyCode = [
|
|
@@ -31,7 +29,7 @@ const prettyCode = [
|
|
|
31
29
|
},
|
|
32
30
|
];
|
|
33
31
|
const rehypePlugins = [prettyCode, [rehypeMetaToProps]];
|
|
34
|
-
const remarkPlugins = [remarkGfm, remarkDirective, remarkChoiceGroup
|
|
32
|
+
const remarkPlugins = [remarkGfm, remarkDirective, remarkChoiceGroup];
|
|
35
33
|
const config = {
|
|
36
34
|
root,
|
|
37
35
|
plugins: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brillout/docpress",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@brillout/picocolors": "^1.0.10",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"@vitejs/plugin-react": "^6.0.1",
|
|
63
63
|
"mdast-util-directive": "^3.1.0",
|
|
64
64
|
"mdast-util-mdx-jsx": "^3.2.0",
|
|
65
|
+
"unified": "^11.0.5",
|
|
65
66
|
"vike": "^0.4.255",
|
|
66
67
|
"vite": "^8.0.8"
|
|
67
68
|
},
|
package/types/Config.ts
CHANGED
package/vite.config.ts
CHANGED
|
@@ -14,9 +14,7 @@ import { transformerNotationDiff, transformerNotationWordHighlight } from '@shik
|
|
|
14
14
|
// https://github.com/shikijs/shiki/compare/main...brillout:shiki:brillout/highlight-color-param
|
|
15
15
|
import { transformerNotationHighlight } from '@brillout/shiki-transformers'
|
|
16
16
|
import { rehypeMetaToProps } from './code-blocks/rehypeMetaToProps.js'
|
|
17
|
-
import { remarkDetype } from './code-blocks/remarkDetype.js'
|
|
18
17
|
import { shikiTransformerAutoLinks } from './code-blocks/shikiTransformerAutoLinks.js'
|
|
19
|
-
import { remarkPkgManager } from './code-blocks/remarkPkgManager.js'
|
|
20
18
|
import { remarkChoiceGroup } from './code-blocks/remarkChoiceGroup.js'
|
|
21
19
|
|
|
22
20
|
const root = process.cwd()
|
|
@@ -34,7 +32,7 @@ const prettyCode = [
|
|
|
34
32
|
},
|
|
35
33
|
]
|
|
36
34
|
const rehypePlugins: any = [prettyCode, [rehypeMetaToProps]]
|
|
37
|
-
const remarkPlugins = [remarkGfm, remarkDirective, remarkChoiceGroup
|
|
35
|
+
const remarkPlugins = [remarkGfm, remarkDirective, remarkChoiceGroup]
|
|
38
36
|
|
|
39
37
|
const config: UserConfig = {
|
|
40
38
|
root,
|