@salesforcedevs/dx-components 1.40.6-tlv-alpha-1 → 1.40.7-bug-fix-alpha
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/lwc.config.json +2 -2
- package/package.json +2 -3
- package/src/modules/dx/codeBlock/codeBlock.ts +1 -9
- package/src/modules/dx/dropdownOption/dropdownOption.css +1 -4
- package/src/modules/dx/searchHybrid/searchHybrid.html +1 -1
- package/src/modules/dx/searchHybrid/searchHybrid.ts +4 -0
- package/src/modules/dx/sidebar/sidebar.html +1 -0
- package/src/modules/dx/sidebarOld/sidebarOld.html +1 -0
- package/src/modules/dx/tree/tree.html +1 -0
- package/src/modules/dx/tree/tree.ts +5 -1
- package/src/modules/dx/tree/treeHandler.ts +11 -4
- package/src/modules/dx/treeItem/treeItem.css +8 -0
- package/src/modules/dx/treeItem/treeItem.html +3 -2
- package/src/modules/dx/treeItem/treeItem.ts +14 -5
- package/src/modules/dx/treeTile/treeTile.css +53 -0
- package/src/modules/dx/versionPicker/versionPicker.css +248 -0
- package/src/modules/dx/versionPicker/versionPicker.html +73 -0
- package/src/modules/dx/versionPicker/versionPicker.ts +157 -0
- package/src/modules/dxHelpers/commonSidebar/commonSidebar.css +2 -2
- package/src/modules/dxUtils/topicVersion/topicVersion.ts +58 -9
package/lwc.config.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"modules": [
|
|
3
3
|
{ "dir": "src/modules" },
|
|
4
|
-
{ "npm": "@salesforcedevs/dw-components" }
|
|
5
|
-
{ "npm": "@salesforcedevs/docs-components" }
|
|
4
|
+
{ "npm": "@salesforcedevs/dw-components" }
|
|
6
5
|
],
|
|
7
6
|
"expose": [
|
|
8
7
|
"dx/agenda",
|
|
@@ -111,6 +110,7 @@
|
|
|
111
110
|
"dx/tooltip",
|
|
112
111
|
"dx/trafficLabeler",
|
|
113
112
|
"dx/tree",
|
|
113
|
+
"dx/versionPicker",
|
|
114
114
|
"dx/typeBadge",
|
|
115
115
|
"dx/vimeoPlayer",
|
|
116
116
|
"dxBaseElements/headerBase",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/dx-components",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.7-bug-fix-alpha",
|
|
4
4
|
"description": "DX Lightning web components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -43,6 +43,5 @@
|
|
|
43
43
|
"eventsourcemock": "2.0.0",
|
|
44
44
|
"luxon": "3.4.4",
|
|
45
45
|
"msw": "^2.12.4"
|
|
46
|
-
}
|
|
47
|
-
"stableVersion": "1.40.5"
|
|
46
|
+
}
|
|
48
47
|
}
|
|
@@ -15,9 +15,6 @@ import { normalizeBoolean } from "dxUtils/normalizers";
|
|
|
15
15
|
- SQL Documentation -> SQL
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
// Used for remove enclosing <pre> tag's (if occurs)
|
|
19
|
-
const preTagRegexp: RegExp = /^<pre.*?>(.*)<\/pre>$/is;
|
|
20
|
-
|
|
21
18
|
export const LIGHT = "light";
|
|
22
19
|
export const DARK = "dark";
|
|
23
20
|
export const EVENT_NAME = "dx-codeblock-theme";
|
|
@@ -87,12 +84,7 @@ export default class CodeBlock extends LightningElement {
|
|
|
87
84
|
set codeBlock(value: string) {
|
|
88
85
|
this._codeBlockRendered = false;
|
|
89
86
|
this._isCbHeightCalculated = false;
|
|
90
|
-
|
|
91
|
-
this._codeBlock = (
|
|
92
|
-
(match = preTagRegexp.exec(value.trim())) === null
|
|
93
|
-
? value.trim()
|
|
94
|
-
: match[1]
|
|
95
|
-
).trim();
|
|
87
|
+
this._codeBlock = value.trim();
|
|
96
88
|
}
|
|
97
89
|
|
|
98
90
|
@api
|
|
@@ -29,10 +29,7 @@
|
|
|
29
29
|
/* Opt-in hooks; defaults preserve today's look. */
|
|
30
30
|
padding: var(--dx-c-dropdown-option-padding, var(--dx-g-spacing-sm));
|
|
31
31
|
font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-sm));
|
|
32
|
-
border-radius: var(
|
|
33
|
-
--dx-c-dropdown-option-border-radius,
|
|
34
|
-
var(--dx-g-spacing-xs)
|
|
35
|
-
);
|
|
32
|
+
border-radius: var(--dx-c-dropdown-option-border-radius, 0);
|
|
36
33
|
transition: var(--dx-g-transition-hue-1x);
|
|
37
34
|
align-items: center;
|
|
38
35
|
outline: none !important;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
decodeHtmlEntities,
|
|
9
9
|
highlightedSnippetHtml,
|
|
10
10
|
isSortOption,
|
|
11
|
+
resultHref,
|
|
11
12
|
SORT_OPTIONS,
|
|
12
13
|
type SearchResult,
|
|
13
14
|
type SortOption
|
|
@@ -30,6 +31,8 @@ const SORT_LABELS: Record<SortOption, string> = {
|
|
|
30
31
|
interface DisplayResult {
|
|
31
32
|
key: string;
|
|
32
33
|
url: string;
|
|
34
|
+
// Matched-section link; falls back to the page url.
|
|
35
|
+
href: string;
|
|
33
36
|
title: string;
|
|
34
37
|
description: string | null;
|
|
35
38
|
hasDescription: boolean;
|
|
@@ -167,6 +170,7 @@ export default class SearchHybrid extends LightningElement {
|
|
|
167
170
|
return {
|
|
168
171
|
key: `${r.url}-${i}`,
|
|
169
172
|
url: r.url,
|
|
173
|
+
href: resultHref(r),
|
|
170
174
|
title: decodeHtmlEntities(r.title),
|
|
171
175
|
description,
|
|
172
176
|
hasDescription: description !== null,
|
|
@@ -4,6 +4,10 @@ import { toJson } from "dxUtils/normalizers";
|
|
|
4
4
|
import { TreeHandler } from "./treeHandler";
|
|
5
5
|
|
|
6
6
|
export default class Tree extends LightningElement {
|
|
7
|
+
// Must be set before `treeRoot`. Both sidebar templates pass `language`
|
|
8
|
+
// first; the tree resolves stored topic versions with it while being built.
|
|
9
|
+
@api language?: string;
|
|
10
|
+
|
|
7
11
|
@api
|
|
8
12
|
get treeRoot() {
|
|
9
13
|
return this._treeRoot!;
|
|
@@ -11,7 +15,7 @@ export default class Tree extends LightningElement {
|
|
|
11
15
|
|
|
12
16
|
set treeRoot(value: TreeNode | string) {
|
|
13
17
|
value = toJson(value);
|
|
14
|
-
this._treeHandler = new TreeHandler();
|
|
18
|
+
this._treeHandler = new TreeHandler(this.language);
|
|
15
19
|
this._treeRoot = this._treeHandler.initialize(value as TreeNode);
|
|
16
20
|
if (this._value) {
|
|
17
21
|
this.selectedKey = this._treeHandler.getKeyByName(this._value);
|
|
@@ -22,8 +22,10 @@ const ROOT_KEY = "0";
|
|
|
22
22
|
export class TreeHandler {
|
|
23
23
|
private _nodeMap: { [key: string]: MapItem };
|
|
24
24
|
private _nameKeyMap: { [key: string]: string };
|
|
25
|
+
private _language?: string;
|
|
25
26
|
|
|
26
|
-
constructor() {
|
|
27
|
+
constructor(language?: string) {
|
|
28
|
+
this._language = language;
|
|
27
29
|
this._nodeMap = {};
|
|
28
30
|
this._nameKeyMap = {};
|
|
29
31
|
}
|
|
@@ -73,7 +75,7 @@ export class TreeHandler {
|
|
|
73
75
|
}
|
|
74
76
|
): VersionedInternalTreeNode {
|
|
75
77
|
const ownSelected = node.versions?.length
|
|
76
|
-
? resolveSelectedVersion(node)
|
|
78
|
+
? resolveSelectedVersion(node, this._language)
|
|
77
79
|
: undefined;
|
|
78
80
|
const versionContext =
|
|
79
81
|
ownSelected && node.versions
|
|
@@ -115,8 +117,13 @@ export class TreeHandler {
|
|
|
115
117
|
};
|
|
116
118
|
|
|
117
119
|
const mapItem = { node: convertedNode, parent };
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
// Prefer the selected version's own topic list when the server sent one.
|
|
121
|
+
// Otherwise keep the latest children and rewrite their hrefs.
|
|
122
|
+
const childNodes = Array.isArray(ownSelected?.children)
|
|
123
|
+
? ownSelected.children
|
|
124
|
+
: node.children;
|
|
125
|
+
if (childNodes && childNodes.length > 0) {
|
|
126
|
+
convertedNode.children = childNodes.map((child, index) =>
|
|
120
127
|
this.convert(child, level + 1, mapItem, index, versionContext)
|
|
121
128
|
);
|
|
122
129
|
}
|
|
@@ -16,6 +16,14 @@
|
|
|
16
16
|
display: flex;
|
|
17
17
|
align-items: center;
|
|
18
18
|
min-width: 0;
|
|
19
|
+
position: relative;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* The tile's keyboard focus layers fill this row, picker included. isolate
|
|
23
|
+
keeps the fill above the row's background and below its content. Only while
|
|
24
|
+
focus is inside: a row that is always isolated traps its picker's menu. */
|
|
25
|
+
.tree-item-row:focus-within {
|
|
26
|
+
isolation: isolate;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
.tree-item-row dx-tree-tile {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
has-version-picker={showVersionPicker}
|
|
16
16
|
onclick={onLinkClick}
|
|
17
17
|
></dx-tree-tile>
|
|
18
|
-
<
|
|
18
|
+
<dx-version-picker
|
|
19
19
|
if:true={showVersionPicker}
|
|
20
20
|
class="tree-item-version-picker"
|
|
21
21
|
small="true"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
onchange={onVersionChange}
|
|
26
26
|
onclick={onVersionPickerClick}
|
|
27
27
|
onmousedown={onVersionPickerClick}
|
|
28
|
-
></
|
|
28
|
+
></dx-version-picker>
|
|
29
29
|
</div>
|
|
30
30
|
<img
|
|
31
31
|
if:true={showChildrenLoading}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
key={child.key}
|
|
40
40
|
tree-node={child}
|
|
41
41
|
selected-key={selectedKey}
|
|
42
|
+
language={language}
|
|
42
43
|
></dx-tree-item>
|
|
43
44
|
</template>
|
|
44
45
|
</template>
|
|
@@ -23,6 +23,7 @@ type VersionedInternalTreeNode = InternalTreeNode & {
|
|
|
23
23
|
export default class TreeItem extends LightningElement {
|
|
24
24
|
@api selectedKey?: string;
|
|
25
25
|
@api isRoot: boolean = false;
|
|
26
|
+
@api language?: string;
|
|
26
27
|
|
|
27
28
|
@api
|
|
28
29
|
public get treeNode() {
|
|
@@ -127,7 +128,11 @@ export default class TreeItem extends LightningElement {
|
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
private get selectedVersion(): TopicVersionOption | undefined {
|
|
130
|
-
return resolveSelectedVersion(this._treeNode);
|
|
131
|
+
return resolveSelectedVersion(this._treeNode, this.language);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private get topicVersionKey(): string {
|
|
135
|
+
return getTopicVersionKey(this._treeNode, this.language);
|
|
131
136
|
}
|
|
132
137
|
|
|
133
138
|
private get isLatestVersion(): boolean {
|
|
@@ -171,8 +176,8 @@ export default class TreeItem extends LightningElement {
|
|
|
171
176
|
|
|
172
177
|
// Remember this topic's resolved version (own URL, descendant URL, or
|
|
173
178
|
// session) so sibling pickers stay independent for the tab.
|
|
174
|
-
const selected =
|
|
175
|
-
const topicKey =
|
|
179
|
+
const selected = this.selectedVersion;
|
|
180
|
+
const topicKey = this.topicVersionKey;
|
|
176
181
|
if (this.versions?.length && selected && topicKey) {
|
|
177
182
|
persistTopicVersion(topicKey, selected.id);
|
|
178
183
|
}
|
|
@@ -180,7 +185,7 @@ export default class TreeItem extends LightningElement {
|
|
|
180
185
|
|
|
181
186
|
private onVersionChange(event: CustomEvent): void {
|
|
182
187
|
event.stopPropagation();
|
|
183
|
-
persistTopicVersion(
|
|
188
|
+
persistTopicVersion(this.topicVersionKey, event.detail);
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
private onVersionPickerClick(event: Event): void {
|
|
@@ -213,8 +218,12 @@ export default class TreeItem extends LightningElement {
|
|
|
213
218
|
}
|
|
214
219
|
|
|
215
220
|
private preventDefaultLinkBehavior(event: Event) {
|
|
216
|
-
//
|
|
221
|
+
// Nodes with no link.href (AMF items use name) must not follow the
|
|
222
|
+
// anchor. Parent-only topics are excluded: their href is the child page.
|
|
223
|
+
// Otherwise cancel only an empty href or a click on the current path.
|
|
217
224
|
if (
|
|
225
|
+
(!this._treeNode.link?.href &&
|
|
226
|
+
!isParentOnlyTopic(this._treeNode)) ||
|
|
218
227
|
!this.href ||
|
|
219
228
|
normalizePath(this.href) === normalizePath(window.location.pathname)
|
|
220
229
|
) {
|
|
@@ -217,3 +217,56 @@ a:visited.tile-root,
|
|
|
217
217
|
a:link.tile-root {
|
|
218
218
|
color: var(--dx-g-blue-vibrant-20);
|
|
219
219
|
}
|
|
220
|
+
|
|
221
|
+
/* Keyboard focus matches dx-dropdown-option: a light fill, a 2px blue inset
|
|
222
|
+
ring, and a white inner band. The layers fill dx-tree-item's row (it sets
|
|
223
|
+
position: relative), so a version picker beside the link sits inside the
|
|
224
|
+
focus box. Undo the shared tree item focus box (commonTreeItem) first. */
|
|
225
|
+
:host .sidebar-item:focus-visible {
|
|
226
|
+
margin: 0;
|
|
227
|
+
padding: 6px var(--dx-g-spacing-lg) 6px
|
|
228
|
+
var(--dx-g-global-header-padding-horizontal);
|
|
229
|
+
outline: none;
|
|
230
|
+
border-radius: 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* Keep the picker row's 4px right padding on focus, so the label does not rewrap. */
|
|
234
|
+
:host .tile-with-version-picker:focus-visible {
|
|
235
|
+
padding-right: var(--dx-g-spacing-xs);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
:host .sidebar-item:focus-visible .indentation {
|
|
239
|
+
padding-top: 0;
|
|
240
|
+
padding-bottom: 0;
|
|
241
|
+
outline: none;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
:host .sidebar-item:focus-visible::before,
|
|
245
|
+
:host .sidebar-item:focus-visible::after {
|
|
246
|
+
content: "";
|
|
247
|
+
position: absolute;
|
|
248
|
+
inset: 0;
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
:host .sidebar-item:focus-visible::before {
|
|
253
|
+
z-index: -1;
|
|
254
|
+
background: var(--dx-g-cloud-blue-vibrant-95);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
:host .sidebar-item:focus-visible::after {
|
|
258
|
+
z-index: 1;
|
|
259
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-60),
|
|
260
|
+
inset 0 0 0 4px white;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* A selected item keeps its left bar, inside the ring, when it also has keyboard focus. */
|
|
264
|
+
:host
|
|
265
|
+
.sidebar-item:is(
|
|
266
|
+
.sidebar-item-selected,
|
|
267
|
+
.sidebar-item-selected-text
|
|
268
|
+
):focus-visible::after {
|
|
269
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-60),
|
|
270
|
+
inset 0 0 0 4px white,
|
|
271
|
+
inset calc(2 * var(--dx-g-spacing-xs)) 0 0 0 var(--dx-g-blue-vibrant-40);
|
|
272
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
@import "dxHelpers/reset";
|
|
2
|
+
|
|
3
|
+
/* NOTE: dx-version-picker-width is used by both dx-button and dx-dropdown to maintain a consistent width. */
|
|
4
|
+
:host {
|
|
5
|
+
/* Always positioned so the popover's containing block stays the picker.
|
|
6
|
+
Toggling position with aria-expanded lets the menu jump to the sidebar
|
|
7
|
+
(top of the ToC) for a frame before LWC hides it. */
|
|
8
|
+
position: relative;
|
|
9
|
+
|
|
10
|
+
--dx-c-dropdown-option-font-weight: normal;
|
|
11
|
+
--dx-c-dropdown-option-font-size: var(--dx-g-text-base);
|
|
12
|
+
--dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
|
|
13
|
+
--dx-c-dropdown-option-active-label-color: var(--dx-g-gray-10);
|
|
14
|
+
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
15
|
+
var(--dx-g-spacing-lg);
|
|
16
|
+
--dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
|
|
17
|
+
--popover-container-open-transform: translateY(4px);
|
|
18
|
+
--dx-c-version-picker-focus-ring: 0 0 0 2px white,
|
|
19
|
+
0 0 0 4px var(--dx-g-blue-vibrant-60);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Open menus must stack above later sibling pickers in the sidebar. */
|
|
23
|
+
:host(:has([aria-expanded="true"])) {
|
|
24
|
+
z-index: 2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.version-picker-container {
|
|
28
|
+
/* Override --dx-version-picker-padding to change the inset (e.g. when the
|
|
29
|
+
picker sits inside an already-padded container). */
|
|
30
|
+
padding: var(
|
|
31
|
+
--dx-version-picker-padding,
|
|
32
|
+
var(--dx-g-spacing-sm) var(--dx-g-spacing-lg) var(--dx-g-spacing-sm)
|
|
33
|
+
var(--dx-g-global-header-padding-horizontal)
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/* Override --dx-version-picker-divider (e.g. `none`) to restyle dividers. */
|
|
37
|
+
border-top: var(--dx-version-picker-divider, 1px solid var(--dx-g-gray-90));
|
|
38
|
+
border-bottom: var(
|
|
39
|
+
--dx-version-picker-divider,
|
|
40
|
+
1px solid var(--dx-g-gray-90)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Inline sidebar variant: drop the header-slot chrome so the picker sits
|
|
45
|
+
beside a tree node without extra padding or divider lines. */
|
|
46
|
+
.version-picker-container-small {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
padding: 0;
|
|
50
|
+
border: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.version-picker-button {
|
|
54
|
+
display: flex;
|
|
55
|
+
width: var(--dx-version-picker-width, 296px);
|
|
56
|
+
overflow: visible;
|
|
57
|
+
|
|
58
|
+
--dx-c-button-horizontal-spacing: var(--dx-g-spacing-sm);
|
|
59
|
+
--dx-g-button-icon-color: var(--dx-g-gray-50);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.version-picker-button:hover,
|
|
63
|
+
.version-picker-button[aria-expanded="true"] {
|
|
64
|
+
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* NOTE: This CSS ensures the span inside the button stays within the parent's width, avoiding overflow.
|
|
69
|
+
* Not keeping this in common component to ensure that existing functionality works as it is.
|
|
70
|
+
*/
|
|
71
|
+
dx-button::part(content) {
|
|
72
|
+
width: inherit;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Border width stays 1px in every state. Hover, open, and focus paint the
|
|
77
|
+
2px blue stroke with an inset shadow so the box does not grow.
|
|
78
|
+
Tertiary's hover border is overridden. */
|
|
79
|
+
.version-picker-button::part(container) {
|
|
80
|
+
box-sizing: border-box;
|
|
81
|
+
background: white;
|
|
82
|
+
border: 1px solid var(--dx-g-gray-50);
|
|
83
|
+
border-radius: var(--dx-g-spacing-xs);
|
|
84
|
+
box-shadow: none;
|
|
85
|
+
color: var(--dx-g-gray-10);
|
|
86
|
+
outline: none;
|
|
87
|
+
transition: var(--dx-g-transition-hue-1x), box-shadow 0.15s linear;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Hover only (not open): 2px #032D60 on #EEF4FF, no ring.
|
|
91
|
+
Border color matches the fill so the 1px border does not add a third
|
|
92
|
+
pixel around the inset stroke.
|
|
93
|
+
:focus-visible is included so click-open + Escape (which upgrades the
|
|
94
|
+
trigger to keyboard focus) still looks like hover while the pointer is on
|
|
95
|
+
the picker. */
|
|
96
|
+
.version-picker-button:hover:not([aria-expanded="true"])::part(container),
|
|
97
|
+
.version-picker-button:hover:not([aria-expanded="true"])::part(
|
|
98
|
+
container
|
|
99
|
+
):focus-visible {
|
|
100
|
+
background: var(--dx-g-blue-vibrant-95);
|
|
101
|
+
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
102
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Open keeps the fill and the 2px stroke. The focus ring is keyboard-only,
|
|
106
|
+
on the closed picker below. */
|
|
107
|
+
.version-picker-button[aria-expanded="true"]::part(container) {
|
|
108
|
+
background: var(--dx-g-blue-vibrant-95);
|
|
109
|
+
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
110
|
+
border-radius: var(--dx-g-spacing-xs);
|
|
111
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Keyboard focus while closed. */
|
|
115
|
+
.version-picker-button:not([aria-expanded="true"])::part(
|
|
116
|
+
container
|
|
117
|
+
):focus-visible {
|
|
118
|
+
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
119
|
+
|
|
120
|
+
background: var(--dx-g-blue-vibrant-95);
|
|
121
|
+
border: 1px solid var(--dx-g-blue-vibrant-95);
|
|
122
|
+
border-radius: var(--dx-g-spacing-xs);
|
|
123
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20),
|
|
124
|
+
var(--dx-c-version-picker-focus-ring);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.selected-version {
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: row;
|
|
130
|
+
align-items: center;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.selected-version-label {
|
|
134
|
+
flex: 1;
|
|
135
|
+
overflow: hidden;
|
|
136
|
+
text-align: left;
|
|
137
|
+
text-overflow: ellipsis;
|
|
138
|
+
white-space: nowrap;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
dx-type-badge.latest-badge,
|
|
142
|
+
dx-type-badge.not-latest-badge {
|
|
143
|
+
margin-left: var(--dx-g-spacing-sm);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ------------------------------------------------------------------ *
|
|
147
|
+
* Small "dot" variant (opt-in via `small`). Additive
|
|
148
|
+
* ------------------------------------------------------------------ */
|
|
149
|
+
|
|
150
|
+
/* Menu is capped at the ToC width (296px), not the 104px trigger, so options
|
|
151
|
+
keep the same 16px / 8px 24px / gray-10 type as the default picker. */
|
|
152
|
+
.version-picker-dropdown-small {
|
|
153
|
+
--dx-c-dropdown-option-font-size: var(--dx-g-text-base);
|
|
154
|
+
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
155
|
+
var(--dx-g-spacing-lg);
|
|
156
|
+
--dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
|
|
157
|
+
--dx-c-dropdown-option-border-radius: 0;
|
|
158
|
+
--dx-c-popover-border: none;
|
|
159
|
+
|
|
160
|
+
/* Hug the labels (down to the shortest) but never wider than the ToC
|
|
161
|
+
content: 296px on desktop, and on the smallest breakpoint the sidebar
|
|
162
|
+
variable grows with the viewport up to 720px. Long labels wrap inside
|
|
163
|
+
that cap. */
|
|
164
|
+
--dx-c-dropdown-menu-max-width: var(--dx-version-picker-width, 296px);
|
|
165
|
+
--dx-c-dropdown-fit-white-space: normal;
|
|
166
|
+
--dx-c-dropdown-fit-overflow: hidden;
|
|
167
|
+
--dx-c-dropdown-fit-text-overflow: clip;
|
|
168
|
+
--dx-c-dropdown-option-overflow-wrap: anywhere;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.version-picker-button-small {
|
|
172
|
+
width: fit-content;
|
|
173
|
+
max-width: 104px;
|
|
174
|
+
|
|
175
|
+
--dx-c-button-font-size: var(--dx-g-text-xs);
|
|
176
|
+
--dx-c-button-font-weight: var(--dx-g-font-normal);
|
|
177
|
+
--dx-c-button-line-height: var(--dx-g-spacing-lg);
|
|
178
|
+
--dx-c-button-icon-gap: var(--dx-g-spacing-2xs);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* min-width: 0 on each flex ancestor lets the label truncate: a flex item's
|
|
182
|
+
default min-width: auto refuses to shrink below its content and overrides the
|
|
183
|
+
host's max-width, so without this the trigger overflows instead. */
|
|
184
|
+
.version-picker-button-small::part(content) {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex: 1;
|
|
187
|
+
min-width: 0;
|
|
188
|
+
width: auto;
|
|
189
|
+
overflow: hidden;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* width:100% re-ties to the 104px-capped host (container is width:inherit,
|
|
193
|
+
which copies width but not max-width). Border/bg/states are shared above. */
|
|
194
|
+
.version-picker-button-small::part(container) {
|
|
195
|
+
width: 100%;
|
|
196
|
+
height: var(--dx-g-spacing-lg);
|
|
197
|
+
padding: 0 var(--dx-g-spacing-xs);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.selected-version-small {
|
|
201
|
+
flex: 1;
|
|
202
|
+
min-width: 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.version-picker-dot {
|
|
206
|
+
flex: 0 0 auto;
|
|
207
|
+
width: var(--dx-g-spacing-sm);
|
|
208
|
+
height: var(--dx-g-spacing-sm);
|
|
209
|
+
margin-right: var(--dx-g-spacing-xs);
|
|
210
|
+
border-radius: 50%;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.version-picker-dot-latest {
|
|
214
|
+
background: var(--dx-g-green-vibrant-60);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.version-picker-dot-not-latest {
|
|
218
|
+
background: var(--dx-g-yellow-vibrant-80);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* inline-block (not flex) so the label can truncate; line-height centers it. */
|
|
222
|
+
.version-picker-readonly {
|
|
223
|
+
box-sizing: border-box;
|
|
224
|
+
display: inline-block;
|
|
225
|
+
width: var(--dx-version-picker-width, 296px);
|
|
226
|
+
height: var(--dx-g-spacing-xl);
|
|
227
|
+
margin: 0;
|
|
228
|
+
padding: 0 var(--dx-g-spacing-sm);
|
|
229
|
+
border: 1px solid var(--dx-g-gray-80);
|
|
230
|
+
border-radius: var(--dx-g-spacing-xs);
|
|
231
|
+
overflow: hidden;
|
|
232
|
+
color: var(--dx-g-gray-10);
|
|
233
|
+
text-overflow: ellipsis;
|
|
234
|
+
white-space: nowrap;
|
|
235
|
+
font: var(--dx-g-font-normal) var(--dx-g-text-sm) / var(--dx-g-spacing-xl)
|
|
236
|
+
var(--dx-g-font-sans),
|
|
237
|
+
sans-serif;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.version-picker-readonly-small {
|
|
241
|
+
width: fit-content;
|
|
242
|
+
max-width: 104px;
|
|
243
|
+
height: var(--dx-g-spacing-lg);
|
|
244
|
+
padding: 0 var(--dx-g-spacing-xs);
|
|
245
|
+
font: var(--dx-g-font-normal) var(--dx-g-text-xs) / var(--dx-g-spacing-lg)
|
|
246
|
+
var(--dx-g-font-sans),
|
|
247
|
+
sans-serif;
|
|
248
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div lwc:if={showVersionPicker} class={containerClass}>
|
|
3
|
+
<!-- Small read-only: plain value, no dropdown -->
|
|
4
|
+
<p
|
|
5
|
+
lwc:if={readOnly}
|
|
6
|
+
class={readOnlyClass}
|
|
7
|
+
title={selectedVersion.label}
|
|
8
|
+
>
|
|
9
|
+
{selectedVersion.label}
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<dx-dropdown
|
|
13
|
+
lwc:else
|
|
14
|
+
class={dropdownClass}
|
|
15
|
+
options={versions}
|
|
16
|
+
analytics-event="custEv_docVersionSelect"
|
|
17
|
+
analytics-payload={analyticsPayload}
|
|
18
|
+
value={selectedVersion.id}
|
|
19
|
+
width-mode={dropdownWidthMode}
|
|
20
|
+
onchange={onVersionChange}
|
|
21
|
+
>
|
|
22
|
+
<dx-button
|
|
23
|
+
class={triggerClass}
|
|
24
|
+
variant="tertiary"
|
|
25
|
+
size="small"
|
|
26
|
+
font="sans"
|
|
27
|
+
icon-symbol="chevrondown"
|
|
28
|
+
icon-size={triggerIconSize}
|
|
29
|
+
aria-label={triggerAriaLabel}
|
|
30
|
+
>
|
|
31
|
+
<div class={selectedVersionClass}>
|
|
32
|
+
<!-- Small: leading colored dot -->
|
|
33
|
+
<template lwc:if={small}>
|
|
34
|
+
<template lwc:if={showLatestTag}>
|
|
35
|
+
<span class={dotClass} aria-hidden="true"></span>
|
|
36
|
+
</template>
|
|
37
|
+
<p
|
|
38
|
+
class="selected-version-label"
|
|
39
|
+
title={selectedVersion.label}
|
|
40
|
+
>
|
|
41
|
+
{selectedVersion.label}
|
|
42
|
+
</p>
|
|
43
|
+
</template>
|
|
44
|
+
<!-- Default: trailing Latest/Not-Latest badge -->
|
|
45
|
+
<template lwc:else>
|
|
46
|
+
<p
|
|
47
|
+
class="selected-version-label"
|
|
48
|
+
title={selectedVersion.label}
|
|
49
|
+
>
|
|
50
|
+
{selectedVersion.label}
|
|
51
|
+
</p>
|
|
52
|
+
<template lwc:if={showLatestTag}>
|
|
53
|
+
<dx-type-badge
|
|
54
|
+
class="latest-badge"
|
|
55
|
+
lwc:if={latestVersion}
|
|
56
|
+
variant="status-success"
|
|
57
|
+
value="Latest"
|
|
58
|
+
size="small"
|
|
59
|
+
></dx-type-badge>
|
|
60
|
+
<dx-type-badge
|
|
61
|
+
class="not-latest-badge"
|
|
62
|
+
lwc:else
|
|
63
|
+
variant="status-warning"
|
|
64
|
+
value="Not Latest"
|
|
65
|
+
size="small"
|
|
66
|
+
></dx-type-badge>
|
|
67
|
+
</template>
|
|
68
|
+
</template>
|
|
69
|
+
</div>
|
|
70
|
+
</dx-button>
|
|
71
|
+
</dx-dropdown>
|
|
72
|
+
</div>
|
|
73
|
+
</template>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { LightningElement, api, track } from "lwc";
|
|
2
|
+
import cx from "classnames";
|
|
3
|
+
|
|
4
|
+
import { AnalyticsPayload, OptionWithNested } from "typings/custom";
|
|
5
|
+
|
|
6
|
+
import { toJson, normalizeBoolean } from "dxUtils/normalizers";
|
|
7
|
+
|
|
8
|
+
export default class VersionPicker extends LightningElement {
|
|
9
|
+
@api analyticsEvent?: string;
|
|
10
|
+
@api analyticsPayload?: AnalyticsPayload;
|
|
11
|
+
|
|
12
|
+
@track private _versions!: OptionWithNested[];
|
|
13
|
+
private _selectedVersion?: OptionWithNested;
|
|
14
|
+
private _latestVersion: boolean = false;
|
|
15
|
+
private _hideBadge: boolean = false;
|
|
16
|
+
private _small: boolean = false;
|
|
17
|
+
private _readOnly: boolean = false;
|
|
18
|
+
|
|
19
|
+
@api
|
|
20
|
+
get versions() {
|
|
21
|
+
return this._versions;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
set versions(value: OptionWithNested[]) {
|
|
25
|
+
this._versions = toJson(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@api
|
|
29
|
+
get selectedVersion(): OptionWithNested | undefined {
|
|
30
|
+
return this._selectedVersion || this.versions[0];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set selectedVersion(value: OptionWithNested) {
|
|
34
|
+
if (value) {
|
|
35
|
+
this._selectedVersion = toJson(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@api
|
|
40
|
+
get latestVersion() {
|
|
41
|
+
return this._latestVersion;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set latestVersion(value) {
|
|
45
|
+
this._latestVersion = normalizeBoolean(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@api
|
|
49
|
+
get hideBadge() {
|
|
50
|
+
return this._hideBadge;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
set hideBadge(value) {
|
|
54
|
+
this._hideBadge = normalizeBoolean(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Opt-in compact "dot" variant (24px, abbreviated label). Off by default so
|
|
58
|
+
// existing consumers render the default trigger unchanged.
|
|
59
|
+
@api
|
|
60
|
+
get small() {
|
|
61
|
+
return this._small;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
set small(value) {
|
|
65
|
+
this._small = normalizeBoolean(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Opt-in read-only state: plain value, no dropdown menu.
|
|
69
|
+
@api
|
|
70
|
+
get readOnly() {
|
|
71
|
+
return this._readOnly;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
set readOnly(value) {
|
|
75
|
+
this._readOnly = normalizeBoolean(value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private get showVersionPicker() {
|
|
79
|
+
return this._versions && this._versions.length !== 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private get containerClass(): string {
|
|
83
|
+
return this.small
|
|
84
|
+
? "version-picker-container-small"
|
|
85
|
+
: "version-picker-container";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private get showLatestTag(): boolean {
|
|
89
|
+
return !this.hideBadge;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private get readOnlyClass(): string {
|
|
93
|
+
return cx(
|
|
94
|
+
"version-picker-readonly",
|
|
95
|
+
this.small && "version-picker-readonly-small"
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private get dotClass(): string {
|
|
100
|
+
return `version-picker-dot ${
|
|
101
|
+
this.latestVersion
|
|
102
|
+
? "version-picker-dot-latest"
|
|
103
|
+
: "version-picker-dot-not-latest"
|
|
104
|
+
}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Accessible name for the small trigger: includes the latest state so it
|
|
108
|
+
// isn't conveyed by the dot's color alone (WCAG 1.4.1). Omits the state
|
|
109
|
+
// when the indicator is hidden.
|
|
110
|
+
private get smallTriggerAriaLabel(): string {
|
|
111
|
+
const label = this.selectedVersion?.label ?? "";
|
|
112
|
+
if (!this.showLatestTag) {
|
|
113
|
+
return label;
|
|
114
|
+
}
|
|
115
|
+
return `${label}, ${this.latestVersion ? "Latest" : "Not Latest"}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Per-variant values for the shared dropdown/button markup below. The
|
|
119
|
+
// small variant opts into extra classes and a compact chevron.
|
|
120
|
+
|
|
121
|
+
// Not cx(): the default must have NO class attribute (cx returns "", which
|
|
122
|
+
// would render class="" and break the frozen consumer snapshots).
|
|
123
|
+
private get dropdownClass(): string | undefined {
|
|
124
|
+
return this.small ? "version-picker-dropdown-small" : undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// "full" matches the trigger (ToC width). "fit-end" sizes to the labels,
|
|
128
|
+
// caps at the ToC width, and opens inward.
|
|
129
|
+
private get dropdownWidthMode(): "full" | "fit-end" {
|
|
130
|
+
return this.small ? "fit-end" : "full";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private get triggerClass(): string {
|
|
134
|
+
return cx(
|
|
135
|
+
"version-picker-button",
|
|
136
|
+
this.small && "version-picker-button-small"
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private get triggerIconSize(): string {
|
|
141
|
+
return this.small ? "xsmall" : "medium";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Default returns "" (dx-button's own default) so the trigger's rendered
|
|
145
|
+
// aria-label is unchanged for the default variant.
|
|
146
|
+
private get triggerAriaLabel(): string {
|
|
147
|
+
return this.small ? this.smallTriggerAriaLabel : "";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private get selectedVersionClass(): string {
|
|
151
|
+
return cx("selected-version", this.small && "selected-version-small");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private onVersionChange(e: CustomEvent) {
|
|
155
|
+
this.dispatchEvent(new CustomEvent("change", { detail: e.detail }));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
--dx-c-sidebar-button-border-hover: var(--dx-g-blue-vibrant-50);
|
|
12
12
|
--dx-c-sidebar-left-padding: var(--dx-g-global-header-padding-horizontal);
|
|
13
13
|
--dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
|
|
14
|
-
--
|
|
14
|
+
--dx-version-picker-width: 296px;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.header {
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
|
|
173
173
|
@media screen and (max-width: 768px) {
|
|
174
174
|
:host {
|
|
175
|
-
--
|
|
175
|
+
--dx-version-picker-width: calc(
|
|
176
176
|
100vw - var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-lg)
|
|
177
177
|
);
|
|
178
178
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export const TOPIC_VERSION_SESSION_KEY = "sfdocs-topic-version-selections";
|
|
2
|
+
export const DOCS_PATH_SEGMENT = "docs";
|
|
3
|
+
// Locale position in the path segments: `/docs/<locale>` for Docs,
|
|
4
|
+
// `/docs/<brand>/<locale>` for Guides/References.
|
|
5
|
+
export const DOCS_LOCALE_SEGMENT_INDEX = 1;
|
|
6
|
+
export const REFERENCES_LOCALE_SEGMENT_INDEX = 2;
|
|
2
7
|
|
|
3
8
|
export type TopicVersionOption = {
|
|
4
9
|
id: string;
|
|
@@ -9,6 +14,10 @@ export type TopicVersionOption = {
|
|
|
9
14
|
href?: string;
|
|
10
15
|
target?: string;
|
|
11
16
|
};
|
|
17
|
+
/** Topics authored under this version. Absent when the client only rewrites hrefs. */
|
|
18
|
+
// VersionedNode also points back here through `versions`.
|
|
19
|
+
// eslint-disable-next-line no-use-before-define
|
|
20
|
+
children?: VersionedNode[];
|
|
12
21
|
};
|
|
13
22
|
|
|
14
23
|
export type VersionedNode = {
|
|
@@ -90,20 +99,48 @@ export function persistTopicVersion(
|
|
|
90
99
|
);
|
|
91
100
|
}
|
|
92
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Normalizes the path, then removes `locale` when it sits in the docs or
|
|
104
|
+
* references locale position, so every locale shares one session key.
|
|
105
|
+
*/
|
|
106
|
+
function stripLocaleFromPath(path: string, locale?: string): string {
|
|
107
|
+
const normalized = normalizePath(path);
|
|
108
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
109
|
+
const normalizedLocale = locale?.toLowerCase();
|
|
110
|
+
if (!normalizedLocale || segments[0] !== DOCS_PATH_SEGMENT) {
|
|
111
|
+
return normalized;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const localeIndex = [
|
|
115
|
+
DOCS_LOCALE_SEGMENT_INDEX,
|
|
116
|
+
REFERENCES_LOCALE_SEGMENT_INDEX
|
|
117
|
+
].find((index) => segments[index]?.toLowerCase() === normalizedLocale);
|
|
118
|
+
if (localeIndex === undefined) {
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
segments.splice(localeIndex, 1);
|
|
123
|
+
return `/${segments.join("/")}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
93
126
|
/**
|
|
94
127
|
* Stable session key for a versioned topic. Parent-only sections have an empty
|
|
95
128
|
* `name`/`href`, so fall back to the latest switching URL (then the label).
|
|
129
|
+
* The locale is stripped so a selection carries across locales.
|
|
96
130
|
*/
|
|
97
|
-
export function getTopicVersionKey(
|
|
131
|
+
export function getTopicVersionKey(
|
|
132
|
+
node: VersionedNode | undefined,
|
|
133
|
+
locale?: string
|
|
134
|
+
): string {
|
|
98
135
|
if (!node) {
|
|
99
136
|
return "";
|
|
100
137
|
}
|
|
101
138
|
if (node.name) {
|
|
102
|
-
return node.name;
|
|
139
|
+
return stripLocaleFromPath(node.name, locale);
|
|
103
140
|
}
|
|
104
141
|
const latestHref = node.versions?.[0]?.link?.href;
|
|
105
142
|
if (latestHref) {
|
|
106
|
-
return latestHref;
|
|
143
|
+
return stripLocaleFromPath(latestHref, locale);
|
|
107
144
|
}
|
|
108
145
|
return node.label || "";
|
|
109
146
|
}
|
|
@@ -336,11 +373,18 @@ export function collectSubtreeCanonicalHrefs(
|
|
|
336
373
|
if (includeSelf && own) {
|
|
337
374
|
hrefs.push(own);
|
|
338
375
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
376
|
+
const collectChildHrefs = (children?: VersionedNode[]) => {
|
|
377
|
+
for (const child of children || []) {
|
|
378
|
+
if (child.versions?.length || child.link?.target === "_blank") {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
hrefs.push(...collectSubtreeCanonicalHrefs(child));
|
|
342
382
|
}
|
|
343
|
-
|
|
383
|
+
};
|
|
384
|
+
collectChildHrefs(node.children);
|
|
385
|
+
// Topics that exist only on an older version are not in `children`.
|
|
386
|
+
for (const version of node.versions || []) {
|
|
387
|
+
collectChildHrefs(version.children);
|
|
344
388
|
}
|
|
345
389
|
return hrefs;
|
|
346
390
|
}
|
|
@@ -431,6 +475,9 @@ export function findTopicVersionForCurrentPage(
|
|
|
431
475
|
match = { node, version };
|
|
432
476
|
}
|
|
433
477
|
walk(node.children);
|
|
478
|
+
for (const versionOption of node.versions || []) {
|
|
479
|
+
walk(versionOption.children);
|
|
480
|
+
}
|
|
434
481
|
}
|
|
435
482
|
};
|
|
436
483
|
walk(nodes);
|
|
@@ -457,7 +504,8 @@ export function latestHrefForCurrentPage(
|
|
|
457
504
|
}
|
|
458
505
|
|
|
459
506
|
export function resolveSelectedVersion(
|
|
460
|
-
node: VersionedNode | undefined
|
|
507
|
+
node: VersionedNode | undefined,
|
|
508
|
+
locale?: string
|
|
461
509
|
): TopicVersionOption | undefined {
|
|
462
510
|
const versions = node?.versions;
|
|
463
511
|
if (!node || !versions?.length) {
|
|
@@ -471,6 +519,7 @@ export function resolveSelectedVersion(
|
|
|
471
519
|
return fromUrl;
|
|
472
520
|
}
|
|
473
521
|
|
|
474
|
-
const storedId =
|
|
522
|
+
const storedId =
|
|
523
|
+
readTopicVersionSelections()[getTopicVersionKey(node, locale)];
|
|
475
524
|
return versions.find((version) => version.id === storedId) || versions[0];
|
|
476
525
|
}
|