@salesforcedevs/docs-components 0.0.4 → 0.0.5-edit

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.
Files changed (140) hide show
  1. package/lwc.config.json +25 -2
  2. package/package.json +18 -7
  3. package/src/modules/README.md +41 -0
  4. package/src/modules/doc/amfModelParser/amfModelParser.ts +674 -0
  5. package/src/modules/doc/amfReference/amfReference.css +25 -0
  6. package/src/modules/doc/amfReference/amfReference.html +60 -0
  7. package/src/modules/doc/amfReference/amfReference.ts +1494 -0
  8. package/src/modules/doc/amfReference/constants.ts +76 -0
  9. package/src/modules/doc/amfReference/types.ts +125 -0
  10. package/src/modules/doc/amfTopic/amfTopic.css +21 -0
  11. package/src/modules/doc/amfTopic/amfTopic.html +3 -0
  12. package/src/modules/doc/amfTopic/amfTopic.ts +111 -0
  13. package/src/modules/doc/amfTopic/types.ts +56 -0
  14. package/src/modules/doc/amfTopic/utils.ts +136 -0
  15. package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +51 -0
  16. package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +5 -0
  17. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +71 -0
  18. package/src/modules/doc/breadcrumbs/breadcrumbs.css +27 -0
  19. package/src/modules/doc/breadcrumbs/breadcrumbs.html +58 -0
  20. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +183 -0
  21. package/src/modules/doc/chat/README.md +179 -0
  22. package/src/modules/doc/chat/chat.css +821 -0
  23. package/src/modules/doc/chat/chat.html +241 -0
  24. package/src/modules/doc/chat/chat.ts +586 -0
  25. package/src/modules/doc/componentPlayground/componentPlayground.css +22 -0
  26. package/src/modules/doc/componentPlayground/componentPlayground.html +20 -0
  27. package/src/modules/doc/componentPlayground/componentPlayground.ts +29 -0
  28. package/src/modules/doc/content/content.css +382 -6
  29. package/src/modules/doc/content/content.html +3 -2
  30. package/src/modules/doc/content/content.ts +287 -110
  31. package/src/modules/doc/contentCallout/contentCallout.css +25 -26
  32. package/src/modules/doc/contentCallout/contentCallout.html +13 -4
  33. package/src/modules/doc/contentCallout/contentCallout.ts +22 -11
  34. package/src/modules/doc/contentLayout/contentLayout.css +13 -0
  35. package/src/modules/doc/contentLayout/contentLayout.html +73 -0
  36. package/src/modules/doc/contentLayout/contentLayout.ts +531 -0
  37. package/src/modules/doc/contentMedia/contentMedia.css +49 -0
  38. package/src/modules/doc/contentMedia/contentMedia.html +23 -0
  39. package/src/modules/doc/contentMedia/contentMedia.ts +34 -0
  40. package/src/modules/doc/doDont/doDont.css +47 -0
  41. package/src/modules/doc/doDont/doDont.html +27 -0
  42. package/src/modules/doc/doDont/doDont.ts +17 -0
  43. package/src/modules/doc/editFile/editFile.css +505 -0
  44. package/src/modules/doc/editFile/editFile.html +164 -0
  45. package/src/modules/doc/editFile/editFile.ts +213 -0
  46. package/src/modules/doc/header/header.css +132 -0
  47. package/src/modules/doc/header/header.html +55 -0
  48. package/src/modules/doc/header/header.ts +120 -0
  49. package/src/modules/doc/heading/heading.css +33 -0
  50. package/src/modules/doc/heading/heading.html +14 -0
  51. package/src/modules/doc/heading/heading.ts +67 -0
  52. package/src/modules/doc/headingAnchor/headingAnchor.css +33 -0
  53. package/src/modules/doc/headingAnchor/headingAnchor.html +19 -0
  54. package/src/modules/doc/headingAnchor/headingAnchor.ts +43 -0
  55. package/src/modules/doc/headingContent/headingContent.css +53 -0
  56. package/src/modules/doc/headingContent/headingContent.html +13 -0
  57. package/src/modules/doc/headingContent/headingContent.ts +30 -0
  58. package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
  59. package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +68 -0
  60. package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +168 -0
  61. package/src/modules/doc/nav/nav.css +4 -2
  62. package/src/modules/doc/nav/nav.html +8 -13
  63. package/src/modules/doc/nav/nav.ts +1 -1
  64. package/src/modules/doc/overview/overview.css +40 -0
  65. package/src/modules/doc/overview/overview.html +34 -0
  66. package/src/modules/doc/overview/overview.ts +12 -0
  67. package/src/modules/doc/phase/phase.css +70 -0
  68. package/src/modules/doc/phase/phase.html +38 -0
  69. package/src/modules/doc/phase/phase.ts +93 -0
  70. package/src/modules/doc/specificationContent/specificationContent.css +36 -0
  71. package/src/modules/doc/specificationContent/specificationContent.html +171 -0
  72. package/src/modules/doc/specificationContent/specificationContent.ts +127 -0
  73. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  74. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  75. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  76. package/src/modules/doc/toc/toc.html +11 -6
  77. package/src/modules/doc/toc/toc.ts +2 -6
  78. package/src/modules/doc/toolbar/toolbar.html +8 -1
  79. package/src/modules/doc/toolbar/toolbar.ts +1 -1
  80. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  81. package/src/modules/doc/versionPicker/versionPicker.html +38 -0
  82. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  83. package/src/modules/doc/xmlContent/types.ts +120 -0
  84. package/src/modules/doc/xmlContent/utils.ts +163 -0
  85. package/src/modules/doc/xmlContent/xmlContent.css +54 -0
  86. package/src/modules/doc/xmlContent/xmlContent.html +52 -0
  87. package/src/modules/doc/xmlContent/xmlContent.ts +792 -0
  88. package/src/modules/docHelpers/amfStyle/amfStyle.css +355 -0
  89. package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
  90. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  91. package/src/modules/docHelpers/status/status.css +22 -0
  92. package/src/modules/docUtils/searchSyncer/searchSyncer.ts +86 -0
  93. package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
  94. package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
  95. package/src/modules/docUtils/utils/utils.ts +32 -0
  96. package/src/modules/doc/container/__benchmarks__/container.benchmark.js +0 -43
  97. package/src/modules/doc/container/__mocks__/mockAvailableLanguages.js +0 -8
  98. package/src/modules/doc/container/__mocks__/mockAvailableVersions.js +0 -122
  99. package/src/modules/doc/container/__mocks__/mockContentFetchResponse.json +0 -5
  100. package/src/modules/doc/container/__mocks__/mockDocContent.js +0 -29
  101. package/src/modules/doc/container/__mocks__/mockNavigationFetchResponse.json +0 -4061
  102. package/src/modules/doc/container/__mocks__/mockPageReference.js +0 -8
  103. package/src/modules/doc/container/__mocks__/mockPdfUrl.js +0 -1
  104. package/src/modules/doc/container/__mocks__/mockSelectedLanguage.js +0 -8
  105. package/src/modules/doc/container/__mocks__/mockSelectedVersion.js +0 -8
  106. package/src/modules/doc/container/__mocks__/mockToc.js +0 -146
  107. package/src/modules/doc/container/__tests__/container.test.ts +0 -82
  108. package/src/modules/doc/container/container.css +0 -33
  109. package/src/modules/doc/container/container.html +0 -23
  110. package/src/modules/doc/container/container.stories.ts +0 -18
  111. package/src/modules/doc/container/container.ts +0 -360
  112. package/src/modules/doc/content/__tests__/content.test.ts +0 -30
  113. package/src/modules/doc/content/__tests__/mockDocContent.ts +0 -29
  114. package/src/modules/doc/content/__tests__/mockPageReference.ts +0 -8
  115. package/src/modules/doc/contentCallout/__tests__/contentCallout.test.ts +0 -80
  116. package/src/modules/doc/contentCallout/__tests__/mockProps.ts +0 -14
  117. package/src/modules/doc/contentCallout/contentCallout.stories.ts +0 -29
  118. package/src/modules/doc/nav/__tests__/mockAvailableLanguages.ts +0 -8
  119. package/src/modules/doc/nav/__tests__/mockAvailableVersions.ts +0 -122
  120. package/src/modules/doc/nav/__tests__/mockPageReference.ts +0 -8
  121. package/src/modules/doc/nav/__tests__/mockPdfUrl.ts +0 -1
  122. package/src/modules/doc/nav/__tests__/mockSelectedLanguage.ts +0 -8
  123. package/src/modules/doc/nav/__tests__/mockSelectedVersion.ts +0 -8
  124. package/src/modules/doc/nav/__tests__/mockToc.ts +0 -146
  125. package/src/modules/doc/nav/__tests__/nav.test.ts +0 -66
  126. package/src/modules/doc/prismcss/prismcss.css +0 -184
  127. package/src/modules/doc/prismjs/prismjs.html +0 -3
  128. package/src/modules/doc/prismjs/prismjs.ts +0 -1842
  129. package/src/modules/doc/search/__tests__/search.test.ts +0 -20
  130. package/src/modules/doc/search/search.html +0 -1
  131. package/src/modules/doc/search/search.ts +0 -3
  132. package/src/modules/doc/toc/__tests__/mockPageReference.ts +0 -8
  133. package/src/modules/doc/toc/__tests__/mockToc.ts +0 -146
  134. package/src/modules/doc/toc/__tests__/toc.test.ts +0 -29
  135. package/src/modules/doc/toolbar/__tests__/mockAvailableLanguages.ts +0 -8
  136. package/src/modules/doc/toolbar/__tests__/mockAvailableVersions.ts +0 -122
  137. package/src/modules/doc/toolbar/__tests__/mockPdfUrl.ts +0 -1
  138. package/src/modules/doc/toolbar/__tests__/mockSelectedLanguage.ts +0 -8
  139. package/src/modules/doc/toolbar/__tests__/mockSelectedVersion.ts +0 -8
  140. package/src/modules/doc/toolbar/__tests__/toolbar.test.ts +0 -44
