@salesforcedevs/docs-components 0.6.0 → 0.7.59-sppage-alpha1

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