@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 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}\n${initializeJsToggle_SSR}` }}></script>
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: 100px;
11
+ --select-width: 85px;
13
12
  --select-top-position: 10px;
14
13
  --select-right-position: 42px;
15
- --has-toggle: calc(var(--select-right-position) + 61px);
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
- &.has-toggle {
29
- --select-right-position: var(--has-toggle);
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, { useEffect, useRef, useState } from '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
- function ChoiceGroup({ children, choices }: { children: React.ReactNode; choices: string[] }) {
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 group = findGroup(pageContext, choices)
15
- const [selectedChoice, setSelectedChoice] = useSelectedChoice(group.name, group.default)
16
- const [hasJsToggle, setHasJsToggle] = useState(false)
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 || !choices.includes(selectedChoice)
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-name={group.name} className="choice-group">
38
+ <div ref={choiceGroupRef} data-choice-group={choiceGroup.name} className="choice-group">
29
39
  <select
30
- name={`${group.name}-choices`}
40
+ name={`choicesFor-${choiceGroup.name}`}
31
41
  value={selectedChoice}
32
42
  onChange={onChange}
33
- className={cls(['select-choice', hasJsToggle && 'has-toggle', isHidden && 'hidden'])}
43
+ className={cls(['select-choice', isJsDropdownVisible && 'show-js-dropdown', isHidden && 'hidden'])}
34
44
  >
35
- {group.choices.map((choice, i) => (
36
- <option key={i} value={choice} disabled={!choices.includes(choice)}>
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 findGroup(pageContext: PageContext, choices: string[]) {
53
- const { choices: choicesGroup } = pageContext.globalContext.config.docpress
54
- assertUsage(choicesGroup, `+docpress.choices is not defined.`)
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(choicesGroup).find((key) => {
57
- // get only the values that exist in both choices and choicesGroup[key].choices
58
- const relevantChoices = choicesGroup[key].choices.filter((choice) => choices.includes(choice))
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, `the group name for [${choices}] was not found.`)
84
+ assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`)
75
85
 
76
- const mergedChoices = [...new Set([...choices, ...choicesGroup[groupName].choices])]
86
+ const mergedChoices = [...new Set([...choices, ...choicesAll[groupName].choices])]
77
87
 
