@salesforcedevs/docs-components 0.56.2-seo-test17 → 0.56.2-snyk

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 (76) hide show
  1. package/lwc.config.json +10 -2
  2. package/package.json +13 -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 +257 -72
  7. package/src/modules/doc/amfReference/types.ts +4 -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 +15 -0
  20. package/src/modules/doc/componentPlayground/componentPlayground.ts +20 -0
  21. package/src/modules/doc/content/content.css +70 -76
  22. package/src/modules/doc/content/content.ts +20 -16
  23. package/src/modules/doc/contentCallout/contentCallout.css +15 -7
  24. package/src/modules/doc/contentCallout/contentCallout.html +13 -4
  25. package/src/modules/doc/contentCallout/contentCallout.ts +14 -2
  26. package/src/modules/doc/contentLayout/contentLayout.css +1 -100
  27. package/src/modules/doc/contentLayout/contentLayout.html +26 -17
  28. package/src/modules/doc/contentLayout/contentLayout.ts +312 -70
  29. package/src/modules/doc/doDont/doDont.css +47 -0
  30. package/src/modules/doc/doDont/doDont.html +27 -0
  31. package/src/modules/doc/doDont/doDont.ts +17 -0
  32. package/src/modules/doc/header/header.css +65 -36
  33. package/src/modules/doc/header/header.html +40 -139
  34. package/src/modules/doc/header/header.ts +32 -77
  35. package/src/modules/doc/heading/heading.css +16 -37
  36. package/src/modules/doc/heading/heading.html +4 -4
  37. package/src/modules/doc/heading/heading.ts +12 -10
  38. package/src/modules/doc/headingAnchor/headingAnchor.css +2 -2
  39. package/src/modules/doc/headingAnchor/headingAnchor.ts +2 -2
  40. package/src/modules/doc/headingContent/headingContent.css +6 -8
  41. package/src/modules/doc/headingContent/headingContent.html +9 -15
  42. package/src/modules/doc/headingContent/headingContent.ts +2 -14
  43. package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
  44. package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +64 -0
  45. package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +168 -0
  46. package/src/modules/doc/overview/overview.css +40 -0
  47. package/src/modules/doc/overview/overview.html +34 -0
  48. package/src/modules/doc/overview/overview.ts +12 -0
  49. package/src/modules/doc/phase/phase.css +18 -3
  50. package/src/modules/doc/phase/phase.html +15 -3
  51. package/src/modules/doc/phase/phase.ts +44 -8
  52. package/src/modules/doc/specificationContent/specificationContent.css +31 -0
  53. package/src/modules/doc/specificationContent/specificationContent.html +164 -0
  54. package/src/modules/doc/specificationContent/specificationContent.ts +121 -0
  55. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  56. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  57. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  58. package/src/modules/doc/toc/toc.ts +1 -1
  59. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  60. package/src/modules/doc/versionPicker/versionPicker.html +38 -0
  61. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  62. package/src/modules/doc/xmlContent/types.ts +12 -3
  63. package/src/modules/doc/xmlContent/utils.ts +17 -12
  64. package/src/modules/doc/xmlContent/xmlContent.css +32 -3
  65. package/src/modules/doc/xmlContent/xmlContent.html +33 -15
  66. package/src/modules/doc/xmlContent/xmlContent.ts +278 -88
  67. package/src/modules/docHelpers/amfStyle/amfStyle.css +10 -45
  68. package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
  69. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  70. package/src/modules/docHelpers/status/status.css +1 -1
  71. package/src/modules/docUtils/{SearchSyncer/SearchSyncer.ts → searchSyncer/searchSyncer.ts} +1 -0
  72. package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
  73. package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
  74. package/src/modules/docUtils/utils/utils.ts +32 -0
  75. package/src/modules/docBaseElements/lightningElementWithState/lightningElementWithState.ts +0 -93
  76. package/src/modules/docHelpers/phaseContentLayout/phaseContentLayout.css +0 -39
@@ -1,30 +1,48 @@
1
1
  <template>
