@salesforcedevs/dx-components 1.40.4-fix-redoc-topic-v-1 → 1.40.4-scroll-3
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 +0 -1
- package/package.json +1 -1
- package/src/modules/dx/button/button.css +2 -2
- package/src/modules/dx/codeBlock/codeBlock.ts +6 -0
- package/src/modules/dx/dropdown/dropdown.css +22 -22
- package/src/modules/dx/dropdown/dropdown.html +1 -1
- package/src/modules/dx/dropdown/dropdown.ts +1 -16
- package/src/modules/dx/dropdownOption/dropdownOption.css +2 -21
- package/src/modules/dx/popover/popover.ts +3 -15
- package/src/modules/dx/tree/treeHandler.ts +10 -77
- package/src/modules/dx/treeItem/treeItem.css +0 -27
- package/src/modules/dx/treeItem/treeItem.html +2 -16
- package/src/modules/dx/treeItem/treeItem.ts +6 -91
- package/src/modules/dx/treeTile/treeTile.css +0 -4
- package/src/modules/dx/treeTile/treeTile.ts +1 -7
- package/src/modules/dx/typeBadge/typeBadge.ts +2 -23
- package/src/modules/dxBaseElements/headerBase/headerBase.ts +6 -1
- package/src/modules/dxHelpers/commonTreeItem/commonTreeItem.css +0 -4
- package/src/modules/dxHelpers/typeBadge/typeBadge.css +0 -22
- package/src/modules/dxUtils/topicVersion/topicVersion.ts +0 -467
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -86,11 +86,11 @@
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.button.icon-right:not(.slot-empty) > *:nth-child(2) {
|
|
89
|
-
margin-left: var(--dx-
|
|
89
|
+
margin-left: var(--dx-g-spacing-sm);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
.button.icon-left:not(.slot-empty) > *:nth-child(2) {
|
|
93
|
-
margin-right: var(--dx-
|
|
93
|
+
margin-right: var(--dx-g-spacing-sm);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
.button.style-icon.slot-empty {
|
|
@@ -95,6 +95,12 @@ export default class CodeBlock extends LightningElement {
|
|
|
95
95
|
).trim();
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** True once the highlighted code has been rendered into the DOM. */
|
|
99
|
+
@api
|
|
100
|
+
get rendered(): boolean {
|
|
101
|
+
return this._codeBlockRendered;
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
@api
|
|
99
105
|
get showLoadingIndicator() {
|
|
100
106
|
return this._showLoadingIndicator;
|
|
@@ -14,30 +14,30 @@
|
|
|
14
14
|
display: none;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.menu-fit-content::part(content) {
|
|
21
|
-
box-sizing: border-box;
|
|
22
|
-
width: max-content;
|
|
23
|
-
max-width: var(
|
|
24
|
-
--dx-c-dropdown-menu-max-width,
|
|
25
|
-
calc(100vw - var(--dx-g-spacing-lg))
|
|
26
|
-
);
|
|
17
|
+
.menu-fit-content {
|
|
18
|
+
--dx-c-popover-width: max-content;
|
|
19
|
+
--dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
|
|
27
20
|
}
|
|
28
21
|
|
|
29
|
-
.menu-fit-content {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
--dx-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
.menu-fit-content .menu_list {
|
|
23
|
+
width: 100%;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
gap: var(--dx-g-spacing-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.menu-fit-content.menu-nested .menu_option:not(:first-child) {
|
|
29
|
+
border-top: var(--border);
|
|
30
|
+
margin-top: 0;
|
|
31
|
+
padding-top: var(--dx-g-spacing-sm);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.menu-fit-content .menu_heading {
|
|
35
|
+
margin: 0;
|
|
36
|
+
padding: var(--dx-g-spacing-smd) var(--dx-g-spacing-lg);
|
|
37
|
+
font-family: var(--dx-g-font-sans);
|
|
38
|
+
line-height: var(--dx-g-spacing-mlg);
|
|
39
|
+
text-transform: none;
|
|
40
|
+
color: var(--dx-g-gray-10);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
@media screen and (max-width: 1000px) {
|
|
@@ -82,35 +82,20 @@ export default class Dropdown extends LightningElement {
|
|
|
82
82
|
return this.areOptionsEmpty ? null : "menu";
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
// "fit-end" is fit-content, aligned to the trigger's end edge.
|
|
86
|
-
// "full" matches the trigger width. "fixed" and "fit-content" are unchanged.
|
|
87
85
|
private get isFitContent() {
|
|
88
|
-
return this.widthMode === "fit-content"
|
|
86
|
+
return this.widthMode === "fit-content";
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
private get effectivePlacement(): PopperPlacement | undefined {
|
|
92
|
-
return this.widthMode === "fit-end" ? "bottom-end" : this.placement;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// fit-content takes precedence; the misuse is surfaced in className() below.
|
|
96
89
|
private get effectiveWidth() {
|
|
97
90
|
return this.isFitContent ? null : this.width;
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
private get effectiveFullWidth() {
|
|
101
|
-
if (this.widthMode === "full") {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
94
|
return this.isFitContent ? false : this.fullWidth;
|
|
105
95
|
}
|
|
106
96
|
|
|
107
97
|
private get className() {
|
|
108
98
|
const fitContent = this.isFitContent;
|
|
109
|
-
if (fitContent && (this.width || this.fullWidth)) {
|
|
110
|
-
console.error(
|
|
111
|
-
'The "width-mode=fit-content" and "width"/"full-width" properties cannot be provided simultaneously. "width-mode=fit-content" is prioritized; "width"/"full-width" are ignored.'
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
99
|
return cx(
|
|
115
100
|
this.isNested && "menu-nested",
|
|
116
101
|
this.isNested &&
|
|
@@ -25,14 +25,8 @@
|
|
|
25
25
|
display: flex;
|
|
26
26
|
font-family: var(--dx-g-font-sans);
|
|
27
27
|
width: 100%;
|
|
28
|
-
|
|
29
|
-
/* Opt-in hooks; defaults preserve today's look. */
|
|
30
|
-
padding: var(--dx-c-dropdown-option-padding, var(--dx-g-spacing-sm));
|
|
28
|
+
padding: var(--dx-g-spacing-sm);
|
|
31
29
|
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
|
-
);
|
|
36
30
|
transition: var(--dx-g-transition-hue-1x);
|
|
37
31
|
align-items: center;
|
|
38
32
|
outline: none !important;
|
|
@@ -65,9 +59,6 @@
|
|
|
65
59
|
font-family: var(--dx-g-font-sans);
|
|
66
60
|
font-size: var(--dx-g-text-sm);
|
|
67
61
|
text-align: left;
|
|
68
|
-
|
|
69
|
-
/* Opt-in: set to 0 to let a long label truncate instead of overflow. */
|
|
70
|
-
min-width: var(--dx-c-dropdown-option-min-width, auto);
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
.option-indented .option_details {
|
|
@@ -77,20 +68,10 @@
|
|
|
77
68
|
|
|
78
69
|
.option_label {
|
|
79
70
|
color: var(--dx-c-dropdown-option-label-color, var(--dx-g-blue-vibrant-50));
|
|
80
|
-
|
|
81
|
-
/* Opt-in: set to block so text-overflow:ellipsis applies (ignored on flex). */
|
|
82
|
-
display: var(--dx-c-dropdown-option-label-display, flex);
|
|
71
|
+
display: flex;
|
|
83
72
|
align-items: center;
|
|
84
73
|
font-weight: var(--dx-c-dropdown-option-font-weight, var(--dx-g-font-bold));
|
|
85
74
|
font-size: var(--dx-c-dropdown-option-font-size, var(--dx-g-text-base));
|
|
86
|
-
|
|
87
|
-
/* Opt-in truncation: consumers set --dx-c-dropdown-option-white-space:
|
|
88
|
-
nowrap to truncate labels with an ellipsis instead of wrapping.
|
|
89
|
-
Defaults preserve today's wrapping behavior. */
|
|
90
|
-
white-space: var(--dx-c-dropdown-option-white-space, normal);
|
|
91
|
-
overflow: var(--dx-c-dropdown-option-overflow, visible);
|
|
92
|
-
overflow-wrap: var(--dx-c-dropdown-option-overflow-wrap, normal);
|
|
93
|
-
text-overflow: var(--dx-c-dropdown-option-text-overflow, clip);
|
|
94
75
|
}
|
|
95
76
|
|
|
96
77
|
.option-indented .option_label {
|
|
@@ -106,11 +106,7 @@ export default class Popover extends LightningElement {
|
|
|
106
106
|
if (this.control) {
|
|
107
107
|
this.control.setAttribute("aria-expanded", "false");
|
|
108
108
|
}
|
|
109
|
-
document.removeEventListener(
|
|
110
|
-
"click",
|
|
111
|
-
this.interactOutsideListener,
|
|
112
|
-
true
|
|
113
|
-
);
|
|
109
|
+
document.removeEventListener("click", this.interactOutsideListener);
|
|
114
110
|
document.removeEventListener("focusout", this.interactOutsideListener);
|
|
115
111
|
if (this.openOnHover) {
|
|
116
112
|
document.removeEventListener(
|
|
@@ -136,11 +132,7 @@ export default class Popover extends LightningElement {
|
|
|
136
132
|
// LIFECYCLE METHODS
|
|
137
133
|
|
|
138
134
|
disconnectedCallback() {
|
|
139
|
-
document.removeEventListener(
|
|
140
|
-
"click",
|
|
141
|
-
this.interactOutsideListener,
|
|
142
|
-
true
|
|
143
|
-
);
|
|
135
|
+
document.removeEventListener("click", this.interactOutsideListener);
|
|
144
136
|
document.removeEventListener("mousemove", this.interactOutsideListener);
|
|
145
137
|
document.removeEventListener("focusout", this.interactOutsideListener);
|
|
146
138
|
this.removeControlListeners();
|
|
@@ -303,11 +295,7 @@ export default class Popover extends LightningElement {
|
|
|
303
295
|
}
|
|
304
296
|
|
|
305
297
|
private attachListenersTopopover() {
|
|
306
|
-
|
|
307
|
-
// calls stopPropagation (topic version pickers do, so the tree row
|
|
308
|
-
// does not navigate). Without this, opening a second picker leaves
|
|
309
|
-
// the first menu open.
|
|
310
|
-
document.addEventListener("click", this.interactOutsideListener, true);
|
|
298
|
+
document.addEventListener("click", this.interactOutsideListener);
|
|
311
299
|
document.addEventListener("focusout", this.interactOutsideListener);
|
|
312
300
|
if (this.openOnHover) {
|
|
313
301
|
document.addEventListener(
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import type { InternalTreeNode, TreeNode } from "typings/custom";
|
|
2
|
-
import type { TopicVersionOption } from "dxUtils/topicVersion";
|
|
3
|
-
import {
|
|
4
|
-
applyVersionToHref,
|
|
5
|
-
normalizePath,
|
|
6
|
-
resolveSelectedVersion
|
|
7
|
-
} from "dxUtils/topicVersion";
|
|
8
|
-
|
|
9
|
-
type VersionedTreeNode = TreeNode & { versions?: TopicVersionOption[] };
|
|
10
|
-
type VersionedInternalTreeNode = InternalTreeNode & {
|
|
11
|
-
versions?: TopicVersionOption[];
|
|
12
|
-
};
|
|
13
|
-
|
|
14
2
|
type MapItem = {
|
|
15
|
-
node:
|
|
3
|
+
node: InternalTreeNode;
|
|
16
4
|
parent?: MapItem;
|
|
17
5
|
setExpanded?: Function;
|
|
18
6
|
};
|
|
@@ -33,10 +21,7 @@ export class TreeHandler {
|
|
|
33
21
|
}
|
|
34
22
|
|
|
35
23
|
getKeyByName(name: string): string | undefined {
|
|
36
|
-
|
|
37
|
-
return undefined;
|
|
38
|
-
}
|
|
39
|
-
return this._nameKeyMap[name] || this._nameKeyMap[normalizePath(name)];
|
|
24
|
+
return name && this._nameKeyMap[name];
|
|
40
25
|
}
|
|
41
26
|
|
|
42
27
|
initialize(treeRoot: TreeNode): InternalTreeNode {
|
|
@@ -63,41 +48,12 @@ export class TreeHandler {
|
|
|
63
48
|
}
|
|
64
49
|
|
|
65
50
|
private convert(
|
|
66
|
-
node:
|
|
51
|
+
node: TreeNode,
|
|
67
52
|
level: number = 0,
|
|
68
53
|
parent?: MapItem,
|
|
69
|
-
position?: number
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
versions: TopicVersionOption[];
|
|
73
|
-
}
|
|
74
|
-
): VersionedInternalTreeNode {
|
|
75
|
-
const ownSelected = node.versions?.length
|
|
76
|
-
? resolveSelectedVersion(node)
|
|
77
|
-
: undefined;
|
|
78
|
-
const versionContext =
|
|
79
|
-
ownSelected && node.versions
|
|
80
|
-
? { selected: ownSelected, versions: node.versions }
|
|
81
|
-
: ancestorVersion;
|
|
82
|
-
|
|
83
|
-
let link = node.link;
|
|
84
|
-
if (
|
|
85
|
-
versionContext &&
|
|
86
|
-
!node.versions?.length &&
|
|
87
|
-
link?.href &&
|
|
88
|
-
link.target !== "_blank"
|
|
89
|
-
) {
|
|
90
|
-
const rewritten = applyVersionToHref(
|
|
91
|
-
link.href,
|
|
92
|
-
versionContext.selected,
|
|
93
|
-
versionContext.versions
|
|
94
|
-
);
|
|
95
|
-
if (rewritten && rewritten !== link.href) {
|
|
96
|
-
link = { ...link, href: rewritten };
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const convertedNode: VersionedInternalTreeNode = {
|
|
54
|
+
position?: number
|
|
55
|
+
): InternalTreeNode {
|
|
56
|
+
const convertedNode: InternalTreeNode = {
|
|
101
57
|
label: node.label,
|
|
102
58
|
name: node.name,
|
|
103
59
|
key: this.calculateKey(
|
|
@@ -107,47 +63,24 @@ export class TreeHandler {
|
|
|
107
63
|
level: level,
|
|
108
64
|
isExpanded: node.isExpanded || false,
|
|
109
65
|
isChildrenLoading: node.isChildrenLoading || false,
|
|
110
|
-
link,
|
|
66
|
+
link: node.link,
|
|
111
67
|
method: node.method,
|
|
112
68
|
showForwardArrow: node.showForwardArrow,
|
|
113
|
-
|
|
69
|
+
redirectToChild: node.redirectToChild
|
|
114
70
|
};
|
|
115
71
|
|
|
116
72
|
const mapItem = { node: convertedNode, parent };
|
|
117
73
|
if (node.children && node.children.length > 0) {
|
|
118
74
|
convertedNode.children = node.children.map((child, index) =>
|
|
119
|
-
this.convert(child, level + 1, mapItem, index
|
|
75
|
+
this.convert(child, level + 1, mapItem, index)
|
|
120
76
|
);
|
|
121
77
|
}
|
|
122
78
|
|
|
123
79
|
this._nodeMap[convertedNode.key] = mapItem;
|
|
124
|
-
this.
|
|
125
|
-
this.indexName(convertedNode.link?.href, convertedNode.key);
|
|
126
|
-
// Index this topic's own versioned URLs only. Parent-only switching
|
|
127
|
-
// URLs point at a child with content and must not steal that child's
|
|
128
|
-
// selection.
|
|
129
|
-
if (node.name && node.versions) {
|
|
130
|
-
for (const version of node.versions) {
|
|
131
|
-
this.indexName(
|
|
132
|
-
applyVersionToHref(node.name, version, node.versions),
|
|
133
|
-
convertedNode.key
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
80
|
+
this._nameKeyMap[convertedNode.name] = convertedNode.key;
|
|
137
81
|
return convertedNode;
|
|
138
82
|
}
|
|
139
83
|
|
|
140
|
-
private indexName(name: string | undefined, key: string): void {
|
|
141
|
-
if (!name) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
this._nameKeyMap[name] = key;
|
|
145
|
-
const normalized = normalizePath(name);
|
|
146
|
-
if (normalized && normalized !== name) {
|
|
147
|
-
this._nameKeyMap[normalized] = key;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
84
|
private calculateKey(parentKey: string, position?: number): string {
|
|
152
85
|
if (position === undefined) {
|
|
153
86
|
return parentKey;
|
|
@@ -4,33 +4,6 @@
|
|
|
4
4
|
display: block;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
/* Lift this row above later sibling pickers while its menu is open. */
|
|
8
|
-
:host(:focus-within) {
|
|
9
|
-
position: relative;
|
|
10
|
-
z-index: 2;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.tree-item-row {
|
|
14
|
-
display: flex;
|
|
15
|
-
align-items: center;
|
|
16
|
-
min-width: 0;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.tree-item-row dx-tree-tile {
|
|
20
|
-
flex: 1;
|
|
21
|
-
min-width: 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.tree-item-version-picker {
|
|
25
|
-
flex: 0 0 auto;
|
|
26
|
-
margin-right: var(--dx-g-spacing-lg);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.tree-item-row-selected {
|
|
30
|
-
background: var(--dx-g-cloud-blue-vibrant-95);
|
|
31
|
-
box-shadow: inset var(--dx-g-spacing-xs) 0 0 0 var(--dx-g-blue-vibrant-40);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
7
|
button {
|
|
35
8
|
width: 100%;
|
|
36
9
|
text-align: left;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<a href={href} target={target} onclick={onLinkClick} tabindex="-1">
|
|
3
3
|
<dx-tree-tile
|
|
4
4
|
href={href}
|
|
5
5
|
is-root={isRoot}
|
|
@@ -7,26 +7,12 @@
|
|
|
7
7
|
is-expanded={isExpanded}
|
|
8
8
|
has-children={hasChildren}
|
|
9
9
|
is-selected={isSelected}
|
|
10
|
-
is-contained-selection={isContainedSelection}
|
|
11
10
|
oniconclick={onIconClick}
|
|
12
11
|
show-arrow={isParent}
|
|
13
12
|
target={target}
|
|
14
13
|
is-external-link={isExternalLink}
|
|
15
|
-
has-version-picker={showVersionPicker}
|
|
16
|
-
onclick={onLinkClick}
|
|
17
14
|
></dx-tree-tile>
|
|
18
|
-
|
|
19
|
-
if:true={showVersionPicker}
|
|
20
|
-
class="tree-item-version-picker"
|
|
21
|
-
small="true"
|
|
22
|
-
versions={versionPickerOptions}
|
|
23
|
-
selected-version={selectedVersion}
|
|
24
|
-
latest-version={isLatestVersion}
|
|
25
|
-
onchange={onVersionChange}
|
|
26
|
-
onclick={onVersionPickerClick}
|
|
27
|
-
onmousedown={onVersionPickerClick}
|
|
28
|
-
></doc-version-picker>
|
|
29
|
-
</div>
|
|
15
|
+
</a>
|
|
30
16
|
<img
|
|
31
17
|
if:true={showChildrenLoading}
|
|
32
18
|
class="tree-children-loading"
|
|
@@ -2,24 +2,9 @@ import { LightningElement, api } from "lwc";
|
|
|
2
2
|
import { InternalTreeNode } from "typings/custom";
|
|
3
3
|
import { track } from "dxUtils/analytics";
|
|
4
4
|
import { isInViewport } from "dxUtils/browser";
|
|
5
|
-
import type { TopicVersionOption } from "dxUtils/topicVersion";
|
|
6
|
-
import {
|
|
7
|
-
firstContentNode,
|
|
8
|
-
getTopicVersionKey,
|
|
9
|
-
getVersionPickerOptions,
|
|
10
|
-
isParentOnlyTopic,
|
|
11
|
-
normalizePath,
|
|
12
|
-
persistTopicVersion,
|
|
13
|
-
resolveSelectedVersion,
|
|
14
|
-
shouldShowTopicVersionPicker
|
|
15
|
-
} from "dxUtils/topicVersion";
|
|
16
5
|
|
|
17
6
|
const DEFAULT_TARGET = "_self";
|
|
18
7
|
|
|
19
|
-
type VersionedInternalTreeNode = InternalTreeNode & {
|
|
20
|
-
versions?: TopicVersionOption[];
|
|
21
|
-
};
|
|
22
|
-
|
|
23
8
|
export default class TreeItem extends LightningElement {
|
|
24
9
|
@api selectedKey?: string;
|
|
25
10
|
@api isRoot: boolean = false;
|
|
@@ -35,7 +20,7 @@ export default class TreeItem extends LightningElement {
|
|
|
35
20
|
this.isChildrenLoading = (node && node.isChildrenLoading) || false;
|
|
36
21
|
}
|
|
37
22
|
|
|
38
|
-
private _treeNode!:
|
|
23
|
+
private _treeNode!: InternalTreeNode;
|
|
39
24
|
private isExpanded: boolean = false;
|
|
40
25
|
private isChildrenLoading: boolean = false;
|
|
41
26
|
private rootParentLabel: string = "";
|
|
@@ -45,22 +30,9 @@ export default class TreeItem extends LightningElement {
|
|
|
45
30
|
}
|
|
46
31
|
|
|
47
32
|
private get isSelected(): boolean {
|
|
48
|
-
if (isParentOnlyTopic(this._treeNode)) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
33
|
return this._treeNode.key === this.selectedKey && !this.isExternalLink;
|
|
52
34
|
}
|
|
53
35
|
|
|
54
|
-
private get rowClass(): string {
|
|
55
|
-
return this.isSelected && this.showVersionPicker
|
|
56
|
-
? "tree-item-row tree-item-row-selected"
|
|
57
|
-
: "tree-item-row";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private get isContainedSelection(): boolean {
|
|
61
|
-
return this.isSelected && this.showVersionPicker;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
36
|
private get hasChildren(): boolean {
|
|
65
37
|
return !!(
|
|
66
38
|
this.treeNode &&
|
|
@@ -111,44 +83,8 @@ export default class TreeItem extends LightningElement {
|
|
|
111
83
|
return `${parentLabels.reverse().join(":")}:${this.treeNode.label}`;
|
|
112
84
|
}
|
|
113
85
|
|
|
114
|
-
private get versions(): TopicVersionOption[] | undefined {
|
|
115
|
-
return this._treeNode?.versions;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private get versionPickerOptions(): TopicVersionOption[] | undefined {
|
|
119
|
-
return getVersionPickerOptions(this._treeNode);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private get showVersionPicker(): boolean {
|
|
123
|
-
return shouldShowTopicVersionPicker(
|
|
124
|
-
this.versions,
|
|
125
|
-
this._treeNode?.showForwardArrow
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private get selectedVersion(): TopicVersionOption | undefined {
|
|
130
|
-
return resolveSelectedVersion(this._treeNode);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
private get isLatestVersion(): boolean {
|
|
134
|
-
return (
|
|
135
|
-
!!this.selectedVersion &&
|
|
136
|
-
this.selectedVersion.id === this.versions?.[0]?.id
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
86
|
private get href(): string | undefined {
|
|
141
|
-
if (isParentOnlyTopic(this._treeNode)) {
|
|
142
|
-
const contentNode = firstContentNode(this._treeNode);
|
|
143
|
-
return (
|
|
144
|
-
contentNode?.link?.href ||
|
|
145
|
-
contentNode?.name ||
|
|
146
|
-
this.selectedVersion?.link?.href ||
|
|
147
|
-
""
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
87
|
return (
|
|
151
|
-
this.selectedVersion?.link?.href ||
|
|
152
88
|
(this._treeNode.link && this._treeNode.link.href) ||
|
|
153
89
|
this._treeNode.name ||
|
|
154
90
|
""
|
|
@@ -168,23 +104,6 @@ export default class TreeItem extends LightningElement {
|
|
|
168
104
|
setExpanded: this.setExpanded.bind(this)
|
|
169
105
|
});
|
|
170
106
|
}
|
|
171
|
-
|
|
172
|
-
// Remember this topic's resolved version (own URL, descendant URL, or
|
|
173
|
-
// session) so sibling pickers stay independent for the tab.
|
|
174
|
-
const selected = resolveSelectedVersion(this._treeNode);
|
|
175
|
-
const topicKey = getTopicVersionKey(this._treeNode);
|
|
176
|
-
if (this.versions?.length && selected && topicKey) {
|
|
177
|
-
persistTopicVersion(topicKey, selected.id);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private onVersionChange(event: CustomEvent): void {
|
|
182
|
-
event.stopPropagation();
|
|
183
|
-
persistTopicVersion(getTopicVersionKey(this._treeNode), event.detail);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private onVersionPickerClick(event: Event): void {
|
|
187
|
-
event.stopPropagation();
|
|
188
107
|
}
|
|
189
108
|
|
|
190
109
|
renderedCallback(): void {
|
|
@@ -215,8 +134,8 @@ export default class TreeItem extends LightningElement {
|
|
|
215
134
|
private preventDefaultLinkBehavior(event: Event) {
|
|
216
135
|
// prevent page refresh if href isn't present or link is already active
|
|
217
136
|
if (
|
|
218
|
-
!this.href ||
|
|
219
|
-
|
|
137
|
+
!this._treeNode.link?.href ||
|
|
138
|
+
this._treeNode.link?.href === window.location.pathname
|
|
220
139
|
) {
|
|
221
140
|
event.preventDefault();
|
|
222
141
|
}
|
|
@@ -248,12 +167,8 @@ export default class TreeItem extends LightningElement {
|
|
|
248
167
|
}
|
|
249
168
|
|
|
250
169
|
this.sendGtm(event);
|
|
251
|
-
const contentNode = isParentOnlyTopic(this._treeNode)
|
|
252
|
-
? firstContentNode(this._treeNode)
|
|
253
|
-
: undefined;
|
|
254
170
|
this.fireEvent("tileselected", {
|
|
255
|
-
name:
|
|
256
|
-
...(contentNode?.key ? { key: contentNode.key } : {}),
|
|
171
|
+
name: this._treeNode.name,
|
|
257
172
|
isExternalLink: this.isExternalLink
|
|
258
173
|
});
|
|
259
174
|
}
|
|
@@ -271,8 +186,8 @@ export default class TreeItem extends LightningElement {
|
|
|
271
186
|
this.dispatchEvent(
|
|
272
187
|
new CustomEvent(type, {
|
|
273
188
|
detail: {
|
|
274
|
-
|
|
275
|
-
|
|
189
|
+
...detail,
|
|
190
|
+
key: this._treeNode.key
|
|
276
191
|
},
|
|
277
192
|
bubbles: true,
|
|
278
193
|
composed: true
|
|
@@ -28,12 +28,10 @@ export default class TreeTile extends LightningElement {
|
|
|
28
28
|
@api isExpanded = false;
|
|
29
29
|
@api hasChildren = false;
|
|
30
30
|
@api isSelected = false;
|
|
31
|
-
@api isContainedSelection = false;
|
|
32
31
|
@api href!: string;
|
|
33
32
|
@api showArrow = false;
|
|
34
33
|
@api target!: string;
|
|
35
34
|
@api isExternalLink = false;
|
|
36
|
-
@api hasVersionPicker = false;
|
|
37
35
|
|
|
38
36
|
get tileAriaLevel() {
|
|
39
37
|
return this.treeNode.level + 2;
|
|
@@ -103,11 +101,7 @@ export default class TreeTile extends LightningElement {
|
|
|
103
101
|
"tile-root": this.isRoot,
|
|
104
102
|
"tile-with-children": !this.isRoot && this.hasChildren,
|
|
105
103
|
"tile-root-with-child": this.isRoot && this.showArrow,
|
|
106
|
-
"sidebar-item-selected":
|
|
107
|
-
this.isSelected && !this.isContainedSelection,
|
|
108
|
-
"sidebar-item-selected-text":
|
|
109
|
-
this.isSelected && this.isContainedSelection,
|
|
110
|
-
"tile-with-version-picker": this.hasVersionPicker
|
|
104
|
+
"sidebar-item-selected": this.isSelected
|
|
111
105
|
});
|
|
112
106
|
}
|
|
113
107
|
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
IconSprite,
|
|
8
8
|
IconData,
|
|
9
9
|
IconSymbol,
|
|
10
|
-
StatusBadgeVariant,
|
|
11
10
|
TypeBadgeSize
|
|
12
11
|
} from "typings/custom";
|
|
13
12
|
import { colorToDxColors, buildStyleColorVariables } from "dxUtils/css";
|
|
@@ -21,16 +20,9 @@ import {
|
|
|
21
20
|
getContentTypeColorVariables
|
|
22
21
|
} from "dxUtils/contentTypes";
|
|
23
22
|
|
|
24
|
-
// Opt-in solid status variants (status-success/-warning/-error).
|
|
25
|
-
export const STATUS_BADGE_VARIANTS: StatusBadgeVariant[] = [
|
|
26
|
-
"status-success",
|
|
27
|
-
"status-warning",
|
|
28
|
-
"status-error"
|
|
29
|
-
];
|
|
30
|
-
|
|
31
23
|
export default class TypeBadge extends LightningElement {
|
|
32
24
|
@api size: TypeBadgeSize = "default";
|
|
33
|
-
@api variant: Brand | ContentType
|
|
25
|
+
@api variant: Brand | ContentType = "default";
|
|
34
26
|
@api color?: Color;
|
|
35
27
|
@api dark?: false;
|
|
36
28
|
|
|
@@ -95,24 +87,11 @@ export default class TypeBadge extends LightningElement {
|
|
|
95
87
|
return isContentType(this.variant);
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
private get isStatus(): boolean {
|
|
99
|
-
return STATUS_BADGE_VARIANTS.includes(
|
|
100
|
-
this.variant as StatusBadgeVariant
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
90
|
private get className(): string {
|
|
105
|
-
return cx(
|
|
106
|
-
"dx-badge",
|
|
107
|
-
`size-${this.size}`,
|
|
108
|
-
this.isStatus && this.variant
|
|
109
|
-
);
|
|
91
|
+
return cx("dx-badge", `size-${this.size}`);
|
|
110
92
|
}
|
|
111
93
|
|
|
112
94
|
private get style(): string {
|
|
113
|
-
if (this.isStatus) {
|
|
114
|
-
return "";
|
|
115
|
-
}
|
|
116
95
|
if (this.dark) {
|
|
117
96
|
return buildStyleColorVariables({
|
|
118
97
|
background: "--dx-g-purple-vibrant-40",
|
|
@@ -154,7 +154,12 @@ export abstract class HeaderBase extends LightningElement {
|
|
|
154
154
|
setTimeout(() => {
|
|
155
155
|
const id = hash.replace("#", "");
|
|
156
156
|
const element = document.getElementById(id);
|
|
157
|
-
|
|
157
|
+
// doc-heading targets are scrolled by doc-content-layout once the page has rendered
|
|
158
|
+
if (
|
|
159
|
+
element &&
|
|
160
|
+
element.tagName !== "DOC-HEADING" &&
|
|
161
|
+
element.scrollIntoView
|
|
162
|
+
) {
|
|
158
163
|
element.scrollIntoView();
|
|
159
164
|
}
|
|
160
165
|
}, 0);
|
|
@@ -13,10 +13,6 @@
|
|
|
13
13
|
color: var(--dx-g-blue-vibrant-40) !important;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
.sidebar-item-selected-text {
|
|
17
|
-
color: var(--dx-g-blue-vibrant-40) !important;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
16
|
.sidebar-item {
|
|
21
17
|
padding: 6px var(--dx-g-spacing-lg) 6px
|
|
22
18
|
var(--dx-g-global-header-padding-horizontal);
|
|
@@ -26,28 +26,6 @@
|
|
|
26
26
|
--height: var(--dx-g-spacing-mlg);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
.dx-badge.status-success,
|
|
30
|
-
.dx-badge.status-warning,
|
|
31
|
-
.dx-badge.status-error {
|
|
32
|
-
/* No spacing token maps to 10px; status badges need this exact padding. */
|
|
33
|
-
--horizontal-spacing: 10px;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
.dx-badge.status-success {
|
|
37
|
-
--color: var(--sds-g-gray-1);
|
|
38
|
-
--background: var(--dx-g-green-vibrant-50);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.dx-badge.status-warning {
|
|
42
|
-
--color: var(--dx-g-gray-15);
|
|
43
|
-
--background: var(--dx-g-yellow-vibrant-80);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.dx-badge.status-error {
|
|
47
|
-
--color: var(--sds-g-gray-1);
|
|
48
|
-
--background: var(--dx-g-red-vibrant-40);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
29
|
svg,
|
|
52
30
|
dx-icon {
|
|
53
31
|
margin-right: var(--dx-g-spacing-sm);
|
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
export const TOPIC_VERSION_SESSION_KEY = "sfdocs-topic-version-selections";
|
|
2
|
-
|
|
3
|
-
export type TopicVersionOption = {
|
|
4
|
-
id: string;
|
|
5
|
-
label: string;
|
|
6
|
-
deprecated?: boolean;
|
|
7
|
-
selected?: boolean;
|
|
8
|
-
link?: {
|
|
9
|
-
href?: string;
|
|
10
|
-
target?: string;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type VersionedNode = {
|
|
15
|
-
name?: string;
|
|
16
|
-
label?: string;
|
|
17
|
-
key?: string;
|
|
18
|
-
link?: {
|
|
19
|
-
href?: string;
|
|
20
|
-
target?: string;
|
|
21
|
-
};
|
|
22
|
-
children?: VersionedNode[];
|
|
23
|
-
versions?: TopicVersionOption[];
|
|
24
|
-
/** Set on Redoc spec tiles after the docs layout decorates the TOC. */
|
|
25
|
-
showForwardArrow?: boolean;
|
|
26
|
-
/** Docs TOC topic type (`markdown` | `spec`). May be stripped after decoration. */
|
|
27
|
-
topicType?: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
type VersionPathDelta = {
|
|
31
|
-
prefix: string;
|
|
32
|
-
inserted: string;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function isBrowser(): boolean {
|
|
36
|
-
return typeof window !== "undefined";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function normalizePath(href?: string): string {
|
|
40
|
-
if (!href) {
|
|
41
|
-
return "";
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const url = href.startsWith("http")
|
|
46
|
-
? new URL(href)
|
|
47
|
-
: new URL(
|
|
48
|
-
href,
|
|
49
|
-
isBrowser() ? window.location.origin : "http://localhost"
|
|
50
|
-
);
|
|
51
|
-
return url.pathname.replace(/\/$/, "") || "/";
|
|
52
|
-
} catch {
|
|
53
|
-
return href.replace(/\/$/, "");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function hrefMatchesCurrentPage(href?: string): boolean {
|
|
58
|
-
if (!isBrowser()) {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
return normalizePath(href) === normalizePath(window.location.pathname);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function readTopicVersionSelections(): Record<string, string> {
|
|
65
|
-
if (!isBrowser() || !window.sessionStorage) {
|
|
66
|
-
return {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const raw = window.sessionStorage.getItem(TOPIC_VERSION_SESSION_KEY);
|
|
71
|
-
return raw ? JSON.parse(raw) : {};
|
|
72
|
-
} catch {
|
|
73
|
-
return {};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function persistTopicVersion(
|
|
78
|
-
topicName: string,
|
|
79
|
-
versionId: string
|
|
80
|
-
): void {
|
|
81
|
-
if (!topicName || !versionId || !isBrowser() || !window.sessionStorage) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const stored = readTopicVersionSelections();
|
|
86
|
-
stored[topicName] = versionId;
|
|
87
|
-
window.sessionStorage.setItem(
|
|
88
|
-
TOPIC_VERSION_SESSION_KEY,
|
|
89
|
-
JSON.stringify(stored)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Stable session key for a versioned topic. Parent-only sections have an empty
|
|
95
|
-
* `name`/`href`, so fall back to the latest switching URL (then the label).
|
|
96
|
-
*/
|
|
97
|
-
export function getTopicVersionKey(node: VersionedNode | undefined): string {
|
|
98
|
-
if (!node) {
|
|
99
|
-
return "";
|
|
100
|
-
}
|
|
101
|
-
if (node.name) {
|
|
102
|
-
return node.name;
|
|
103
|
-
}
|
|
104
|
-
const latestHref = node.versions?.[0]?.link?.href;
|
|
105
|
-
if (latestHref) {
|
|
106
|
-
return latestHref;
|
|
107
|
-
}
|
|
108
|
-
return node.label || "";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Section header with children but no page of its own. */
|
|
112
|
-
export function isParentOnlyTopic(node: VersionedNode | undefined): boolean {
|
|
113
|
-
if (!node) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
const ownHref = node.link?.href || node.name;
|
|
117
|
-
return !ownHref && !!node.children?.length;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function isSpecNode(node: VersionedNode): boolean {
|
|
121
|
-
return node.showForwardArrow === true || node.topicType === "spec";
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* First descendant that has a markdown page (own href/name). Specs (Redoc)
|
|
126
|
-
* and external links are skipped so parent-only sections land on the first
|
|
127
|
-
* markdown topic even when a spec is authored first in the TOC.
|
|
128
|
-
*/
|
|
129
|
-
export function firstContentNode(
|
|
130
|
-
node: VersionedNode | undefined
|
|
131
|
-
): VersionedNode | undefined {
|
|
132
|
-
if (!node || node.link?.target === "_blank" || isSpecNode(node)) {
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
if (node.link?.href || node.name) {
|
|
136
|
-
return node;
|
|
137
|
-
}
|
|
138
|
-
for (const child of node.children || []) {
|
|
139
|
-
const found = firstContentNode(child);
|
|
140
|
-
if (found) {
|
|
141
|
-
return found;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function shouldShowTopicVersionPicker(
|
|
148
|
-
versions?: TopicVersionOption[],
|
|
149
|
-
showForwardArrow?: boolean
|
|
150
|
-
): boolean {
|
|
151
|
-
return !showForwardArrow && Array.isArray(versions) && versions.length > 1;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Difference between a picker's latest switching URL and an older one.
|
|
156
|
-
*
|
|
157
|
-
* latest: /docs/project/errors.html
|
|
158
|
-
* v1: /docs/project/v1/errors.html
|
|
159
|
-
* → prefix `/docs/project/`, inserted `v1/`
|
|
160
|
-
*/
|
|
161
|
-
function getVersionPathDelta(
|
|
162
|
-
latestPath: string,
|
|
163
|
-
selectedPath: string
|
|
164
|
-
): VersionPathDelta | undefined {
|
|
165
|
-
const latestDirEnd = latestPath.lastIndexOf("/") + 1;
|
|
166
|
-
if (latestDirEnd <= 0) {
|
|
167
|
-
return undefined;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const prefix = latestPath.slice(0, latestDirEnd);
|
|
171
|
-
const latestSlug = latestPath.slice(latestDirEnd);
|
|
172
|
-
if (!selectedPath.startsWith(prefix) || !latestSlug) {
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const selectedAfterPrefix = selectedPath.slice(prefix.length);
|
|
177
|
-
if (!selectedAfterPrefix.endsWith(latestSlug)) {
|
|
178
|
-
return undefined;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const inserted = selectedAfterPrefix.slice(
|
|
182
|
-
0,
|
|
183
|
-
selectedAfterPrefix.length - latestSlug.length
|
|
184
|
-
);
|
|
185
|
-
if (!inserted) {
|
|
186
|
-
return undefined;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return { prefix, inserted };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function applyVersionPathDelta(
|
|
193
|
-
hrefPath: string,
|
|
194
|
-
delta: VersionPathDelta
|
|
195
|
-
): string | undefined {
|
|
196
|
-
if (!hrefPath.startsWith(delta.prefix)) {
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const rest = hrefPath.slice(delta.prefix.length);
|
|
201
|
-
if (rest.startsWith(delta.inserted)) {
|
|
202
|
-
return hrefPath;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return delta.prefix + delta.inserted + rest;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function stripVersionPathDelta(
|
|
209
|
-
hrefPath: string,
|
|
210
|
-
delta: VersionPathDelta
|
|
211
|
-
): string | undefined {
|
|
212
|
-
if (!hrefPath.startsWith(delta.prefix)) {
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const rest = hrefPath.slice(delta.prefix.length);
|
|
217
|
-
if (!rest.startsWith(delta.inserted)) {
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return delta.prefix + rest.slice(delta.inserted.length);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Maps a canonical (latest) href onto another version using the picker's
|
|
226
|
-
* latest vs selected switching URLs as the template.
|
|
227
|
-
*
|
|
228
|
-
* latest parent: /docs/project/errors.html
|
|
229
|
-
* v1 parent: /docs/project/v1/errors.html
|
|
230
|
-
* latest child: /docs/project/extra-child-a.html
|
|
231
|
-
* → v1 child: /docs/project/v1/extra-child-a.html
|
|
232
|
-
*/
|
|
233
|
-
export function applyVersionToHref(
|
|
234
|
-
canonicalHref: string | undefined,
|
|
235
|
-
selectedVersion: TopicVersionOption | undefined,
|
|
236
|
-
versions?: TopicVersionOption[]
|
|
237
|
-
): string | undefined {
|
|
238
|
-
if (!canonicalHref) {
|
|
239
|
-
return canonicalHref;
|
|
240
|
-
}
|
|
241
|
-
if (!selectedVersion || !versions?.length) {
|
|
242
|
-
return canonicalHref;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const latest = versions[0];
|
|
246
|
-
if (!latest || selectedVersion.id === latest.id) {
|
|
247
|
-
return canonicalHref;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const latestPath = normalizePath(latest.link?.href);
|
|
251
|
-
const selectedPath = normalizePath(selectedVersion.link?.href);
|
|
252
|
-
const hrefPath = normalizePath(canonicalHref);
|
|
253
|
-
|
|
254
|
-
if (!latestPath || !selectedPath || latestPath === selectedPath) {
|
|
255
|
-
return insertVersionBeforeSlug(canonicalHref, selectedVersion.id);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const delta = getVersionPathDelta(latestPath, selectedPath);
|
|
259
|
-
return (
|
|
260
|
-
(delta && applyVersionPathDelta(hrefPath, delta)) ||
|
|
261
|
-
insertVersionBeforeSlug(canonicalHref, selectedVersion.id)
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function insertVersionBeforeSlug(href: string, versionId: string): string {
|
|
266
|
-
const path = normalizePath(href);
|
|
267
|
-
const lastSlash = path.lastIndexOf("/");
|
|
268
|
-
if (lastSlash <= 0 || !versionId) {
|
|
269
|
-
return href;
|
|
270
|
-
}
|
|
271
|
-
const dir = path.slice(0, lastSlash);
|
|
272
|
-
const slug = path.slice(lastSlash);
|
|
273
|
-
if (dir.endsWith(`/${versionId}`)) {
|
|
274
|
-
return path;
|
|
275
|
-
}
|
|
276
|
-
return `${dir}/${versionId}${slug}`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function toCanonicalHref(
|
|
280
|
-
href: string | undefined,
|
|
281
|
-
versions?: TopicVersionOption[]
|
|
282
|
-
): string {
|
|
283
|
-
if (!href) {
|
|
284
|
-
return "";
|
|
285
|
-
}
|
|
286
|
-
if (!versions?.length) {
|
|
287
|
-
return normalizePath(href);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const latestPath = normalizePath(versions[0].link?.href);
|
|
291
|
-
const hrefPath = normalizePath(href);
|
|
292
|
-
if (!latestPath) {
|
|
293
|
-
return hrefPath;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
for (const version of versions) {
|
|
297
|
-
if (version.id === versions[0].id) {
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
const delta = getVersionPathDelta(
|
|
301
|
-
latestPath,
|
|
302
|
-
normalizePath(version.link?.href)
|
|
303
|
-
);
|
|
304
|
-
const stripped = delta && stripVersionPathDelta(hrefPath, delta);
|
|
305
|
-
if (stripped) {
|
|
306
|
-
return stripped;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return hrefPath;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function hrefMatchesVersion(
|
|
314
|
-
href: string | undefined,
|
|
315
|
-
version: TopicVersionOption,
|
|
316
|
-
versions: TopicVersionOption[]
|
|
317
|
-
): boolean {
|
|
318
|
-
return hrefMatchesCurrentPage(applyVersionToHref(href, version, versions));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function collectSubtreeCanonicalHrefs(
|
|
322
|
-
node: VersionedNode,
|
|
323
|
-
includeSelf = true
|
|
324
|
-
): string[] {
|
|
325
|
-
const hrefs: string[] = [];
|
|
326
|
-
const own = node.link?.href || node.name;
|
|
327
|
-
if (includeSelf && own) {
|
|
328
|
-
hrefs.push(own);
|
|
329
|
-
}
|
|
330
|
-
for (const child of node.children || []) {
|
|
331
|
-
if (child.versions?.length || child.link?.target === "_blank") {
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
hrefs.push(...collectSubtreeCanonicalHrefs(child));
|
|
335
|
-
}
|
|
336
|
-
return hrefs;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export function isCurrentPageInSubtree(
|
|
340
|
-
node: VersionedNode,
|
|
341
|
-
versions?: TopicVersionOption[]
|
|
342
|
-
): boolean {
|
|
343
|
-
if (!isBrowser() || !versions?.length) {
|
|
344
|
-
return false;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return collectSubtreeCanonicalHrefs(node).some((href) =>
|
|
348
|
-
versions.some((version) => hrefMatchesVersion(href, version, versions))
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Picker option hrefs. When the current page is this topic or a descendant,
|
|
354
|
-
* each option points at that same topic in the chosen version so the child
|
|
355
|
-
* stays selected. Otherwise keep the backend switching URL (parent page, or
|
|
356
|
-
* first content child for parent-only sections).
|
|
357
|
-
*/
|
|
358
|
-
export function getVersionPickerOptions(
|
|
359
|
-
node: VersionedNode | undefined
|
|
360
|
-
): TopicVersionOption[] | undefined {
|
|
361
|
-
const versions = node?.versions;
|
|
362
|
-
if (!node || !versions?.length) {
|
|
363
|
-
return versions;
|
|
364
|
-
}
|
|
365
|
-
if (!isCurrentPageInSubtree(node, versions)) {
|
|
366
|
-
return versions;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const canonical = toCanonicalHref(window.location.pathname, versions);
|
|
370
|
-
return versions.map((version) => ({
|
|
371
|
-
...version,
|
|
372
|
-
link: {
|
|
373
|
-
...version.link,
|
|
374
|
-
href:
|
|
375
|
-
applyVersionToHref(canonical, version, versions) ||
|
|
376
|
-
version.link?.href
|
|
377
|
-
}
|
|
378
|
-
}));
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Version whose page (or a descendant of it) is the current URL.
|
|
383
|
-
* Ignores the session-stored picker selection, which can remember an older
|
|
384
|
-
* version while the reader is still on the latest page.
|
|
385
|
-
*/
|
|
386
|
-
export function versionForCurrentPage(
|
|
387
|
-
node: VersionedNode | undefined
|
|
388
|
-
): TopicVersionOption | undefined {
|
|
389
|
-
const versions = node?.versions;
|
|
390
|
-
if (!node || !versions?.length || !isBrowser()) {
|
|
391
|
-
return undefined;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const hrefs = collectSubtreeCanonicalHrefs(node);
|
|
395
|
-
const matches = (version: TopicVersionOption) =>
|
|
396
|
-
hrefMatchesCurrentPage(version.link?.href) ||
|
|
397
|
-
hrefs.some((href) => hrefMatchesVersion(href, version, versions));
|
|
398
|
-
|
|
399
|
-
// Older versions first so a rewritten child URL is not mistaken for latest.
|
|
400
|
-
const older = versions.find(
|
|
401
|
-
(version) => version.id !== versions[0].id && matches(version)
|
|
402
|
-
);
|
|
403
|
-
if (older) {
|
|
404
|
-
return older;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return matches(versions[0]) ? versions[0] : undefined;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Deepest versioned topic that contains the current page, with the version
|
|
412
|
-
* the URL selects. Nested versioned topics own their pages.
|
|
413
|
-
*/
|
|
414
|
-
export function findTopicVersionForCurrentPage(
|
|
415
|
-
nodes: VersionedNode[] | undefined | null
|
|
416
|
-
): { node: VersionedNode; version: TopicVersionOption } | undefined {
|
|
417
|
-
let match: { node: VersionedNode; version: TopicVersionOption } | undefined;
|
|
418
|
-
const walk = (list?: VersionedNode[] | null) => {
|
|
419
|
-
for (const node of list || []) {
|
|
420
|
-
const version = versionForCurrentPage(node);
|
|
421
|
-
if (version) {
|
|
422
|
-
match = { node, version };
|
|
423
|
-
}
|
|
424
|
-
walk(node.children);
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
walk(nodes);
|
|
428
|
-
return match;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Current page rewritten onto the latest topic version. Falls back to the
|
|
433
|
-
* latest switching URL when the path delta cannot be applied.
|
|
434
|
-
*/
|
|
435
|
-
export function latestHrefForCurrentPage(
|
|
436
|
-
versions: TopicVersionOption[] | undefined
|
|
437
|
-
): string | undefined {
|
|
438
|
-
const latest = versions?.[0];
|
|
439
|
-
if (!latest) {
|
|
440
|
-
return undefined;
|
|
441
|
-
}
|
|
442
|
-
if (!isBrowser()) {
|
|
443
|
-
return latest.link?.href;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const canonical = toCanonicalHref(window.location.pathname, versions);
|
|
447
|
-
return applyVersionToHref(canonical, latest, versions) || latest.link?.href;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
export function resolveSelectedVersion(
|
|
451
|
-
node: VersionedNode | undefined
|
|
452
|
-
): TopicVersionOption | undefined {
|
|
453
|
-
const versions = node?.versions;
|
|
454
|
-
if (!node || !versions?.length) {
|
|
455
|
-
return undefined;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// The open page wins, including a canonical child opened from "View Latest".
|
|
459
|
-
// Session is only for a picker whose topic is not the current page.
|
|
460
|
-
const fromUrl = versionForCurrentPage(node);
|
|
461
|
-
if (fromUrl) {
|
|
462
|
-
return fromUrl;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const storedId = readTopicVersionSelections()[getTopicVersionKey(node)];
|
|
466
|
-
return versions.find((version) => version.id === storedId) || versions[0];
|
|
467
|
-
}
|