@@ -0,0 +1,792 @@
1
+ /* eslint-disable @lwc/lwc/no-document-query */
2
+ import { api, track } from "lwc";
3
+ import { normalizeBoolean } from "dxUtils/normalizers";
4
+ import { FetchContent } from "./utils";
5
+ import {
6
+ CoveoAdvancedQueryXMLConfig,
7
+ DocLanguage,
8
+ DocVersion,
9
+ TreeNode,
10
+ Header,
11
+ SiderbarFooter,
12
+ HistoryState,
13
+ PageReference,
14
+ TocMap
15
+ } from "./types";
16
+ import { SearchSyncer } from "docUtils/searchSyncer";
17
+ import { LightningElementWithState } from "dxBaseElements/lightningElementWithState";
18
+ import { logCoveoPageView, oldVersionDocInfo } from "docUtils/utils";
19
+ import { Breadcrumb, DocPhaseInfo, Language } from "typings/custom";
20
+ import { track as trackGTM } from "dxUtils/analytics";
21
+ import DOMPurify from "dompurify";
22
+
23
+ // TODO: Imitating from actual implementation as doc-content use it like this. We should refactor it later.
24
+ const handleContentError = (error: any): void => console.log(error);
25
+
26
+ const PIXEL_PER_CHARACTER_MAP: { [key: string]: number } = {
27
+ default: 7.7,
28
+ "ja-jp": 12.5
29
+ };
30
+
31
+ const defaultSidebarFooter: SiderbarFooter = {
32
+ bailHref: "",
33
+ bailLabel: "",
34
+ languages: [],
35
+ language: ""
36
+ };
37
+ export default class DocXmlContent extends LightningElementWithState<{
38
+ isFetchingDocument: boolean;
39
+ isFetchingContent: boolean;
40
+ lastHighlightedSearch: string;
41
+ internalLinkClicked: boolean;
42
+ }> {
43
+ @api apiDomain = "https://developer.salesforce.com";
44
+ @api coveoOrganizationId!: string;
45
+ @api coveoPublicAccessToken!: string;
46
+ @api coveoAnalyticsToken!: string;
47
+ @api coveoSearchHub!: string;
48
+ @api hideFooter = false;
49
+
50
+ @api
51
+ get allLanguages(): Array<Language> {
52
+ return this._allLanguages;
53
+ }
54
+
55
+ set allLanguages(value: string) {
56
+ if (value) {
57
+ this._allLanguages = JSON.parse(value);
58
+ }
59
+ }
60
+
61
+ @api
62
+ get enableCoveo() {
63
+ return this._enableCoveo;
64
+ }
65
+
66
+ set enableCoveo(value) {
67
+ this._enableCoveo = normalizeBoolean(value);
68
+ }
69
+
70
+ private availableLanguages: Array<DocLanguage> = [];
71
+ @track private availableVersions: Array<DocVersion> = [];
72
+ private contentProvider?: FetchContent;
73
+ private docContent = "";
74
+ private language?: DocLanguage | null = null;
75
+ private loaded = false;
76
+ private _pageHeader?: Header;
77
+ private pdfUrl = "";
78
+ private tocMap: TocMap = {};
79
+ private sidebarContent: Array<TreeNode> | null = null;
80
+ private version: DocVersion | null = null;
81
+ private docTitle = "";
82
+ private _pathName = "";
83
+ private listenerAttached = false;
84
+ private _enableCoveo?: boolean = false;
85
+ private sidebarFooterContent: SiderbarFooter = { ...defaultSidebarFooter };
86
+ private latestVersion = false;
87
+ private previewVersion = false;
88
+
89
+ private get enableFooter(): boolean {
90
+ return !this.hideFooter;
91
+ }
92
+
93
+ private searchSyncer = new SearchSyncer({
94
+ callbacks: {
95
+ onSearchChange: (nextSearchString: string): void => {
96
+ if (nextSearchString !== this.pageReference.search) {
97
+ this.updatePageReference({
98
+ ...this.pageReference,
99
+ search: nextSearchString
100
+ });
101
+ }
102
+ if (nextSearchString !== this.state.lastHighlightedSearch) {
103
+ this.updateHighlighting(
104
+ new URLSearchParams(nextSearchString).get("q") || ""
105
+ );
106
+ }
107
+ },
108
+ onUrlChange: (nextSearchString: string): void => {
109
+ if (nextSearchString !== this.pageReference.search) {
110
+ this.updatePageReference({
111
+ ...this.pageReference,
112
+ search: nextSearchString
113
+ });
114
+ }
115
+ const nextSearchParam =
116
+ new URLSearchParams(nextSearchString).get("q") || "";
117
+ this.updateSearchInput(nextSearchParam);
118
+ if (nextSearchString !== this.state.lastHighlightedSearch) {
119
+ this.updateHighlighting(nextSearchParam);
120
+ }
121
+ }
122
+ },
123
+ eventName: "sidebarsearchchange",
124
+ historyMethod: window.history.pushState,
125
+ searchParam: "q",
126
+ shouldStopPropagation: true,
127
+ target: window
128
+ });
129
+ private _allLanguages: Array<Language> = [];
130
+
131
+ private get oldVersionInfo(): DocPhaseInfo | null {
132
+ let info = null;
133
+ if (!this.disableVersion) {
134
+ const currentGAVersion = this.versionOptions.find(
135
+ (version) => !version.url.includes(version.id)
136
+ );
137
+ if (currentGAVersion?.link?.href && this.version?.id) {
138
+ const versionNo = currentGAVersion.id;
139
+ /**
140
+ * Need to show old version doc banner only if the version is less than the current ga version
141
+ * We should not show it to the preview version whose version is more than ga
142
+ **/
143
+ try {
144
+ if (parseFloat(this.version.id) < parseFloat(versionNo)) {
145
+ info = oldVersionDocInfo(currentGAVersion.link.href);
146
+ } else if (
147
+ parseFloat(this.version.id) > parseFloat(versionNo)
148
+ ) {
149
+ this.previewVersion = true;
150
+ }
151
+ } catch (exception) {
152
+ /* Ideally this use case should not happen, but just added to not to break the page*/
153
+ console.warn(exception);
154
+ }
155
+ }
156
+ }
157
+ return info;
158
+ }
159
+
160
+ @track showVersionBanner = false;
161
+
162
+ @track private pageReference: PageReference = {};
163
+ @track breadcrumbs: Array<Breadcrumb> = [];
164
+
165
+ constructor() {
166
+ super();
167
+ this.pageReference = this.getReferenceFromUrl();
168
+ // In order to prevent dispatching unnecessary highlight changes, we
169
+ // track these items and use their previous values for comparisons in
170
+ // `renderedCallback`:
171
+ this.state = {
172
+ isFetchingContent: false,
173
+ isFetchingDocument: false,
174
+ lastHighlightedSearch: "",
175
+ internalLinkClicked: false
176
+ };
177
+ }
178
+
179
+ connectedCallback(): void {
180
+ if (!this.pageReference?.deliverable) {
181
+ window.location.href = "/docs";
182
+ return;
183
+ }
184
+ this.contentProvider = new FetchContent(
185
+ this.apiDomain,
186
+ this.allLanguages
187
+ );
188
+ this.fetchDocument().then(() => (this.loaded = true));
189
+ window.addEventListener("popstate", this.handlePopState);
190
+
191
+ this.searchSyncer.init();
192
+ }
193
+
194
+ renderedCallback(): void {
195
+ this.setState({ internalLinkClicked: true });
196
+ const urlSectionLink =
197
+ this.pageReference?.hash?.split("#").length! > 1
198
+ ? this.pageReference.hash!.split("#")[1]
199
+ : this.pageReference?.hash;
200
+
201
+ const contentEl = this.template.querySelector("doc-content");
202
+ const anchorEl =
203
+ urlSectionLink &&
204
+ (contentEl?.shadowRoot?.querySelector(`[id='${urlSectionLink}']`) ||
205
+ contentEl?.shadowRoot?.querySelector(
206
+ `[name='${urlSectionLink}']`
207
+ ));
208
+
209
+ if (anchorEl) {
210
+ anchorEl.scrollIntoView();
211
+
212
+ this.setState({ internalLinkClicked: false });
213
+ }
214
+
215
+ if (
216
+ (this.prevState.isFetchingContent &&
217
+ !this.state.isFetchingContent) ||
218
+ (this.prevState.isFetchingDocument &&
219
+ !this.state.isFetchingDocument)
220
+ ) {
221
+ const prevSearchParam =
222
+ new URLSearchParams(this.state.lastHighlightedSearch).get(
223
+ "q"
224
+ ) || "";
225
+ const nextSearchParam =
226
+ new URLSearchParams(this.pageReference.search).get("q") || "";
227
+ this.updateHighlighting(nextSearchParam);
228
+ if (prevSearchParam !== nextSearchParam) {
229
+ this.updateSearchInput(nextSearchParam);
230
+ }
231
+ }
232
+ }
233
+
234
+ disconnectedCallback(): void {
235
+ window.removeEventListener("popstate", this.handlePopState);
236
+ this.searchSyncer.dispose();
237
+ }
238
+
239
+ private get languageId(): string | undefined {
240
+ return this.language?.id.replace("-", "_");
241
+ }
242
+
243
+ private get releaseVersionId(): string | undefined {
244
+ return this.version?.id;
245
+ }
246
+
247
+ private get deliverable(): string | undefined {
248
+ return this.pageReference.deliverable;
249
+ }
250
+
251
+ private get useOldSidebar(): boolean {
252
+ // Coveo is enabled and the version is greater than 51 (within the latest 3 versions)
253
+ // TODO: we need a better fix for version number check
254
+ return !(
255
+ this.enableCoveo &&
256
+ this.coveoOrganizationId &&
257
+ this.coveoPublicAccessToken &&
258
+ (!this.version?.releaseVersion ||
259
+ (this.version?.releaseVersion &&
260
+ parseInt(
261
+ this.version.releaseVersion.replace("v", ""),
262
+ 10
263
+ ) >= 53))
264
+ );
265
+ }
266
+
267
+ private get coveoAdvancedQueryConfig(): CoveoAdvancedQueryXMLConfig {
268
+ const config: {
269
+ locale?: string;
270
+ topicid?: string;
271
+ version?: string;
272
+ } = {
273
+ locale: this.languageId,
274
+ topicid: this.deliverable
275
+ };
276
+
277
+ if (this.releaseVersionId && this.releaseVersionId !== "noversion") {
278
+ config.version = this.releaseVersionId;
279
+ }
280
+
281
+ return config;
282
+ }
283
+
284
+ private get pageHeader(): Header {
285
+ if (!this._pageHeader) {
286
+ this._pageHeader = document.querySelector("doc-header")!;
287
+ }
288
+
289
+ return this._pageHeader;
290
+ }
291
+
292
+ private get sidebarValue(): string {
293
+ if (this.pageReference?.contentDocumentId) {
294
+ const hashedUri = `${
295
+ this.pageReference.contentDocumentId
296
+ }${this.normalizeHash(this.pageReference.hash)}`;
297
+ if (hashedUri in this.tocMap) {
298
+ return hashedUri;
299
+ }
300
+
301
+ if (this.pageReference.contentDocumentId in this.tocMap) {
302
+ return this.pageReference.contentDocumentId;
303
+ }
304
+ }
305
+
306
+ return "";
307
+ }
308
+
309
+ private get disableVersion(): boolean {
310
+ return !this.availableVersions || this.availableVersions.length <= 1;
311
+ }
312
+
313
+ private get versionOptions(): Array<DocVersion> {
314
+ return this.disableVersion
315
+ ? this.availableVersions
316
+ : this.availableVersions.map((version) => ({
317
+ ...version,
318
+ link: {
319
+ href: this.pageReferenceToString({
320
+ ...this.pageReference,
321
+ docId: version.url
322
+ })
323
+ }
324
+ }));
325
+ }
326
+
327
+ private get breadcrumbPixelPerCharacter() {
328
+ return (
329
+ PIXEL_PER_CHARACTER_MAP[this.language!.id] ||
330
+ PIXEL_PER_CHARACTER_MAP.default
331
+ );
332
+ }
333
+
334
+ private get ANALYTICS_PAYLOAD() {
335
+ return {
336
+ element_title: "version picker",
337
+ content_category: "cta"
338
+ };
339
+ }
340
+
341
+ private handlePopState = (event: PopStateEvent): void =>
342
+ this.updatePageReference(this.getReferenceFromUrl(), event);
343
+
344
+ handleDismissVersionBanner() {
345
+ this.showVersionBanner = false;
346
+ }
347
+
348
+ handleSelect(event: CustomEvent<{ name: string }>): void {
349
+ event.stopPropagation();
350
+ const { name } = event.detail;
351
+
352
+ if (this.sidebarValue === name) {
353
+ return;
354
+ }
355
+
356
+ if (name) {
357
+ const hashIndex = name.indexOf("#");
358
+ const hash = hashIndex > -1 ? name.slice(hashIndex) : "";
359
+
360
+ const contentDocumentId =
361
+ hashIndex > -1 ? name.slice(0, hashIndex) : name;
362
+ this.updatePageReference({
363
+ ...this.pageReference,
364
+ contentDocumentId,
365
+ hash
366
+ });
367
+ this.updateUrl();
368
+ }
369
+ }
370
+
371
+ handleNavClick(event: CustomEvent<{ pageReference: PageReference }>): void {
372
+ event.stopPropagation();
373
+ const { pageReference } = event.detail;
374
+ this.updatePageReference(pageReference);
375
+ this.updateUrl();
376
+ }
377
+
378
+ handleLanguageChange = (event: any) => {
379
+ if (this.language && this.language.id === event.detail) {
380
+ return;
381
+ }
382
+
383
+ this.language = this.availableLanguages.find(
384
+ ({ id }) => id === event.detail
385
+ );
386
+ this.pageReference.docId = this.language!.url;
387
+
388
+ trackGTM(event.target!, "custEv_ctaLinkClick", {
389
+ click_text: event.detail,
390
+ element_title: "language selector",
391
+ click_url: `${window.location.origin}${this.pageReferenceToString(
392
+ this.pageReference
393
+ )}`,
394
+ element_type: "link",
395
+ content_category: "cta"
396
+ });
397
+
398
+ this.updateUrl();
399
+ this.fetchDocument();
400
+ };
401
+
402
+ updatePageReference(
403
+ newPageReference: PageReference,
404
+ event: PopStateEvent | undefined = undefined
405
+ ): void {
406
+ this.pageReference.hash = newPageReference.hash;
407
+ this.pageReference.search = newPageReference.search;
408
+
409
+ if (this.isSamePage(newPageReference)) {
410
+ return;
411
+ }
412
+
413
+ const isSameDocId = this.pageReference.docId === newPageReference.docId;
414
+ this.pageReference = newPageReference;
415
+
416
+ if (!isSameDocId) {
417
+ this.fetchDocument();
418
+ return;
419
+ }
420
+
421
+ this.fetchContent()
422
+ .then(() => {
423
+ this.buildBreadcrumbs();
424
+ document.body.scrollTop = event?.state?.scroll?.value || 0;
425
+ })
426
+ .catch(handleContentError);
427
+ }
428
+
429
+ private sanitizeUrlPart(part: string | undefined): string | undefined {
430
+ if (!part) {
431
+ return part;
432
+ }
433
+ return DOMPurify.sanitize(part);
434
+ }
435
+
436
+ getReferenceFromUrl(): PageReference {
437
+ const [page, docId, deliverable, contentDocumentId] =
438
+ window.location.pathname
439
+ .substr(1)
440
+ .split("/")
441
+ .map(this.sanitizeUrlPart);
442
+
443
+ const { origin: domain, hash, search } = window.location;
444
+
445
+ return {
446
+ contentDocumentId,
447
+ deliverable,
448
+ docId,
449
+ domain,
450
+ hash: this.sanitizeUrlPart(hash),
451
+ page,
452
+ search: this.sanitizeUrlPart(search)
453
+ };
454
+ }
455
+
456
+ isSamePage(reference: PageReference): boolean {
457
+ return (
458
+ this.pageReference.contentDocumentId ===
459
+ reference.contentDocumentId &&
460
+ this.pageReference.docId === reference.docId &&
461
+ this.pageReference.page === reference.page &&
462
+ this.pageReference.deliverable === reference.deliverable
463
+ );
464
+ }
465
+
466
+ async fetchDocument(): Promise<void> {
467
+ this.setState({
468
+ isFetchingDocument: true
469
+ });
470
+
471
+ const data = await this.contentProvider!.fetchDocumentData(
472
+ this.pageReference.docId!
473
+ );
474
+
475
+ // This could be a 404 scenario.
476
+ if (!data) {
477
+ this.setState({
478
+ isFetchingDocument: false
479
+ });
480
+ return;
481
+ }
482
+
483
+ this.docTitle = data.docTitle;
484
+ this.tocMap = data.tocMap;
485
+ this.sidebarContent = data.toc;
486
+ this.version = data.version;
487
+ this.language = data.language;
488
+ this.availableLanguages = data.availableLanguages;
489
+ this.availableVersions = data.availableVersions;
490
+ this.pdfUrl = data.pdfUrl;
491
+
492
+ this.updateHeaderAndSidebarFooter();
493
+
494
+ this.buildBreadcrumbs();
495
+
496
+ if (this.pageReference.deliverable !== data.deliverable) {
497
+ this.pageReference.deliverable = data.deliverable;
498
+ this.updateUrl(HistoryState.REPLACE_STATE);
499
+ }
500
+
501
+ if (this.oldVersionInfo) {
502
+ this.showVersionBanner = true;
503
+ } else {
504
+ this.latestVersion = true;
505
+ }
506
+
507
+ if (
508
+ this.pageReference?.contentDocumentId?.replace(/\.htm$/, "") !==
509
+ data.id
510
+ ) {
511
+ try {
512
+ await this.fetchContent();
513
+ this.setState({
514
+ isFetchingDocument: false
515
+ });
516
+ return;
517
+ } catch (error) {
518
+ this.pageReference.contentDocumentId = `${data.id}.htm`;
519
+ this.pageReference.hash = "";
520
+ this.pageReference.search = "";
521
+ this.updateUrl(HistoryState.REPLACE_STATE);
522
+ }
523
+ }
524
+
525
+ this.docContent = data.content;
526
+ this.addMetatags();
527
+ this.setState({
528
+ isFetchingDocument: false
529
+ });
530
+ }
531
+
532
+ async fetchContent(): Promise<void> {
533
+ this.setState({
534
+ isFetchingContent: true
535
+ });
536
+ const data = await this.contentProvider!.fetchContent(
537
+ this.pageReference.deliverable!,
538
+ this.pageReference.contentDocumentId!,
539
+ {
540
+ language: this.language!.id,
541
+ version: this.version!.id
542
+ }
543
+ );
544
+
545
+ if (data) {
546
+ this.docContent = data.content;
547
+ this.addMetatags();
548
+
549
+ if (!this.pageReference.hash) {
550
+ document.body.scrollIntoView();
551
+ }
552
+ }
553
+ this.setState({
554
+ isFetchingContent: false
555
+ });
556
+ }
557
+
558
+ updateHeaderAndSidebarFooter(): void {
559
+ if (!this.pageHeader) {
560
+ return;
561
+ }
562
+
563
+ if (this.docTitle) {
564
+ this.pageHeader.subtitle = this.docTitle;
565
+ }
566
+
567
+ if (this.pdfUrl) {
568
+ this.sidebarFooterContent.bailHref = this.pdfUrl;
569
+ this.sidebarFooterContent.bailLabel = "PDF";
570
+ }
571
+
572
+ this.sidebarFooterContent.languages = this.availableLanguages;
573
+ this.sidebarFooterContent.language = this.language?.id;
574
+
575
+ if (this.pageReference) {
576
+ const { docId, deliverable, page } = this.pageReference;
577
+ this.pageHeader.headerHref = `/${page}/${docId}/${deliverable}/`;
578
+ }
579
+ }
580
+
581
+ updateUrl(method = HistoryState.PUSH_STATE): void {
582
+ logCoveoPageView(this.coveoOrganizationId, this.coveoAnalyticsToken);
583
+ window.history[method](
584
+ {},
585
+ "docs",
586
+ this.pageReferenceToString(this.pageReference)
587
+ );
588
+ }
589
+
590
+ private updateHighlighting(searchParam: string): void {
591
+ this.dispatchHighlightChange(searchParam);
592
+ this.setState({
593
+ lastHighlightedSearch: this.pageReference.search
594
+ });
595
+ }
596
+
597
+ private updateSearchInput(searchParam: string): void {
598
+ (this.refs.docContentLayout as any)?.setSidebarInputValue(searchParam);
599
+ }
600
+
601
+ private pageReferenceToString(reference: PageReference): string {
602
+ const { page, docId, deliverable, contentDocumentId, hash, search } =
603
+ reference;
604
+ return `/${page}/${docId}/${deliverable}/${contentDocumentId}${this.normalizeSearch(
605
+ search!
606
+ )}${this.normalizeHash(hash)}`;
607
+ }
608
+
609
+ private normalizeUrlPart(
610
+ part: string | undefined,
611
+ sentinel: string
612
+ ): string {
613
+ return (
614
+ (part &&
615
+ (part.startsWith(sentinel!) ? part : `${sentinel}${part}`)) ||
616
+ ""
617
+ );
618
+ }
619
+
620
+ private normalizeSearch(search: string): string {
621
+ return this.normalizeUrlPart(search, "?");
622
+ }
623
+
624
+ private normalizeHash(hash?: string): string {
625
+ return this.normalizeUrlPart(hash, "#");
626
+ }
627
+
628
+ private getComposedTitle(
629
+ topicTitle: string | null | undefined,
630
+ docTitle: string | undefined
631
+ ): string {
632
+ // map to avoid duplicates
633
+ const titleMap: { [key: string]: any } = {};
634
+ if (topicTitle) {
635
+ // sometimes the h1 tag text (which is docSubTitle) contains text with new line character. For e.g, "Bulk API 2.0 Older\n Documentation",
636
+ // here it contains \n in the text context which needs to be removed
637
+ // also, there are multiple spaces in between the text, which needs to be replaced with a single space
638
+ const docTopicTitle = topicTitle
639
+ .replace(/[\t\r\n]/g, " ")
640
+ .replace(/ +/g, " ")
641
+ .trim();
642
+ titleMap[docTopicTitle] = true;
643
+ }
644
+
645
+ if (docTitle) {
646
+ titleMap[docTitle] = true;
647
+ }
648
+
649
+ titleMap["Salesforce Developers"] = true;
650
+
651
+ return Object.keys(titleMap).join(" | ");
652
+ }
653
+
654
+ private dispatchHighlightChange(term: string): void {
655
+ this.dispatchEvent(
656
+ new CustomEvent("highlightedtermchange", {
657
+ detail: term,
658
+ bubbles: true,
659
+ composed: true
660
+ })
661
+ );
662
+ }
663
+
664
+ get showBreadcrumbs(): boolean {
665
+ return this.breadcrumbs && this.breadcrumbs.length > 1;
666
+ }
667
+
668
+ private buildBreadcrumbs(): void {
669
+ const { contentDocumentId } = this.pageReference;
670
+ if (!contentDocumentId) {
671
+ return;
672
+ }
673
+
674
+ const currentNode = this.tocMap[contentDocumentId];
675
+
676
+ if (currentNode?.parent) {
677
+ this.breadcrumbs = this.nodeToBreadcrumb(currentNode);
678
+ } else {
679
+ this.breadcrumbs = [];
680
+ }
681
+ }
682
+
683
+ private nodeToBreadcrumb(node: TreeNode): Breadcrumb[] {
684
+ const item = {
685
+ href: this.pageReferenceToString({
686
+ ...this.pageReference,
687
+ contentDocumentId: node.name
688
+ }),
689
+ label: node.label
690
+ };
691
+
692
+ if (node.parent) {
693
+ return [...this.nodeToBreadcrumb(node.parent), item];
694
+ }
695
+
696
+ return [item];
697
+ }
698
+
699
+ // This method take docId and drops the version from the docId.
700
+ // Example:
701
+ // Takes input string: docId = "atlas.en-us.238.0.b2b_b2c_comm_dev.meta"
702
+ // Output string: filteredDocId = "atlas.en-us.b2b_b2c_comm_dev.meta"
703
+ dropVersionFromDocId(docId: string): string {
704
+ if (!this.version?.id) {
705
+ return docId;
706
+ }
707
+
708
+ const curVersion = this.version.id + ".";
709
+ const filteredDocId = docId.replace(curVersion, "");
710
+ return filteredDocId;
711
+ }
712
+
713
+ addMetatags(): void {
714
+ const div = document.createElement("div");
715
+ div.innerHTML = DOMPurify.sanitize(this.docContent);
716
+ const docDescription = div.querySelector(".shortdesc")?.textContent;
717
+ const topicTitle = div.querySelector("h1")?.textContent;
718
+
719
+ const title = document.querySelector("title");
720
+ const composedTitle = this.getComposedTitle(topicTitle, this.docTitle);
721
+
722
+ if (title && title.textContent) {
723
+ title.textContent = composedTitle;
724
+ }
725
+ const metatitle = document.querySelector('meta[name="title"]');
726
+ if (metatitle) {
727
+ metatitle.setAttribute("content", composedTitle);
728
+ }
729
+
730
+ if (docDescription) {
731
+ const metadescription = document.querySelector(
732
+ 'meta[name="description"]'
733
+ );
734
+ if (metadescription) {
735
+ metadescription.setAttribute("content", docDescription);
736
+ }
737
+ }
738
+
739
+ if (this.pageReference) {
740
+ const metadescription = document.querySelector(
741
+ 'link[rel="canonical"]'
742
+ );
743
+ if (metadescription) {
744
+ const copyPageReference = { ...this.pageReference };
745
+ copyPageReference.docId = copyPageReference.docId
746
+ ? this.dropVersionFromDocId(copyPageReference.docId)
747
+ : copyPageReference.docId;
748
+ metadescription.setAttribute(
749
+ "href",
750
+ window.location.protocol +
751
+ "//" +
752
+ window.location.host +
753
+ this.pageReferenceToString(copyPageReference)
754
+ );
755
+ }
756
+ }
757
+
758
+ this.addNoIndexMetaForOlderDocVersions();
759
+ }
760
+
761
+ /**
762
+ * Method adds noindex, follow meta tag to the older Couch DB doc pages.
763
+ * Fixes W-12547462.
764
+ */
765
+ private addNoIndexMetaForOlderDocVersions() {
766
+ // eslint-disable-next-line @lwc/lwc/no-document-query
767
+ const headTag = document.getElementsByTagName("head");
768
+ // this checks if the selected version is not the latest version,
769
+ // then it adds the noindex, follow meta tag to the older version pages.
770
+ const versionId = this.version!.id;
771
+ const docId = this.pageReference.docId;
772
+
773
+ // SEO fix:
774
+ // Doc id without version id is always considered latest and should be used for SEO.
775
+ // Condition is to find a docId which includes version id,
776
+ // these docs are always considered as old and should not be indexed including the preview docs.
777
+ if (
778
+ headTag.length &&
779
+ docId?.includes(versionId) &&
780
+ !document.querySelector('meta[name="robots"]')
781
+ ) {
782
+ const robotsMeta = document.createElement("meta");
783
+ robotsMeta.setAttribute("name", "robots");
784
+ robotsMeta.setAttribute("content", "noindex, follow");
785
+ headTag[0].appendChild(robotsMeta);
786
+ }
787
+ }
788
+
789
+ private get showVersionPicker(): boolean {
790
+ return !this.disableVersion;
791
+ }
792
+ }