@salesforcedevs/dx-components 1.40.5-topic-alpha.7 → 1.40.5
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/LICENSE +12 -0
- package/lwc.config.json +0 -1
- package/package.json +3 -2
- package/src/modules/dx/button/button.css +4 -40
- package/src/modules/dx/checkboxGroup/checkboxGroup.css +0 -5
- package/src/modules/dx/dropdown/dropdown.css +16 -27
- package/src/modules/dx/dropdown/dropdown.html +1 -1
- package/src/modules/dx/dropdown/dropdown.ts +2 -18
- package/src/modules/dx/dropdownOption/dropdownOption.css +40 -59
- package/src/modules/dx/dropdownOption/dropdownOption.html +6 -23
- package/src/modules/dx/dropdownOption/dropdownOption.ts +6 -1
- package/src/modules/dx/popover/popover.css +1 -3
- package/src/modules/dx/popover/popover.ts +3 -25
- package/src/modules/dx/sidebarFooterNav/sidebarFooterNav.css +2 -0
- package/src/modules/dx/tabPanelList/tabPanelList.css +14 -0
- package/src/modules/dx/tabPanelList/tabPanelList.html +19 -10
- package/src/modules/dx/tabPanelList/tabPanelList.ts +4 -24
- package/src/modules/dx/tree/treeHandler.ts +10 -77
- package/src/modules/dx/treeItem/treeItem.css +0 -36
- 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 -52
- 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 +2 -1
- package/src/modules/dxConstants/brands/brands.ts +2 -1
- package/src/modules/dxHelpers/commonSidebar/commonSidebar.css +16 -18
- 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
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
@import "dxHelpers/text";
|
|
3
3
|
|
|
4
4
|
:host {
|
|
5
|
-
|
|
6
|
-
and desktop. The sidebar adds its left padding and a 24px right inset. */
|
|
7
|
-
--dx-c-sidebar-content-width: 296px;
|
|
5
|
+
--dx-c-sidebar-width: 337px;
|
|
8
6
|
--dx-c-sidebar-button-color: var(--dx-g-blue-vibrant-50);
|
|
9
7
|
--dx-c-sidebar-button-background: white;
|
|
10
8
|
--dx-c-sidebar-button-border: white;
|
|
@@ -13,9 +11,7 @@
|
|
|
13
11
|
--dx-c-sidebar-button-border-hover: var(--dx-g-blue-vibrant-50);
|
|
14
12
|
--dx-c-sidebar-left-padding: var(--dx-g-global-header-padding-horizontal);
|
|
15
13
|
--dx-c-sidebar-vertical-padding: var(--dx-g-spacing-md);
|
|
16
|
-
|
|
17
|
-
/* Widest a small version picker menu can grow. */
|
|
18
|
-
--doc-version-picker-width: var(--dx-c-sidebar-content-width);
|
|
14
|
+
--doc-version-picker-width: 296px;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
.header {
|
|
@@ -51,7 +47,7 @@
|
|
|
51
47
|
color: var(--dx-g-blue-vibrant-30);
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
.container.collapsed
|
|
50
|
+
.container.collapsed {
|
|
55
51
|
width: var(--dx-g-spacing-xl);
|
|
56
52
|
}
|
|
57
53
|
|
|
@@ -86,19 +82,15 @@
|
|
|
86
82
|
display: flex;
|
|
87
83
|
flex-direction: column;
|
|
88
84
|
height: var(--dx-c-sidebar-height);
|
|
89
|
-
|
|
90
|
-
/* Mobile (container-mobile, set at 768px and below) fills the viewport. */
|
|
91
|
-
width: 100%;
|
|
85
|
+
width: calc(var(--dx-c-sidebar-width) + var(--left-spacing));
|
|
92
86
|
}
|
|
93
87
|
|
|
94
|
-
/* Tablet and desktop: left padding + content + right inset. */
|
|
95
88
|
.container:not(.container-mobile) {
|
|
96
89
|
--border-color: transparent;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
var(--dx-c-sidebar-left-padding) + var(--dx-c-sidebar-content-width) +
|
|
100
|
-
var(--dx-g-spacing-lg)
|
|
90
|
+
--left-spacing: calc(
|
|
91
|
+
var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-md)
|
|
101
92
|
);
|
|
93
|
+
|
|
102
94
|
box-shadow: 2px 0 0 0 var(--border-color);
|
|
103
95
|
min-height: var(--dx-c-sidebar-min-height);
|
|
104
96
|
padding: var(--dx-c-sidebar-vertical-padding) 0 var(--dx-g-spacing-2xs) 0;
|
|
@@ -114,9 +106,7 @@
|
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
.container.container-mobile {
|
|
117
|
-
|
|
118
|
-
100vw - var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-lg)
|
|
119
|
-
);
|
|
109
|
+
width: 100%;
|
|
120
110
|
}
|
|
121
111
|
|
|
122
112
|
.container.container-mobile .header {
|
|
@@ -179,3 +169,11 @@
|
|
|
179
169
|
.sidebar-content-hasfooter {
|
|
180
170
|
margin-bottom: var(--dx-g-spacing-2xl);
|
|
181
171
|
}
|
|
172
|
+
|
|
173
|
+
@media screen and (max-width: 767px) {
|
|
174
|
+
:host {
|
|
175
|
+
--doc-version-picker-width: calc(
|
|
176
|
+
100vw - var(--dx-c-sidebar-left-padding) - var(--dx-g-spacing-lg)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -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
|
-
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
|
-
}
|