@brillout/docpress 0.16.39 → 0.16.40
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 +71 -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 +62 -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,15 @@ 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
|
+
parent.data = undefined
|
|
72
|
+
}
|
|
36
73
|
|
|
37
74
|
for (const choiceNode of mergedChoiceNodes) {
|
|
38
75
|
const choiceChildren: (BlockContent | DefinitionContent)[] = []
|
|
@@ -51,34 +88,36 @@ function generateChoiceGroupCode(choiceNodes: ChoiceNode[], parent: Parent, hide
|
|
|
51
88
|
],
|
|
52
89
|
children: choiceChildren,
|
|
53
90
|
data: {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
...(!Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name) && {
|
|
92
|
+
customDataParentChoiceGroup: {
|
|
93
|
+
name: choiceGroup.name,
|
|
94
|
+
choice: choiceNode.choiceValue,
|
|
95
|
+
default: choiceGroup.default,
|
|
96
|
+
lvl,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
60
99
|
},
|
|
61
100
|
})
|
|
62
101
|
}
|
|
63
102
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
lvl = parentLvl + 1
|
|
70
|
-
parent.data.customDataParentChoiceGroup = undefined
|
|
103
|
+
const choiceGroupAttr: ChoiceGroup = {
|
|
104
|
+
...choiceGroup,
|
|
105
|
+
hidden: choiceNodes.length === 1 || hidden,
|
|
106
|
+
lvl,
|
|
107
|
+
isBuiltIn: Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name),
|
|
71
108
|
}
|
|
72
109
|
|
|
73
|
-
attributes.push(
|
|
74
|
-
expressionToAttribute('choiceGroup', { ...choiceGroup, hidden: choiceNodes.length === 1 || hidden, lvl }),
|
|
75
|
-
)
|
|
110
|
+
attributes.push(expressionToAttribute('choiceGroup', choiceGroupAttr))
|
|
76
111
|
|
|
77
112
|
const choiceGroupNode: MdxJsxFlowElement = {
|
|
78
113
|
type: 'mdxJsxFlowElement',
|
|
79
114
|
name: 'ChoiceGroup',
|
|
80
115
|
attributes,
|
|
81
116
|
children,
|
|
117
|
+
data: {
|
|
118
|
+
...data,
|
|
119
|
+
customDataChoiceGroup: choiceGroupAttr,
|
|
120
|
+
},
|
|
82
121
|
}
|
|
83
122
|
|
|
84
123
|
if (lvl === 0) {
|
|
@@ -99,16 +138,17 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
|
|
|
99
138
|
const { choices: choicesConfig } = vikeConfig.config.docpress
|
|
100
139
|
const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig }
|
|
101
140
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
})
|
|
141
|
+
// Resolve to the group that defines ALL of the block's values. Matching a group that merely
|
|
142
|
+
// shares ANY value would mis-resolve a custom group that collides with a built-in on a single
|
|
143
|
+
// value — e.g. a `runtime` group [Node, Bun, Deno, Cloudflare] sharing `Bun` with `pkgManager`.
|
|
144
|
+
const groupName = Object.keys(choicesAll).find((key) =>
|
|
145
|
+
choices.every((choice) => choicesAll[key]!.choices.some(({ name }) => name === choice)),
|
|
146
|
+
)
|
|
109
147
|
assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`)
|
|
110
148
|
|
|
111
|
-
const emptyChoices = choicesAll[groupName]!.choices.filter((choice) => !choices.includes(choice))
|
|
149
|
+
const emptyChoices = choicesAll[groupName]!.choices.filter((choice) => !choices.includes(choice.name)).map(
|
|
150
|
+
(choice) => choice.name,
|
|
151
|
+
)
|
|
112
152
|
|
|
113
153
|
const choiceGroup = {
|
|
114
154
|
name: groupName,
|
|
@@ -117,10 +157,10 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
|
|
|
117
157
|
}
|
|
118
158
|
|
|
119
159
|
const mergedChoiceNodes: ChoiceNode[] = choiceGroup.choices.map((choice) => {
|
|
120
|
-
const node = choiceNodes.find((node) => node.choiceValue === choice)
|
|
160
|
+
const node = choiceNodes.find((node) => node.choiceValue === choice.name)
|
|
121
161
|
|
|
122
162
|
return {
|
|
123
|
-
choiceValue: choice,
|
|
163
|
+
choiceValue: choice.name,
|
|
124
164
|
children: node?.children ?? [],
|
|
125
165
|
}
|
|
126
166
|
})
|
|
@@ -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,13 @@ 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
|
+
parent.data = undefined;
|
|
54
|
+
}
|
|
22
55
|
for (const choiceNode of mergedChoiceNodes) {
|
|
23
56
|
const choiceChildren = [];
|
|
24
57
|
if (choiceNode.children.every((node) => node.type === 'containerDirective')) {
|
|
@@ -36,27 +69,33 @@ function generateChoiceGroupCode(choiceNodes, parent, hide = false) {
|
|
|
36
69
|
],
|
|
37
70
|
children: choiceChildren,
|
|
38
71
|
data: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
...(!Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name) && {
|
|
73
|
+
customDataParentChoiceGroup: {
|
|
74
|
+
name: choiceGroup.name,
|
|
75
|
+
choice: choiceNode.choiceValue,
|
|
76
|
+
default: choiceGroup.default,
|
|
77
|
+
lvl,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
45
80
|
},
|
|
46
81
|
});
|
|
47
82
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
lvl
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
attributes.push(expressionToAttribute('choiceGroup',
|
|
83
|
+
const choiceGroupAttr = {
|
|
84
|
+
...choiceGroup,
|
|
85
|
+
hidden: choiceNodes.length === 1 || hidden,
|
|
86
|
+
lvl,
|
|
87
|
+
isBuiltIn: Object.keys(CHOICES_BUILT_IN).includes(choiceGroup.name),
|
|
88
|
+
};
|
|
89
|
+
attributes.push(expressionToAttribute('choiceGroup', choiceGroupAttr));
|
|
55
90
|
const choiceGroupNode = {
|
|
56
91
|
type: 'mdxJsxFlowElement',
|
|
57
92
|
name: 'ChoiceGroup',
|
|
58
93
|
attributes,
|
|
59
94
|
children,
|
|
95
|
+
data: {
|
|
96
|
+
...data,
|
|
97
|
+
customDataChoiceGroup: choiceGroupAttr,
|
|
98
|
+
},
|
|
60
99
|
};
|
|
61
100
|
if (lvl === 0) {
|
|
62
101
|
return {
|
|
@@ -73,25 +112,21 @@ function resolveChoiceGroupNodes(choiceNodes) {
|
|
|
73
112
|
const choices = choiceNodes.map((choiceNode) => choiceNode.choiceValue);
|
|
74
113
|
const { choices: choicesConfig } = vikeConfig.config.docpress;
|
|
75
114
|
const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig };
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (existsChoices.length === 0)
|
|
81
|
-
return false;
|
|
82
|
-
return true;
|
|
83
|
-
});
|
|
115
|
+
// Resolve to the group that defines ALL of the block's values. Matching a group that merely
|
|
116
|
+
// shares ANY value would mis-resolve a custom group that collides with a built-in on a single
|
|
117
|
+
// value — e.g. a `runtime` group [Node, Bun, Deno, Cloudflare] sharing `Bun` with `pkgManager`.
|
|
118
|
+
const groupName = Object.keys(choicesAll).find((key) => choices.every((choice) => choicesAll[key].choices.some(({ name }) => name === choice)));
|
|
84
119
|
assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`);
|
|
85
|
-
const emptyChoices = choicesAll[groupName].choices.filter((choice) => !choices.includes(choice));
|
|
120
|
+
const emptyChoices = choicesAll[groupName].choices.filter((choice) => !choices.includes(choice.name)).map((choice) => choice.name);
|
|
86
121
|
const choiceGroup = {
|
|
87
122
|
name: groupName,
|
|
88
123
|
...choicesAll[groupName],
|
|
89
124
|
emptyChoices,
|
|
90
125
|
};
|
|
91
126
|
const mergedChoiceNodes = choiceGroup.choices.map((choice) => {
|
|
92
|
-
const node = choiceNodes.find((node) => node.choiceValue === choice);
|
|
127
|
+
const node = choiceNodes.find((node) => node.choiceValue === choice.name);
|
|
93
128
|
return {
|
|
94
|
-
choiceValue: choice,
|
|
129
|
+
choiceValue: choice.name,
|
|
95
130
|
children: node?.children ?? [],
|
|
96
131
|
};
|
|
97
132
|
});
|
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.40",
|
|
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,
|