78
- const group = {
88
+ const choiceGroup = {
79
89
  name: groupName,
80
- ...choicesGroup[groupName],
90
+ ...choicesAll[groupName],
81
91
  choices: mergedChoices,
82
92
  }
83
93
 
84
- return group
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
- * Tracks the selected choice.
11
- * Uses `useLocalStorage` if `persistId` is provided, otherwise regular state.
9
+ * Stores and retrieves a selected choice from local storage.
12
10
  *
13
- * @param persistId Optional ID to persist selection.
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(persistId: string | null, defaultValue: string) {
18
- if (!persistId) return useState(defaultValue)
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 the keyPrefix variable here: closures don't work because we serialize the function.
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-name]')
22
+ const groupsElements = document.querySelectorAll<HTMLDivElement>('[data-choice-group]')
27
23
  for (const groupEl of groupsElements) {
28
- const groupName = groupEl.getAttribute('data-group-name')!
29
- const selectedChoice = localStorage.getItem(`docpress:${groupName}`)
30
- if (!selectedChoice) continue
31
- const selectEl = groupEl.querySelector<HTMLSelectElement>(`.select-choice`)
32
- if (selectEl) selectEl.value = selectedChoice
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 { Code, Root } from 'mdast'
3
+ import type { Root } from 'mdast'
4
4
  import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'
5
- import type { ContainerDirective } from 'mdast-util-directive'
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 { generateChoiceGroup } from './utils/generateChoiceGroup.js'
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 Node[]
44
- const groupedNodes = groupByNodeType(nodes)
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 groupedNode of groupedNodes) {
48
- const replacement = generateChoiceGroup(groupedNode)
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?.choice) {
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
- type NodeGroup = {
82
- value: string
83
- children: Node[]
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, Node[]>()
84
+ const nodesByChoice = new Map<string, ChoiceNode['children']>()
92
85
  nodes
93
- .filter((node) => (node.type === 'code' ? node.lang! : node.name) === filter)
86
+ .filter((node) => node.data!.customDataFilter! === filter)
94
87
  .map((node) => {
95
- const choice = node.data!.choice!
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
- groupedNodes.add([...nodesByChoice].map(([name, nodes]) => ({ value: name, children: nodes })))
95
+ const choiceNodes = [...nodesByChoice].map(([name, nodes]) => ({ choiceValue: name, children: nodes }))
96
+ filteredChoices.add(choiceNodes)
103
97
  })
104
98
 
105
- return [...groupedNodes]
99
+ return [...filteredChoices]
106
100
  }
107
101
 
108
102
  declare module 'mdast' {
109
103
  export interface Data {
110
- choice?: string
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 <CodeSnippets>
69
- const yamlContainer: MdxJsxFlowElement = {
70
- type: 'mdxJsxFlowElement',
71
- name: 'CodeSnippets',
72
- children: [yamlJsCode, codeBlock],
73
- attributes: [
74
- {
75
- name: 'hideToggle',
76
- type: 'mdxJsxAttribute',
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
- // Add `hideToggle` attribute (prop) to `CodeSnippets` if the only change was replacing `.ts` with `.js`
141
- if (codeBlockReplacedJs === codeBlockContentJs) {
142
- attributes.push({
143
- type: 'mdxJsxAttribute',
144
- name: 'hideToggle',
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
- // Wrap both the original `codeBlock` and `jsCode` with <CodeSnippets>
149
- const container: MdxJsxFlowElement = {
150
- type: 'mdxJsxFlowElement',
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
- if (choice) container.data ??= { choice }
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
- // @detype-replace DummyLayout Layout
174
- const renameCommentRE = /^\s*\/\/\s@detype-replace\s([^ ]+) ([^ ]+)\n/gm
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('// @detype-uncomment ', '')
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 { generateChoiceGroup } from './utils/generateChoiceGroup.js'
7
+ import { generateChoiceGroupCode } from './utils/generateChoiceGroupCode.js'
8
8
 
9
- const PKG_MANAGERS = ['pnpm', 'yarn', 'bun'] as const
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 groupedNodes = [...nodes].map(([name, node]) => ({ value: name, children: [node] }))
39
- const replacement = generateChoiceGroup(groupedNodes)
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
+ }
@@ -3,6 +3,7 @@ import type { Root } from 'mdast';
3
3
  declare function remarkChoiceGroup(): (tree: Root) => void;
4
4
  declare module 'mdast' {
5
5
  interface Data {
6
- choice?: string;
6
+ customDataChoice?: string;
7
+ customDataFilter?: string;
7
8
  }
8
9
  }
@@ -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 { generateChoiceGroup } from './utils/generateChoiceGroup.js';
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 groupedNodes = groupByNodeType(nodes);
39
+ const choiceNodesFiltered = filterChoices(nodes);
40
40
  const replacements = [];
41
- for (const groupedNode of groupedNodes) {
42
- const replacement = generateChoiceGroup(groupedNode);
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?.choice) {
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 groupByNodeType(nodes) {
68
- const groupedNodes = new Set();
69
- const filters = [...new Set(nodes.flat().map((node) => (node.type === 'code' ? node.lang : node.name)))];
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) => (node.type === 'code' ? node.lang : node.name) === filter)
73
+ .filter((node) => node.data.customDataFilter === filter)
74
74
  .map((node) => {
75
- const choice = node.data.choice;
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
- groupedNodes.add([...nodesByChoice].map(([name, nodes]) => ({ value: name, children: nodes })));
81
+ const choiceNodes = [...nodesByChoice].map(([name, nodes]) => ({ choiceValue: name, children: nodes }));
82
+ filteredChoices.add(choiceNodes);
82
83
  });
83
- return [...groupedNodes];
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 <CodeSnippets>
53
- const yamlContainer = {
54
- type: 'mdxJsxFlowElement',
55
- name: 'CodeSnippets',
56
- children: [yamlJsCode, codeBlock],
57
- attributes: [
58
- {
59
- name: 'hideToggle',
60
- type: 'mdxJsxAttribute',
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 attributes = [];
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
- // Add `hideToggle` attribute (prop) to `CodeSnippets` if the only change was replacing `.ts` with `.js`
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
- // Wrap both the original `codeBlock` and `jsCode` with <CodeSnippets>
124
- const container = {
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
- // @detype-replace DummyLayout Layout
146
- const renameCommentRE = /^\s*\/\/\s@detype-replace\s([^ ]+) ([^ ]+)\n/gm;
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('// @detype-uncomment ', '');
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 { generateChoiceGroup } from './utils/generateChoiceGroup.js';
6
- const PKG_MANAGERS = ['pnpm', 'yarn', 'bun'];
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 groupedNodes = [...nodes].map(([name, node]) => ({ value: name, children: [node] }));
33
- const replacement = generateChoiceGroup(groupedNodes);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.16.9",
3
+ "version": "0.16.11",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -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
- }