@salesforcedevs/docs-components 0.56.2 → 0.56.3-example

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