2
2
  <doc-content-layout
3
- if:true={loaded}
3
+ lwc:if={loaded}
4
+ lwc:ref="docContentLayout"
4
5
  coveo-organization-id={coveoOrganizationId}
5
6
  coveo-public-access-token={coveoPublicAccessToken}
6
- coveo-search-hub="Developer_Docs_SS"
7
+ coveo-analytics-token={coveoAnalyticsToken}
8
+ coveo-search-hub={coveoSearchHub}
7
9
  coveo-advanced-query-config={coveoAdvancedQueryConfig}
8
- sidebar-header="Pages"
10
+ sidebar-header={docTitle}
9
11
  sidebar-content={sidebarContent}
10
12
  sidebar-value={sidebarValue}
11
13
  onselect={handleSelect}
12
14
  use-old-sidebar={useOldSidebar}
15
+ onlangchange={handleLanguageChange}
16
+ languages={sidebarFooterContent.languages}
17
+ language={sidebarFooterContent.language}
18
+ bail-href={sidebarFooterContent.bailHref}
19
+ bail-label={sidebarFooterContent.bailLabel}
20
+ show-footer={enableFooter}
13
21
  >
14
- <div slot="sidebar-header" class="document-pickers">
15
- <dx-dropdown
22
+ <doc-phase
23
+ slot="version-banner"
24
+ lwc:if={showVersionBanner}
25
+ doc-phase-info={oldVersionInfo}
26
+ icon-name="warning"
27
+ dismissible="true"
28
+ ondismissphase={handleDismissVersionBanner}
29
+ ></doc-phase>
30
+ <div lwc:if={showVersionPicker} slot="sidebar-header">
31
+ <doc-version-picker
16
32
  data-type="version"
17
- suppress-gtm-nav-headings
18
- analytics-event={analyticsEvent}
19
- options={versionOptions}
20
- value={version.id}
21
- width="290px"
22
- >
23
- <dx-button variant="inline" disabled={disableVersion}>
24
- {version.releaseVersion}
25
- </dx-button>
26
- </dx-dropdown>
33
+ analytics-event="custEv_ctaLinkClick"
34
+ analytics-payload={ANALYTICS_PAYLOAD}
35
+ versions={versionOptions}
36
+ selected-version={version}
37
+ latest-version={latestVersion}
38
+ hide-badge={previewVersion}
39
+ ></doc-version-picker>
27
40
  </div>
41
+ <doc-breadcrumbs
42
+ lwc:if={showBreadcrumbs}
43
+ breadcrumbs={breadcrumbs}
44
+ pixel-per-character={breadcrumbPixelPerCharacter}
45
+ ></doc-breadcrumbs>
28
46
  <doc-content
29
47
  docs-data={docContent}
30
48
  page-reference={pageReference}
@@ -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,44 @@ import {
7
8
  DocVersion,
8
9
  TreeNode,
9
10
  Header,
11
+ SiderbarFooter,
10
12
  HistoryState,
11
- PageReference
13
+ PageReference,
14
+ TocMap
12
15
  } from "./types";
13
- import { SearchSyncer } from "docUtils/SearchSyncer";
14
- import { LightningElementWithState } from "docBaseElements/lightningElementWithState";
15
- import { Language } from "typings/custom";
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";
16
22
 
17
23
  // 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
