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