@salesforcedevs/docs-components 1.34.0-alpha2 → 1.34.0-banner-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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "1.34.0-alpha2",
3
+ "version": "1.34.0-banner-alpha",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -9,7 +9,8 @@ import SprigSurvey from "doc/sprigSurvey";
9
9
  import { throttle } from "throttle-debounce";
10
10
  import { pollUntil } from "dxUtils/async";
11
11
  import { toJson } from "dxUtils/normalizers";
12
- import type { OptionWithLink } from "typings/custom";
12
+ import { oldVersionDocInfo } from "docUtils/utils";
13
+ import type { DocPhaseInfo, OptionWithLink } from "typings/custom";
13
14
 
14
15
  declare global {
15
16
  interface Window {
@@ -185,6 +186,14 @@ export default class RedocReference extends LightningElement {
185
186
  return this.versions.length > 0;
186
187
  }
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
+
188
197
  /** A single version has nothing to choose, so it renders read-only. */
189
198
  get isVersionReadOnly(): boolean {
190
199
  return this.versions.length === 1;
@@ -586,10 +595,7 @@ export default class RedocReference extends LightningElement {
586
595
  const apiContentDiv = await this.waitForApiContent(redocContainer);
587
596
  apiContentDiv.setAttribute("lwc:dom", "manual");
588
597
 
589
- const docPhaseInfo = this.getDocPhaseInfo();
590
- if (docPhaseInfo) {
591
- this.insertDocPhase(apiContentDiv, docPhaseInfo);
592
- }
598
+ this.insertStatusItems(apiContentDiv);
593
599
 
594
600
  this.appendFooterItems(apiContentDiv);
595
601
 
@@ -780,15 +786,40 @@ export default class RedocReference extends LightningElement {
780
786
  return container.querySelector<HTMLElement>(".api-content")!;
781
787
  }
782
788
 
783
- // Creates and inserts doc phase component at container start
784
- private insertDocPhase(container: HTMLElement, docPhaseInfo: string): void {
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
+
785
798
  const wrapper = document.createElement("div");
786
799
  wrapper.className = "doc-phase-wrapper";
787
800
  container.insertBefore(wrapper, container.firstChild);
788
801
 
789
- const docPhaseElement = createElement("doc-phase", { is: DocPhase });
790
- Object.assign(docPhaseElement, { docPhaseInfo });
791
- wrapper.appendChild(docPhaseElement);
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
+ }
792
823
  }
793
824
 
794
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 { OptionWithLink, TreeNode } from "typings/custom";
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
@@ -67,6 +73,9 @@ export default class UnifiedContentLayout extends LightningElement {
67
73
 
68
74
  private _docPhaseInfo: string | null = null;
69
75
  private _sidebarContent: unknown = null;
76
+ private _oldVersionInfo: DocPhaseInfo | null = null;
77
+
78
+ @track showVersionBanner = false;
70
79
 
71
80
  @api
72
81
  get docPhaseInfo(): string | null {
@@ -83,9 +92,40 @@ export default class UnifiedContentLayout extends LightningElement {
83
92
  }
84
93
 
85
94
  set sidebarContent(value: string) {
86
- this._sidebarContent = decorateTopicsWithForwardArrow(
87
- toJson(value)?.topics
88
- );
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;
89
129
  }
90
130
 
91
131
  private get enableFooter(): boolean {
@@ -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="${latestVersionLink}">View Latest</a>`
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