@salesforcedevs/docs-components 0.3.11 → 0.3.14-banner-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 (107) hide show
  1. package/lwc.config.json +12 -3
  2. package/package.json +17 -7
  3. package/src/modules/README.md +41 -0
  4. package/src/modules/doc/amfReference/amfReference.css +5 -0
  5. package/src/modules/doc/amfReference/amfReference.html +47 -0
  6. package/src/modules/doc/amfReference/amfReference.ts +1361 -0
  7. package/src/modules/doc/amfReference/constants.ts +76 -0
  8. package/src/modules/doc/amfReference/types.ts +133 -0
  9. package/src/modules/doc/amfReference/utils.ts +669 -0
  10. package/src/modules/doc/amfTopic/amfTopic.css +1 -0
  11. package/src/modules/doc/amfTopic/amfTopic.html +3 -0
  12. package/src/modules/doc/amfTopic/amfTopic.ts +94 -0
  13. package/src/modules/doc/amfTopic/types.ts +54 -0
  14. package/src/modules/doc/amfTopic/utils.ts +130 -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 +64 -0
  18. package/src/modules/doc/breadcrumbs/breadcrumbs.css +27 -0
  19. package/src/modules/doc/breadcrumbs/breadcrumbs.html +60 -0
  20. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +187 -0
  21. package/src/modules/doc/content/content.css +58 -102
  22. package/src/modules/doc/content/content.ts +261 -174
  23. package/src/modules/doc/contentCallout/contentCallout.css +7 -24
  24. package/src/modules/doc/contentCallout/contentCallout.html +4 -2
  25. package/src/modules/doc/contentCallout/contentCallout.ts +8 -2
  26. package/src/modules/doc/contentLayout/contentLayout.css +98 -0
  27. package/src/modules/doc/contentLayout/contentLayout.html +51 -0
  28. package/src/modules/doc/contentLayout/contentLayout.ts +322 -0
  29. package/src/modules/doc/header/header.css +103 -0
  30. package/src/modules/doc/header/header.html +160 -0
  31. package/src/modules/doc/header/header.ts +146 -0
  32. package/src/modules/doc/heading/heading.css +54 -0
  33. package/src/modules/doc/heading/heading.html +14 -0
  34. package/src/modules/doc/heading/heading.ts +65 -0
  35. package/src/modules/doc/headingAnchor/headingAnchor.css +33 -0
  36. package/src/modules/doc/headingAnchor/headingAnchor.html +19 -0
  37. package/src/modules/doc/headingAnchor/headingAnchor.ts +43 -0
  38. package/src/modules/doc/headingContent/headingContent.css +53 -0
  39. package/src/modules/doc/headingContent/headingContent.html +13 -0
  40. package/src/modules/doc/headingContent/headingContent.ts +30 -0
  41. package/src/modules/doc/phase/phase.css +55 -0
  42. package/src/modules/doc/phase/phase.html +28 -0
  43. package/src/modules/doc/phase/phase.ts +57 -0
  44. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  45. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +12 -0
  46. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  47. package/src/modules/doc/toc/toc.html +3 -1
  48. package/src/modules/doc/toolbar/toolbar.ts +6 -6
  49. package/src/modules/doc/xmlContent/types.ts +114 -0
  50. package/src/modules/doc/xmlContent/utils.ts +161 -0
  51. package/src/modules/doc/xmlContent/xmlContent.css +32 -0
  52. package/src/modules/doc/xmlContent/xmlContent.html +40 -0
  53. package/src/modules/doc/xmlContent/xmlContent.ts +677 -0
  54. package/src/modules/docBaseElements/lightningElementWithState/lightningElementWithState.ts +93 -0
  55. package/src/modules/docHelpers/amfStyle/amfStyle.css +355 -0
  56. package/src/modules/docHelpers/phaseContentLayout/phaseContentLayout.css +39 -0
  57. package/src/modules/docHelpers/status/status.css +22 -0
  58. package/src/modules/docUtils/SearchSyncer/SearchSyncer.ts +85 -0
  59. package/LICENSE +0 -12
  60. package/src/modules/doc/container/__benchmarks__/container.benchmark.js +0 -43
  61. package/src/modules/doc/container/__mocks__/mockAvailableLanguages.js +0 -8
  62. package/src/modules/doc/container/__mocks__/mockAvailableVersions.js +0 -122
  63. package/src/modules/doc/container/__mocks__/mockContentFetchResponse.json +0 -5
  64. package/src/modules/doc/container/__mocks__/mockDocContent.js +0 -29
  65. package/src/modules/doc/container/__mocks__/mockNavigationFetchResponse.json +0 -4061
  66. package/src/modules/doc/container/__mocks__/mockPageReference.js +0 -8
  67. package/src/modules/doc/container/__mocks__/mockPdfUrl.js +0 -1
  68. package/src/modules/doc/container/__mocks__/mockSelectedLanguage.js +0 -8
  69. package/src/modules/doc/container/__mocks__/mockSelectedVersion.js +0 -8
  70. package/src/modules/doc/container/__mocks__/mockToc.js +0 -146
  71. package/src/modules/doc/container/__tests__/container.test.ts +0 -117
  72. package/src/modules/doc/container/container.css +0 -37
  73. package/src/modules/doc/container/container.html +0 -28
  74. package/src/modules/doc/container/container.stories.ts +0 -44
  75. package/src/modules/doc/container/container.ts +0 -367
  76. package/src/modules/doc/content/__tests__/content.test.ts +0 -99
  77. package/src/modules/doc/content/__tests__/mockDocContent.ts +0 -258
  78. package/src/modules/doc/content/__tests__/mockPageReference.ts +0 -8
  79. package/src/modules/doc/content/content.stories.ts +0 -82
  80. package/src/modules/doc/contentCallout/__tests__/contentCallout.test.ts +0 -80
  81. package/src/modules/doc/contentCallout/__tests__/mockProps.ts +0 -14
  82. package/src/modules/doc/contentCallout/contentCallout.stories.ts +0 -29
  83. package/src/modules/doc/contentMedia/__tests__/contentMedia.test.ts +0 -97
  84. package/src/modules/doc/contentMedia/contentMedia.stories.ts +0 -113
  85. package/src/modules/doc/landing/__tests__/landing.test.ts +0 -60
  86. package/src/modules/doc/landing/__tests__/mockFetch.json +0 -370
  87. package/src/modules/doc/landing/landing.css +0 -63
  88. package/src/modules/doc/landing/landing.html +0 -122
  89. package/src/modules/doc/landing/landing.stories.ts +0 -21
  90. package/src/modules/doc/landing/landing.ts +0 -222
  91. package/src/modules/doc/nav/__tests__/mockAvailableLanguages.ts +0 -8
  92. package/src/modules/doc/nav/__tests__/mockAvailableVersions.ts +0 -122
  93. package/src/modules/doc/nav/__tests__/mockPageReference.ts +0 -8
  94. package/src/modules/doc/nav/__tests__/mockPdfUrl.ts +0 -1
  95. package/src/modules/doc/nav/__tests__/mockSelectedLanguage.ts +0 -8
  96. package/src/modules/doc/nav/__tests__/mockSelectedVersion.ts +0 -8
  97. package/src/modules/doc/nav/__tests__/mockToc.ts +0 -146
  98. package/src/modules/doc/nav/__tests__/nav.test.ts +0 -58
  99. package/src/modules/doc/toc/__tests__/mockPageReference.ts +0 -8
  100. package/src/modules/doc/toc/__tests__/mockToc.ts +0 -146
  101. package/src/modules/doc/toc/__tests__/toc.test.ts +0 -29
  102. package/src/modules/doc/toolbar/__tests__/mockAvailableLanguages.ts +0 -8
  103. package/src/modules/doc/toolbar/__tests__/mockAvailableVersions.ts +0 -122
  104. package/src/modules/doc/toolbar/__tests__/mockPdfUrl.ts +0 -1
  105. package/src/modules/doc/toolbar/__tests__/mockSelectedLanguage.ts +0 -8
  106. package/src/modules/doc/toolbar/__tests__/mockSelectedVersion.ts +0 -8
  107. package/src/modules/doc/toolbar/__tests__/toolbar.test.ts +0 -44
