@salesforcedevs/docs-components 0.56.2-comp-flex-ref-2 → 0.56.2-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 (77) hide show
  1. package/lwc.config.json +10 -2
  2. package/package.json +18 -12
  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 +34 -21
  6. package/src/modules/doc/amfReference/amfReference.ts +377 -97
  7. package/src/modules/doc/amfReference/types.ts +5 -12
  8. package/src/modules/doc/amfTopic/amfTopic.css +20 -0
  9. package/src/modules/doc/amfTopic/amfTopic.ts +35 -18
  10. package/src/modules/doc/amfTopic/types.ts +15 -13
  11. package/src/modules/doc/amfTopic/utils.ts +12 -6
  12. package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +2 -1
  13. package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +1 -1
  14. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +22 -0
  15. package/src/modules/doc/breadcrumbs/breadcrumbs.css +9 -1
  16. package/src/modules/doc/breadcrumbs/breadcrumbs.html +44 -34
  17. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +62 -23
  18. package/src/modules/doc/componentPlayground/componentPlayground.css +22 -0
  19. package/src/modules/doc/componentPlayground/componentPlayground.html +20 -0
  20. package/src/modules/doc/componentPlayground/componentPlayground.ts +42 -0
  21. package/src/modules/doc/content/content.css +70 -76
  22. package/src/modules/doc/content/content.html +1 -0
  23. package/src/modules/doc/content/content.ts +26 -47
  24. package/src/modules/doc/contentCallout/contentCallout.css +15 -7
  25. package/src/modules/doc/contentCallout/contentCallout.html +13 -4
  26. package/src/modules/doc/contentCallout/contentCallout.ts +14 -2
  27. package/src/modules/doc/contentLayout/contentLayout.css +1 -100
  28. package/src/modules/doc/contentLayout/contentLayout.html +30 -17
  29. package/src/modules/doc/contentLayout/contentLayout.ts +314 -70
  30. package/src/modules/doc/doDont/doDont.css +47 -0
  31. package/src/modules/doc/doDont/doDont.html +27 -0
  32. package/src/modules/doc/doDont/doDont.ts +17 -0
  33. package/src/modules/doc/header/header.css +65 -36
  34. package/src/modules/doc/header/header.html +41 -139
  35. package/src/modules/doc/header/header.ts +54 -76
  36. package/src/modules/doc/heading/heading.css +16 -37
  37. package/src/modules/doc/heading/heading.html +4 -4
  38. package/src/modules/doc/heading/heading.ts +12 -10
  39. package/src/modules/doc/headingAnchor/headingAnchor.css +2 -2
  40. package/src/modules/doc/headingAnchor/headingAnchor.ts +2 -2
  41. package/src/modules/doc/headingContent/headingContent.css +6 -8
  42. package/src/modules/doc/headingContent/headingContent.html +9 -15
  43. package/src/modules/doc/headingContent/headingContent.ts +2 -14
  44. package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
  45. package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +68 -0
  46. package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +256 -0
  47. package/src/modules/doc/overview/overview.css +40 -0
  48. package/src/modules/doc/overview/overview.html +34 -0
  49. package/src/modules/doc/overview/overview.ts +12 -0
  50. package/src/modules/doc/phase/phase.css +18 -3
  51. package/src/modules/doc/phase/phase.html +15 -3
  52. package/src/modules/doc/phase/phase.ts +44 -8
  53. package/src/modules/doc/specificationContent/specificationContent.css +36 -0
  54. package/src/modules/doc/specificationContent/specificationContent.html +167 -0
  55. package/src/modules/doc/specificationContent/specificationContent.ts +127 -0
  56. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  57. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  58. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  59. package/src/modules/doc/toc/toc.ts +1 -1
  60. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  61. package/src/modules/doc/versionPicker/versionPicker.html +38 -0
  62. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  63. package/src/modules/doc/xmlContent/types.ts +12 -3
  64. package/src/modules/doc/xmlContent/utils.ts +17 -12
  65. package/src/modules/doc/xmlContent/xmlContent.css +32 -3
  66. package/src/modules/doc/xmlContent/xmlContent.html +41 -15
  67. package/src/modules/doc/xmlContent/xmlContent.ts +310 -96
  68. package/src/modules/docHelpers/amfStyle/amfStyle.css +10 -45
  69. package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
  70. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  71. package/src/modules/docHelpers/status/status.css +1 -1
  72. package/src/modules/docUtils/{SearchSyncer/SearchSyncer.ts → searchSyncer/searchSyncer.ts} +1 -0
  73. package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
  74. package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
  75. package/src/modules/docUtils/utils/utils.ts +32 -0
  76. package/src/modules/docBaseElements/lightningElementWithState/lightningElementWithState.ts +0 -93
  77. 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,25 +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";
