@brillout/docpress 0.16.43 → 0.16.45

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.
@@ -5,6 +5,7 @@ import React, { useId, useState } from 'react'
5
5
  import { usePageContext } from '../../renderer/usePageContext.js'
6
6
  import { useCurrentSelection } from '../hooks/useCurrentSelection.js'
7
7
  import { useRestoreScroll } from '../hooks/useRestoreScroll.js'
8
+ import { getAvailableChoice } from '../utils/getAvailableChoice.js'
8
9
  import { cls } from '../../utils/cls.js'
9
10
  import './ChoiceGroup.css'
10
11
 
@@ -12,7 +13,7 @@ function ChoiceGroupContainer({
12
13
  children,
13
14
  choiceGroupAll,
14
15
  }: { children: React.ReactNode; choiceGroupAll: ChoiceGroupWithParent[] }) {
15
- const renderCustomSelect = choiceGroupAll.some((choiceGroup) => choiceGroup.lvl === 0 && !choiceGroup.hidden)
16
+ const renderCustomSelect = (choiceGroupAll ?? []).some((choiceGroup) => choiceGroup.lvl === 0 && !choiceGroup.hidden)
16
17
  return (
17
18
  <div className="choice-group-container">
18
19
  {children}
@@ -28,15 +29,17 @@ function ChoiceGroupContainer({
28
29
  }
29
30
 
30
31
  function ChoiceGroup({ children, choiceGroup }: { children: React.ReactNode; choiceGroup: TChoiceGroup }) {
31
- const { name: groupName, choices, default: defaultChoice } = choiceGroup
32
- const [selectedChoice] = useCurrentSelection(groupName, defaultChoice)
32
+ const { name: groupName, choices, default: defaultChoice, emptyChoices } = choiceGroup
33
+ const [selectedChoiceStored] = useCurrentSelection(groupName, defaultChoice)
34
+ const selectedChoice = getAvailableChoice(selectedChoiceStored, choices, emptyChoices, defaultChoice)
33
35
 
34
36
  return (
35
37
  <div className="choice-group">
36
38
  {/* Hidden select used to control choice visibility via CSS */}
37
39
  <select data-choice-group={groupName} name={`choicesFor-${groupName}`} value={selectedChoice} hidden disabled>
38
40
  {choices.map(({ name: choice }) => (
39
- <option key={choice} value={choice}>
41
+ // data-empty is read by the initializeChoiceGroup SSR script (useCurrentSelection.ts)
42
+ <option key={choice} value={choice} data-empty={emptyChoices.includes(choice) ? '' : undefined}>
40
43
  {choice}
41
44
  </option>
42
45
  ))}
@@ -49,16 +52,22 @@ function ChoiceGroup({ children, choiceGroup }: { children: React.ReactNode; cho
49
52
  const OPTION_HEIGHT = 25
50
53
  function CustomSelect({ choiceGroup }: { choiceGroup: ChoiceGroupWithParent }) {
51
54
  const radioId = useId()
52
- const choicesAll = usePageContext().config.docpress.choices
55
+ const choicesAll = usePageContext().resolved.choices
53
56
  const { name: groupName, emptyChoices, default: defaultChoice, hidden, parentChoiceGroup, isBuiltIn } = choiceGroup
54
- const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
57
+ const choices = (isBuiltIn ? choiceGroup : choicesAll![groupName]!).choices
58
+ const [selectedChoiceStored, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
59
+ const selectedChoice = getAvailableChoice(selectedChoiceStored, choices, emptyChoices, defaultChoice)
55
60
  const [expanded, setExpanded] = useState(false)
56
61
  const [isHovered, setIsHovered] = useState(false)
57
- const [parentSelectedChoice] = useCurrentSelection(parentChoiceGroup?.name || '', parentChoiceGroup?.default || '')
62
+ let [parentSelectedChoice] = useCurrentSelection(parentChoiceGroup?.name || '', parentChoiceGroup?.default || '')
63
+ let isHidden = hidden
58
64
  const setPrevPosition = useRestoreScroll([selectedChoice])
59
65
 
60
- const { choices } = isBuiltIn ? choiceGroup : choicesAll![groupName]!
61
- const isHidden = parentChoiceGroup ? !parentChoiceGroup.choices.includes(parentSelectedChoice) : hidden
66
+ if (parentChoiceGroup) {
67
+ const { choices, emptyChoices, default: defaultChoice } = parentChoiceGroup
68
+ parentSelectedChoice = getAvailableChoice(parentSelectedChoice, choices, emptyChoices, defaultChoice)
69
+ isHidden = !parentChoiceGroup.choices.includes(parentSelectedChoice)
70
+ }
62
71
  const isEmptyChoice = (choice: string) => emptyChoices.includes(choice)
63
72
  const filteredChoices = choices.filter((choice) => !isEmptyChoice(choice.name))
64
73
  const selectedIndex = filteredChoices.findIndex((choice) => choice.name === selectedChoice)
@@ -104,7 +113,7 @@ function CustomSelect({ choiceGroup }: { choiceGroup: ChoiceGroupWithParent }) {
104
113
  readOnly
105
114
  />
106
115
  <span className="choice-select__option-content">
107
- <img src={icon} alt="" aria-hidden="true" style={iconStyle} />
116
+ {icon && <img src={icon} alt="" aria-hidden="true" style={iconStyle} />}
108
117
  {choice}
109
118
  </span>
110
119
  </label>
@@ -3,6 +3,7 @@ export { Tabs }
3
3
  import React, { useId } from 'react'
4
4
  import { useCurrentSelection } from '../hooks/useCurrentSelection.js'
5
5
  import { useRestoreScroll } from '../hooks/useRestoreScroll.js'
6
+ import { getAvailableChoice } from '../utils/getAvailableChoice.js'
6
7
  import { usePageContext } from '../../renderer/usePageContext.js'
7
8
  import { assertUsage } from '../../utils/assert.js'
8
9
  import './Tabs.css'
@@ -11,10 +12,12 @@ function Tabs({ choice, hide = [] }: { choice: string; hide: string[] }) {
11
12
  const radioId = useId()
12
13
  const groupName = choice
13
14
  const pageContext = usePageContext()
14
- const choicesAll = pageContext.config.docpress.choices
15
+ const choicesAll = pageContext.resolved.choices
15
16
  assertUsage(choicesAll && choicesAll[groupName], `${groupName} is unknown`)
16
17
  const { choices, default: defaultChoice } = choicesAll[groupName]
17
- const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
18
+ const [selectedChoiceStored, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice)
19
+ // A hidden tab can't be shown, so treat `hide` as the unavailable choices (#169)
20
+ const selectedChoice = getAvailableChoice(selectedChoiceStored, choices, hide, defaultChoice)
18
21
  const setPrevPosition = useRestoreScroll([selectedChoice])
19
22
  const isHidden = (choice: string) => hide.includes(choice)
20
23
 
@@ -40,7 +43,7 @@ function Tabs({ choice, hide = [] }: { choice: string; hide: string[] }) {
40
43
  }}
41
44
  />
42
45
  <span className="choice-tabs__tab-content">
43
- <img src={icon} alt="" aria-hidden="true" style={iconStyle} />
46
+ {icon && <img src={icon} alt="" aria-hidden="true" style={iconStyle} />}
44
47
  {choice}
45
48
  </span>
46
49
  </label>
@@ -32,13 +32,17 @@ function initializeChoiceGroup() {
32
32
  switch (groupEl.tagName) {
33
33
  case 'SELECT':
34
34
  const selectEl = groupEl as HTMLSelectElement
35
- const optionExists = [...selectEl.options].some((opt) => opt.value === selectedChoice)
36
- if (!optionExists) localStorage.removeItem(storageKey)
37
- else selectEl.value = selectedChoice
35
+ const option = [...selectEl.options].find((opt) => opt.value === selectedChoice)
36
+ // No option: stale choice removed from the config → forget it.
37
+ // data-empty: choice exists in the group but has no content on this page → keep the
38
+ // server-rendered fallback rather than selecting a blank choice (#169).
39
+ if (!option) localStorage.removeItem(storageKey)
40
+ else if (!option.hasAttribute('data-empty')) selectEl.value = selectedChoice
38
41
  break
39
42
  case 'DIV':
40
43
  const radioEl = groupEl.querySelector<HTMLInputElement>(`input[type="radio"][value="${selectedChoice}"]`)
41
44
  if (radioEl) radioEl.checked = true
45
+ break
42
46
  default:
43
47
  break
44
48
  }
@@ -109,6 +109,7 @@ const remarkChoiceGroup: Plugin<[], Root> = (): Transformer<Root> => {
109
109
  name: parentChoiceGroup.name,
110
110
  default: parentChoiceGroup.default,
111
111
  choices: !choiceGroup.hidden ? [parentChoiceGroup.choice] : [],
112
+ emptyChoices: parentChoiceGroup.emptyChoices,
112
113
  },
113
114
  }),
114
115
  })
@@ -1,13 +1,14 @@
1
1
  export type { ChoiceGroup, ChoiceGroupWithParent, ParentChoiceGroup }
2
2
 
3
- import type { Config } from '../types/Config.js'
3
+ import type { Config, ChoiceItem } from '../types/Config.js'
4
4
 
5
- type ChoiceGroup = NonNullable<Config['choices']>[string] & {
5
+ type ChoiceGroup = Omit<NonNullable<Config['choices']>[string], 'choices'> & {
6
6
  name: string
7
+ choices: ChoiceItem[]
7
8
  emptyChoices: string[]
8
9
  hidden: boolean
9
10
  lvl: number
10
11
  isBuiltIn?: boolean
11
12
  }
12
- type ParentChoiceGroup = { name: string; default: string }
13
+ type ParentChoiceGroup = { name: string; default: string; emptyChoices: string[] }
13
14
  type ChoiceGroupWithParent = ChoiceGroup & { parentChoiceGroup?: ParentChoiceGroup & { choices: string[] } }
@@ -8,6 +8,7 @@ import type { MdxJsxAttribute, MdxJsxFlowElement, MdxJsxFlowElementData } from '
8
8
  import { getVikeConfig } from 'vike/plugin'
9
9
  import { assertUsage } from '../../utils/assert.js'
10
10
  import { valueToEstree } from 'estree-util-value-to-estree'
11
+ import { resolveChoices } from './resolveChoices.js'
11
12
 
12
13
  type ChoiceNode = {
13
14
  choiceValue: string
@@ -100,6 +101,7 @@ function generateChoiceGroupCode(choiceNodes: ChoiceNode[], parent: Parent, hide
100
101
  name: choiceGroup.name,
101
102
  choice: choiceNode.choiceValue,
102
103
  default: choiceGroup.default,
104
+ emptyChoices: choiceGroup.emptyChoices,
103
105
  lvl,
104
106
  },
105
107
  }),
@@ -143,7 +145,7 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
143
145
  const vikeConfig = getVikeConfig()
144
146
  const choices = choiceNodes.map((choiceNode) => choiceNode.choiceValue)
145
147
  const { choices: choicesConfig } = vikeConfig.config.docpress
146
- const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig }
148
+ const choicesAll = resolveChoices({ ...CHOICES_BUILT_IN, ...choicesConfig })
147
149
 
148
150
  // Resolve to the group that defines ALL of the block's values. Matching a group that merely
149
151
  // shares ANY value would mis-resolve a custom group that collides with a built-in on a single
@@ -153,13 +155,12 @@ function resolveChoiceGroupNodes(choiceNodes: ChoiceNode[]) {
153
155
  )
154
156
  assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`)
155
157
 
156
- const emptyChoices = choicesAll[groupName]!.choices.filter((choice) => !choices.includes(choice.name)).map(
157
- (choice) => choice.name,
158
- )
158
+ const group = choicesAll[groupName]!
159
+ const emptyChoices = group.choices.filter((choice) => !choices.includes(choice.name)).map((choice) => choice.name)
159
160
 
160
161
  const choiceGroup = {
161
162
  name: groupName,
162
- ...choicesAll[groupName]!,
163
+ ...group,
163
164
  emptyChoices,
164
165
  }
165
166
 
@@ -0,0 +1,20 @@
1
+ export { getAvailableChoice }
2
+
3
+ import type { ChoiceItem } from '../../types/Config.js'
4
+ import { resolveChoice } from './resolveChoices.js'
5
+
6
+ // A page may include only some of a group's choices, so a choice selected elsewhere (and persisted in
7
+ // localStorage) can be absent here — which would otherwise render nothing (#169). Resolve to a choice
8
+ // that exists on the current page.
9
+ function getAvailableChoice(
10
+ selectedChoice: string,
11
+ choices: (string | ChoiceItem)[],
12
+ emptyChoices: string[],
13
+ defaultChoice: string,
14
+ ): string {
15
+ const isAvailable = (choiceName: string) => !emptyChoices.includes(choiceName)
16
+ if (isAvailable(selectedChoice)) return selectedChoice
17
+ if (isAvailable(defaultChoice)) return defaultChoice
18
+ const choicesResolved = choices.map(resolveChoice)
19
+ return choicesResolved.find((choice) => isAvailable(choice.name))?.name ?? selectedChoice
20
+ }
@@ -0,0 +1,18 @@
1
+ export { resolveChoices, resolveChoice }
2
+ export type { ResolvedChoices }
3
+
4
+ import type { Choice, ChoiceItem } from '../../types/Config.js'
5
+
6
+ type ResolvedChoices = Record<string, Omit<Choice, 'choices'> & { choices: ChoiceItem[] }>
7
+
8
+ function resolveChoices(choicesConfig: Record<string, Choice>): ResolvedChoices {
9
+ return Object.fromEntries(
10
+ Object.entries(choicesConfig).map(([name, group]) => [
11
+ name,
12
+ { ...group, choices: group.choices.map(resolveChoice) },
13
+ ]),
14
+ )
15
+ }
16
+ function resolveChoice(choice: string | ChoiceItem): ChoiceItem {
17
+ return typeof choice === 'string' ? { name: choice } : choice
18
+ }
@@ -2,6 +2,7 @@ export { Tabs };
2
2
  import React, { useId } from 'react';
3
3
  import { useCurrentSelection } from '../hooks/useCurrentSelection.js';
4
4
  import { useRestoreScroll } from '../hooks/useRestoreScroll.js';
5
+ import { getAvailableChoice } from '../utils/getAvailableChoice.js';
5
6
  import { usePageContext } from '../../renderer/usePageContext.js';
6
7
  import { assertUsage } from '../../utils/assert.js';
7
8
  import './Tabs.css';
@@ -9,10 +10,12 @@ function Tabs({ choice, hide = [] }) {
9
10
  const radioId = useId();
10
11
  const groupName = choice;
11
12
  const pageContext = usePageContext();
12
- const choicesAll = pageContext.config.docpress.choices;
13
+ const choicesAll = pageContext.resolved.choices;
13
14
  assertUsage(choicesAll && choicesAll[groupName], `${groupName} is unknown`);
14
15
  const { choices, default: defaultChoice } = choicesAll[groupName];
15
- const [selectedChoice, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice);
16
+ const [selectedChoiceStored, setSelectedChoice] = useCurrentSelection(groupName, defaultChoice);
17
+ // A hidden tab can't be shown, so treat `hide` as the unavailable choices (#169)
18
+ const selectedChoice = getAvailableChoice(selectedChoiceStored, choices, hide, defaultChoice);
16
19
  const setPrevPosition = useRestoreScroll([selectedChoice]);
17
20
  const isHidden = (choice) => hide.includes(choice);
18
21
  return (React.createElement("div", { className: "choice-tabs" },
@@ -22,6 +25,6 @@ function Tabs({ choice, hide = [] }) {
22
25
  setSelectedChoice(choice);
23
26
  } }),
24
27
  React.createElement("span", { className: "choice-tabs__tab-content" },
25
- React.createElement("img", { src: icon, alt: "", "aria-hidden": "true", style: iconStyle }),
28
+ icon && React.createElement("img", { src: icon, alt: "", "aria-hidden": "true", style: iconStyle }),
26
29
  choice)))))));
27
30
  }
@@ -30,16 +30,20 @@ function initializeChoiceGroup() {
30
30
  switch (groupEl.tagName) {
31
31
  case 'SELECT':
32
32
  const selectEl = groupEl;
33
- const optionExists = [...selectEl.options].some((opt) => opt.value === selectedChoice);
34
- if (!optionExists)
33
+ const option = [...selectEl.options].find((opt) => opt.value === selectedChoice);
34
+ // No option: stale choice removed from the config → forget it.
35
+ // data-empty: choice exists in the group but has no content on this page → keep the
36
+ // server-rendered fallback rather than selecting a blank choice (#169).
37
+ if (!option)
35
38
  localStorage.removeItem(storageKey);
36
- else
39
+ else if (!option.hasAttribute('data-empty'))
37
40
  selectEl.value = selectedChoice;
38
41
  break;
39
42
  case 'DIV':
40
43
  const radioEl = groupEl.querySelector(`input[type="radio"][value="${selectedChoice}"]`);
41
44
  if (radioEl)
42
45
  radioEl.checked = true;
46
+ break;
43
47
  default:
44
48
  break;
45
49
  }
@@ -90,6 +90,7 @@ const remarkChoiceGroup = () => {
90
90
  name: parentChoiceGroup.name,
91
91
  default: parentChoiceGroup.default,
92
92
  choices: !choiceGroup.hidden ? [parentChoiceGroup.choice] : [],
93
+ emptyChoices: parentChoiceGroup.emptyChoices,
93
94
  },
94
95
  }),
95
96
  });
@@ -1,7 +1,8 @@
1
1
  export type { ChoiceGroup, ChoiceGroupWithParent, ParentChoiceGroup };
2
- import type { Config } from '../types/Config.js';
3
- type ChoiceGroup = NonNullable<Config['choices']>[string] & {
2
+ import type { Config, ChoiceItem } from '../types/Config.js';
3
+ type ChoiceGroup = Omit<NonNullable<Config['choices']>[string], 'choices'> & {
4
4
  name: string;
5
+ choices: ChoiceItem[];
5
6
  emptyChoices: string[];
6
7
  hidden: boolean;
7
8
  lvl: number;
@@ -10,6 +11,7 @@ type ChoiceGroup = NonNullable<Config['choices']>[string] & {
10
11
  type ParentChoiceGroup = {
11
12
  name: string;
12
13
  default: string;
14
+ emptyChoices: string[];
13
15
  };
14
16
  type ChoiceGroupWithParent = ChoiceGroup & {
15
17
  parentChoiceGroup?: ParentChoiceGroup & {
@@ -2,6 +2,7 @@ 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
+ import { resolveChoices } from './resolveChoices.js';
5
6
  // TODO: determine icon representation for CHOICES_BUILT_IN given lack of SVG/file import support
6
7
  // use SVG URLs for now
7
8
  const CHOICES_BUILT_IN = {
@@ -81,6 +82,7 @@ function generateChoiceGroupCode(choiceNodes, parent, hide = false) {
81
82
  name: choiceGroup.name,
82
83
  choice: choiceNode.choiceValue,
83
84
  default: choiceGroup.default,
85
+ emptyChoices: choiceGroup.emptyChoices,
84
86
  lvl,
85
87
  },
86
88
  }),
@@ -118,16 +120,17 @@ function resolveChoiceGroupNodes(choiceNodes) {
118
120
  const vikeConfig = getVikeConfig();
119
121
  const choices = choiceNodes.map((choiceNode) => choiceNode.choiceValue);
120
122
  const { choices: choicesConfig } = vikeConfig.config.docpress;
121
- const choicesAll = { ...CHOICES_BUILT_IN, ...choicesConfig };
123
+ const choicesAll = resolveChoices({ ...CHOICES_BUILT_IN, ...choicesConfig });
122
124
  // Resolve to the group that defines ALL of the block's values. Matching a group that merely
123
125
  // shares ANY value would mis-resolve a custom group that collides with a built-in on a single
124
126
  // value — e.g. a `runtime` group [Node, Bun, Deno, Cloudflare] sharing `Bun` with `pkgManager`.
125
127
  const groupName = Object.keys(choicesAll).find((key) => choices.every((choice) => choicesAll[key].choices.some(({ name }) => name === choice)));
126
128
  assertUsage(groupName, `Missing group name for [${choices}]. Define it in +docpress.choices.`);
127
- const emptyChoices = choicesAll[groupName].choices.filter((choice) => !choices.includes(choice.name)).map((choice) => choice.name);
129
+ const group = choicesAll[groupName];
130
+ const emptyChoices = group.choices.filter((choice) => !choices.includes(choice.name)).map((choice) => choice.name);
128
131
  const choiceGroup = {
129
132
  name: groupName,
130
- ...choicesAll[groupName],
133
+ ...group,
131
134
  emptyChoices,
132
135
  };
133
136
  const mergedChoiceNodes = choiceGroup.choices.map((choice) => {
@@ -0,0 +1,3 @@
1
+ export { getAvailableChoice };
2
+ import type { ChoiceItem } from '../../types/Config.js';
3
+ declare function getAvailableChoice(selectedChoice: string, choices: (string | ChoiceItem)[], emptyChoices: string[], defaultChoice: string): string;
@@ -0,0 +1,14 @@
1
+ export { getAvailableChoice };
2
+ import { resolveChoice } from './resolveChoices.js';
3
+ // A page may include only some of a group's choices, so a choice selected elsewhere (and persisted in
4
+ // localStorage) can be absent here — which would otherwise render nothing (#169). Resolve to a choice
5
+ // that exists on the current page.
6
+ function getAvailableChoice(selectedChoice, choices, emptyChoices, defaultChoice) {
7
+ const isAvailable = (choiceName) => !emptyChoices.includes(choiceName);
8
+ if (isAvailable(selectedChoice))
9
+ return selectedChoice;
10
+ if (isAvailable(defaultChoice))
11
+ return defaultChoice;
12
+ const choicesResolved = choices.map(resolveChoice);
13
+ return choicesResolved.find((choice) => isAvailable(choice.name))?.name ?? selectedChoice;
14
+ }
@@ -0,0 +1,8 @@
1
+ export { resolveChoices, resolveChoice };
2
+ export type { ResolvedChoices };
3
+ import type { Choice, ChoiceItem } from '../../types/Config.js';
4
+ type ResolvedChoices = Record<string, Omit<Choice, 'choices'> & {
5
+ choices: ChoiceItem[];
6
+ }>;
7
+ declare function resolveChoices(choicesConfig: Record<string, Choice>): ResolvedChoices;
8
+ declare function resolveChoice(choice: string | ChoiceItem): ChoiceItem;
@@ -0,0 +1,10 @@
1
+ export { resolveChoices, resolveChoice };
2
+ function resolveChoices(choicesConfig) {
3
+ return Object.fromEntries(Object.entries(choicesConfig).map(([name, group]) => [
4
+ name,
5
+ { ...group, choices: group.choices.map(resolveChoice) },
6
+ ]));
7
+ }
8
+ function resolveChoice(choice) {
9
+ return typeof choice === 'string' ? { name: choice } : choice;
10
+ }
@@ -16,6 +16,7 @@ declare function usePageContextLegacy(): {
16
16
  pageTitle: string | null;
17
17
  documentTitle: string;
18
18
  activeCategoryName: string;
19
+ choices: import("../code-blocks/utils/resolveChoices.js").ResolvedChoices | undefined;
19
20
  };
20
21
  declare function usePageContext(): PageContext;
21
22
  declare function PageContextProvider({ pageContext, children, }: {
@@ -16,4 +16,5 @@ declare function resolvePageContext(pageContext: PageContextServer): {
16
16
  pageTitle: string | null;
17
17
  documentTitle: string;
18
18
  activeCategoryName: string;
19
+ choices: import("./code-blocks/utils/resolveChoices.js").ResolvedChoices | undefined;
19
20
  };
@@ -4,6 +4,7 @@ import { jsxToTextContent } from './utils/jsxToTextContent.js';
4
4
  import pc from '@brillout/picocolors';
5
5
  import { parseMarkdownMini } from './parseMarkdownMini.js';
6
6
  import { determineNavItemsColumnLayout } from './determineNavItemsColumnLayout.js';
7
+ import { resolveChoices } from './code-blocks/utils/resolveChoices.js';
7
8
  function resolvePageContext(pageContext) {
8
9
  const config = pageContext.globalContext.config.docpress;
9
10
  const { urlPathname } = pageContext;
@@ -45,6 +46,7 @@ function resolvePageContext(pageContext) {
45
46
  }
46
47
  // Don't show landing page in navigation
47
48
  navItemsAll = navItemsAll.filter((navItem) => navItem.url !== '/');
49
+ const choices = config.choices && resolveChoices(config.choices);
48
50
  const resolved = {
49
51
  navItemsAll,
50
52
  navItemsDetached,
@@ -54,6 +56,7 @@ function resolvePageContext(pageContext) {
54
56
  pageTitle,
55
57
  documentTitle,
56
58
  activeCategoryName,
59
+ choices,
57
60
  };
58
61
  return resolved;
59
62
  }
@@ -1,4 +1,4 @@
1
- export type { Config };
1
+ export type { Config, Choice, ChoiceItem };
2
2
  import type { HeadingDefinition, HeadingDetachedDefinition } from './Heading.js';
3
3
  import type React from 'react';
4
4
  type Config = {
@@ -47,11 +47,13 @@ type Category = string | {
47
47
  /** Hide from Algolia search */
48
48
  hide?: boolean;
49
49
  };
50
+ /** A choice. A plain `string` is shorthand for `{ name: string }` (no icon). */
51
+ type ChoiceItem = {
52
+ name: string;
53
+ icon?: string;
54
+ iconStyle?: React.CSSProperties;
55
+ };
50
56
  type Choice = {
51
- choices: {
52
- name: string;
53
- icon: string;
54
- iconStyle?: React.CSSProperties;
55
- }[];
57
+ choices: (string | ChoiceItem)[];
56
58
  default: string;
57
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brillout/docpress",
3
- "version": "0.16.43",
3
+ "version": "0.16.45",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@brillout/picocolors": "^1.0.10",
@@ -18,6 +18,7 @@ import { jsxToTextContent } from './utils/jsxToTextContent.js'
18
18
  import pc from '@brillout/picocolors'
19
19
  import { parseMarkdownMini } from './parseMarkdownMini.js'
20
20
  import { determineNavItemsColumnLayout } from './determineNavItemsColumnLayout.js'
21
+ import { resolveChoices } from './code-blocks/utils/resolveChoices.js'
21
22
 
22
23
  type PageSectionResolved = {
23
24
  url: string | null
@@ -80,6 +81,8 @@ function resolvePageContext(pageContext: PageContextServer) {
80
81
  // Don't show landing page in navigation
81
82
  navItemsAll = navItemsAll.filter((navItem) => navItem.url !== '/')
82
83
 
84
+ const choices = config.choices && resolveChoices(config.choices)
85
+
83
86
  const resolved = {
84
87
  navItemsAll,
85
88
  navItemsDetached,
@@ -89,6 +92,7 @@ function resolvePageContext(pageContext: PageContextServer) {
89
92
  pageTitle,
90
93
  documentTitle,
91
94
  activeCategoryName,
95
+ choices,
92
96
  }
93
97
  return resolved
94
98
  }
package/types/Config.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { Config }
1
+ export type { Config, Choice, ChoiceItem }
2
2
 
3
3
  import type { HeadingDefinition, HeadingDetachedDefinition } from './Heading.js'
4
4
  import type React from 'react'
@@ -62,7 +62,9 @@ type Category =
62
62
  hide?: boolean
63
63
  }
64
64
 
65
+ /** A choice. A plain `string` is shorthand for `{ name: string }` (no icon). */
66
+ type ChoiceItem = { name: string; icon?: string; iconStyle?: React.CSSProperties }
65
67
  type Choice = {
66
- choices: { name: string; icon: string; iconStyle?: React.CSSProperties }[]
68
+ choices: (string | ChoiceItem)[]
67
69
  default: string
68
70
  }