@salesforcedevs/docs-components 0.54.0-alpha04 → 0.54.1-a01

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 (27) hide show
  1. package/package.json +1 -2
  2. package/src/modules/doc/amfReference/amfReference.html +13 -5
  3. package/src/modules/doc/amfReference/amfReference.ts +799 -303
  4. package/src/modules/doc/amfReference/constants.ts +76 -0
  5. package/src/modules/doc/amfReference/types.ts +50 -5
  6. package/src/modules/doc/amfTopic/types.ts +1 -1
  7. package/src/modules/doc/amfTopic/utils.ts +20 -7
  8. package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +2 -1
  9. package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +1 -1
  10. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +17 -1
  11. package/src/modules/doc/breadcrumbs/breadcrumbs.css +9 -1
  12. package/src/modules/doc/breadcrumbs/breadcrumbs.html +12 -2
  13. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +49 -8
  14. package/src/modules/doc/content/content.css +31 -7
  15. package/src/modules/doc/content/content.ts +17 -2
  16. package/src/modules/doc/contentCallout/contentCallout.ts +5 -0
  17. package/src/modules/doc/contentLayout/contentLayout.css +8 -10
  18. package/src/modules/doc/contentLayout/contentLayout.html +2 -9
  19. package/src/modules/doc/contentLayout/contentLayout.ts +74 -29
  20. package/src/modules/doc/header/header.ts +3 -3
  21. package/src/modules/doc/headingContent/headingContent.css +4 -0
  22. package/src/modules/doc/xmlContent/types.ts +6 -11
  23. package/src/modules/doc/xmlContent/utils.ts +18 -20
  24. package/src/modules/doc/xmlContent/xmlContent.css +7 -0
  25. package/src/modules/doc/xmlContent/xmlContent.html +6 -1
  26. package/src/modules/doc/xmlContent/xmlContent.ts +132 -34
  27. package/src/modules/doc/amfReference/route-meta.ts +0 -22
@@ -20,6 +20,7 @@ const HIGHLIGHTABLE_SELECTOR = [
20
20
  "th",
21
21
  "td"
22
22
  ].join(",");
23
+ const OBSERVER_ATTACH_WAIT_TIME = 500;
23
24
 
24
25
  export default class ContentLayout extends LightningElement {
25
26
  @api sidebarValue: string;
@@ -95,6 +96,8 @@ export default class ContentLayout extends LightningElement {
95
96
  target: window
96
97
  });
97
98
  private tocValue?: string = undefined;
99
+ private observerTimerId = null;
100
+ private didScrollToSelectedHash = false;
98
101
 
99
102
  get showToc(): boolean {
100
103
  return this.tocOptions && this.tocOptions.length > 0;
@@ -119,6 +122,18 @@ export default class ContentLayout extends LightningElement {
119
122
  }
120
123
  }
121
124
 