@@ -0,0 +1,1361 @@
1
+ import { LightningElement, api, track } from "lwc";
2
+ import { noCase } from "no-case";
3
+ import { sentenceCase } from "sentence-case";
4
+ import qs from "query-string";
5
+ import { AmfModelParser } from "./utils";
6
+ import { normalizeBoolean } from "dxUtils/normalizers";
7
+ import type {
8
+ AmfConfig,
9
+ AmfMetadataTopic,
10
+ AmfModel,
11
+ AmfModelRecord,
12
+ NavItem,
13
+ ParsedTopicModel,
14
+ TopicModel,
15
+ ReferenceVersion,
16
+ ReferenceSetConfig,
17
+ AmfMetaTopicType,
18
+ RouteMeta,
19
+ ParsedMarkdownTopic
20
+ } from "./types";
21
+
22
+ import {
23
+ NAVIGATION_ITEMS,
24
+ URL_CONFIG,
25
+ REFERENCE_TYPES,
26
+ oldReferenceIdNewReferenceIdMap
27
+ } from "./constants";
28
+
29
+ export default class AmfReference extends LightningElement {
30
+ @api breadcrumbs?: string | null | undefined = null;
31
+ @api sidebarHeader!: string;
32
+ @api coveoOrganizationId!: string;
33
+ @api coveoPublicAccessToken!: string;
34
+ @api coveoAdvancedQueryConfig!: string;
35
+ @api coveoSearchHub!: string;
36
+ @api useOldSidebar?: boolean = false;
37
+ @api tocTitle?: string;
38
+ @api tocOptions?: string;
39
+ @track navigation = [];
40
+ @track versions: Array<ReferenceVersion> = [];
41
+
42
+ // Update this to update what component gets rendered in the content block
43
+ @track
44
+ protected topicModel!: TopicModel;
45
+
46
+ get isVersionEnabled(): boolean {
47
+ return !!this._referenceSetConfig?.versions?.length;
48
+ }
49
+
50
+ /**
51
+ * Gives if the currently selected reference is spec based or not
52
+ */
53
+ get showSpecBasedReference(): boolean {
54
+ return this.isSpecBasedReference(this._currentReferenceId);
55
+ }
56
+
57
+ @api
58
+ get referenceSetConfig(): ReferenceSetConfig {
59
+ return this._referenceSetConfig;
60
+ }
61
+
62
+ set referenceSetConfig(value: ReferenceSetConfig) {
63
+ // No change, do nothing.
64
+ if (value === this._referenceSetConfig) {
65
+ return;
66
+ }
67
+
68
+ try {
69
+ const refConfig =
70
+ typeof value === "string" ? JSON.parse(value) : value;
71
+ if (!(<ReferenceSetConfig>refConfig).versions) {
72
+ return;
73
+ }
74
+ this._referenceSetConfig = refConfig;
75
+ } catch (e) {
76
+ this._referenceSetConfig = {
77
+ refList: [],
78
+ versions: []
79
+ };
80
+ }
81
+
82
+ this._amfConfigList = this._referenceSetConfig.refList || [];
83
+
84
+ this._amfConfigList.forEach((amfConfig) => {
85
+ this._amfConfigMap.set(amfConfig.id, amfConfig);
86
+ });
87
+
88
+ if (this._amfConfigList.length > 0) {
89
+ this._currentReferenceId =
90
+ this._referenceSetConfig.refId || this._amfConfigList[0].id;
91
+ }
92
+
93
+ if (this.isVersionEnabled) {
94
+ const selectedVersion = this.getSelectedVersion();
95
+
96
+ /**
97
+ * If current selected is markdown based reference,
98
+ * We will assign versions once the selected item url is updated
99
+ */
100
+ if (this.isSpecBasedReference(this._currentReferenceId)) {
101
+ this.versions = this.getVersions();
102
+ }
103
+ this.selectedVersion = selectedVersion;
104
+ }
105
+
106
+ // This is to check if the url is hash based and redirect if needed
107
+ const redirectUrl = this.getHashBasedRedirectUrl();
108
+ if (redirectUrl) {
109
+ window.location.href = redirectUrl;
110
+ } else {
111
+ this.updateAmfConfigInView();
112
+ }
113
+ }
114
+
115
+ @api
116
+ get docPhaseInfo() {
117
+ return this.selectedReferenceDocPhase;
118
+ }
119
+
120
+ set docPhaseInfo(value: string) {
121
+ if (value) {
122
+ this.isParentLevelDocPhaseEnabled = true;
123
+ this.selectedReferenceDocPhase = value;
124
+ }
125
+ }
126
+
127
+ @api
128
+ get expandChildren() {
129
+ return this._expandChildren;
130
+ }
131
+
132
+ set expandChildren(value) {
133
+ this._expandChildren = normalizeBoolean(value);
134
+ }
135
+
136
+ // model
137
+ protected _amfConfigList: AmfConfig[] = [];
138
+ protected _amfConfigMap: Map<string, AmfConfig> = new Map();
139
+ protected _referenceSetConfig!: ReferenceSetConfig;
140
+ protected _currentReferenceId = "";
141
+ protected _scrollInterval = 0;
142
+
143
+ protected parentReferenceUrls = [];
144
+ protected amfMap: Record<string, AmfModelRecord> = {};
145
+ protected amfFetchPromiseMap = {};
146
+ protected metadata: { [key: string]: AmfMetadataTopic } = {};
147
+ protected selectedTopic?: AmfMetaTopicType = undefined;
148
+ protected selectedSidebarValue = undefined;
149
+
150
+ protected selectedVersion: ReferenceVersion | null = null;
151
+
152
+ private hasRendered = false;
153
+
154
+ private isParentLevelDocPhaseEnabled = false;
155
+ private selectedReferenceDocPhase?: string | null = null;
156
+ private _expandChildren?: boolean = false;
157
+
158
+ /**
159
+ * Key for storing the currently selected reference url. This will be used to save the
160
+ * previously selected reference url and restoring it when changing between reference versions.
161
+ */
162
+ private readonly docsReferenceUrlSessionKey: string = "docsReferenceUrl";
163
+
164
+ _boundOnApiNavigationChanged;
165
+ _boundUpdateSelectedItemFromUrlQuery;
166
+
167
+ constructor() {
168
+ super();
169
+
170
+ this._boundOnApiNavigationChanged =
171
+ this.onApiNavigationChanged.bind(this);
172
+ this._boundUpdateSelectedItemFromUrlQuery =
173
+ this.updateSelectedItemFromUrlQuery.bind(this);
174
+ }
175
+
176
+ connectedCallback(): void {
177
+ this.addEventListener(
178
+ "api-navigation-selection-changed",
179
+ this._boundOnApiNavigationChanged
180
+ );
181
+ window.addEventListener(
182
+ "popstate",
183
+ this._boundUpdateSelectedItemFromUrlQuery
184
+ );
185
+ this._scrollInterval = window.setInterval(() => {
186
+ this.saveScroll();
187
+ }, 1000);
188
+ }
189
+
190
+ disconnectedCallback(): void {
191
+ this.removeEventListener(
192
+ "api-navigation-selection-changed",
193
+ this._boundOnApiNavigationChanged
194
+ );
195
+ window.removeEventListener(
196
+ "popstate",
197
+ this._boundUpdateSelectedItemFromUrlQuery
198
+ );
199
+ window.clearInterval(this._scrollInterval);
200
+ }
201
+
202
+ saveScroll() {
203
+ window.history.replaceState(
204
+ { scrollValue: document.body.scrollTop },
205
+ "",
206
+ window.location.href
207
+ );
208
+ }
209
+
210
+ restoreScroll() {
211
+ document.body.scrollTop = document.documentElement.scrollTop =
212
+ window.history.state?.scrollValue;
213
+ }
214
+
215
+ renderedCallback(): void {
216
+ if (!this.hasRendered) {
217
+ this.hasRendered = true;
218
+ if (this._amfConfigList && this._amfConfigList.length) {
219
+ // If amfConfig has a value and length, it is assumed that fetch
220
+ // has already been called and promises stored.
221
+ this.updateView();
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Check if the URL hash to see whether this is one we want to redirect
228
+ * See GUS W-10718771 for references where we want hash-based redirects
229
+ * Return if we needs to redirect url to updated url
230
+ */
231
+ private getHashBasedRedirectUrl(): string | undefined {
232
+ const { hash } = window.location;
233
+ let hashBasedRedirectUrl = "";
234
+ if (hash) {
235
+ const strippedHash = hash.startsWith("#") ? hash.slice(1) : hash;
236
+ const strippedHashItems = strippedHash
237
+ ? strippedHash.split(":")
238
+ : [];
239
+ if (strippedHashItems.length) {
240
+ const referenceId = strippedHashItems[0];
241
+ const meta = strippedHashItems[1];
242
+ const encodedMeta = this.getUrlEncoded(meta);
243
+ const updatedReferenceId =
244
+ oldReferenceIdNewReferenceIdMap[referenceId];
245
+ const newReferenceId = updatedReferenceId || referenceId;
246
+ const referenceItemConfig =
247
+ this.getAmfConfigWithId(newReferenceId);
248
+ if (referenceItemConfig) {
249
+ hashBasedRedirectUrl = `${referenceItemConfig.href}?meta=${encodedMeta}`;
250
+ }
251
+ }
252
+ }
253
+ return hashBasedRedirectUrl;
254
+ }
255
+
256
+ /**
257
+ * @param referenceId
258
+ * @returns AMFConfig with given reference Id
259
+ */
260
+ private getAmfConfigWithId(referenceId: string): AmfConfig | undefined {
261
+ return this._amfConfigMap.get(referenceId);
262
+ }
263
+
264
+ /**
265
+ * @param referenceId
266
+ * @returns if the reference is spec based one or not with given referenceId.
267
+ */
268
+ private isSpecBasedReference(referenceId: string): boolean {
269
+ const selectedReference = this.getAmfConfigWithId(referenceId);
270
+ return selectedReference
271
+ ? selectedReference.referenceType !== REFERENCE_TYPES.markdown
272
+ : false;
273
+ }
274
+
275
+ /*
276
+ * Refactor below method when sidebar allows sending extraData along with the name for each item.
277
+ * See if we can refactor the below method using regex.
278
+ */
279
+
280
+ /**
281
+ * @param url
282
+ * @returns reference Id from url path / selected sidebar item.
283
+ */
284
+ private getReferenceIdFromUrl(url: string): string {
285
+ let referenceId = "";
286
+ const urlItems = url.split("/references/");
287
+ if (urlItems.length > 1) {
288
+ const rightSidePart = urlItems[1];
289
+
290
+ //This covers urls like "/project-name/references/reference-id/..."
291
+ const slashSeparatorItems = rightSidePart.split("/");
292
+
293
+ //This covers urls like "/project-name/references/reference-id?meta=Summary"
294
+ const querySeparatorItems = slashSeparatorItems[0].split("?");
295
+
296
+ referenceId = querySeparatorItems[0];
297
+ }
298
+
299
+ return referenceId;
300
+ }
301
+
302
+ /**
303
+ * @returns versions to be shown in the dropdown
304
+ * For markdown based specs, Adds selected markdown topic url to same references
305
+ */
306
+ private getVersions(): Array<ReferenceVersion> {
307
+ const allVersions = this._referenceSetConfig.versions;
308
+ if (!this.isSpecBasedReference(this._currentReferenceId)) {
309
+ const currentRefMeta = this.getMarkdownReferenceMeta(
310
+ window.location.href
311
+ );
312
+ if (currentRefMeta) {
313
+ for (let i = 0; i < allVersions.length; i++) {
314
+ const versionItem = allVersions[i];
315
+ const referenceLink = versionItem.link.href;
316
+ const referenceId =
317
+ this.getReferenceIdFromUrl(referenceLink);
318
+ if (this._currentReferenceId === referenceId) {
319
+ // This is to navigate to respective topic in the changed version
320
+ versionItem.link.href = `${referenceLink}/${currentRefMeta}`;
321
+ allVersions[i] = versionItem;
322
+ }
323
+ }
324
+ }
325
+ }
326
+ return allVersions;
327
+ }
328
+
329
+ /**
330
+ * Returns the selected version or the first available version.
331
+ */
332
+ private getSelectedVersion(): ReferenceVersion {
333
+ const versions = this._referenceSetConfig?.versions || [];
334
+ const selectedVersion = versions.find(
335
+ (v: ReferenceVersion) => v.selected
336
+ );
337
+ // return a selected version if there is one, else return the first one.
338
+ return selectedVersion || (versions.length && versions[0]);
339
+ }
340
+
341
+ private updateAmfConfigInView(): void {
342
+ if (this._amfConfigList && this._amfConfigList.length) {
343
+ // fetch AMF Json as soon as config is set
344
+ this.populateReferenceItems();
345
+ // update() must be called after renderedCallback.
346
+ if (this.hasRendered) {
347
+ this.updateView();
348
+ }
349
+ }
350
+ }
351
+
352
+ private async fetchAmf(amfConfig): Promise<AmfModel | AmfModel[]> {
353
+ const { amf } = amfConfig;
354
+ const response = await fetch(amf, {
355
+ headers: {
356
+ "Cache-Control": `max-age=86400`
357
+ }
358
+ });
359
+ const json = await response.json();
360
+ return json;
361
+ }
362
+
363
+ /**
364
+ * Returns whether current location is project root path like ../example-project/references
365
+ */
366
+ private isProjectRootPath(): boolean {
367
+ return this.getReferenceIdFromUrl(window.location.href) === "";
368
+ }
369
+
370
+ /**
371
+ * Returns whether given url is parent reference path like ../example-project/references/reference-id
372
+ */
373
+ private isParentReferencePath(urlPath: string): boolean {
374
+ if (!urlPath) {
375
+ return false;
376
+ }
377
+ const parentReferenceIndex = this.parentReferenceUrls.findIndex(
378
+ (referenceUrl: string) => {
379
+ return urlPath.endsWith(referenceUrl);
380
+ }
381
+ );
382
+ return parentReferenceIndex !== -1;
383
+ }
384
+
385
+ /**
386
+ * Expands the children of Markdown-based References.
387
+ */
388
+ private expandChildrenForMarkdownReferences(
389
+ children: ParsedMarkdownTopic[]
390
+ ): void {
391
+ if (!children) {
392
+ return;
393
+ }
394
+ for (const childNode of children) {
395
+ childNode.isExpanded = true;
396
+ this.expandChildrenForMarkdownReferences(childNode.children);
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Populates reference Items from amfConfigList and assigns it to navigation for sidebar
402
+ */
403
+ private populateReferenceItems(): void {
404
+ const navAmfOrder = [];
405
+ for (const [index, amfConfig] of this._amfConfigList.entries()) {
406
+ let navItemChildren = [];
407
+ let isChildrenLoading = false;
408
+ if (amfConfig.referenceType !== REFERENCE_TYPES.markdown) {
409
+ if (amfConfig.isSelected) {
410
+ const amfPromise = this.fetchAmf(amfConfig).then(
411
+ (amfJson) => {
412
+ this.updateModel(amfConfig.id, amfJson);
413
+ this.assignNavigationItemsFromAmf(amfConfig, index);
414
+ }
415
+ );
416
+ this.amfFetchPromiseMap[amfConfig.id] = amfPromise;
417
+ }
418
+ isChildrenLoading = true;
419
+ } else {
420
+ const isExpandChildrenEnabled = this.isExpandChildrenEnabled(
421
+ amfConfig.id
422
+ );
423
+ // check whether we should expand all the child nodes, this is required for Coveo to crawl.
424
+ if (isExpandChildrenEnabled) {
425
+ this.expandChildrenForMarkdownReferences(
426
+ amfConfig.topic.children
427
+ );
428
+ }
429
+ navItemChildren = amfConfig.topic.children;
430
+ }
431
+ // store nav items for each spec in order
432
+ navAmfOrder[index] = {
433
+ label: amfConfig.title,
434
+ name: amfConfig.href,
435
+ isExpanded:
436
+ amfConfig.isSelected ||
437
+ this.isExpandChildrenEnabled(amfConfig.id),
438
+ children: navItemChildren,
439
+ isChildrenLoading
440
+ };
441
+ this.parentReferenceUrls.push(amfConfig.href);
442
+ }
443
+ this.navigation = navAmfOrder;
444
+ }
445
+
446
+ /**
447
+ * Returns a boolean indicating whether the children should be expanded or not.
448
+ */
449
+ private isExpandChildrenEnabled(referenceId: string): boolean {
450
+ return this.expandChildren && this._currentReferenceId === referenceId;
451
+ }
452
+
453
+ /**
454
+ * Stores fetched AMF JSON value.
455
+ * Creates and stores a new AmfModelParser instance for the AMF spec.
456
+ * @param {*} referenceId
457
+ * @param {*} amf
458
+ */
459
+ private updateModel(referenceId: string, amf: AmfModel | AmfModel[]): void {
460
+ const parser = new AmfModelParser(amf);
461
+ this.amfMap[referenceId] = {
462
+ model: amf,
463
+ parser: parser,
464
+ parsedModel: parser.parsedModel
465
+ };
466
+ }
467
+
468
+ /**
469
+ * Convert any case to a readable Title
470
+ * ex: snake_case => Snake case
471
+ * ex: camelCase => Camel case
472
+ * ex: PascalCase => Pascal case
473
+ * @param label
474
+ * @returns string
475
+ */
476
+ private getTitleForLabel(label: string): string {
477
+ return sentenceCase(noCase(label));
478
+ }
479
+
480
+ /**
481
+ * Transforms a list of model data for endpoints into corresponding
482
+ * navigation list items that are compatible with dx-sidebar.
483
+ * Compatible with transforming AMF data parsed from both RAML and OAS spec.
484
+ * Transforms a flat list of endpoints into a nested list based on indentation level
485
+ * for RAML spec.
486
+ * @param {Array<Object>} items An array of endpoints.
487
+ * @returns {array<Object>} List of navigation items
488
+ */
489
+ private assignEndpointNavItems(
490
+ parentReferencePath: string,
491
+ referenceId: string,
492
+ items: ParsedTopicModel[]
493
+ ): NavItem[] {
494
+ const methodList = [];
495
+
496
+ items.forEach((item) => {
497
+ item.methods?.forEach((method) => {
498
+ const title =
499
+ this.getTitleForLabel(method.label) || method.method;
500
+ const meta = this.addToMetadata(
501
+ parentReferencePath,
502
+ referenceId,
503
+ "method",
504
+ method,
505
+ title
506
+ );
507
+ methodList.push(
508
+ Object.assign(method, {
509
+ name: this.getReferencePathWithMeta(
510
+ parentReferencePath,
511
+ meta
512
+ ),
513
+ label: title
514
+ })
515
+ );
516
+ });
517
+ });
518
+ return methodList;
519
+ }
520
+
521
+ /**
522
+ * Gives URL path for reference items, Which can be used to push to the history
523
+ */
524
+ private getReferencePathWithMeta(
525
+ parentReferencePath: string,
526
+ meta: string
527
+ ): string {
528
+ // update the encoded url meta param
529
+ const encodedMeta = meta ? this.getUrlEncoded(meta) : "";
530
+ return encodedMeta ? `${parentReferencePath}?meta=${encodedMeta}` : "";
531
+ }
532
+
533
+ /**
534
+ * Assigns Navigation Items to dx-sidebar from the parsed AMF model.
535
+ * Adds each nav item to the Metadata by type.
536
+ * The 'summary' nav item has no children.
537
+ * The 'endpoint' nav item may have nested children.
538
+ */
539
+ private assignNavigationItemsFromAmf(
540
+ amfConfig: AmfConfig,
541
+ amfIdx: number
542
+ ): void {
543
+ const referenceId = amfConfig.id;
544
+ const parentReferencePath = amfConfig.href;
545
+ const model = this.amfMap[referenceId].parser.parsedModel;
546
+
547
+ const children = [];
548
+ const expandChildren = this.isExpandChildrenEnabled(referenceId);
549
+
550
+ NAVIGATION_ITEMS.forEach(
551
+ ({ label, name, childrenPropertyName, type }) => {
552
+ const indexedName = `${name}-${amfIdx}`;
553
+ switch (type) {
554
+ case "summary": {
555
+ const summary = model[type];
556
+ const meta = this.addToMetadata(
557
+ parentReferencePath,
558
+ referenceId,
559
+ type,
560
+ summary,
561
+ label
562
+ );
563
+ children.push({
564
+ label,
565
+ name: this.getReferencePathWithMeta(
566
+ parentReferencePath,
567
+ meta
568
+ )
569
+ });
570
+ break;
571
+ }
572
+ case "endpoint":
573
+ if (
574
+ model[childrenPropertyName] &&
575
+ model[childrenPropertyName].length
576
+ ) {
577
+ const amfTopicId = this.getFormattedIdentifier(
578
+ referenceId,
579
+ indexedName
580
+ );
581
+ const childTopics = this.assignEndpointNavItems(
582
+ parentReferencePath,
583
+ referenceId,
584
+ model[childrenPropertyName]
585
+ );
586
+ children.push({
587
+ label,
588
+ name: this.getReferencePathWithMeta(
589
+ parentReferencePath,
590
+ this.metadata[amfTopicId]?.meta
591
+ ),
592
+ isExpanded: expandChildren,
593
+ children: childTopics
594
+ });
595
+ }
596
+ break;
597
+ case "security":
598
+ case "type":
599
+ if (model[childrenPropertyName]?.length) {
600
+ // Sorting the types alphabetically
601
+ model[childrenPropertyName].sort((typeA, typeB) => {
602
+ const typeALbl = typeA.label.toLowerCase();
603
+ const typeBLbl = typeB.label.toLowerCase();
604
+ return typeALbl < typeBLbl
605
+ ? -1
606
+ : typeALbl > typeBLbl
607
+ ? 1
608
+ : 0;
609
+ });
610
+ }
611
+ // eslint-disable-next-line no-fallthrough
612
+ default:
613
+ if (
614
+ model[childrenPropertyName] &&
615
+ model[childrenPropertyName].length
616
+ ) {
617
+ const amfTopicId = this.getFormattedIdentifier(
618
+ referenceId,
619
+ indexedName
620
+ );
621
+ children.push({
622
+ label,
623
+ name: this.getReferencePathWithMeta(
624
+ parentReferencePath,
625
+ this.metadata[amfTopicId]?.meta
626
+ ),
627
+ isExpanded: expandChildren,
628
+ children: model[childrenPropertyName].map(
629
+ (topic) => {
630
+ const meta = this.addToMetadata(
631
+ parentReferencePath,
632
+ referenceId,
633
+ type,
634
+ topic,
635
+ topic.label
636
+ );
637
+ return {
638
+ label: topic.label,
639
+ name: this.getReferencePathWithMeta(
640
+ parentReferencePath,
641
+ meta
642
+ )
643
+ };
644
+ }
645
+ )
646
+ });
647
+ }
648
+ }
649
+ }
650
+ );
651
+
652
+ this.navigation[amfIdx] = {
653
+ ...this.navigation[amfIdx],
654
+ children,
655
+ isChildrenLoading: false
656
+ };
657
+ this.navigation = [...this.navigation];
658
+ }
659
+
660
+ protected addToMetadata(
661
+ parentReferencePath: string,
662
+ referenceId: string,
663
+ type: string,
664
+ topic: { id: string; domId: string },
665
+ navTitle: string
666
+ ): string | undefined {
667
+ const { urlIdentifer, prefix } = URL_CONFIG[type];
668
+
669
+ // encodeURI to avoid special characters in the URL meta.
670
+ const identifier =
671
+ topic[urlIdentifer] && this.encodeIdentifier(topic[urlIdentifer]);
672
+ let meta;
673
+ // Assuming that there will be an identifier always
674
+ if (identifier) {
675
+ meta = prefix ? `${prefix}${identifier}` : `${identifier}`;
676
+ this.metadata[meta] = {
677
+ parentReferencePath,
678
+ meta,
679
+ referenceId,
680
+ amfId: topic.id,
681
+ elementId: topic.domId,
682
+ identifier,
683
+ type,
684
+ navTitle
685
+ };
686
+ }
687
+ return meta;
688
+ }
689
+
690
+ /**
691
+ * Returns metadata given route meta
692
+ */
693
+ protected getMetadataByUrlQuery(routeMeta: RouteMeta): AmfMetadataTopic {
694
+ return Object.values(this.metadata).find(
695
+ (metadata: AmfMetadataTopic) => {
696
+ return routeMeta.meta === metadata.meta;
697
+ }
698
+ );
699
+ }
700
+
701
+ /**
702
+ * Returns metadata given reference ID and topic amf ID
703
+ */
704
+ protected getMetadataByAmfId(
705
+ referenceId: string,
706
+ amfId: string
707
+ ): AmfMetadataTopic {
708
+ // Lets make a map based on the hash values so we don't need to loop like this.
709
+ return Object.values(this.metadata).find(
710
+ (metadata: AmfMetadataTopic) =>
711
+ referenceId === metadata.referenceId && amfId === metadata.amfId
712
+ );
713
+ }
714
+
715
+ /**
716
+ * Returns metadata given reference ID and topic identifier
717
+ */
718
+ protected getMetadataByIdentifier(
719
+ referenceId: string,
720
+ identifier: string
721
+ ): AmfMetadataTopic {
722
+ // Lets make a map based on the hash values so we don't need to loop like this.
723
+ return Object.values(this.metadata).find(
724
+ (metadata: AmfMetadataTopic) =>
725
+ referenceId === metadata.referenceId &&
726
+ identifier === metadata.identifier
727
+ );
728
+ }
729
+
730
+ /**
731
+ * Returns metadata given reference ID and topic type
732
+ */
733
+ protected getMetadataByType(
734
+ referenceId: string,
735
+ type: string
736
+ ): AmfMetadataTopic {
737
+ // Lets make a map based on the hash values so we don't need to loop like this.
738
+ return Object.values(this.metadata).find(
739
+ (metadata: AmfMetadataTopic) =>
740
+ referenceId === metadata.referenceId && type === metadata.type
741
+ );
742
+ }
743
+
744
+ /**
745
+ * Parses URL query params without decoding of params
746
+ */
747
+ private parseParams(params: string): qs.ParsedQuery<string> {
748
+ if (!params) {
749
+ return {};
750
+ }
751
+ return qs.parse(params, {
752
+ decode: false
753
+ });
754
+ }
755
+
756
+ /**
757
+ * Normalizes topic identifier by replacing spaces with '+'
758
+ * and running encodeURI() on it
759
+ * @param identifier raw identifier for a topic as parsed from the spec file
760
+ * @returns normalized and encoded identifier
761
+ */
762
+ protected encodeIdentifier(identifier: string): string {
763
+ let result = identifier.trim();
764
+ result = result.replace(new RegExp(/\s+/, "g"), "+");
765
+ return encodeURI(result);
766
+ }
767
+
768
+ /**
769
+ * Constructs a Reference Topic ID
770
+ */
771
+ protected getFormattedIdentifier(referenceId: string, id: string): string {
772
+ return `${referenceId}:${id}`;
773
+ }
774
+
775
+ protected updateUrlWithSelected(
776
+ parentReferencePath: string,
777
+ meta?: string
778
+ ): void {
779
+ if (meta) {
780
+ // update the encoded url meta param
781
+ const encodedMeta = this.getUrlEncoded(meta);
782
+
783
+ window.history.pushState(
784
+ {},
785
+ "",
786
+ `${parentReferencePath}?meta=${encodedMeta}`
787
+ );
788
+ }
789
+ }
790
+
791
+ /**
792
+ * Does a replace on the URL meta, so it does not create a history entry.
793
+ */
794
+ protected replaceUrlWithSelected(
795
+ parentReferencePath: string,
796
+ meta?: string
797
+ ): void {
798
+ if (meta) {
799
+ // update the encoded url meta param
800
+ const encodedMeta = this.getUrlEncoded(meta);
801
+ window.history.replaceState(
802
+ window.history.state,
803
+ "",
804
+ `${parentReferencePath}?meta=${encodedMeta}`
805
+ );
806
+ }
807
+ }
808
+
809
+ /**
810
+ * This method gets called when the user navigates back and forth using browser arrows
811
+ * Updates content depending on the type of reference - spec based or markdown
812
+ */
813
+ protected updateSelectedItemFromUrlQuery(): void {
814
+ const specBasedReference = this.isSpecBasedReference(
815
+ this._currentReferenceId
816
+ );
817
+ if (specBasedReference) {
818
+ const currentMeta: RouteMeta | null = this.getReferenceMetaInfo(
819
+ window.location.href
820
+ );
821
+ const metadata =
822
+ currentMeta && this.getMetadataByUrlQuery(currentMeta);
823
+ if (metadata) {
824
+ const {
825
+ parentReferencePath,
826
+ referenceId,
827
+ amfId,
828
+ type,
829
+ elementId
830
+ }: AmfMetadataTopic = metadata;
831
+ this.loadSpecReferenceContent(
832
+ parentReferencePath,
833
+ referenceId,
834
+ amfId,
835
+ type,
836
+ elementId,
837
+ currentMeta.meta
838
+ );
839
+ }
840
+ } else {
841
+ this.loadMarkdownBasedReference();
842
+ }
843
+
844
+ this.restoreScroll();
845
+ }
846
+
847
+ /**
848
+ * The API Navigation event will always intend to navigate within the current reference
849
+ * @param event
850
+ */
851
+ protected onApiNavigationChanged(): void {
852
+ this.saveScroll();
853
+
854
+ const specBasedReference = this.isSpecBasedReference(
855
+ this._currentReferenceId
856
+ );
857
+ if (specBasedReference) {
858
+ const { meta } = this.selectedTopic;
859
+ const metadata = this.metadata[meta];
860
+ if (metadata) {
861
+ const {
862
+ parentReferencePath,
863
+ referenceId,
864
+ amfId,
865
+ type,
866
+ elementId
867
+ }: AmfMetadataTopic = metadata;
868
+ this.loadSpecReferenceContent(
869
+ parentReferencePath,
870
+ referenceId,
871
+ amfId,
872
+ type,
873
+ elementId,
874
+ metadata.meta
875
+ );
876
+ }
877
+ } else {
878
+ this.loadMarkdownBasedReference();
879
+ }
880
+ }
881
+
882
+ /**
883
+ * Updates the currently selected amf and topic
884
+ */
885
+ protected loadSpecReferenceContent(
886
+ parentReferencePath: string,
887
+ referenceId: string,
888
+ amfId: string,
889
+ type: string,
890
+ elementId: string,
891
+ meta: string
892
+ ): void {
893
+ this.selectedTopic = {
894
+ referenceId,
895
+ parentReferencePath,
896
+ amfId,
897
+ elementId,
898
+ type,
899
+ meta
900
+ };
901
+ this.selectedSidebarValue = this.getReferencePathWithMeta(
902
+ parentReferencePath,
903
+ meta
904
+ );
905
+
906
+ this.handleSelectedItem();
907
+
908
+ this.updateDocPhase();
909
+ }
910
+
911
+ /**
912
+ * Updates doc phase of selected reference
913
+ */
914
+ updateDocPhase(): void {
915
+ /* If parent level doc phase is enabled, Individual reference level doc phase should not be considered */
916
+
917
+ if (!this.isParentLevelDocPhaseEnabled) {
918
+ const selectedReference = this._amfConfigList.find(
919
+ (referenceItem: AmfConfig) => {
920
+ return referenceItem.id === this._currentReferenceId;
921
+ }
922
+ );
923
+ if (selectedReference) {
924
+ this.selectedReferenceDocPhase = JSON.stringify(
925
+ selectedReference.docPhase
926
+ );
927
+ }
928
+ }
929
+ }
930
+
931
+ /**
932
+ * Returns the decoded meta query param from given Url as it is being used internally.
933
+ */
934
+ getMetaFromUrl(referenceUrl: string): string {
935
+ const indexOfQueryParam = referenceUrl.indexOf("?");
936
+ const urlPath = referenceUrl.substring(
937
+ indexOfQueryParam >= 0 ? indexOfQueryParam : referenceUrl.length
938
+ );
939
+ const meta = this.parseParams(urlPath).meta as string;
940
+ // Always get the meta query param encoded and decode it and store it for internal use
941
+ // This has 2 advantages,
942
+ // 1. Supports backward compatible meta query param, so there is no need for redirects.
943
+ // 2. Supports Prerender and Coveo for their crawling.
944
+ const encodedMeta = meta && this.getUrlEncoded(meta);
945
+ const decodedMeta = encodedMeta && decodeURIComponent(encodedMeta);
946
+ return decodedMeta || "";
947
+ }
948
+
949
+ /**
950
+ *
951
+ * @returns meta for given markdown based referenceUrl
952
+ * Consider last topic url in ../references/reference-name/example.html
953
+ */
954
+ getMarkdownReferenceMeta(referenceUrl: string): string {
955
+ let meta = "";
956
+ if (referenceUrl) {
957
+ const slashSeparatorItems = referenceUrl.split("/");
958
+ const lastItem =
959
+ slashSeparatorItems[slashSeparatorItems.length - 1];
960
+ if (lastItem.endsWith(".html")) {
961
+ meta = lastItem;
962
+ }
963
+ }
964
+ return meta;
965
+ }
966
+
967
+ /**
968
+ * Gets the encoded url.
969
+ * This method will return the encoded url for 2 cases,
970
+ * 1. If the url is encoded already
971
+ * 2. If the url is decoded
972
+ */
973
+ getUrlEncoded(url: string) {
974
+ // if url matches, then return the encoded url.
975
+ if (decodeURIComponent(url) === url) {
976
+ return encodeURIComponent(url);
977
+ }
978
+ // return the encoded url.
979
+ return this.getUrlEncoded(decodeURIComponent(url));
980
+ }
981
+
982
+ /**
983
+ *
984
+ * @returns RouteMeta object for given referenceUrl
985
+ * referenceId - gets referenceId from url
986
+ * For spec based references gets meta parm from url and then topicId & type from meta
987
+ * For markdown based references gets topicId as last html path in the name, meta & type will be empty
988
+ */
989
+ getReferenceMetaInfo(referenceUrl: string): RouteMeta | undefined {
990
+ let metaReferenceInfo;
991
+ if (referenceUrl) {
992
+ const referenceId = this.getReferenceIdFromUrl(referenceUrl);
993
+ let meta = "";
994
+ let topicId = "";
995
+ let type = "";
996
+ if (this.isSpecBasedReference(referenceId)) {
997
+ meta = this.getMetaFromUrl(referenceUrl);
998
+ if (meta) {
999
+ if (meta.includes(":")) {
1000
+ const metaInfo = meta.split(":");
1001
+ type = metaInfo[0];
1002
+ topicId = metaInfo[1] || type;
1003
+ } else {
1004
+ topicId = meta;
1005
+ }
1006
+ }
1007
+ } else {
1008
+ topicId = this.getMarkdownReferenceMeta(referenceUrl);
1009
+ }
1010
+ metaReferenceInfo = {
1011
+ referenceId,
1012
+ meta,
1013
+ topicId,
1014
+ type
1015
+ };
1016
+ }
1017
+ return metaReferenceInfo;
1018
+ }
1019
+
1020
+ /**
1021
+ * Finds and returns referenceUrl and topicTitle if given topic url matches
1022
+ */
1023
+ getReferenceDetailsInGivenTopics(
1024
+ topics: ParsedMarkdownTopic[],
1025
+ topicMeta: string
1026
+ ): { referenceUrl: string; topicTitle: string } {
1027
+ let referenceUrl = "";
1028
+ let topicTitle = "";
1029
+ for (let i = 0; i < topics.length; i++) {
1030
+ const topic = topics[i];
1031
+ const meta = this.getMarkdownReferenceMeta(topic.link.href);
1032
+ const childTopics = topic.children;
1033
+ if (meta === topicMeta) {
1034
+ referenceUrl = topic.link.href;
1035
+ topicTitle = topic.label;
1036
+ } else if (childTopics && childTopics.length) {
1037
+ const referenceDetails = this.getReferenceDetailsInGivenTopics(
1038
+ childTopics,
1039
+ topicMeta
1040
+ );
1041
+ referenceUrl = referenceDetails.referenceUrl;
1042
+ topicTitle = referenceDetails.topicTitle;
1043
+ }
1044
+ if (referenceUrl && topicTitle) {
1045
+ break;
1046
+ }
1047
+ }
1048
+ return {
1049
+ referenceUrl,
1050
+ topicTitle
1051
+ };
1052
+ }
1053
+
1054
+ /**
1055
+ * Gives referenceUrl and topicTitle for given markdown topic url
1056
+ */
1057
+ getRefDetailsForGivenTopicMeta(
1058
+ referenceId: string,
1059
+ topicMeta: string
1060
+ ): { referenceUrl: string; topicTitle: string } | undefined {
1061
+ const amfConfig = this.getAmfConfigWithId(referenceId);
1062
+ let referenceDetails;
1063
+ if (amfConfig) {
1064
+ const topics = amfConfig.topic?.children || [];
1065
+ referenceDetails = this.getReferenceDetailsInGivenTopics(
1066
+ topics,
1067
+ topicMeta
1068
+ );
1069
+ }
1070
+ return referenceDetails;
1071
+ }
1072
+
1073
+ /**
1074
+ * Updates the DOM on the first load
1075
+ */
1076
+ updateView(): void {
1077
+ const previousRefUrlInSession = window.sessionStorage.getItem(
1078
+ this.docsReferenceUrlSessionKey
1079
+ );
1080
+ window.sessionStorage.removeItem(this.docsReferenceUrlSessionKey);
1081
+ let previousRefInfo = this.getReferenceMetaInfo(
1082
+ previousRefUrlInSession
1083
+ );
1084
+
1085
+ // For spec based reference, We should consider urlData to show same topic when user reloads after navigating to specific topic
1086
+ if (!previousRefInfo) {
1087
+ const currentUrl = window.location.href;
1088
+ const urlReferenceId = this.getReferenceIdFromUrl(currentUrl);
1089
+ if (urlReferenceId && this.isSpecBasedReference(urlReferenceId)) {
1090
+ if (
1091
+ !this.isProjectRootPath() &&
1092
+ !this.isParentReferencePath(currentUrl)
1093
+ ) {
1094
+ previousRefInfo = this.getReferenceMetaInfo(currentUrl);
1095
+ }
1096
+ }
1097
+ }
1098
+
1099
+ let referenceId: string;
1100
+ let topicId = "";
1101
+
1102
+ if (
1103
+ previousRefInfo &&
1104
+ this._amfConfigMap.has(previousRefInfo.referenceId)
1105
+ ) {
1106
+ referenceId = previousRefInfo.referenceId;
1107
+ topicId = previousRefInfo.topicId;
1108
+ } else {
1109
+ referenceId = this._currentReferenceId;
1110
+ }
1111
+
1112
+ const specBasedReference = this.isSpecBasedReference(referenceId);
1113
+ if (specBasedReference) {
1114
+ // Wait till the AMF is loaded.
1115
+ this.amfFetchPromiseMap[referenceId].then(() => {
1116
+ let selectedItemMetaData = this.getMetadataByIdentifier(
1117
+ referenceId,
1118
+ topicId
1119
+ );
1120
+ if (!selectedItemMetaData) {
1121
+ // Doesn't exist, let's use the summary.
1122
+ selectedItemMetaData = this.getMetadataByType(
1123
+ referenceId,
1124
+ "summary"
1125
+ );
1126
+ }
1127
+
1128
+ if (selectedItemMetaData) {
1129
+ this.loadSpecReferenceContent(
1130
+ selectedItemMetaData.parentReferencePath,
1131
+ selectedItemMetaData.referenceId,
1132
+ selectedItemMetaData.amfId,
1133
+ selectedItemMetaData.type,
1134
+ "",
1135
+ selectedItemMetaData.meta
1136
+ );
1137
+ this.updateUrlWithSelected(
1138
+ selectedItemMetaData.parentReferencePath,
1139
+ selectedItemMetaData.meta
1140
+ );
1141
+ this.updateNavTitleMetaTag(selectedItemMetaData.navTitle);
1142
+ }
1143
+ });
1144
+ } else {
1145
+ let invalidTopicReferenceUrl = "";
1146
+ if (topicId) {
1147
+ const referenceDetails = this.getRefDetailsForGivenTopicMeta(
1148
+ referenceId,
1149
+ topicId
1150
+ );
1151
+ const selectedItemUrl = referenceDetails?.referenceUrl;
1152
+ if (!selectedItemUrl) {
1153
+ invalidTopicReferenceUrl = previousRefUrlInSession;
1154
+ }
1155
+ }
1156
+ this.loadMarkdownBasedReference(invalidTopicReferenceUrl);
1157
+ }
1158
+ }
1159
+
1160
+ /**
1161
+ * Navigates to reference of the given URL
1162
+ * @param url
1163
+ */
1164
+ private loadNewReferenceItem(url: string): void {
1165
+ const referenceId = this.getReferenceIdFromUrl(url);
1166
+ const referenceItem = this.getAmfConfigWithId(referenceId);
1167
+ if (referenceItem) {
1168
+ window.location.href = referenceItem.href;
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * @param referenceUrl to which user wants to navigate
1174
+ * Redirect to first sub item if it's root level item, otherwise content will be loaded
1175
+ * Push the history as a first child item
1176
+ * set selected sidebar value as a pathname
1177
+ */
1178
+
1179
+ private loadMarkdownBasedReference(referenceUrl?: string): void {
1180
+ let referenceId = "";
1181
+ const currentUrl = window.location.href;
1182
+ if (this.isProjectRootPath()) {
1183
+ /**
1184
+ * CASE1: This case is to consider when the user navigates to references by clicking a project card
1185
+ * Ex: /docs/example-project/references should navigate to the first topic in the first reference
1186
+ */
1187
+ referenceId = this._currentReferenceId;
1188
+ } else if (this.isParentReferencePath(referenceUrl)) {
1189
+ /**
1190
+ * CASE2: This case is to navigate to respective reference when the user clicked on root item
1191
+ * Ex: .../references/markdown-ref should navigate to first topic.
1192
+ */
1193
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1194
+ } else if (this.isParentReferencePath(currentUrl)) {
1195
+ /**
1196
+ * CASE3: This case is to navigate to respective reference when the user entered url with reference id
1197
+ * Ex: .../references/markdown-ref should navigate to first topic.
1198
+ */
1199
+ referenceId = this.getReferenceIdFromUrl(currentUrl);
1200
+ } else if (referenceUrl) {
1201
+ /**
1202
+ * CASE4: This case is to navigate to first item when we don't have topic in the selected version
1203
+ * Ex: .../references/markdown-ref/not-existed-topic-url should navigate to first topic.
1204
+ */
1205
+ const referenceMeta = this.getMarkdownReferenceMeta(referenceUrl);
1206
+ const selectedItemRefId = this.getReferenceIdFromUrl(referenceUrl);
1207
+ const referenceDetails = this.getRefDetailsForGivenTopicMeta(
1208
+ selectedItemRefId,
1209
+ referenceMeta
1210
+ );
1211
+ const selectedItemUrl = referenceDetails?.referenceUrl;
1212
+ if (!selectedItemUrl) {
1213
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1214
+ }
1215
+ }
1216
+
1217
+ let isRedirecting = false;
1218
+ if (referenceId) {
1219
+ const amfConfig = this.getAmfConfigWithId(referenceId);
1220
+ let redirectReferenceUrl = "";
1221
+ if (amfConfig) {
1222
+ const childrenItems = amfConfig.topic.children;
1223
+ if (childrenItems.length > 0) {
1224
+ redirectReferenceUrl = childrenItems[0].link.href;
1225
+ }
1226
+ }
1227
+ if (redirectReferenceUrl) {
1228
+ if (this.isParentReferencePath(referenceUrl)) {
1229
+ // This is for CASE2 mentioned above, Where we need to navigate user to respective href
1230
+ isRedirecting = true;
1231
+ window.location.href = redirectReferenceUrl;
1232
+ } else {
1233
+ // This is for CASE 1,3 and 4 mentioned above, Where we need to update the browser history
1234
+ window.history.replaceState(
1235
+ window.history.state,
1236
+ "",
1237
+ redirectReferenceUrl
1238
+ );
1239
+ }
1240
+ }
1241
+ }
1242
+ if (!isRedirecting) {
1243
+ const currentReferenceUrl = window.location.href;
1244
+ const referenceMeta =
1245
+ this.getMarkdownReferenceMeta(currentReferenceUrl);
1246
+ const selectedItemRefId =
1247
+ this.getReferenceIdFromUrl(currentReferenceUrl);
1248
+ const referenceDetails = this.getRefDetailsForGivenTopicMeta(
1249
+ selectedItemRefId,
1250
+ referenceMeta
1251
+ );
1252
+ if (referenceDetails) {
1253
+ this.updateNavTitleMetaTag(referenceDetails.topicTitle);
1254
+ }
1255
+
1256
+ this.versions = this.getVersions();
1257
+ this.updateDocPhase();
1258
+ this.selectedSidebarValue = window.location.pathname;
1259
+ }
1260
+ }
1261
+
1262
+ /**
1263
+ * Currently, used to handle the version change and store the current reference Url.
1264
+ */
1265
+ handleVersionChange(): void {
1266
+ const currentUrl = window.location.href;
1267
+ window.sessionStorage.setItem(
1268
+ this.docsReferenceUrlSessionKey,
1269
+ currentUrl
1270
+ );
1271
+ }
1272
+
1273
+ private updateNavTitleMetaTag(navTitle = ""): void {
1274
+ // this is required to update the nav title meta tag.
1275
+ // eslint-disable-next-line @lwc/lwc/no-document-query
1276
+ const metaNavTitle = document.querySelector('meta[name="nav-title"]');
1277
+ if (metaNavTitle && navTitle) {
1278
+ metaNavTitle.setAttribute("content", navTitle);
1279
+ }
1280
+ }
1281
+
1282
+ onNavSelect(event: CustomEvent): void {
1283
+ this.saveScroll();
1284
+
1285
+ const name = event.detail.name;
1286
+ if (name) {
1287
+ const urlReferenceId = this.getReferenceIdFromUrl(name);
1288
+ const specBasedReference =
1289
+ this.isSpecBasedReference(urlReferenceId);
1290
+ if (specBasedReference) {
1291
+ const metaVal = this.getMetaFromUrl(name);
1292
+ const currentSelectedMeta = this.selectedTopic
1293
+ ? this.selectedTopic.meta
1294
+ : "";
1295
+
1296
+ if (metaVal && metaVal === currentSelectedMeta) {
1297
+ // selecting the same nav item, skip update
1298
+ return;
1299
+ }
1300
+
1301
+ const metadata = this.metadata[metaVal];
1302
+ if (metadata) {
1303
+ const {
1304
+ parentReferencePath,
1305
+ referenceId,
1306
+ amfId,
1307
+ type,
1308
+ elementId
1309
+ } = metadata;
1310
+ this.loadSpecReferenceContent(
1311
+ parentReferencePath,
1312
+ referenceId,
1313
+ amfId,
1314
+ type,
1315
+ elementId,
1316
+ metaVal
1317
+ );
1318
+ this.updateUrlWithSelected(parentReferencePath, metaVal);
1319
+ this.updateNavTitleMetaTag(metadata.navTitle);
1320
+ } else {
1321
+ if (this.isParentReferencePath(name)) {
1322
+ this.loadNewReferenceItem(name);
1323
+ }
1324
+ }
1325
+ } else {
1326
+ this.loadMarkdownBasedReference(name);
1327
+ }
1328
+ }
1329
+ }
1330
+
1331
+ onExpandCollapse(event: CustomEvent): void {
1332
+ const { name, isSelectAction, isExpanded } = event.detail;
1333
+ if (!isSelectAction && isExpanded) {
1334
+ const referenceId = this.getReferenceIdFromUrl(name);
1335
+ const currentUrl = window.location.href;
1336
+ const currentReferenceId = this.getReferenceIdFromUrl(currentUrl);
1337
+ //No need to do anything if user is expanding currently selected reference
1338
+ if (referenceId !== currentReferenceId) {
1339
+ const isSpecBasedReference =
1340
+ this.isSpecBasedReference(referenceId);
1341
+ if (isSpecBasedReference) {
1342
+ // Perform functionality same as item selection
1343
+ this.onNavSelect(event);
1344
+ }
1345
+ }
1346
+ }
1347
+ }
1348
+
1349
+ handleSelectedItem(): void {
1350
+ // update topic view
1351
+ const { referenceId, amfId, type } = this.selectedTopic;
1352
+
1353
+ // This updates the component in the content section.
1354
+ this.topicModel = {
1355
+ type,
1356
+ amf: this.amfMap[referenceId].model,
1357
+ parser: this.amfMap[referenceId].parser,
1358
+ id: amfId
1359
+ };
1360
+ }
1361
+ }