@salesforcedevs/docs-components 1.34.0-tlv-alpha → 1.34.0-topic-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/package.json +1 -1
- package/src/modules/doc/redocReference/redocReference.css +1 -0
- package/src/modules/doc/redocReference/redocReference.ts +192 -20
- package/src/modules/doc/unifiedContentLayout/unifiedContentLayout.html +8 -0
- package/src/modules/doc/unifiedContentLayout/unifiedContentLayout.ts +53 -7
- package/src/modules/doc/versionPicker/versionPicker.css +101 -65
- package/src/modules/doc/versionPicker/versionPicker.html +18 -6
- package/src/modules/doc/versionPicker/versionPicker.ts +16 -15
- package/src/modules/docUtils/utils/utils.ts +20 -2
package/package.json
CHANGED
|
@@ -4,11 +4,13 @@ import DocPhase from "doc/phase";
|
|
|
4
4
|
import DxFooter from "dx/footer";
|
|
5
5
|
import DxIcon from "dx/icon";
|
|
6
6
|
import SidebarFooterNav from "dx/sidebarFooterNav";
|
|
7
|
+
import VersionPicker from "doc/versionPicker";
|
|
7
8
|
import SprigSurvey from "doc/sprigSurvey";
|
|
8
9
|
import { throttle } from "throttle-debounce";
|
|
9
10
|
import { pollUntil } from "dxUtils/async";
|
|
10
11
|
import { toJson } from "dxUtils/normalizers";
|
|
11
|
-
import
|
|
12
|
+
import { oldVersionDocInfo } from "docUtils/utils";
|
|
13
|
+
import type { DocPhaseInfo, OptionWithLink } from "typings/custom";
|
|
12
14
|
|
|
13
15
|
declare global {
|
|
14
16
|
interface Window {
|
|
@@ -33,8 +35,18 @@ type ReferenceItem = {
|
|
|
33
35
|
topic?: ReferenceTopic;
|
|
34
36
|
};
|
|
35
37
|
|
|
38
|
+
/** A selectable spec version. Mirrors `ReferenceVersion` in `doc/amfReference`. */
|
|
39
|
+
type ReferenceVersion = {
|
|
40
|
+
id: string;
|
|
41
|
+
label: string;
|
|
42
|
+
deprecated?: boolean;
|
|
43
|
+
selected?: boolean;
|
|
44
|
+
link: { href: string };
|
|
45
|
+
};
|
|
46
|
+
|
|
36
47
|
type ReferenceConfig = {
|
|
37
48
|
refList: ReferenceItem[];
|
|
49
|
+
versions?: ReferenceVersion[];
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
const SCROLL_THROTTLE_DELAY = 50;
|
|
@@ -42,6 +54,7 @@ const ELEMENT_TIMEOUT = 10000;
|
|
|
42
54
|
const ELEMENT_CHECK_INTERVAL = 100;
|
|
43
55
|
const DEFAULT_PROJECT_TITLE = "All Reference";
|
|
44
56
|
const BACK_TARGET_STORAGE_KEY = "redoc-back-target";
|
|
57
|
+
const SELECTED_VIEW_STORAGE_KEY = "redoc-selected-view";
|
|
45
58
|
const DOCS_PATH_SEGMENT = "docs";
|
|
46
59
|
const DEFAULT_LOCALE = "en-us";
|
|
47
60
|
|
|
@@ -152,6 +165,40 @@ export default class RedocReference extends LightningElement {
|
|
|
152
165
|
return refCount > 1;
|
|
153
166
|
}
|
|
154
167
|
|
|
168
|
+
get versions(): ReferenceVersion[] {
|
|
169
|
+
return this._referenceConfig?.versions ?? [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The version flagged `selected`, else the first (the latest/GA version). */
|
|
173
|
+
get selectedVersion(): ReferenceVersion | null {
|
|
174
|
+
const versions = this.versions;
|
|
175
|
+
return versions.find((v) => v.selected) ?? versions[0] ?? null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** True when the selected version is the latest/GA one (index 0). */
|
|
179
|
+
get latestVersion(): boolean {
|
|
180
|
+
return this.versions.length
|
|
181
|
+
? this.selectedVersion?.id === this.versions[0].id
|
|
182
|
+
: false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get hasVersionPicker(): boolean {
|
|
186
|
+
return this.versions.length > 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** "Newer version available" banner info when on a non-latest version, else null. */
|
|
190
|
+
private get oldVersionInfo(): DocPhaseInfo | null {
|
|
191
|
+
if (this.versions.length > 1 && !this.latestVersion) {
|
|
192
|
+
return oldVersionDocInfo(this.versions[0].link.href);
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A single version has nothing to choose, so it renders read-only. */
|
|
198
|
+
get isVersionReadOnly(): boolean {
|
|
199
|
+
return this.versions.length === 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
155
202
|
// Reads stored back target
|
|
156
203
|
private getBackTargetFromSession(): string | null {
|
|
157
204
|
return sessionStorage.getItem(BACK_TARGET_STORAGE_KEY);
|
|
@@ -202,7 +249,9 @@ export default class RedocReference extends LightningElement {
|
|
|
202
249
|
}
|
|
203
250
|
|
|
204
251
|
const referrer = this.getSameOriginReferrerHref();
|
|
205
|
-
return referrer &&
|
|
252
|
+
return referrer &&
|
|
253
|
+
!this.isLocaleHref(new URL(referrer).pathname) &&
|
|
254
|
+
!this.isVersionHref(new URL(referrer).pathname)
|
|
206
255
|
? referrer
|
|
207
256
|
: null;
|
|
208
257
|
}
|
|
@@ -241,7 +290,10 @@ export default class RedocReference extends LightningElement {
|
|
|
241
290
|
}
|
|
242
291
|
|
|
243
292
|
const referrerUrl = new URL(referrerHref);
|
|
244
|
-
if (
|
|
293
|
+
if (
|
|
294
|
+
this.isLocaleHref(referrerUrl.pathname) ||
|
|
295
|
+
this.isVersionHref(referrerUrl.pathname)
|
|
296
|
+
) {
|
|
245
297
|
return;
|
|
246
298
|
}
|
|
247
299
|
|
|
@@ -282,6 +334,19 @@ export default class RedocReference extends LightningElement {
|
|
|
282
334
|
});
|
|
283
335
|
}
|
|
284
336
|
|
|
337
|
+
/**
|
|
338
|
+
* switching version on this same page.
|
|
339
|
+
*/
|
|
340
|
+
private isVersionHref(pathname: string): boolean {
|
|
341
|
+
return this.versions.some((version) => {
|
|
342
|
+
const href = version?.link?.href;
|
|
343
|
+
return (
|
|
344
|
+
!!href &&
|
|
345
|
+
new URL(href, window.location.origin).pathname === pathname
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
285
350
|
/** When origin is provided, pass it to the footer; otherwise use dx-footer's default. */
|
|
286
351
|
get effectiveFooterOrigin(): string {
|
|
287
352
|
return (
|
|
@@ -354,6 +419,14 @@ export default class RedocReference extends LightningElement {
|
|
|
354
419
|
return parseInt(value, 10) || 0;
|
|
355
420
|
}
|
|
356
421
|
|
|
422
|
+
/** Redoc's LNB background color, themeable via `--doc-c-redoc-sidebar-bg`. */
|
|
423
|
+
private get sidebarBackgroundColor(): string {
|
|
424
|
+
const value = getComputedStyle(this.template.host).getPropertyValue(
|
|
425
|
+
"--doc-c-redoc-sidebar-bg"
|
|
426
|
+
);
|
|
427
|
+
return value.trim() || "white";
|
|
428
|
+
}
|
|
429
|
+
|
|
357
430
|
/*
|
|
358
431
|
** Since we could not use --dx-g-global-header-height as getPropertyValue returns a calc expression,
|
|
359
432
|
** we are using the respective CSS variables to calculate the height.
|
|
@@ -469,7 +542,14 @@ export default class RedocReference extends LightningElement {
|
|
|
469
542
|
specUrl,
|
|
470
543
|
{
|
|
471
544
|
// Dynamic scroll offset to account for headers
|
|
472
|
-
scrollYOffset: this.calculateScrollYOffset
|
|
545
|
+
scrollYOffset: this.calculateScrollYOffset,
|
|
546
|
+
// Redoc's own styled background outranks injected
|
|
547
|
+
// CSS, so the LNB background must be set via theme.
|
|
548
|
+
theme: {
|
|
549
|
+
sidebar: {
|
|
550
|
+
backgroundColor: this.sidebarBackgroundColor
|
|
551
|
+
}
|
|
552
|
+
}
|
|
473
553
|
},
|
|
474
554
|
redocContainer,
|
|
475
555
|
(error: any) => {
|
|
@@ -515,10 +595,7 @@ export default class RedocReference extends LightningElement {
|
|
|
515
595
|
const apiContentDiv = await this.waitForApiContent(redocContainer);
|
|
516
596
|
apiContentDiv.setAttribute("lwc:dom", "manual");
|
|
517
597
|
|
|
518
|
-
|
|
519
|
-
if (docPhaseInfo) {
|
|
520
|
-
this.insertDocPhase(apiContentDiv, docPhaseInfo);
|
|
521
|
-
}
|
|
598
|
+
this.insertStatusItems(apiContentDiv);
|
|
522
599
|
|
|
523
600
|
this.appendFooterItems(apiContentDiv);
|
|
524
601
|
|
|
@@ -527,10 +604,22 @@ export default class RedocReference extends LightningElement {
|
|
|
527
604
|
|
|
528
605
|
// Wait for footer to be rendered before updating styles
|
|
529
606
|
requestAnimationFrame(() => {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
607
|
+
try {
|
|
608
|
+
this.updateRedocThirdColumnStyle(redocContainer);
|
|
609
|
+
|
|
610
|
+
// Restore the view selected before a version switch only
|
|
611
|
+
// after all layout mutations (footer, header) are
|
|
612
|
+
// complete, so the scroll lands on the correct element.
|
|
613
|
+
this.restoreSelectedView();
|
|
614
|
+
|
|
615
|
+
// Fix initial hash scroll after doc phase insertion
|
|
616
|
+
this.handleInitialHashScrollFix();
|
|
617
|
+
} catch (error) {
|
|
618
|
+
this.showErrorUI(
|
|
619
|
+
"Failed to integrate custom components:",
|
|
620
|
+
error
|
|
621
|
+
);
|
|
622
|
+
}
|
|
534
623
|
});
|
|
535
624
|
} catch (error) {
|
|
536
625
|
this.showErrorUI("Failed to integrate custom components:", error);
|
|
@@ -560,6 +649,53 @@ export default class RedocReference extends LightningElement {
|
|
|
560
649
|
}
|
|
561
650
|
}
|
|
562
651
|
|
|
652
|
+
/** Builds the version picker DOM by reusing `doc-version-picker`. */
|
|
653
|
+
private buildVersionPickerDom(): HTMLElement {
|
|
654
|
+
const wrapper = document.createElement("div");
|
|
655
|
+
wrapper.className = "redoc-version-picker";
|
|
656
|
+
|
|
657
|
+
const picker = createElement("doc-version-picker", {
|
|
658
|
+
is: VersionPicker
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
Object.assign(picker, {
|
|
662
|
+
versions: this.versions,
|
|
663
|
+
selectedVersion: this.selectedVersion,
|
|
664
|
+
latestVersion: this.latestVersion,
|
|
665
|
+
readOnly: this.isVersionReadOnly
|
|
666
|
+
});
|
|
667
|
+
picker.addEventListener("change", this.onVersionChange);
|
|
668
|
+
wrapper.appendChild(picker);
|
|
669
|
+
|
|
670
|
+
return wrapper;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** Stashes the current view (URL hash) before the version link navigates. */
|
|
674
|
+
private onVersionChange = (): void => {
|
|
675
|
+
sessionStorage.setItem(
|
|
676
|
+
SELECTED_VIEW_STORAGE_KEY,
|
|
677
|
+
window.location.hash || ""
|
|
678
|
+
);
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Re-applies the view stashed before a version switch, if its anchor exists
|
|
683
|
+
* in the new spec; otherwise a no-op (lands on the spec root).
|
|
684
|
+
*/
|
|
685
|
+
private restoreSelectedView(): void {
|
|
686
|
+
const storedHash = sessionStorage.getItem(SELECTED_VIEW_STORAGE_KEY);
|
|
687
|
+
sessionStorage.removeItem(SELECTED_VIEW_STORAGE_KEY);
|
|
688
|
+
|
|
689
|
+
if (!storedHash || window.location.hash) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const targetId = storedHash.replace(/^#/, "");
|
|
694
|
+
if (targetId && document.getElementById(targetId)) {
|
|
695
|
+
window.location.hash = storedHash;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
563
699
|
/**
|
|
564
700
|
* Builds the locale picker DOM by reusing `dx-sidebar-footer-nav`
|
|
565
701
|
*/
|
|
@@ -581,12 +717,17 @@ export default class RedocReference extends LightningElement {
|
|
|
581
717
|
}
|
|
582
718
|
|
|
583
719
|
/**
|
|
584
|
-
* Builds a
|
|
720
|
+
* Builds the doc header: a title group (back link + spec title) and, when
|
|
721
|
+
* versions are available, the version picker as a sibling row. Layout/gaps
|
|
722
|
+
* are styled by the developer-website's redoc CSS.
|
|
585
723
|
*/
|
|
586
724
|
private buildProjectHeaderDom(): HTMLElement {
|
|
587
725
|
const wrapper = document.createElement("div");
|
|
588
726
|
wrapper.className = "redoc-project-header";
|
|
589
727
|
|
|
728
|
+
const main = document.createElement("div");
|
|
729
|
+
main.className = "redoc-project-header-main";
|
|
730
|
+
|
|
590
731
|
if (this.projectTitle) {
|
|
591
732
|
const backLink = document.createElement("a");
|
|
592
733
|
backLink.className = "redoc-project-back";
|
|
@@ -607,14 +748,20 @@ export default class RedocReference extends LightningElement {
|
|
|
607
748
|
|
|
608
749
|
backLink.appendChild(icon);
|
|
609
750
|
backLink.appendChild(label);
|
|
610
|
-
|
|
751
|
+
main.appendChild(backLink);
|
|
611
752
|
}
|
|
612
753
|
|
|
613
754
|
if (this.specTitle) {
|
|
614
755
|
const specEl = document.createElement("h2");
|
|
615
756
|
specEl.className = "redoc-spec-title dx-text-display-7";
|
|
616
757
|
specEl.textContent = this.specTitle;
|
|
617
|
-
|
|
758
|
+
main.appendChild(specEl);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
wrapper.appendChild(main);
|
|
762
|
+
|
|
763
|
+
if (this.hasVersionPicker) {
|
|
764
|
+
wrapper.appendChild(this.buildVersionPickerDom());
|
|
618
765
|
}
|
|
619
766
|
|
|
620
767
|
return wrapper;
|
|
@@ -639,15 +786,40 @@ export default class RedocReference extends LightningElement {
|
|
|
639
786
|
return container.querySelector<HTMLElement>(".api-content")!;
|
|
640
787
|
}
|
|
641
788
|
|
|
642
|
-
//
|
|
643
|
-
|
|
789
|
+
// Inserts the doc phase and, on a non-latest version, a dismissible
|
|
790
|
+
// "newer version" banner into a shared doc-phase-wrapper at container start
|
|
791
|
+
private insertStatusItems(container: HTMLElement): void {
|
|
792
|
+
const docPhaseInfo = this.getDocPhaseInfo();
|
|
793
|
+
const oldVersionInfo = this.oldVersionInfo;
|
|
794
|
+
if (!docPhaseInfo && !oldVersionInfo) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
644
798
|
const wrapper = document.createElement("div");
|
|
645
799
|
wrapper.className = "doc-phase-wrapper";
|
|
646
800
|
container.insertBefore(wrapper, container.firstChild);
|
|
647
801
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
802
|
+
if (docPhaseInfo) {
|
|
803
|
+
const docPhaseElement = createElement("doc-phase", {
|
|
804
|
+
is: DocPhase
|
|
805
|
+
});
|
|
806
|
+
Object.assign(docPhaseElement, { docPhaseInfo });
|
|
807
|
+
wrapper.appendChild(docPhaseElement);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
if (oldVersionInfo) {
|
|
811
|
+
const versionBanner = createElement("doc-phase", { is: DocPhase });
|
|
812
|
+
Object.assign(versionBanner, {
|
|
813
|
+
docPhaseInfo: oldVersionInfo,
|
|
814
|
+
dismissible: true,
|
|
815
|
+
iconName: "warning"
|
|
816
|
+
});
|
|
817
|
+
versionBanner.addEventListener("dismissphase", () => {
|
|
818
|
+
versionBanner.remove();
|
|
819
|
+
this.updateSidebarPosition();
|
|
820
|
+
});
|
|
821
|
+
wrapper.appendChild(versionBanner);
|
|
822
|
+
}
|
|
651
823
|
}
|
|
652
824
|
|
|
653
825
|
// Appends footer component to container
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
lwc:if={docPhaseInfo}
|
|
25
25
|
doc-phase-info={docPhaseInfo}
|
|
26
26
|
></doc-phase>
|
|
27
|
+
<doc-phase
|
|
28
|
+
slot="version-banner"
|
|
29
|
+
lwc:if={showVersionBanner}
|
|
30
|
+
doc-phase-info={oldVersionInfo}
|
|
31
|
+
icon-name="warning"
|
|
32
|
+
dismissible="true"
|
|
33
|
+
ondismissphase={handleDismissVersionBanner}
|
|
34
|
+
></doc-phase>
|
|
27
35
|
<slot></slot>
|
|
28
36
|
</doc-content-layout>
|
|
29
37
|
</template>
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { LightningElement, api } from "lwc";
|
|
1
|
+
import { LightningElement, api, track } from "lwc";
|
|
2
2
|
import { toJson } from "dxUtils/normalizers";
|
|
3
|
-
import type {
|
|
3
|
+
import type { VersionedNode } from "dxUtils/topicVersion";
|
|
4
|
+
import {
|
|
5
|
+
findTopicVersionForCurrentPage,
|
|
6
|
+
latestHrefForCurrentPage
|
|
7
|
+
} from "dxUtils/topicVersion";
|
|
8
|
+
import { oldVersionDocInfo } from "docUtils/utils";
|
|
9
|
+
import type { DocPhaseInfo, OptionWithLink, TreeNode } from "typings/custom";
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Per-topic type emitted by the docs content-type parser
|
|
@@ -15,12 +21,18 @@ const TOPIC_TYPE_SPEC = "spec";
|
|
|
15
21
|
* that `dx-tree-tile` reads (spec topics get the forward arrow icon for Redoc).
|
|
16
22
|
*/
|
|
17
23
|
function decorateTopicsWithForwardArrow(
|
|
18
|
-
topics:
|
|
24
|
+
topics:
|
|
25
|
+
| Array<TreeNode & { topicType?: string; versions?: unknown }>
|
|
26
|
+
| undefined
|
|
19
27
|
): TreeNode[] | undefined {
|
|
20
28
|
return topics?.map((topic) => {
|
|
21
|
-
const { topicType, ...decorated } = topic;
|
|
29
|
+
const { topicType, versions, ...decorated } = topic;
|
|
22
30
|
if (topicType === TOPIC_TYPE_SPEC) {
|
|
23
31
|
decorated.showForwardArrow = true;
|
|
32
|
+
// Nested and standalone specs never get a sidebar picker; only the
|
|
33
|
+
// parent MD topic (when versioned) shows one.
|
|
34
|
+
} else if (versions) {
|
|
35
|
+
decorated.versions = versions;
|
|
24
36
|
}
|
|
25
37
|
if (decorated.children) {
|
|
26
38
|
decorated.children = decorateTopicsWithForwardArrow(
|
|
@@ -61,6 +73,9 @@ export default class UnifiedContentLayout extends LightningElement {
|
|
|
61
73
|
|
|
62
74
|
private _docPhaseInfo: string | null = null;
|
|
63
75
|
private _sidebarContent: unknown = null;
|
|
76
|
+
private _oldVersionInfo: DocPhaseInfo | null = null;
|
|
77
|
+
|
|
78
|
+
@track showVersionBanner = false;
|
|
64
79
|
|
|
65
80
|
@api
|
|
66
81
|
get docPhaseInfo(): string | null {
|
|
@@ -77,9 +92,40 @@ export default class UnifiedContentLayout extends LightningElement {
|
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
set sidebarContent(value: string) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
95
|
+
const topics = decorateTopicsWithForwardArrow(toJson(value)?.topics);
|
|
96
|
+
this._sidebarContent = topics;
|
|
97
|
+
this.updateTopicVersionBanner(topics as VersionedNode[] | undefined);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get oldVersionInfo(): DocPhaseInfo | null {
|
|
101
|
+
return this._oldVersionInfo;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
handleDismissVersionBanner(): void {
|
|
105
|
+
this.showVersionBanner = false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Same banner references use when the reader is on an older version.
|
|
110
|
+
* Topic versions are not a separate page property: the sidebar tree
|
|
111
|
+
* carries each version's switching URL, and the current pathname says
|
|
112
|
+
* which one is open.
|
|
113
|
+
*/
|
|
114
|
+
private updateTopicVersionBanner(
|
|
115
|
+
topics: VersionedNode[] | undefined
|
|
116
|
+
): void {
|
|
117
|
+
const match = findTopicVersionForCurrentPage(topics);
|
|
118
|
+
const latest = match?.node.versions?.[0];
|
|
119
|
+
const latestHref = latestHrefForCurrentPage(match?.node.versions);
|
|
120
|
+
|
|
121
|
+
if (match && latest && match.version.id !== latest.id && latestHref) {
|
|
122
|
+
this._oldVersionInfo = oldVersionDocInfo(latestHref);
|
|
123
|
+
this.showVersionBanner = true;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this._oldVersionInfo = null;
|
|
128
|
+
this.showVersionBanner = false;
|
|
83
129
|
}
|
|
84
130
|
|
|
85
131
|
private get enableFooter(): boolean {
|
|
@@ -4,26 +4,59 @@
|
|
|
4
4
|
:host {
|
|
5
5
|
--dx-c-dropdown-option-font-weight: normal;
|
|
6
6
|
--dx-c-dropdown-option-label-color: var(--dx-g-gray-10);
|
|
7
|
+
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
8
|
+
var(--dx-g-spacing-lg);
|
|
9
|
+
--dx-c-popover-padding: var(--dx-g-spacing-sm) 0;
|
|
7
10
|
--popover-container-open-transform: translateY(4px);
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
/* Open menus must stack above later sibling pickers in the sidebar. */
|
|
14
|
+
:host(:has([aria-expanded="true"])) {
|
|
15
|
+
position: relative;
|
|
16
|
+
z-index: 2;
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
.version-picker-container {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/* Override --doc-version-picker-padding to change the inset (e.g. when the
|
|
21
|
+
picker sits inside an already-padded container). */
|
|
22
|
+
padding: var(
|
|
23
|
+
--doc-version-picker-padding,
|
|
24
|
+
var(--dx-g-spacing-sm) var(--dx-g-spacing-lg) var(--dx-g-spacing-sm)
|
|
25
|
+
var(--dx-g-global-header-padding-horizontal)
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/* Override --doc-version-picker-divider (e.g. `none`) to restyle dividers. */
|
|
29
|
+
border-top: var(
|
|
30
|
+
--doc-version-picker-divider,
|
|
31
|
+
1px solid var(--dx-g-gray-90)
|
|
32
|
+
);
|
|
33
|
+
border-bottom: var(
|
|
34
|
+
--doc-version-picker-divider,
|
|
35
|
+
1px solid var(--dx-g-gray-90)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Inline sidebar variant: drop the header-slot chrome so the picker sits
|
|
40
|
+
beside a tree node without extra padding or divider lines. */
|
|
41
|
+
.version-picker-container-small {
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
padding: 0;
|
|
45
|
+
border: none;
|
|
15
46
|
}
|
|
16
47
|
|
|
17
48
|
.version-picker-button {
|
|
18
49
|
display: flex;
|
|
19
50
|
width: var(--doc-version-picker-width, 296px);
|
|
51
|
+
|
|
52
|
+
--dx-c-button-horizontal-spacing: var(--dx-g-spacing-sm);
|
|
53
|
+
--dx-g-button-icon-color: var(--dx-g-gray-50);
|
|
20
54
|
}
|
|
21
55
|
|
|
22
56
|
.version-picker-button:hover,
|
|
23
|
-
.version-picker-button:
|
|
24
|
-
.version-picker-button
|
|
25
|
-
--dx-
|
|
26
|
-
--dx-c-button-primary-color: var(--dx-g-blue-vibrant-40);
|
|
57
|
+
.version-picker-button:focus-within,
|
|
58
|
+
.version-picker-button[aria-expanded="true"] {
|
|
59
|
+
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
27
60
|
}
|
|
28
61
|
|
|
29
62
|
/**
|
|
@@ -35,6 +68,27 @@ dx-button::part(content) {
|
|
|
35
68
|
overflow: hidden;
|
|
36
69
|
}
|
|
37
70
|
|
|
71
|
+
/* The border is an inset box-shadow so thickening it on hover/focus doesn't
|
|
72
|
+
resize the small variant's fit-content box. */
|
|
73
|
+
.version-picker-button::part(container) {
|
|
74
|
+
background: white;
|
|
75
|
+
box-shadow: inset 0 0 0 1px var(--dx-g-gray-50);
|
|
76
|
+
color: var(--dx-g-gray-10);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.version-picker-button::part(container):hover {
|
|
80
|
+
background: var(--dx-g-blue-vibrant-95);
|
|
81
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* aria-expanded keeps the focus ring while the menu is open. */
|
|
85
|
+
.version-picker-button:focus-within::part(container),
|
|
86
|
+
.version-picker-button[aria-expanded="true"]::part(container) {
|
|
87
|
+
background: var(--dx-g-blue-vibrant-95);
|
|
88
|
+
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20), 0 0 0 2px white,
|
|
89
|
+
0 0 0 4px var(--dx-g-blue-vibrant-60);
|
|
90
|
+
}
|
|
91
|
+
|
|
38
92
|
.selected-version {
|
|
39
93
|
display: flex;
|
|
40
94
|
flex-direction: row;
|
|
@@ -49,17 +103,8 @@ dx-button::part(content) {
|
|
|
49
103
|
white-space: nowrap;
|
|
50
104
|
}
|
|
51
105
|
|
|
52
|
-
dx-type-badge.latest-badge
|
|
53
|
-
--dx-c-type-badge-color: var(--dx-g-green-vibrant-40);
|
|
54
|
-
--dx-c-type-badge-background: var(--dx-g-green-vibrant-95);
|
|
55
|
-
|
|
56
|
-
margin-left: var(--dx-g-spacing-sm);
|
|
57
|
-
}
|
|
58
|
-
|
|
106
|
+
dx-type-badge.latest-badge,
|
|
59
107
|
dx-type-badge.not-latest-badge {
|
|
60
|
-
--dx-c-type-badge-color: var(--dx-g-red-vibrant-40);
|
|
61
|
-
--dx-c-type-badge-background: var(--dx-g-red-vibrant-95);
|
|
62
|
-
|
|
63
108
|
margin-left: var(--dx-g-spacing-sm);
|
|
64
109
|
}
|
|
65
110
|
|
|
@@ -67,26 +112,33 @@ dx-type-badge.not-latest-badge {
|
|
|
67
112
|
* Small "dot" variant (opt-in via `small`). Additive
|
|
68
113
|
* ------------------------------------------------------------------ */
|
|
69
114
|
|
|
70
|
-
/*
|
|
115
|
+
/* Option padding is sm both axes: the small menu tracks the ~104px trigger, so
|
|
116
|
+
the default 24px horizontal would crowd the labels. */
|
|
71
117
|
.version-picker-dropdown-small {
|
|
72
118
|
--dx-c-dropdown-option-font-size: var(--dx-g-text-xs);
|
|
73
119
|
--dx-c-dropdown-option-padding: var(--dx-g-spacing-sm)
|
|
74
|
-
var(--dx-g-spacing-
|
|
120
|
+
var(--dx-g-spacing-sm);
|
|
75
121
|
--dx-c-dropdown-option-border-radius: 0;
|
|
76
122
|
--dx-c-popover-border: none;
|
|
77
|
-
|
|
123
|
+
|
|
124
|
+
/* Hug the labels (down to the shortest) but never wider than the ToC
|
|
125
|
+
content: 296px on desktop, and on the smallest breakpoint the sidebar
|
|
126
|
+
variable grows with the viewport up to 720px. Long labels wrap inside
|
|
127
|
+
that cap. */
|
|
128
|
+
--dx-c-dropdown-menu-max-width: var(--doc-version-picker-width, 296px);
|
|
129
|
+
--dx-c-dropdown-fit-white-space: normal;
|
|
130
|
+
--dx-c-dropdown-fit-overflow: hidden;
|
|
131
|
+
--dx-c-dropdown-fit-text-overflow: clip;
|
|
132
|
+
--dx-c-dropdown-option-overflow-wrap: anywhere;
|
|
78
133
|
}
|
|
79
134
|
|
|
80
135
|
.version-picker-button-small {
|
|
81
|
-
display: flex;
|
|
82
136
|
width: fit-content;
|
|
83
137
|
max-width: 104px;
|
|
84
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
85
138
|
|
|
86
139
|
--dx-c-button-font-size: var(--dx-g-text-xs);
|
|
87
140
|
--dx-c-button-font-weight: var(--dx-g-font-normal);
|
|
88
|
-
--dx-c-button-line-height:
|
|
89
|
-
--dx-g-button-icon-color: var(--dx-g-gray-50);
|
|
141
|
+
--dx-c-button-line-height: var(--dx-g-spacing-lg);
|
|
90
142
|
--dx-c-button-icon-gap: var(--dx-g-spacing-2xs);
|
|
91
143
|
}
|
|
92
144
|
|
|
@@ -101,43 +153,12 @@ dx-type-badge.not-latest-badge {
|
|
|
101
153
|
overflow: hidden;
|
|
102
154
|
}
|
|
103
155
|
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
box-sizing: border-box;
|
|
107
|
-
height: var(--dx-g-spacing-lg);
|
|
108
|
-
padding: 0 var(--dx-g-spacing-xs);
|
|
109
|
-
border-radius: var(--dx-g-spacing-xs);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/* Border is an inset box-shadow, not a real border, so thickening it on
|
|
113
|
-
hover/focus doesn't widen the fit-content box (no horizontal shift). */
|
|
156
|
+
/* width:100% re-ties to the 104px-capped host (container is width:inherit,
|
|
157
|
+
which copies width but not max-width). Border/bg/states are shared above. */
|
|
114
158
|
.version-picker-button-small::part(container) {
|
|
115
|
-
/* width:100% re-ties to the 104px-capped host (container is width:inherit,
|
|
116
|
-
which copies width but not max-width). */
|
|
117
159
|
width: 100%;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
color: var(--dx-g-gray-10);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.version-picker-button-small::part(container):hover {
|
|
124
|
-
background: var(--dx-g-blue-vibrant-95);
|
|
125
|
-
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.version-picker-button-small:hover,
|
|
129
|
-
.version-picker-button-small:focus-within,
|
|
130
|
-
.version-picker-button-small[aria-expanded="true"] {
|
|
131
|
-
--dx-g-button-icon-color: var(--dx-g-blue-vibrant-20);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/* Also matches aria-expanded so the ring stays while the menu is open (opening
|
|
135
|
-
moves focus into the menu, which would otherwise drop :focus-within). */
|
|
136
|
-
.version-picker-button-small:focus-within::part(container),
|
|
137
|
-
.version-picker-button-small[aria-expanded="true"]::part(container) {
|
|
138
|
-
background: var(--dx-g-blue-vibrant-95);
|
|
139
|
-
box-shadow: inset 0 0 0 2px var(--dx-g-blue-vibrant-20), 0 0 0 2px white,
|
|
140
|
-
0 0 0 4px var(--dx-g-blue-vibrant-60);
|
|
160
|
+
height: var(--dx-g-spacing-lg);
|
|
161
|
+
padding: 0 var(--dx-g-spacing-xs);
|
|
141
162
|
}
|
|
142
163
|
|
|
143
164
|
.selected-version-small {
|
|
@@ -161,16 +182,31 @@ dx-type-badge.not-latest-badge {
|
|
|
161
182
|
background: var(--dx-g-yellow-vibrant-80);
|
|
162
183
|
}
|
|
163
184
|
|
|
164
|
-
|
|
185
|
+
/* inline-block (not flex) so the label can truncate; line-height centers it. */
|
|
186
|
+
.version-picker-readonly {
|
|
187
|
+
box-sizing: border-box;
|
|
165
188
|
display: inline-block;
|
|
166
|
-
width:
|
|
167
|
-
|
|
189
|
+
width: var(--doc-version-picker-width, 296px);
|
|
190
|
+
height: var(--dx-g-spacing-xl);
|
|
168
191
|
margin: 0;
|
|
192
|
+
padding: 0 var(--dx-g-spacing-sm);
|
|
169
193
|
border: 1px solid var(--dx-g-gray-80);
|
|
194
|
+
border-radius: var(--dx-g-spacing-xs);
|
|
170
195
|
overflow: hidden;
|
|
171
196
|
color: var(--dx-g-gray-10);
|
|
172
197
|
text-overflow: ellipsis;
|
|
173
198
|
white-space: nowrap;
|
|
174
|
-
font: var(--dx-g-font-normal) var(--dx-g-text-
|
|
175
|
-
|
|
199
|
+
font: var(--dx-g-font-normal) var(--dx-g-text-sm) / var(--dx-g-spacing-xl)
|
|
200
|
+
var(--dx-g-font-sans),
|
|
201
|
+
sans-serif;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.version-picker-readonly-small {
|
|
205
|
+
width: fit-content;
|
|
206
|
+
max-width: 104px;
|
|
207
|
+
height: var(--dx-g-spacing-lg);
|
|
208
|
+
padding: 0 var(--dx-g-spacing-xs);
|
|
209
|
+
font: var(--dx-g-font-normal) var(--dx-g-text-xs) / var(--dx-g-spacing-lg)
|
|
210
|
+
var(--dx-g-font-sans),
|
|
211
|
+
sans-serif;
|
|
176
212
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div lwc:if={showVersionPicker} class=
|
|
2
|
+
<div lwc:if={showVersionPicker} class={containerClass}>
|
|
3
3
|
<!-- Small read-only: plain value, no dropdown -->
|
|
4
|
-
<p
|
|
4
|
+
<p
|
|
5
|
+
lwc:if={readOnly}
|
|
6
|
+
class={readOnlyClass}
|
|
7
|
+
title={selectedVersion.label}
|
|
8
|
+
>
|
|
5
9
|
{selectedVersion.label}
|
|
6
10
|
</p>
|
|
7
11
|
|
|
@@ -12,14 +16,14 @@
|
|
|
12
16
|
analytics-event="custEv_docVersionSelect"
|
|
13
17
|
analytics-payload={analyticsPayload}
|
|
14
18
|
value={selectedVersion.id}
|
|
15
|
-
width={
|
|
19
|
+
width-mode={dropdownWidthMode}
|
|
16
20
|
onchange={onVersionChange}
|
|
17
21
|
>
|
|
18
22
|
<dx-button
|
|
19
23
|
class={triggerClass}
|
|
20
24
|
variant="tertiary"
|
|
21
25
|
size="small"
|
|
22
|
-
font=
|
|
26
|
+
font="sans"
|
|
23
27
|
icon-symbol="chevrondown"
|
|
24
28
|
icon-size={triggerIconSize}
|
|
25
29
|
aria-label={triggerAriaLabel}
|
|
@@ -30,25 +34,33 @@
|
|
|
30
34
|
<template lwc:if={showLatestTag}>
|
|
31
35
|
<span class={dotClass} aria-hidden="true"></span>
|
|
32
36
|
</template>
|
|
33
|
-
<p
|
|
37
|
+
<p
|
|
38
|
+
class="selected-version-label"
|
|
39
|
+
title={selectedVersion.label}
|
|
40
|
+
>
|
|
34
41
|
{selectedVersion.label}
|
|
35
42
|
</p>
|
|
36
43
|
</template>
|
|
37
44
|
<!-- Default: trailing Latest/Not-Latest badge -->
|
|
38
45
|
<template lwc:else>
|
|
39
|
-
<p
|
|
46
|
+
<p
|
|
47
|
+
class="selected-version-label"
|
|
48
|
+
title={selectedVersion.label}
|
|
49
|
+
>
|
|
40
50
|
{selectedVersion.label}
|
|
41
51
|
</p>
|
|
42
52
|
<template lwc:if={showLatestTag}>
|
|
43
53
|
<dx-type-badge
|
|
44
54
|
class="latest-badge"
|
|
45
55
|
lwc:if={latestVersion}
|
|
56
|
+
variant="status-success"
|
|
46
57
|
value="Latest"
|
|
47
58
|
size="small"
|
|
48
59
|
></dx-type-badge>
|
|
49
60
|
<dx-type-badge
|
|
50
61
|
class="not-latest-badge"
|
|
51
62
|
lwc:else
|
|
63
|
+
variant="status-warning"
|
|
52
64
|
value="Not Latest"
|
|
53
65
|
size="small"
|
|
54
66
|
></dx-type-badge>
|
|
@@ -79,13 +79,21 @@ export default class VersionPicker extends LightningElement {
|
|
|
79
79
|
return this._versions && this._versions.length !== 0;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
private get containerClass(): string {
|
|
83
|
+
return this.small
|
|
84
|
+
? "version-picker-container-small"
|
|
85
|
+
: "version-picker-container";
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
private get showLatestTag(): boolean {
|
|
83
89
|
return !this.hideBadge;
|
|
84
90
|
}
|
|
85
91
|
|
|
86
|
-
// Read-only currently only has a small-variant style.
|
|
87
92
|
private get readOnlyClass(): string {
|
|
88
|
-
return
|
|
93
|
+
return cx(
|
|
94
|
+
"version-picker-readonly",
|
|
95
|
+
this.small && "version-picker-readonly-small"
|
|
96
|
+
);
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
private get dotClass(): string {
|
|
@@ -108,9 +116,7 @@ export default class VersionPicker extends LightningElement {
|
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
// --- Per-variant values for the shared dropdown/button markup below. The
|
|
111
|
-
// small variant opts into extra classes and a compact chevron
|
|
112
|
-
// returns undefined so those attributes are omitted (keeps default output
|
|
113
|
-
// unchanged for xmlContent/amfReference).
|
|
119
|
+
// small variant opts into extra classes and a compact chevron.
|
|
114
120
|
|
|
115
121
|
// Not cx(): the default must have NO class attribute (cx returns "", which
|
|
116
122
|
// would render class="" and break the frozen consumer snapshots).
|
|
@@ -118,8 +124,10 @@ export default class VersionPicker extends LightningElement {
|
|
|
118
124
|
return this.small ? "version-picker-dropdown-small" : undefined;
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
|
|
122
|
-
|
|
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";
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
private get triggerClass(): string {
|
|
@@ -129,10 +137,6 @@ export default class VersionPicker extends LightningElement {
|
|
|
129
137
|
);
|
|
130
138
|
}
|
|
131
139
|
|
|
132
|
-
private get triggerFont(): string {
|
|
133
|
-
return this.small ? "sans" : "display";
|
|
134
|
-
}
|
|
135
|
-
|
|
136
140
|
private get triggerIconSize(): string {
|
|
137
141
|
return this.small ? "xsmall" : "medium";
|
|
138
142
|
}
|
|
@@ -144,10 +148,7 @@ export default class VersionPicker extends LightningElement {
|
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
private get selectedVersionClass(): string {
|
|
147
|
-
return cx(
|
|
148
|
-
"selected-version",
|
|
149
|
-
this.small && "selected-version-small"
|
|
150
|
-
);
|
|
151
|
+
return cx("selected-version", this.small && "selected-version-small");
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
private onVersionChange(e: CustomEvent) {
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import { CoveoAnalyticsClient } from "coveo.analytics/dist/browser.mjs"; // This fix is required for Node 20 upgrade, so that Coveo analytics is loaded properly by LWR.
|
|
2
2
|
|
|
3
|
+
// Resolve against the current origin (version links are usually relative) and
|
|
4
|
+
// keep only http/https. Blocks executable schemes like javascript:/data: that
|
|
5
|
+
// would run when the innerHTML-rendered link is clicked
|
|
6
|
+
// encodeURI then keeps the value from breaking out of the href attribute.
|
|
7
|
+
const toSafeHref = (link: string): string => {
|
|
8
|
+
try {
|
|
9
|
+
const { protocol } = new URL(link, window.location.origin);
|
|
10
|
+
if (protocol === "http:" || protocol === "https:") {
|
|
11
|
+
return encodeURI(link);
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
// Fall through to the rejection below.
|
|
15
|
+
}
|
|
16
|
+
console.warn(`Blocked unsafe version link: ${link}`);
|
|
17
|
+
return "#";
|
|
18
|
+
};
|
|
19
|
+
|
|
3
20
|
export const oldVersionDocInfo = (latestVersionLink: string) => {
|
|
21
|
+
const safeLink = toSafeHref(latestVersionLink);
|
|
4
22
|
return {
|
|
5
23
|
title: "Newer Version Available",
|
|
6
|
-
body: `This content describes an older version of this product.
|
|
7
|
-
<a style="font-weight: bold;" href="${
|
|
24
|
+
body: `This content describes an older version of this product.
|
|
25
|
+
<a style="font-weight: bold;" href="${safeLink}">View Latest</a>`
|
|
8
26
|
};
|
|
9
27
|
};
|
|
10
28
|
|