-
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
+ };
20
37
  export default class DocXmlContent extends LightningElementWithState<{
21
38
  isFetchingDocument: boolean;
22
39
  isFetchingContent: boolean;
23
40
  lastHighlightedSearch: string;
41
+ internalLinkClicked: boolean;
24
42
  }> {
25
43
  @api apiDomain = "https://developer.salesforce.com";
26
44
  @api coveoOrganizationId!: string;
27
45
  @api coveoPublicAccessToken!: string;
46
+ @api coveoAnalyticsToken!: string;
47
+ @api coveoSearchHub!: string;
48
+ @api hideFooter = false;
28
49
 
29
50
  @api
30
51
  get allLanguages(): Array<Language> {
@@ -47,21 +68,27 @@ export default class DocXmlContent extends LightningElementWithState<{
47
68
  }
48
69
 
49
70
  private availableLanguages: Array<DocLanguage> = [];
50
- private availableVersions: Array<DocVersion> = [];
51
- private contentProvider: FetchContent;
71
+ @track private availableVersions: Array<DocVersion> = [];
72
+ private contentProvider?: FetchContent;
52
73
  private docContent = "";
53
- private language: DocLanguage = null;
74
+ private language?: DocLanguage | null = null;
54
75
  private loaded = false;
76
+ private _pageHeader?: Header;
55
77
  private pdfUrl = "";
56
- private tocMap = null;
57
- private sidebarContent: Array<TreeNode> = null;
58
- private version: DocVersion = null;
78
+ private tocMap: TocMap = {};
79
+ private sidebarContent: Array<TreeNode> | null = null;
80
+ private version: DocVersion | null = null;
59
81
  private docTitle = "";
60
- private analyticsEvent = "custEv_ctaLinkClick";
61
82
  private _pathName = "";
62
- private _pageHeader?: Header;
63
83
  private listenerAttached = false;
64
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
+ }
65
92
 
66
93
  private searchSyncer = new SearchSyncer({
67
94
  callbacks: {
@@ -101,7 +128,39 @@ export default class DocXmlContent extends LightningElementWithState<{
101
128
  });
102
129
  private _allLanguages: Array<Language> = [];
103
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
+
104
162
  @track private pageReference: PageReference = {};
163
+ @track breadcrumbs: Array<Breadcrumb> = [];
105
164
 
106
165
  constructor() {
107
166
  super();
@@ -135,8 +194,8 @@ export default class DocXmlContent extends LightningElementWithState<{
135
194
  renderedCallback(): void {
136
195
  this.setState({ internalLinkClicked: true });
137
196
  const urlSectionLink =
138
- this.pageReference?.hash?.split("#").length > 1
139
- ? this.pageReference.hash.split("#")[1]
197
+ this.pageReference?.hash?.split("#").length! > 1
198
+ ? this.pageReference.hash!.split("#")[1]
140
199
  : this.pageReference?.hash;
141
200
 
142
201
  const contentEl = this.template.querySelector("doc-content");
@@ -149,6 +208,7 @@ export default class DocXmlContent extends LightningElementWithState<{
149
208
 
150
209
  if (anchorEl) {
151
210
  anchorEl.scrollIntoView();
211
+
152
212
  this.setState({ internalLinkClicked: false });
153
213
  }
154
214
 
@@ -174,24 +234,17 @@ export default class DocXmlContent extends LightningElementWithState<{
174
234
  disconnectedCallback(): void {
175
235
  window.removeEventListener("popstate", this.handlePopState);
176
236
  this.searchSyncer.dispose();
177
- if (this.listenerAttached) {
178
- this.pageHeader.removeEventListener(
179
- "langchange",
180
- this.handleLanguageChange
181
- );
182
- this.listenerAttached = false;
183
- }
184
237
  }
185
238
 
186
- private get languageId(): string {
187
- return this.language.id.replace("-", "_");
239
+ private get languageId(): string | undefined {
240
+ return this.language?.id.replace("-", "_");
188
241
  }
189
242
 
190
- private get releaseVersionId(): string {
191
- return this.version.id;
243
+ private get releaseVersionId(): string | undefined {
244
+ return this.version?.id;
192
245
  }
193
246
 
194
- private get deliverable(): string {
247
+ private get deliverable(): string | undefined {
195
248
  return this.pageReference.deliverable;
196
249
  }
197
250
 
@@ -212,7 +265,11 @@ export default class DocXmlContent extends LightningElementWithState<{
212
265
  }
213
266
 
214
267
  private get coveoAdvancedQueryConfig(): CoveoAdvancedQueryXMLConfig {
215
- const config: { locale: string; topicid: string; version?: string } = {
268
+ const config: {
269
+ locale?: string;
270
+ topicid?: string;
271
+ version?: string;
272
+ } = {
216
273
  locale: this.languageId,
217
274
  topicid: this.deliverable
218
275
  };
@@ -226,7 +283,7 @@ export default class DocXmlContent extends LightningElementWithState<{
226
283
 
227
284
  private get pageHeader(): Header {
228
285
  if (!this._pageHeader) {
229
- this._pageHeader = document.querySelector("doc-header");
286
+ this._pageHeader = document.querySelector("doc-header")!;
230
287
  }
231
288
 
232
289
  return this._pageHeader;
@@ -267,8 +324,26 @@ export default class DocXmlContent extends LightningElementWithState<{
267
324
  }));
268
325
  }
269
326
 
270
- private handlePopState = (): void =>
271
- this.updatePageReference(this.getReferenceFromUrl());
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
+ }
272
347
 
273
348
  handleSelect(event: CustomEvent<{ name: string }>): void {
274
349
  event.stopPropagation();
@@ -280,15 +355,15 @@ export default class DocXmlContent extends LightningElementWithState<{
280
355
 
281
356
  if (name) {
282
357
  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
-
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
+ });
292
367
  this.updateUrl();
293
368
  }
294
369
  }
@@ -300,7 +375,7 @@ export default class DocXmlContent extends LightningElementWithState<{
300
375
  this.updateUrl();
301
376
  }
302
377
 
303
- handleLanguageChange = (event: CustomEvent<string>): Promise<void> => {
378
+ handleLanguageChange = (event: any) => {
304
379
  if (this.language && this.language.id === event.detail) {
305
380
  return;
306
381
  }
@@ -308,12 +383,26 @@ export default class DocXmlContent extends LightningElementWithState<{
308
383
  this.language = this.availableLanguages.find(
309
384
  ({ id }) => id === event.detail
310
385
  );
311
- this.pageReference.docId = this.language.url;
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
+
312
398
  this.updateUrl();
313
399
  this.fetchDocument();
314
400
  };
315
401
 
316
- updatePageReference(newPageReference: PageReference): void {
402
+ updatePageReference(
403
+ newPageReference: PageReference,
404
+ event: PopStateEvent | undefined = undefined
405
+ ): void {
317
406
  this.pageReference.hash = newPageReference.hash;
318
407
  this.pageReference.search = newPageReference.search;
319
408
 
@@ -321,20 +410,35 @@ export default class DocXmlContent extends LightningElementWithState<{
321
410
  return;
322
411
  }
323
412
 
324
- const isSameDocId = this.pageReference.docId !== newPageReference.docId;
413
+ const isSameDocId = this.pageReference.docId === newPageReference.docId;
325
414
  this.pageReference = newPageReference;
326
415
 
327
- if (isSameDocId) {
416
+ if (!isSameDocId) {
328
417
  this.fetchDocument();
329
418
  return;
330
419
  }
331
420
 
332
- this.fetchContent().catch(handleContentError);
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);
333
434
  }
334
435
 
335
436
  getReferenceFromUrl(): PageReference {
336
437
  const [page, docId, deliverable, contentDocumentId] =
337
- window.location.pathname.substr(1).split("/");
438
+ window.location.pathname
439
+ .substr(1)
440
+ .split("/")
441
+ .map(this.sanitizeUrlPart);
338
442
 
339
443
  const { origin: domain, hash, search } = window.location;
340
444
 
@@ -343,9 +447,9 @@ export default class DocXmlContent extends LightningElementWithState<{
343
447
  deliverable,
344
448
  docId,
345
449
  domain,
346
- hash,
450
+ hash: this.sanitizeUrlPart(hash),
347
451
  page,
348
- search
452
+ search: this.sanitizeUrlPart(search)
349
453
  };
350
454
  }
351
455
 
@@ -363,8 +467,9 @@ export default class DocXmlContent extends LightningElementWithState<{
363
467
  this.setState({
364
468
  isFetchingDocument: true
365
469
  });
366
- const data = await this.contentProvider.fetchDocumentData(
367
- this.pageReference.docId
470
+
471
+ const data = await this.contentProvider!.fetchDocumentData(
472
+ this.pageReference.docId!
368
473
  );
369
474
 
370
475
  // This could be a 404 scenario.
@@ -384,13 +489,21 @@ export default class DocXmlContent extends LightningElementWithState<{
384
489
  this.availableVersions = data.availableVersions;
385
490
  this.pdfUrl = data.pdfUrl;
386
491
 
387
- this.updateHeader();
492
+ this.updateHeaderAndSidebarFooter();
493
+
494
+ this.buildBreadcrumbs();
388
495
 
389
496
  if (this.pageReference.deliverable !== data.deliverable) {
390
497
  this.pageReference.deliverable = data.deliverable;
391
498
  this.updateUrl(HistoryState.REPLACE_STATE);
392
499
  }
393
500
 
501
+ if (this.oldVersionInfo) {
502
+ this.showVersionBanner = true;
503
+ } else {
504
+ this.latestVersion = true;
505
+ }
506
+
394
507
  if (
395
508
  this.pageReference?.contentDocumentId?.replace(/\.htm$/, "") !==
396
509
  data.id
@@ -420,12 +533,12 @@ export default class DocXmlContent extends LightningElementWithState<{
420
533
  this.setState({
421
534
  isFetchingContent: true
422
535
  });
423
- const data = await this.contentProvider.fetchContent(
424
- this.pageReference.deliverable,
425
- this.pageReference.contentDocumentId,
536
+ const data = await this.contentProvider!.fetchContent(
537
+ this.pageReference.deliverable!,
538
+ this.pageReference.contentDocumentId!,
426
539
  {
427
- language: this.language.id,
428
- version: this.version.id
540
+ language: this.language!.id,
541
+ version: this.version!.id
429
542
  }
430
543
  );
431
544
 
@@ -434,11 +547,7 @@ export default class DocXmlContent extends LightningElementWithState<{
434
547
  this.addMetatags();
435
548
 
436
549
  if (!this.pageReference.hash) {
437
- document.querySelector("main")?.scrollIntoView({
438
- behavior: "smooth",
439
- block: "start",
440
- inline: "nearest"
441
- });
550
+ document.body.scrollIntoView();
442
551
  }
443
552
  }
444
553
  this.setState({
@@ -446,7 +555,7 @@ export default class DocXmlContent extends LightningElementWithState<{
446
555
  });
447
556
  }
448
557
 
449
- updateHeader(): void {
558
+ updateHeaderAndSidebarFooter(): void {
450
559
  if (!this.pageHeader) {
451
560
  return;
452
561
  }
@@ -456,20 +565,12 @@ export default class DocXmlContent extends LightningElementWithState<{
456
565
  }
457
566
 
458
567
  if (this.pdfUrl) {
459
- this.pageHeader.bailHref = this.pdfUrl;
460
- this.pageHeader.bailLabel = "PDF";
568
+ this.sidebarFooterContent.bailHref = this.pdfUrl;
569
+ this.sidebarFooterContent.bailLabel = "PDF";
461
570
  }
462
571
 
463
- if (!this.listenerAttached) {
464
- this.pageHeader.addEventListener(
465
- "langchange",
466
- this.handleLanguageChange
467
- );
468
- this.listenerAttached = true;
469
- }
470
-
471
- this.pageHeader.languages = this.availableLanguages;
472
- this.pageHeader.language = this.language?.id;
572
+ this.sidebarFooterContent.languages = this.availableLanguages;
573
+ this.sidebarFooterContent.language = this.language?.id;
473
574
 
474
575
  if (this.pageReference) {
475
576
  const { docId, deliverable, page } = this.pageReference;
@@ -478,6 +579,7 @@ export default class DocXmlContent extends LightningElementWithState<{
478
579
  }
479
580
 
480
581
  updateUrl(method = HistoryState.PUSH_STATE): void {
582
+ logCoveoPageView(this.coveoOrganizationId, this.coveoAnalyticsToken);
481
583
  window.history[method](
482
584
  {},
483
585
  "docs",
@@ -493,23 +595,24 @@ export default class DocXmlContent extends LightningElementWithState<{
493
595
  }
494
596
 
495
597
  private updateSearchInput(searchParam: string): void {
496
- this.template
497
- .querySelector("doc-content-layout")
498
- ?.setSidebarInputValue(searchParam);
598
+ (this.refs.docContentLayout as any)?.setSidebarInputValue(searchParam);
499
599
  }
500
600
 
501
601
  private pageReferenceToString(reference: PageReference): string {
502
602
  const { page, docId, deliverable, contentDocumentId, hash, search } =
503
603
  reference;
504
604
  return `/${page}/${docId}/${deliverable}/${contentDocumentId}${this.normalizeSearch(
505
- search
605
+ search!
506
606
  )}${this.normalizeHash(hash)}`;
507
607
  }
508
608
 
509
- private normalizeUrlPart(part: string, sentinel: string): string {
609
+ private normalizeUrlPart(
610
+ part: string | undefined,
611
+ sentinel: string
612
+ ): string {
510
613
  return (
511
614
  (part &&
512
- (part.startsWith(sentinel) ? part : `${sentinel}${part}`)) ||
615
+ (part.startsWith(sentinel!) ? part : `${sentinel}${part}`)) ||
513
616
  ""
514
617
  );
515
618
  }
@@ -518,16 +621,16 @@ export default class DocXmlContent extends LightningElementWithState<{
518
621
  return this.normalizeUrlPart(search, "?");
519
622
  }
520
623
 
521
- private normalizeHash(hash: string): string {
624
+ private normalizeHash(hash?: string): string {
522
625
  return this.normalizeUrlPart(hash, "#");
523
626
  }
524
627
 
525
628
  private getComposedTitle(
526
- topicTitle: string | undefined,
629
+ topicTitle: string | null | undefined,
527
630
  docTitle: string | undefined
528
631
  ): string {
529
632
  // map to avoid duplicates
530
- const titleMap = {};
633
+ const titleMap: { [key: string]: any } = {};
531
634
  if (topicTitle) {
532
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",
533
636
  // here it contains \n in the text context which needs to be removed
@@ -558,16 +661,65 @@ export default class DocXmlContent extends LightningElementWithState<{
558
661
  );
559
662
  }
560
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
+
561
713
  addMetatags(): void {
562
714
  const div = document.createElement("div");
563
- div.innerHTML = this.docContent;
715
+ div.innerHTML = DOMPurify.sanitize(this.docContent);
564
716
  const docDescription = div.querySelector(".shortdesc")?.textContent;
565
717
  const topicTitle = div.querySelector("h1")?.textContent;
566
718
 
567
719
  const title = document.querySelector("title");
568
720
  const composedTitle = this.getComposedTitle(topicTitle, this.docTitle);
569
721
 
570
- if (title) {
722
+ if (title && title.textContent) {
571
723
  title.textContent = composedTitle;
572
724
  }
573
725
  const metatitle = document.querySelector('meta[name="title"]');
@@ -589,14 +741,52 @@ export default class DocXmlContent extends LightningElementWithState<{
589
741
  'link[rel="canonical"]'
590
742
  );
591
743
  if (metadescription) {
744
+ const copyPageReference = { ...this.pageReference };
745
+ copyPageReference.docId = copyPageReference.docId
746
+ ? this.dropVersionFromDocId(copyPageReference.docId)
747
+ : copyPageReference.docId;
592
748
  metadescription.setAttribute(
593
749
  "href",
594
750
  window.location.protocol +
595
751
  "//" +
596
752
  window.location.host +
597
- this.pageReferenceToString(this.pageReference)
753
+ this.pageReferenceToString(copyPageReference)
598
754
  );
599
755
  }
600
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;
601
791
  }
602
792
  }