16
- import { DEFAULT_LANGUAGES } from "dxUtils/constants";
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";
17
23
 
18
24
  // TODO: Imitating from actual implementation as doc-content use it like this. We should refactor it later.
19
- const handleContentError = (error): void => console.log(error);
20
-
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
+ };
21
38
  export default class DocXmlContent extends LightningElementWithState<{
22
39
  isFetchingDocument: boolean;
23
40
  isFetchingContent: boolean;
24
41
  lastHighlightedSearch: string;
42
+ internalLinkClicked: boolean;
25
43
  }> {
26
44
  @api apiDomain = "https://developer.salesforce.com";
27
45
  @api coveoOrganizationId!: string;
28
46
  @api coveoPublicAccessToken!: string;
47
+ @api coveoAnalyticsToken!: string;
48
+ @api coveoSearchHub!: string;
49
+ @api hideFooter = false;
29
50
 
30
51
  @api
31
52
  get allLanguages(): Array<Language> {
@@ -48,21 +69,28 @@ export default class DocXmlContent extends LightningElementWithState<{
48
69
  }
49
70
 
50
71
  private availableLanguages: Array<DocLanguage> = [];
51
- private availableVersions: Array<DocVersion> = [];
52
- private contentProvider: FetchContent;
72
+ @track private availableVersions: Array<DocVersion> = [];
73
+ private contentProvider?: FetchContent;
53
74
  private docContent = "";
54
- private language: DocLanguage = null;
55
- private loaded = false;
75
+ private language?: DocLanguage | null = null;
76
+ private displayContent = false;
77
+ private display404 = false;
78
+ private _pageHeader?: Header;
56
79
  private pdfUrl = "";
57
- private tocMap = null;
58
- private sidebarContent: Array<TreeNode> = null;
59
- private version: DocVersion = null;
80
+ private tocMap: TocMap = {};
81
+ private sidebarContent: Array<TreeNode> | null = null;
82
+ private version: DocVersion | null = null;
60
83
  private docTitle = "";
61
- private analyticsEvent = "custEv_ctaLinkClick";
62
84
  private _pathName = "";
63
- private _pageHeader?: Header;
64
85
  private listenerAttached = false;
65
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
+ }
66
94
 
67
95
  private searchSyncer = new SearchSyncer({
68
96
  callbacks: {
@@ -100,9 +128,41 @@ export default class DocXmlContent extends LightningElementWithState<{
100
128
  shouldStopPropagation: true,
101
129
  target: window
102
130
  });
103
- private _allLanguages: Array<Language> = DEFAULT_LANGUAGES;
131
+ private _allLanguages: Array<Language> = [];
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;
104
163
 
105
164
  @track private pageReference: PageReference = {};
165
+ @track breadcrumbs: Array<Breadcrumb> = [];
106
166
 
107
167
  constructor() {
108
168
  super();
@@ -127,7 +187,13 @@ export default class DocXmlContent extends LightningElementWithState<{
127
187
  this.apiDomain,
128
188
  this.allLanguages
129
189
  );
130
- 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
+ });
131
197
  window.addEventListener("popstate", this.handlePopState);
132
198
 
133
199
  this.searchSyncer.init();
@@ -136,8 +202,8 @@ export default class DocXmlContent extends LightningElementWithState<{
136
202
  renderedCallback(): void {
137
203
  this.setState({ internalLinkClicked: true });
138
204
  const urlSectionLink =
139
- this.pageReference?.hash?.split("#").length > 1
140
- ? this.pageReference.hash.split("#")[1]
205
+ this.pageReference?.hash?.split("#").length! > 1
206
+ ? this.pageReference.hash!.split("#")[1]
141
207
  : this.pageReference?.hash;
142
208
 
143
209
  const contentEl = this.template.querySelector("doc-content");
@@ -150,6 +216,7 @@ export default class DocXmlContent extends LightningElementWithState<{
150
216
 
151
217
  if (anchorEl) {
152
218
  anchorEl.scrollIntoView();
219
+
153
220
  this.setState({ internalLinkClicked: false });
154
221
  }
155
222
 
@@ -175,24 +242,17 @@ export default class DocXmlContent extends LightningElementWithState<{
175
242
  disconnectedCallback(): void {
176
243
  window.removeEventListener("popstate", this.handlePopState);
177
244
  this.searchSyncer.dispose();
178
- if (this.listenerAttached) {
179
- this.pageHeader.removeEventListener(
180
- "langchange",
181
- this.handleLanguageChange
182
- );
183
- this.listenerAttached = false;
184
- }
185
245
  }
186
246
 
187
- private get languageId(): string {
188
- return this.language.id.replace("-", "_");
247
+ private get languageId(): string | undefined {
248
+ return this.language?.id.replace("-", "_");
189
249
  }
190
250
 
191
- private get releaseVersionId(): string {
192
- return this.version.id;
251
+ private get releaseVersionId(): string | undefined {
252
+ return this.version?.id;
193
253
  }
194
254
 
195
- private get deliverable(): string {
255
+ private get deliverable(): string | undefined {
196
256
  return this.pageReference.deliverable;
197
257
  }
198
258
 
@@ -213,7 +273,11 @@ export default class DocXmlContent extends LightningElementWithState<{
213
273
  }
214
274
 
215
275
  private get coveoAdvancedQueryConfig(): CoveoAdvancedQueryXMLConfig {
216
- const config: { locale: string; topicid: string; version?: string } = {
276
+ const config: {
277
+ locale?: string;
278
+ topicid?: string;
279
+ version?: string;
280
+ } = {
217
281
  locale: this.languageId,
218
282
  topicid: this.deliverable
219
283
  };
@@ -227,7 +291,7 @@ export default class DocXmlContent extends LightningElementWithState<{
227
291
 
228
292
  private get pageHeader(): Header {
229
293
  if (!this._pageHeader) {
230
- this._pageHeader = document.querySelector("doc-header");
294
+ this._pageHeader = document.querySelector("doc-header")!;
231
295
  }
232
296
 
233
297
  return this._pageHeader;
@@ -268,8 +332,26 @@ export default class DocXmlContent extends LightningElementWithState<{
268
332
  }));
269
333
  }
270
334
 
271
- private handlePopState = (): void =>
272
- 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
+ }
273
355
 
274
356
  handleSelect(event: CustomEvent<{ name: string }>): void {
275
357
  event.stopPropagation();
@@ -281,15 +363,15 @@ export default class DocXmlContent extends LightningElementWithState<{
281
363
 
282
364
  if (name) {
283
365
  const hashIndex = name.indexOf("#");
284
- this.pageReference.hash =
285
- hashIndex > -1 ? name.slice(hashIndex) : "";
286
-
287
- const contentId = hashIndex > -1 ? name.slice(0, hashIndex) : name;
288
- if (this.pageReference.contentDocumentId !== contentId) {
289
- this.pageReference.contentDocumentId = contentId;
290
- this.fetchContent().catch(handleContentError);
291
- }
292
-
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
+ });
293
375
  this.updateUrl();
294
376
  }
295
377
  }
@@ -301,7 +383,7 @@ export default class DocXmlContent extends LightningElementWithState<{
301
383
  this.updateUrl();
302
384
  }
303
385
 
304
- handleLanguageChange = (event: CustomEvent<string>): Promise<void> => {
386
+ handleLanguageChange = (event: any) => {
305
387
  if (this.language && this.language.id === event.detail) {
306
388
  return;
307
389
  }
@@ -309,12 +391,26 @@ export default class DocXmlContent extends LightningElementWithState<{
309
391
  this.language = this.availableLanguages.find(
310
392
  ({ id }) => id === event.detail
311
393
  );
312
- 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
+
313
406
  this.updateUrl();
314
407
  this.fetchDocument();
315
408
  };
316
409
 
317
- updatePageReference(newPageReference: PageReference): void {
410
+ updatePageReference(
411
+ newPageReference: PageReference,
412
+ event: PopStateEvent | undefined = undefined
413
+ ): void {
318
414
  this.pageReference.hash = newPageReference.hash;
319
415
  this.pageReference.search = newPageReference.search;
320
416
 
@@ -322,20 +418,35 @@ export default class DocXmlContent extends LightningElementWithState<{
322
418
  return;
323
419
  }
324
420
 
325
- const isSameDocId = this.pageReference.docId !== newPageReference.docId;
421
+ const isSameDocId = this.pageReference.docId === newPageReference.docId;
326
422
  this.pageReference = newPageReference;
327
423
 
328
- if (isSameDocId) {
424
+ if (!isSameDocId) {
329
425
  this.fetchDocument();
330
426
  return;
331
427
  }
332
428
 
333
- 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);
334
442
  }
335
443
 
336
444
  getReferenceFromUrl(): PageReference {
337
445
  const [page, docId, deliverable, contentDocumentId] =
338
- window.location.pathname.substr(1).split("/");
446
+ window.location.pathname
447
+ .substr(1)
448
+ .split("/")
449
+ .map(this.sanitizeUrlPart);
339
450
 
340
451
  const { origin: domain, hash, search } = window.location;
341
452
 
@@ -344,9 +455,9 @@ export default class DocXmlContent extends LightningElementWithState<{
344
455
  deliverable,
345
456
  docId,
346
457
  domain,
347
- hash,
458
+ hash: this.sanitizeUrlPart(hash),
348
459
  page,
349
- search
460
+ search: this.sanitizeUrlPart(search)
350
461
  };
351
462
  }
352
463
 
@@ -360,12 +471,13 @@ export default class DocXmlContent extends LightningElementWithState<{
360
471
  );
361
472
  }
362
473
 
363
- async fetchDocument(): Promise<void> {
474
+ async fetchDocument(): Promise<string> {
364
475
  this.setState({
365
476
  isFetchingDocument: true
366
477
  });
367
- const data = await this.contentProvider.fetchDocumentData(
368
- this.pageReference.docId
478
+
479
+ const data = await this.contentProvider!.fetchDocumentData(
480
+ this.pageReference.docId!
369
481
  );
370
482
 
371
483
  // This could be a 404 scenario.
@@ -373,7 +485,7 @@ export default class DocXmlContent extends LightningElementWithState<{
373
485
  this.setState({
374
486
  isFetchingDocument: false
375
487
  });
376
- return;
488
+ return "";
377
489
  }
378
490
 
379
491
  this.docTitle = data.docTitle;
@@ -385,23 +497,31 @@ export default class DocXmlContent extends LightningElementWithState<{
385
497
  this.availableVersions = data.availableVersions;
386
498
  this.pdfUrl = data.pdfUrl;
387
499
 
388
- this.updateHeader();
500
+ this.updateHeaderAndSidebarFooter();
501
+
502
+ this.buildBreadcrumbs();
389
503
 
390
504
  if (this.pageReference.deliverable !== data.deliverable) {
391
505
  this.pageReference.deliverable = data.deliverable;
392
506
  this.updateUrl(HistoryState.REPLACE_STATE);
393
507
  }
394
508
 
509
+ if (this.oldVersionInfo) {
510
+ this.showVersionBanner = true;
511
+ } else {
512
+ this.latestVersion = true;
513
+ }
514
+
395
515
  if (
396
516
  this.pageReference?.contentDocumentId?.replace(/\.htm$/, "") !==
397
517
  data.id
398
518
  ) {
399
519
  try {
400
- await this.fetchContent();
520
+ const moreData = await this.fetchContent();
401
521
  this.setState({
402
522
  isFetchingDocument: false
403
523
  });
404
- return;
524
+ return moreData.content;
405
525
  } catch (error) {
406
526
  this.pageReference.contentDocumentId = `${data.id}.htm`;
407
527
  this.pageReference.hash = "";
@@ -415,18 +535,19 @@ export default class DocXmlContent extends LightningElementWithState<{
415
535
  this.setState({
416
536
  isFetchingDocument: false
417
537
  });
538
+ return data.content;
418
539
  }
419
540
 
420
- async fetchContent(): Promise<void> {
541
+ async fetchContent(): Promise<ContentData> {
421
542
  this.setState({
422
543
  isFetchingContent: true
423
544
  });
424
- const data = await this.contentProvider.fetchContent(
425
- this.pageReference.deliverable,
426
- this.pageReference.contentDocumentId,
545
+ const data = await this.contentProvider!.fetchContent(
546
+ this.pageReference.deliverable!,
547
+ this.pageReference.contentDocumentId!,
427
548
  {
428
- language: this.language.id,
429
- version: this.version.id
549
+ language: this.language!.id,
550
+ version: this.version!.id
430
551
  }
431
552
  );
432
553
 
@@ -435,19 +556,16 @@ export default class DocXmlContent extends LightningElementWithState<{
435
556
  this.addMetatags();
436
557
 
437
558
  if (!this.pageReference.hash) {
438
- document.querySelector("main")?.scrollIntoView({
439
- behavior: "smooth",
440
- block: "start",
441
- inline: "nearest"
442
- });
559
+ document.body.scrollIntoView();
443
560
  }
444
561
  }
445
562
  this.setState({
446
563
  isFetchingContent: false
447
564
  });
565
+ return data;
448
566
  }
449
567
 
450
- updateHeader(): void {
568
+ updateHeaderAndSidebarFooter(): void {
451
569
  if (!this.pageHeader) {
452
570
  return;
453
571
  }
@@ -457,20 +575,12 @@ export default class DocXmlContent extends LightningElementWithState<{
457
575
  }
458
576
 
459
577
  if (this.pdfUrl) {
460
- this.pageHeader.bailHref = this.pdfUrl;
461
- this.pageHeader.bailLabel = "PDF";
462
- }
463
-
464
- if (!this.listenerAttached) {
465
- this.pageHeader.addEventListener(
466
- "langchange",
467
- this.handleLanguageChange
468
- );
469
- this.listenerAttached = true;
578
+ this.sidebarFooterContent.bailHref = this.pdfUrl;
579
+ this.sidebarFooterContent.bailLabel = "PDF";
470
580
  }
471
581
 
472
- this.pageHeader.languages = this.availableLanguages;
473
- this.pageHeader.language = this.language?.id;
582
+ this.sidebarFooterContent.languages = this.availableLanguages;
583
+ this.sidebarFooterContent.language = this.language?.id;
474
584
 
475
585
  if (this.pageReference) {
476
586
  const { docId, deliverable, page } = this.pageReference;
@@ -479,6 +589,7 @@ export default class DocXmlContent extends LightningElementWithState<{
479
589
  }
480
590
 
481
591
  updateUrl(method = HistoryState.PUSH_STATE): void {
592
+ logCoveoPageView(this.coveoOrganizationId, this.coveoAnalyticsToken);
482
593
  window.history[method](
483
594
  {},
484
595
  "docs",
@@ -494,23 +605,24 @@ export default class DocXmlContent extends LightningElementWithState<{
494
605
  }
495
606
 
496
607
  private updateSearchInput(searchParam: string): void {
497
- this.template
498
- .querySelector("doc-content-layout")
499
- ?.setSidebarInputValue(searchParam);
608
+ (this.refs.docContentLayout as any)?.setSidebarInputValue(searchParam);
500
609
  }
501
610
 
502
611
  private pageReferenceToString(reference: PageReference): string {
503
612
  const { page, docId, deliverable, contentDocumentId, hash, search } =
504
613
  reference;
505
614
  return `/${page}/${docId}/${deliverable}/${contentDocumentId}${this.normalizeSearch(
506
- search
615
+ search!
507
616
  )}${this.normalizeHash(hash)}`;
508
617
  }
509
618
 
510
- private normalizeUrlPart(part: string, sentinel: string): string {
619
+ private normalizeUrlPart(
620
+ part: string | undefined,
621
+ sentinel: string
622
+ ): string {
511
623
  return (
512
624
  (part &&
513
- (part.startsWith(sentinel) ? part : `${sentinel}${part}`)) ||
625
+ (part.startsWith(sentinel!) ? part : `${sentinel}${part}`)) ||
514
626
  ""
515
627
  );
516
628
  }
@@ -519,16 +631,16 @@ export default class DocXmlContent extends LightningElementWithState<{
519
631
  return this.normalizeUrlPart(search, "?");
520
632
  }
521
633
 
522
- private normalizeHash(hash: string): string {
634
+ private normalizeHash(hash?: string): string {
523
635
  return this.normalizeUrlPart(hash, "#");
524
636
  }
525
637
 
526
638
  private getComposedTitle(
527
- topicTitle: string | undefined,
639
+ topicTitle: string | null | undefined,
528
640
  docTitle: string | undefined
529
641
  ): string {
530
642
  // map to avoid duplicates
531
- const titleMap = {};
643
+ const titleMap: { [key: string]: any } = {};
532
644
  if (topicTitle) {
533
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",
534
646
  // here it contains \n in the text context which needs to be removed
@@ -559,16 +671,65 @@ export default class DocXmlContent extends LightningElementWithState<{
559
671
  );
560
672
  }
561
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
+
562
723
  addMetatags(): void {
563
724
  const div = document.createElement("div");
564
- div.innerHTML = this.docContent;
725
+ div.innerHTML = DOMPurify.sanitize(this.docContent);
565
726
  const docDescription = div.querySelector(".shortdesc")?.textContent;
566
727
  const topicTitle = div.querySelector("h1")?.textContent;
567
728
 
568
729
  const title = document.querySelector("title");
569
730
  const composedTitle = this.getComposedTitle(topicTitle, this.docTitle);
570
731
 
571
- if (title) {
732
+ if (title && title.textContent) {
572
733
  title.textContent = composedTitle;
573
734
  }
574
735
  const metatitle = document.querySelector('meta[name="title"]');
@@ -584,5 +745,58 @@ export default class DocXmlContent extends LightningElementWithState<{
584
745
  metadescription.setAttribute("content", docDescription);
585
746
  }
586
747
  }
748
+
749
+ if (this.pageReference) {
750
+ const metadescription = document.querySelector(
751
+ 'link[rel="canonical"]'
752
+ );
753
+ if (metadescription) {
754
+ const copyPageReference = { ...this.pageReference };
755
+ copyPageReference.docId = copyPageReference.docId
756
+ ? this.dropVersionFromDocId(copyPageReference.docId)
757
+ : copyPageReference.docId;
758
+ metadescription.setAttribute(
759
+ "href",
760
+ window.location.protocol +
761
+ "//" +
762
+ window.location.host +
763
+ this.pageReferenceToString(copyPageReference)
764
+ );
765
+ }
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;
587
801
  }
588
802
  }