125
+ renderedCallback(): void {
126
+ /**
127
+ * Note: We are adding timeout because chrome is optimizing and not triggering recent renderedCallback though elements reference is changed
128
+ * Also we are considering recent renderedCallback
129
+ */
130
+ this.clearRenderObserverTimer();
131
+ this.observerTimerId = setTimeout(
132
+ this.attachInteractionObserver,
133
+ OBSERVER_ATTACH_WAIT_TIME
134
+ );
135
+ }
136
+
122
137
  disconnectedCallback(): void {
123
138
  this.disconnectObserver();
124
139
  window.removeEventListener(
@@ -126,19 +141,25 @@ export default class ContentLayout extends LightningElement {
126
141
  this.updateHighlighted
127
142
  );
128
143
  this.searchSyncer.dispose();
144
+ this.clearRenderObserverTimer();
129
145
  }
130
146
 
147
+ clearRenderObserverTimer = () => {
148
+ if (this.observerTimerId) {
149
+ clearTimeout(this.observerTimerId);
150
+ }
151
+ };
152
+
131
153
  updateHighlighted = (event: Event): void =>
132
154
  highlightTerms(
133
155
  this.querySelectorAll(HIGHLIGHTABLE_SELECTOR),
134
156
  (event as CustomEvent<string>).detail
135
157
  );
136
158
 
137
- onSlotChange(event: Event): void {
159
+ attachInteractionObserver = (): void => {
138
160
  if (!this.enableSlotChange) {
139
161
  return;
140
162
  }
141
-
142
163
  this.disconnectObserver();
143
164
  this.observer = new IntersectionObserver((entries) => {
144
165
  entries.forEach(
@@ -150,30 +171,54 @@ export default class ContentLayout extends LightningElement {
150
171
  this.calculateActualSection();
151
172
  });
152
173
 
153
- const anchoredTags = (event.target as HTMLSlotElement)
154
- .assignedElements()
155
- .filter(({ tagName }) => tagName === TOC_HEADER_TAG)
156
- .map((tag) => {
157
- tag.id = tag.hash;
158
- return tag;
159
- });
160
-
161
- this._tocOptions = anchoredTags.map((tag) => ({
162
- anchor: `#${tag.hash}`,
163
- id: tag.id,
164
- label: tag.title
165
- }));
166
-
167
- this.scrollToHash(anchoredTags);
168
-
169
- anchoredTags.forEach((section) => {
170
- const id = section.getAttribute("id");
174
+ // Note: We are doing document.querySelectorAll as a quick fix as we are not getting heading elements reference this.querySelectorAll
175
+ const headingElements = document.querySelectorAll(TOC_HEADER_TAG);
176
+ for (const headingElement of headingElements) {
177
+ // Add headingElements to intersectionObserver for highlighting respective RNB item when user scroll
178
+ const id = headingElement.getAttribute("id");
171
179
  this.anchoredElements[id] = {
172
180
  id,
173
181
  intersect: false
174
182
  };
175
- this.observer.observe(section);
176
- });
183
+ this.observer.observe(headingElement);
184
+ }
185
+ if (!this.didScrollToSelectedHash) {
186
+ this.didScrollToSelectedHash = true;
187
+ this.scrollToHash(headingElements);
188
+ }
189
+ };
190
+
191
+ onSlotChange(event: Event): void {
192
+ const slotElements = (
193
+ event.target as HTMLSlotElement
194
+ ).assignedElements();
195
+
196
+ if (slotElements.length) {
197
+ const slotContentElement = slotElements[0];
198
+ const headingElements =
199
+ slotContentElement.ownerDocument?.getElementsByTagName(
200
+ TOC_HEADER_TAG
201
+ );
202
+ for (const headingElement of headingElements) {
203
+ // Sometimes elements hash is not being set when slot content is wrapped with div
204
+ headingElement.hash =
205
+ headingElement.attributes.hash?.nodeValue;
206
+ }
207
+ const tocOptions = [];
208
+ for (const headingElement of headingElements) {
209
+ headingElement.id = headingElement.hash;
210
+
211
+ // Update tocOptions from anchorTags
212
+ const tocItem = {
213
+ anchor: `#${headingElement.hash}`,
214
+ id: headingElement.id,
215
+ label: headingElement.title
216
+ };
217
+ tocOptions.push(tocItem);
218
+ }
219
+
220
+ this._tocOptions = tocOptions;
221
+ }
177
222
  }
178
223
 
179
224
  private disconnectObserver(): void {
@@ -183,16 +228,16 @@ export default class ContentLayout extends LightningElement {
183
228
  }
184
229
  }
185
230
 
186
- private scrollToHash(anchoredTags: Array<Element>): void {
231
+ // eslint-disable-next-line no-undef
232
+ private scrollToHash(headingElements: NodeListOf<Element>): void {
187
233
  let { hash } = window.location;
188
-
189
234
  if (hash) {
190
235
  hash = hash.substr(1);
191
- const toScrollElement = anchoredTags.find(
192
- (element) => element.getAttribute("id") === hash
193
- );
194
- if (toScrollElement) {
195
- toScrollElement.scrollIntoView({ behavior: "auto" });
236
+ for (const headingElement of headingElements) {
237
+ if (headingElement.getAttribute("id") === hash) {
238
+ headingElement.scrollIntoView({ behavior: "auto" });
239
+ break;
240
+ }
196
241
  }
197
242
  }
198
243
  }
@@ -1,6 +1,6 @@
1
1
  import { api } from "lwc";
2
2
  import cx from "classnames";
3
- import type { Option } from "typings/custom";
3
+ import type { OptionWithNested, OptionWithLink } from "typings/custom";
4
4
  import { HeaderBase } from "dxBaseElements/headerBase";
5
5
  import { toJson } from "dxUtils/normalizers";
6
6
  import get from "lodash.get";
@@ -43,8 +43,8 @@ export default class Header extends HeaderBase {
43
43
  }
44
44
 
45
45
  private _language: string | null = null;
46
- private _languages!: Option[];
47
- private _scopedNavItems!: Option[];
46
+ private _languages!: OptionWithLink[];
47
+ private _scopedNavItems!: OptionWithNested[];
48
48
  private smallMobile = false;
49
49
  private smallMobileMatchMedia!: MediaQueryList;
50
50
  private tablet = false;
@@ -34,6 +34,10 @@ dx-icon {
34
34
  --dx-c-icon-size: var(--doc-c-heading-anchor-icon-size);
35
35
  }
36
36
 
37
+ span {
38
+ word-break: break-word;
39
+ }
40
+
37
41
  span:last-of-type {
38
42
  padding-right: var(--dx-g-spacing-xs);
39
43
  }
@@ -1,9 +1,7 @@
1
1
  export type CoveoAdvancedQueryXMLConfig = {
2
- objecttype?: string;
3
- sflocale__c?: string;
4
- sfrelease__c?: string;
5
- sfdelivarable__c?: string;
6
- source?: string;
2
+ locale?: string;
3
+ version?: string;
4
+ topicid?: string;
7
5
  };
8
6
 
9
7
  export type PageReference = {
@@ -21,17 +19,12 @@ export enum HistoryState {
21
19
  REPLACE_STATE = "replaceState"
22
20
  }
23
21
 
24
- export type Labels = {
25
- language_english: string;
26
- language_japanese: string;
27
- toc_title: string;
28
- };
29
-
30
22
  export type TreeNode = {
31
23
  label: string;
32
24
  name: string;
33
25
  children?: Array<TreeNode>;
34
26
  isExpanded?: boolean;
27
+ parent?: TreeNode;
35
28
  };
36
29
 
37
30
  type DropdownOption = {
@@ -117,3 +110,5 @@ export type ContentApiOptions = {
117
110
  version: string;
118
111
  language: string;
119
112
  };
113
+
114
+ export type TocMap = { [key: string]: TreeNode };
@@ -8,22 +8,19 @@ import {
8
8
  DocumentData,
9
9
  DocLanguage,
10
10
  DocVersion,
11
- Labels,
12
- TreeNode
11
+ TreeNode,
12
+ TocMap
13
13
  } from "./types";
14
-
15
- const LOCALE_TO_LABEL = {
16
- "en-us": "language_english",
17
- "ja-jp": "language_japanese"
18
- };
14
+ import { Language } from "typings/custom";
15
+ import { getLanguageDisplayTextById } from "dxUtils/language";
19
16
 
20
17
  export class FetchContent {
21
18
  private apiDomain: string;
22
- private labels: Labels;
19
+ private languages: Array<Language> = [];
23
20
 
24
- constructor(apiDomain: string, labels: Labels) {
21
+ constructor(apiDomain: string, languages: Array<Language>) {
25
22
  this.apiDomain = apiDomain;
26
- this.labels = labels;
23
+ this.languages = languages;
27
24
  }
28
25
 
29
26
  async fetchDocumentData(docId: string): Promise<DocumentData | null> {
@@ -104,26 +101,26 @@ export class FetchContent {
104
101
 
105
102
  private normalizeNavItem(
106
103
  navItem: ApiNavItem,
107
- tocMap: { [key: string]: TreeNode }
104
+ tocMap: TocMap,
105
+ parentNavItem?: TreeNode
108
106
  ): TreeNode {
109
107
  const name = this.calculateNavItemName(navItem, tocMap);
110
108
  const node: TreeNode = {
111
109
  label: navItem.text,
112
- name
110
+ name,
111
+ parent: parentNavItem
113
112
  };
113
+
114
114
  if (name) {
115
115
  tocMap[name] = node;
116
116
  }
117
117
  node.children = navItem.children?.map((child) =>
118
- this.normalizeNavItem(child, tocMap)
118
+ this.normalizeNavItem(child, tocMap, node)
119
119
  );
120
120
  return node;
121
121
  }
122
122
 
123
- private calculateNavItemName(
124
- navItem: ApiNavItem,
125
- tocMap: { [key: string]: TreeNode }
126
- ): string {
123
+ private calculateNavItemName(navItem: ApiNavItem, tocMap: TocMap): string {
127
124
  let href = navItem.a_attr?.href || "";
128
125
  if (href.includes("#")) {
129
126
  const [pathUrl] = href.split("#");
@@ -148,12 +145,13 @@ export class FetchContent {
148
145
  }
149
146
 
150
147
  private normalizeLanguage(language: ApiDocLanguage): DocLanguage {
151
- const labelKey = language.locale && LOCALE_TO_LABEL[language.locale];
152
148
  return (
153
149
  language && {
154
150
  label:
155
- (labelKey && this.labels && this.labels[labelKey]) ||
156
- language.label,
151
+ getLanguageDisplayTextById(
152
+ this.languages,
153
+ language.locale
154
+ ) || language.label,
157
155
  id: language.locale,
158
156
  code: language.code,
159
157
  url: language.url
@@ -3,6 +3,13 @@
3
3
  --button-primary-color-hover: var(--dx-g-blue-vibrant-40);
4
4
  }
5
5
 
6
+ doc-breadcrumbs {
7
+ --dx-c-popover-z-index: 5;
8
+
9
+ display: block;
10
+ margin-bottom: var(--dx-g-spacing-2xl);
11
+ }
12
+
6
13
  dx-dropdown {
7
14
  --dx-c-dropdown-option-font-size: var(--dx-g-text-sm);
8
15
  }
@@ -9,7 +9,7 @@
9
9
  sidebar-content={sidebarContent}
10
10
  sidebar-value={sidebarValue}
11
11
  onselect={handleSelect}
12
- use-old-sidebar
12
+ use-old-sidebar={useOldSidebar}
13
13
  >
14
14
  <div slot="sidebar-header" class="document-pickers">
15
15
  <dx-dropdown
@@ -25,6 +25,11 @@
25
25
  </dx-button>
26
26
  </dx-dropdown>
27
27
  </div>
28
+ <doc-breadcrumbs
29
+ if:true={breadcrumbs}
30
+ breadcrumbs={breadcrumbs}
31
+ pixel-per-character={breadcrumbPixelPerCharacter}
32
+ ></doc-breadcrumbs>
28
33
  <doc-content
29
34
  docs-data={docContent}
30
35
  page-reference={pageReference}
@@ -1,22 +1,33 @@
1
1
  import { api, track } from "lwc";
2
- import { toJson } from "dxUtils/normalizers";
2
+ import { normalizeBoolean } from "dxUtils/normalizers";
3
3
  import { FetchContent } from "./utils";
4
4
  import {
5
5
  CoveoAdvancedQueryXMLConfig,
6
6
  DocLanguage,
7
7
  DocVersion,
8
- Labels,
9
8
  TreeNode,
10
9
  Header,
11
10
  HistoryState,
12
- PageReference
11
+ PageReference,
12
+ TocMap
13
13
  } from "./types";
14
14
  import { SearchSyncer } from "docUtils/SearchSyncer";
15
15
  import { LightningElementWithState } from "docBaseElements/lightningElementWithState";
16
+ import { Breadcrumb, Language } from "typings/custom";
16
17
 
17
18
  // TODO: Imitating from actual implementation as doc-content use it like this. We should refactor it later.
18
19
  const handleContentError = (error): void => console.log(error);
19
20
 
21
+ const FIRST_CRUMB = {
22
+ href: "/docs",
23
+ label: "Documentation"
24
+ };
25
+
26
+ const PIXEL_PER_CHARACTER_MAP: { [key: string]: number } = {
27
+ default: 7.7,
28
+ "ja-jp": 12.5
29
+ };
30
+
20
31
  export default class DocXmlContent extends LightningElementWithState<{
21
32
  isFetchingDocument: boolean;
22
33
  isFetchingContent: boolean;
@@ -27,15 +38,25 @@ export default class DocXmlContent extends LightningElementWithState<{
27
38
  @api coveoPublicAccessToken!: string;
28
39
 
29
40
  @api
30
- get labels() {
31
- return this._labels;
41
+ get allLanguages(): Array<Language> {
42
+ return this._allLanguages;
43
+ }
44
+
45
+ set allLanguages(value: string) {
46
+ if (value) {
47
+ this._allLanguages = JSON.parse(value);
48
+ }
49
+ }
50
+
51
+ @api
52
+ get enableCoveo() {
53
+ return this._enableCoveo;
32
54
  }
33
55
 
34
- set labels(value) {
35
- this._labels = toJson(value);
56
+ set enableCoveo(value) {
57
+ this._enableCoveo = normalizeBoolean(value);
36
58
  }
37
59
 
38
- private _labels: Labels = null;
39
60
  private availableLanguages: Array<DocLanguage> = [];
40
61
  private availableVersions: Array<DocVersion> = [];
41
62
  private contentProvider: FetchContent;
@@ -43,7 +64,7 @@ export default class DocXmlContent extends LightningElementWithState<{
43
64
  private language: DocLanguage = null;
44
65
  private loaded = false;
45
66
  private pdfUrl = "";
46
- private tocMap = null;
67
+ private tocMap: TocMap = {};
47
68
  private sidebarContent: Array<TreeNode> = null;
48
69
  private version: DocVersion = null;
49
70
  private docTitle = "";
@@ -51,6 +72,8 @@ export default class DocXmlContent extends LightningElementWithState<{
51
72
  private _pathName = "";
52
73
  private _pageHeader?: Header;
53
74
  private listenerAttached = false;
75
+ private _enableCoveo?: boolean = false;
76
+
54
77
  private searchSyncer = new SearchSyncer({
55
78
  callbacks: {
56
79
  onSearchChange: (nextSearchString: string): void => {
@@ -87,8 +110,10 @@ export default class DocXmlContent extends LightningElementWithState<{
87
110
  shouldStopPropagation: true,
88
111
  target: window
89
112
  });
113
+ private _allLanguages: Array<Language> = [];
90
114
 
91
115
  @track private pageReference: PageReference = {};
116
+ @track breadcrumbs: Array<Breadcrumb> = [];
92
117
 
93
118
  constructor() {
94
119
  super();
@@ -109,8 +134,10 @@ export default class DocXmlContent extends LightningElementWithState<{
109
134
  window.location.href = "/docs";
110
135
  return;
111
136
  }
112
-
113
- this.contentProvider = new FetchContent(this.apiDomain, this._labels);
137
+ this.contentProvider = new FetchContent(
138
+ this.apiDomain,
139
+ this.allLanguages
140
+ );
114
141
  this.fetchDocument().then(() => (this.loaded = true));
115
142
  window.addEventListener("popstate", this.handlePopState);
116
143
 
@@ -180,18 +207,33 @@ export default class DocXmlContent extends LightningElementWithState<{
180
207
  return this.pageReference.deliverable;
181
208
  }
182
209
 
210
+ private get useOldSidebar(): boolean {
211
+ // Coveo is enabled and the version is greater than 51 (within the latest 3 versions)
212
+ // TODO: we need a better fix for version number check
213
+ return !(
214
+ this.enableCoveo &&
215
+ this.coveoOrganizationId &&
216
+ this.coveoPublicAccessToken &&
217
+ (!this.version?.releaseVersion ||
218
+ (this.version?.releaseVersion &&
219
+ parseInt(
220
+ this.version.releaseVersion.replace("v", ""),
221
+ 10
222
+ ) >= 53))
223
+ );
224
+ }
225
+
183
226
  private get coveoAdvancedQueryConfig(): CoveoAdvancedQueryXMLConfig {
184
- return {
185
- // Temporary fix for empty results on apexref page
186
- ...(this.deliverable === "apexref"
187
- ? { source: "Sitemap - Developer Docs Atlas - Apexref" }
188
- : {
189
- objecttype: "HTDeveloperDocumentsC",
190
- sflocale__c: this.languageId,
191
- sfrelease__c: this.releaseVersionId,
192
- sfdelivarable__c: this.deliverable
193
- })
227
+ const config: { locale: string; topicid: string; version?: string } = {
228
+ locale: this.languageId,
229
+ topicid: this.deliverable
194
230
  };
231
+
232
+ if (this.releaseVersionId && this.releaseVersionId !== "noversion") {
233
+ config.version = this.releaseVersionId;
234
+ }
235
+
236
+ return config;
195
237
  }
196
238
 
197
239
  private get pageHeader(): Header {
@@ -237,6 +279,13 @@ export default class DocXmlContent extends LightningElementWithState<{
237
279
  }));
238
280
  }
239
281
 
282
+ private get breadcrumbPixelPerCharacter() {
283
+ return (
284
+ PIXEL_PER_CHARACTER_MAP[this.language.id] ||
285
+ PIXEL_PER_CHARACTER_MAP.default
286
+ );
287
+ }
288
+
240
289
  private handlePopState = (): void =>
241
290
  this.updatePageReference(this.getReferenceFromUrl());
242
291
 
@@ -250,15 +299,15 @@ export default class DocXmlContent extends LightningElementWithState<{
250
299
 
251
300
  if (name) {
252
301
  const hashIndex = name.indexOf("#");
253
- this.pageReference.hash =
254
- hashIndex > -1 ? name.slice(hashIndex) : "";
255
-
256
- const contentId = hashIndex > -1 ? name.slice(0, hashIndex) : name;
257
- if (this.pageReference.contentDocumentId !== contentId) {
258
- this.pageReference.contentDocumentId = contentId;
259
- this.fetchContent().catch(handleContentError);
260
- }
261
-
302
+ const hash = hashIndex > -1 ? name.slice(hashIndex) : "";
303
+
304
+ const contentDocumentId =
305
+ hashIndex > -1 ? name.slice(0, hashIndex) : name;
306
+ this.updatePageReference({
307
+ ...this.pageReference,
308
+ contentDocumentId,
309
+ hash
310
+ });
262
311
  this.updateUrl();
263
312
  }
264
313
  }
@@ -291,15 +340,17 @@ export default class DocXmlContent extends LightningElementWithState<{
291
340
  return;
292
341
  }
293
342
 
294
- const isSameDocId = this.pageReference.docId !== newPageReference.docId;
343
+ const isSameDocId = this.pageReference.docId === newPageReference.docId;
295
344
  this.pageReference = newPageReference;
296
345
 
297
- if (isSameDocId) {
346
+ if (!isSameDocId) {
298
347
  this.fetchDocument();
299
348
  return;
300
349
  }
301
350
 
302
- this.fetchContent().catch(handleContentError);
351
+ this.fetchContent()
352
+ .then(() => this.buildBreadcrumbs())
353
+ .catch(handleContentError);
303
354
  }
304
355
 
305
356
  getReferenceFromUrl(): PageReference {
@@ -356,6 +407,8 @@ export default class DocXmlContent extends LightningElementWithState<{
356
407
 
357
408
  this.updateHeader();
358
409
 
410
+ this.buildBreadcrumbs();
411
+
359
412
  if (this.pageReference.deliverable !== data.deliverable) {
360
413
  this.pageReference.deliverable = data.deliverable;
361
414
  this.updateUrl(HistoryState.REPLACE_STATE);
@@ -404,7 +457,11 @@ export default class DocXmlContent extends LightningElementWithState<{
404
457
  this.addMetatags();
405
458
 
406
459
  if (!this.pageReference.hash) {
407
- window.scrollTo(0, 0);
460
+ document.querySelector("main")?.scrollIntoView({
461
+ behavior: "smooth",
462
+ block: "start",
463
+ inline: "nearest"
464
+ });
408
465
  }
409
466
  }
410
467
  this.setState({
@@ -524,6 +581,32 @@ export default class DocXmlContent extends LightningElementWithState<{
524
581
  );
525
582
  }
526
583
 
584
+ private buildBreadcrumbs(): void {
585
+ const { contentDocumentId } = this.pageReference;
586
+ if (!contentDocumentId) {
587
+ return;
588
+ }
589
+
590
+ const currentNode = this.tocMap[contentDocumentId];
591
+ this.breadcrumbs = this.nodeToBreadcrumb(currentNode);
592
+ }
593
+
594
+ private nodeToBreadcrumb(node?: TreeNode): Breadcrumb[] {
595
+ if (!node) {
596
+ return [FIRST_CRUMB];
597
+ }
598
+ return [
599
+ ...this.nodeToBreadcrumb(node.parent),
600
+ {
601
+ href: this.pageReferenceToString({
602
+ ...this.pageReference,
603
+ contentDocumentId: node.name
604
+ }),
605
+ label: node.label
606
+ }
607
+ ];
608
+ }
609
+
527
610
  addMetatags(): void {
528
611
  const div = document.createElement("div");
529
612
  div.innerHTML = this.docContent;
@@ -549,5 +632,20 @@ export default class DocXmlContent extends LightningElementWithState<{
549
632
  metadescription.setAttribute("content", docDescription);
550
633
  }
551
634
  }
635
+
636
+ if (this.pageReference) {
637
+ const metadescription = document.querySelector(
638
+ 'link[rel="canonical"]'
639
+ );
640
+ if (metadescription) {
641
+ metadescription.setAttribute(
642
+ "href",
643
+ window.location.protocol +
644
+ "//" +
645
+ window.location.host +
646
+ this.pageReferenceToString(this.pageReference)
647
+ );
648
+ }
649
+ }
552
650
  }
553
651
  }
@@ -1,22 +0,0 @@
1
- /**
2
- * Represents the URL reference meta on Reference page.
3
- * Contains information on selected Reference ID, Topic ID, and Topic Type
4
- * separated by ":"
5
- */
6
- export class RouteMeta {
7
- meta: string;
8
- referenceId = "";
9
- topicId = "";
10
- type = "";
11
-
12
- constructor(meta: string) {
13
- this.meta = meta;
14
-
15
- if (meta && meta.includes(":")) {
16
- const [referenceId, type, topicId] = meta.split(":");
17
- this.referenceId = referenceId;
18
- this.topicId = topicId || type;
19
- this.type = type;
20
- }
21
- }
22
- }