@brillout/docpress 0.16.44 → 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.
- package/code-blocks/components/ChoiceGroup.tsx +16 -7
- package/code-blocks/components/Tabs.tsx +4 -1
- package/code-blocks/hooks/useCurrentSelection.ts +7 -3
- package/code-blocks/remarkChoiceGroup.ts +1 -0
- package/code-blocks/types.ts +1 -1
- package/code-blocks/utils/generateChoiceGroupCode.ts +1 -0
- package/code-blocks/utils/getAvailableChoice.ts +20 -0
- package/code-blocks/utils/resolveChoices.ts +5 -2
- package/dist/code-blocks/components/Tabs.js +4 -1
- package/dist/code-blocks/hooks/useCurrentSelection.js +7 -3
- package/dist/code-blocks/remarkChoiceGroup.js +1 -0
- package/dist/code-blocks/types.d.ts +1 -0
- package/dist/code-blocks/utils/generateChoiceGroupCode.js +1 -0
- package/dist/code-blocks/utils/getAvailableChoice.d.ts +3 -0
- package/dist/code-blocks/utils/getAvailableChoice.js +14 -0
- package/dist/code-blocks/utils/resolveChoices.d.ts +2 -1
- package/dist/code-blocks/utils/resolveChoices.js +5 -2
- package/package.json +1 -1
|
@@ -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
|
|
|
@@ -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 [
|
|
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
|
-
|
|
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
|
))}
|
|
@@ -51,14 +54,20 @@ function CustomSelect({ choiceGroup }: { choiceGroup: ChoiceGroupWithParent }) {
|
|
|
51
54
|
const radioId = useId()
|
|
52
55
|
const choicesAll = usePageContext().resolved.choices
|
|
53
56
|
const { name: groupName, emptyChoices, default: defaultChoice, hidden, parentChoiceGroup, isBuiltIn } = choiceGroup
|
|
54
|
-
const
|
|
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
|
-
|
|
62
|
+
let [parentSelectedChoice] = useCurrentSelection(parentChoiceGroup?.name || '', parentChoiceGroup?.default || '')
|
|
63
|
+
let isHidden = hidden
|
|
58
64
|
const setPrevPosition = useRestoreScroll([selectedChoice])
|
|
59
65
|
|
|
60
|
-
|
|
61
|
-
|
|
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)
|
|
@@ -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'
|
|
@@ -14,7 +15,9 @@ function Tabs({ choice, hide = [] }: { choice: string; hide: string[] }) {
|
|
|
14
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 [
|
|
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
|
|
|
@@ -32,13 +32,17 @@ function initializeChoiceGroup() {
|
|
|
32
32
|
switch (groupEl.tagName) {
|
|
33
33
|
case 'SELECT':
|
|
34
34
|
const selectEl = groupEl as HTMLSelectElement
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
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
|
})
|
package/code-blocks/types.ts
CHANGED
|
@@ -10,5 +10,5 @@ type ChoiceGroup = Omit<NonNullable<Config['choices']>[string], 'choices'> & {
|
|
|
10
10
|
lvl: number
|
|
11
11
|
isBuiltIn?: boolean
|
|
12
12
|
}
|
|
13
|
-
type ParentChoiceGroup = { name: string; default: string }
|
|
13
|
+
type ParentChoiceGroup = { name: string; default: string; emptyChoices: string[] }
|
|
14
14
|
type ChoiceGroupWithParent = ChoiceGroup & { parentChoiceGroup?: ParentChoiceGroup & { choices: string[] } }
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { resolveChoices }
|
|
1
|
+
export { resolveChoices, resolveChoice }
|
|
2
2
|
export type { ResolvedChoices }
|
|
3
3
|
|
|
4
4
|
import type { Choice, ChoiceItem } from '../../types/Config.js'
|
|
@@ -7,7 +7,10 @@ type ResolvedChoices = Record<string, Omit<Choice, 'choices'> & { choices: Choic
|
|
|
7
7
|
|
|
8
8
|
function resolveChoices(choicesConfig: Record<string, Choice>): ResolvedChoices {
|
|
9
9
|
return Object.fromEntries(
|
|
10
|
-
Object.entries(choicesConfig).map(([name, group]) => [
|
|
10
|
+
Object.entries(choicesConfig).map(([name, group]) => [
|
|
11
|
+
name,
|
|
12
|
+
{ ...group, choices: group.choices.map(resolveChoice) },
|
|
13
|
+
]),
|
|
11
14
|
)
|
|
12
15
|
}
|
|
13
16
|
function resolveChoice(choice: string | ChoiceItem): ChoiceItem {
|
|
@@ -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';
|
|
@@ -12,7 +13,9 @@ function Tabs({ choice, hide = [] }) {
|
|
|
12
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 [
|
|
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" },
|
|
@@ -30,16 +30,20 @@ function initializeChoiceGroup() {
|
|
|
30
30
|
switch (groupEl.tagName) {
|
|
31
31
|
case 'SELECT':
|
|
32
32
|
const selectEl = groupEl;
|
|
33
|
-
const
|
|
34
|
-
|
|
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
|
}
|
|
@@ -11,6 +11,7 @@ type ChoiceGroup = Omit<NonNullable<Config['choices']>[string], 'choices'> & {
|
|
|
11
11
|
type ParentChoiceGroup = {
|
|
12
12
|
name: string;
|
|
13
13
|
default: string;
|
|
14
|
+
emptyChoices: string[];
|
|
14
15
|
};
|
|
15
16
|
type ChoiceGroupWithParent = ChoiceGroup & {
|
|
16
17
|
parentChoiceGroup?: ParentChoiceGroup & {
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { resolveChoices };
|
|
1
|
+
export { resolveChoices, resolveChoice };
|
|
2
2
|
export type { ResolvedChoices };
|
|
3
3
|
import type { Choice, ChoiceItem } from '../../types/Config.js';
|
|
4
4
|
type ResolvedChoices = Record<string, Omit<Choice, 'choices'> & {
|
|
5
5
|
choices: ChoiceItem[];
|
|
6
6
|
}>;
|
|
7
7
|
declare function resolveChoices(choicesConfig: Record<string, Choice>): ResolvedChoices;
|
|
8
|
+
declare function resolveChoice(choice: string | ChoiceItem): ChoiceItem;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
export { resolveChoices };
|
|
1
|
+
export { resolveChoices, resolveChoice };
|
|
2
2
|
function resolveChoices(choicesConfig) {
|
|
3
|
-
return Object.fromEntries(Object.entries(choicesConfig).map(([name, group]) => [
|
|
3
|
+
return Object.fromEntries(Object.entries(choicesConfig).map(([name, group]) => [
|
|
4
|
+
name,
|
|
5
|
+
{ ...group, choices: group.choices.map(resolveChoice) },
|
|
6
|
+
]));
|
|
4
7
|
}
|
|
5
8
|
function resolveChoice(choice) {
|
|
6
9
|
return typeof choice === 'string' ? { name: choice } : choice;
|