@salesforcedevs/docs-components 0.54.0 → 0.54.5

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 (40) hide show
  1. package/lwc.config.json +6 -2
  2. package/package.json +12 -4
  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 +39 -0
  6. package/src/modules/doc/amfReference/amfReference.ts +813 -0
  7. package/src/modules/doc/amfReference/route-meta.ts +22 -0
  8. package/src/modules/doc/amfReference/types.ts +93 -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 +2 -2
  16. package/src/modules/doc/breadcrumbs/breadcrumbs.css +2 -2
  17. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +1 -1
  18. package/src/modules/doc/content/content.css +4 -4
  19. package/src/modules/doc/content/content.ts +1 -1
  20. package/src/modules/doc/contentCallout/contentCallout.css +4 -4
  21. package/src/modules/doc/contentLayout/contentLayout.css +100 -0
  22. package/src/modules/doc/contentLayout/contentLayout.html +55 -0
  23. package/src/modules/doc/contentLayout/contentLayout.ts +242 -0
  24. package/src/modules/doc/header/header.css +1 -1
  25. package/src/modules/doc/header/header.ts +2 -2
  26. package/src/modules/doc/headingAnchor/headingAnchor.css +1 -1
  27. package/src/modules/doc/headingContent/headingContent.css +1 -1
  28. package/src/modules/doc/phase/phase.css +3 -3
  29. package/src/modules/doc/phase/phase.ts +1 -1
  30. package/src/modules/doc/xmlContent/types.ts +119 -0
  31. package/src/modules/doc/xmlContent/utils.ts +163 -0
  32. package/src/modules/doc/xmlContent/xmlContent.css +25 -0
  33. package/src/modules/doc/xmlContent/xmlContent.html +34 -0
  34. package/src/modules/doc/xmlContent/xmlContent.ts +553 -0
  35. package/src/modules/docBaseElements/lightningElementWithState/lightningElementWithState.ts +93 -0
  36. package/src/modules/docHelpers/amfStyle/amfStyle.css +390 -0
  37. package/src/modules/docHelpers/phaseContentLayout/phaseContentLayout.css +39 -0
  38. package/src/modules/{helpers → docHelpers}/status/status.css +0 -0
  39. package/src/modules/docUtils/SearchSyncer/SearchSyncer.ts +85 -0
  40. package/LICENSE +0 -12
@@ -0,0 +1,813 @@
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 { RouteMeta } from "./route-meta";
7
+ import type {
8
+ AmfConfig,
9
+ AmfMetadataTopic,
10
+ AmfModel,
11
+ AmfModelRecord,
12
+ NavItem,
13
+ ParsedTopicModel,
14
+ TopicModel,
15
+ ReferenceVersion,
16
+ ReferenceSetConfig,
17
+ AmfMetaTopicType
18
+ } from "./types";
19
+
20
+ const NAVIGATION_ITEMS = [
21
+ {
22
+ label: "Summary",
23
+ name: "summary",
24
+ childrenPropertyName: undefined,
25
+ type: "summary"
26
+ },
27
+ {
28
+ label: "Endpoints",
29
+ name: "endpoints",
30
+ childrenPropertyName: "endpoints",
31
+ type: "endpoint"
32
+ },
33
+ {
34
+ label: "Documentation",
35
+ name: "documentation",
36
+ childrenPropertyName: "docs",
37
+ type: "documentation"
38
+ },
39
+ {
40
+ label: "Types",
41
+ name: "types",
42
+ childrenPropertyName: "types",
43
+ type: "type"
44
+ },
45
+ {
46
+ label: "Security",
47
+ name: "security",
48
+ childrenPropertyName: "security",
49
+ type: "security"
50
+ }
51
+ ];
52
+
53
+ const URL_CONFIG = {
54
+ summary: {
55
+ urlIdentifer: "label"
56
+ },
57
+ endpoint: {
58
+ urlIdentifer: "path"
59
+ },
60
+ method: {
61
+ urlIdentifer: "label"
62
+ },
63
+ documentation: {
64
+ urlIdentifer: "label"
65
+ },
66
+ type: {
67
+ urlIdentifer: "label",
68
+ prefix: "type:"
69
+ },
70
+ security: {
71
+ urlIdentifer: "label",
72
+ prefix: "security:"
73
+ }
74
+ };
75
+
76
+ const urlHashToMetaRedirectMap = {
77
+ "commerce-api-assignments:Summary": "assignments:Summary",
78
+ "commerce-api-campaigns:Summary": "campaigns:Summary",
79
+ "commerce-api-catalogs:Summary": "catalogs:Summary",
80
+ "cdn-zones:Summary": "cdn-api-process-apis:Summary",
81
+ "inventory-impex:Summary": "impex:Summary",
82
+ "inventory-reservations:Summary": "inventory-reservation-service:Summary",
83
+ "shopper-login-and-api-access-service:Summary": "shopper-login:Summary",
84
+ "shopper-login-and-api-access-service-admin:Summary": "slas-admin:Summary",
85
+ "einstein-recommendations:Summary": "einstein-api-quick-start-guide:Summary"
86
+ };
87
+
88
+ export default class AmfReference extends LightningElement {
89
+ @api breadcrumbs?: string = null;
90
+ @api sidebarHeader: string;
91
+ @api coveoOrganizationId!: string;
92
+ @api coveoPublicAccessToken!: string;
93
+ @api coveoAdvancedQueryConfig!: string;
94
+ @api coveoSearchHub!: string;
95
+ @api useOldSidebar?: boolean = false;
96
+
97
+ // Update this to update what component gets rendered in the content block
98
+ @track
99
+ protected topicModel: TopicModel;
100
+
101
+ get isVersionEnabled(): boolean {
102
+ return !!this._referenceSetConfig?.versions?.length;
103
+ }
104
+
105
+ @api
106
+ get referenceSetConfig(): ReferenceSetConfig {
107
+ return this._referenceSetConfig;
108
+ }
109
+
110
+ set referenceSetConfig(value: ReferenceSetConfig) {
111
+ // No change, do nothing.
112
+ if (value === this._referenceSetConfig) {
113
+ return;
114
+ }
115
+
116
+ try {
117
+ const refConfig =
118
+ typeof value === "string" ? JSON.parse(value) : value;
119
+ if (!(<ReferenceSetConfig>refConfig).versions) {
120
+ return;
121
+ }
122
+ this._referenceSetConfig = refConfig;
123
+ } catch (e) {
124
+ this._referenceSetConfig = {
125
+ refList: []
126
+ };
127
+ }
128
+
129
+ if (this.isVersionEnabled) {
130
+ const selectedVersion = this.getSelectedVersion();
131
+ this.versionToRefMap = this._referenceSetConfig.versionToRefMap;
132
+ // if version is not available, then show empty - this will be the default behaviour
133
+ this._amfConfig = this.versionToRefMap[selectedVersion.id] || [];
134
+ this.versions = this._referenceSetConfig.versions;
135
+ this.selectedVersion = selectedVersion;
136
+ } else {
137
+ this._amfConfig = this._referenceSetConfig.refList;
138
+ }
139
+
140
+ this.updateAmfConfigInView();
141
+ }
142
+
143
+ @api
144
+ get docPhaseInfo() {
145
+ return this.selectedReferenceDocPhase;
146
+ }
147
+
148
+ set docPhaseInfo(value: string) {
149
+ if (value) {
150
+ this.isParentLevelDocPhaseEnabled = true;
151
+ this.selectedReferenceDocPhase = value;
152
+ }
153
+ }
154
+
155
+ // model
156
+ protected _amfConfig: AmfConfig[] = [];
157
+ protected _referenceSetConfig: ReferenceSetConfig;
158
+ protected _currentReferenceId = "";
159
+
160
+ protected amfMap: Record<string, AmfModelRecord> = {};
161
+ protected amfFetchPromiseMap = {};
162
+ protected metadata: { [key: string]: AmfMetadataTopic } = {};
163
+ protected selectedTopic: AmfMetaTopicType = undefined;
164
+ protected navigation = [];
165
+ protected selectedSidebarValue = undefined;
166
+ protected versions: Array<ReferenceVersion> = [];
167
+ protected selectedVersion: ReferenceVersion = null;
168
+
169
+ private hasRendered = false;
170
+ private navAmfOrder = [];
171
+
172
+ private versionToRefMap: Map<string, Array<AmfConfig>>;
173
+
174
+ private isParentLevelDocPhaseEnabled = false;
175
+ private selectedReferenceDocPhase?: string = null;
176
+
177
+ /**
178
+ * Key for storing the currently selected reference meta query param. This will be used to save the
179
+ * previously selected reference meta and restoring it when changing between reference versions.
180
+ */
181
+ private readonly docsReferenceMetaSessionKey: string = "docsReferenceMeta";
182
+
183
+ _boundOnApiNavigationChanged;
184
+ _boundUpdateSelectedItemFromUrlQuery;
185
+
186
+ get amfConfigMapped(): AmfConfig[] {
187
+ return this._amfConfig.map((config) => {
188
+ return config.id in this.amfMap
189
+ ? Object.assign(config, this.amfMap[config.id])
190
+ : config;
191
+ });
192
+ }
193
+
194
+ constructor() {
195
+ super();
196
+ this._boundOnApiNavigationChanged = this.onApiNavigationChanged.bind(
197
+ this
198
+ );
199
+ this._boundUpdateSelectedItemFromUrlQuery = this.updateSelectedItemFromUrlQuery.bind(
200
+ this
201
+ );
202
+ }
203
+
204
+ connectedCallback(): void {
205
+ this.addEventListener(
206
+ "api-navigation-selection-changed",
207
+ this._boundOnApiNavigationChanged
208
+ );
209
+ window.addEventListener(
210
+ "popstate",
211
+ this._boundUpdateSelectedItemFromUrlQuery
212
+ );
213
+ }
214
+
215
+ disconnectedCallback(): void {
216
+ this.removeEventListener(
217
+ "api-navigation-selection-changed",
218
+ this._boundOnApiNavigationChanged
219
+ );
220
+ window.removeEventListener(
221
+ "popstate",
222
+ this._boundUpdateSelectedItemFromUrlQuery
223
+ );
224
+ }
225
+
226
+ renderedCallback(): void {
227
+ if (!this.hasRendered) {
228
+ this.hasRendered = true;
229
+ if (this._amfConfig && this._amfConfig.length) {
230
+ // If amfConfig has a value and length, it is assumed that fetch
231
+ // has already been called and promises stored.
232
+ this.updateView();
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Returns the selected version or the first available version.
239
+ */
240
+ private getSelectedVersion(): ReferenceVersion {
241
+ const versions = this._referenceSetConfig?.versions || [];
242
+ const selectedVersion = versions.find(
243
+ (v: ReferenceVersion) => v.selected
244
+ );
245
+ // return a selected version if there is one, else return the first one.
246
+ return selectedVersion || (versions.length && versions[0]);
247
+ }
248
+
249
+ private updateAmfConfigInView(): void {
250
+ if (this._amfConfig && this._amfConfig.length) {
251
+ // fetch AMF Json as soon as config is set
252
+ this.fetchAllAmf();
253
+ // update() must be called after renderedCallback.
254
+ if (this.hasRendered) {
255
+ this.updateView();
256
+ }
257
+ }
258
+ }
259
+
260
+ private async fetchAmf(amfConfig): Promise<AmfModel | AmfModel[]> {
261
+ const { amf } = amfConfig;
262
+ const response = await fetch(amf, {
263
+ headers: {
264
+ "Cache-Control": `max-age=86400`
265
+ }
266
+ });
267
+ const json = await response.json();
268
+ return json;
269
+ }
270
+
271
+ /**
272
+ * Calls the fetch for each AMF in the config.
273
+ * Stores each fetch promise for handling after renderCallback.
274
+ */
275
+ private fetchAllAmf(): void {
276
+ for (const [i, amfConfig] of this._amfConfig.entries()) {
277
+ const p = this.fetchAmf(amfConfig).then((amfJson) => {
278
+ this.updateModel(amfConfig.id, amfJson);
279
+ this.assignNavigationItemsFromAmf(amfConfig.id, i);
280
+ });
281
+ this.amfFetchPromiseMap[amfConfig.id] = p;
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Stores fetched AMF JSON value.
287
+ * Creates and stores a new AmfModelParser instance for the AMF spec.
288
+ * @param {*} referenceId
289
+ * @param {*} amf
290
+ */
291
+ private updateModel(referenceId: string, amf: AmfModel | AmfModel[]): void {
292
+ const parser = new AmfModelParser(amf);
293
+ this.amfMap[referenceId] = {
294
+ model: amf,
295
+ parser: parser,
296
+ parsedModel: parser.parsedModel
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Convert any case to a readable Title
302
+ * ex: snake_case => Snake case
303
+ * ex: camelCase => Camel case
304
+ * ex: PascalCase => Pascal case
305
+ * @param label
306
+ * @returns string
307
+ */
308
+ private getTitleForLabel(label: string): string {
309
+ return sentenceCase(noCase(label));
310
+ }
311
+
312
+ /**
313
+ * Transforms a list of model data for endpoints into corresponding
314
+ * navigation list items that is compatible with dx-sidebar.
315
+ * Compatible with transforming AMF data parsed from both RAML and OAS spec.
316
+ * Transforms a flat list of endpoints into a nested list based on indentation level
317
+ * for RAML spec.
318
+ * @param {Array<Object>} items An array of endpoints.
319
+ * @returns {array<Object>} List of navigation items
320
+ */
321
+ private assignEndpointNavItems(
322
+ referenceId: string,
323
+ items: ParsedTopicModel[]
324
+ ): NavItem[] {
325
+ const methodList = [];
326
+
327
+ items.forEach((item) => {
328
+ item.methods?.forEach((method) => {
329
+ const meta = this.addToMetadata(referenceId, "method", method);
330
+ methodList.push(
331
+ Object.assign(method, {
332
+ name: this.getReferencePathWithMeta(meta),
333
+ label:
334
+ this.getTitleForLabel(method.label) || method.method
335
+ })
336
+ );
337
+ });
338
+ });
339
+
340
+ return methodList;
341
+ }
342
+
343
+ private getReferencePathWithMeta(meta: string): string {
344
+ return meta ? `${window.location.pathname}?meta=${meta}` : "";
345
+ }
346
+
347
+ /**
348
+ * Assigns Navigation Items to dx-sidebar from the parsed AMF model.
349
+ * Adds each nav item to the Metadata by type.
350
+ * The 'summary' nav item has no children.
351
+ * The 'endpoint' nav item may have nested children.
352
+ */
353
+ private assignNavigationItemsFromAmf(
354
+ referenceId: string,
355
+ amfIdx: number
356
+ ): void {
357
+ const model = this.amfMap[referenceId].parser.parsedModel;
358
+
359
+ const children = [];
360
+
361
+ NAVIGATION_ITEMS.forEach(
362
+ ({ label, name, childrenPropertyName, type }) => {
363
+ const indexedName = `${name}-${amfIdx}`;
364
+ switch (type) {
365
+ case "summary": {
366
+ const summary = model[type];
367
+ const meta = this.addToMetadata(
368
+ referenceId,
369
+ type,
370
+ summary
371
+ );
372
+ children.push({
373
+ label,
374
+ name: this.getReferencePathWithMeta(meta)
375
+ });
376
+ break;
377
+ }
378
+ case "endpoint":
379
+ if (
380
+ model[childrenPropertyName] &&
381
+ model[childrenPropertyName].length
382
+ ) {
383
+ const amfTopicId = this.getFormattedIdentifier(
384
+ referenceId,
385
+ indexedName
386
+ );
387
+ const childTopics = this.assignEndpointNavItems(
388
+ referenceId,
389
+ model[childrenPropertyName]
390
+ );
391
+ children.push({
392
+ label,
393
+ name: this.getReferencePathWithMeta(
394
+ this.metadata[amfTopicId]?.meta
395
+ ),
396
+ isExpanded: false,
397
+ children: childTopics
398
+ });
399
+ }
400
+ break;
401
+ case "security":
402
+ case "type":
403
+ if (model[childrenPropertyName]?.length) {
404
+ // Sorting the types alphabetically
405
+ model[childrenPropertyName].sort((typeA, typeB) => {
406
+ const typeALbl = typeA.label.toLowerCase();
407
+ const typeBLbl = typeB.label.toLowerCase();
408
+ return typeALbl < typeBLbl
409
+ ? -1
410
+ : typeALbl > typeBLbl
411
+ ? 1
412
+ : 0;
413
+ });
414
+ }
415
+ // eslint-disable-next-line no-fallthrough
416
+ default:
417
+ if (
418
+ model[childrenPropertyName] &&
419
+ model[childrenPropertyName].length
420
+ ) {
421
+ const amfTopicId = this.getFormattedIdentifier(
422
+ referenceId,
423
+ indexedName
424
+ );
425
+ children.push({
426
+ label,
427
+ name: this.getReferencePathWithMeta(
428
+ this.metadata[amfTopicId]?.meta
429
+ ),
430
+ isExpanded: false,
431
+ children: model[childrenPropertyName].map(
432
+ (topic) => {
433
+ const meta = this.addToMetadata(
434
+ referenceId,
435
+ type,
436
+ topic
437
+ );
438
+ return {
439
+ label: topic.label,
440
+ name: this.getReferencePathWithMeta(
441
+ meta
442
+ )
443
+ };
444
+ }
445
+ )
446
+ });
447
+ }
448
+ }
449
+ }
450
+ );
451
+
452
+ // store nav items for each spec in order
453
+ this.navAmfOrder[amfIdx] = {
454
+ label: model.title,
455
+ name: this.getReferencePathWithMeta(`${referenceId}-root`),
456
+ isExpanded: amfIdx === 0, // only expand the first spec
457
+ children
458
+ };
459
+
460
+ // update navigation with each specs nav items as they become available
461
+ // navigation has to be an array because dx-sidebar expects an array.
462
+ const navigation = [];
463
+ for (const navAmf of this.navAmfOrder) {
464
+ if (navAmf) {
465
+ navigation.push(navAmf);
466
+ }
467
+ }
468
+ this.navigation = navigation;
469
+ }
470
+
471
+ protected addToMetadata(
472
+ referenceId: string,
473
+ type: string,
474
+ topic: { id: string; domId: string }
475
+ ): string {
476
+ const { urlIdentifer, prefix } = URL_CONFIG[type];
477
+
478
+ // encodeURI to avoid special characters in the URL meta.
479
+ const identifier =
480
+ topic[urlIdentifer] && this.encodeIdentifier(topic[urlIdentifer]);
481
+ const meta = prefix
482
+ ? `${referenceId}:${prefix}${identifier}`
483
+ : `${referenceId}:${identifier}`;
484
+ this.metadata[meta] = {
485
+ meta: meta,
486
+ referenceId: referenceId,
487
+ amfId: topic.id,
488
+ elementId: topic.domId,
489
+ identifier,
490
+ type
491
+ };
492
+ return meta;
493
+ }
494
+
495
+ /**
496
+ * Returns metadata given route meta
497
+ */
498
+ protected getMetadataByUrlQuery(routeMeta: RouteMeta): AmfMetadataTopic {
499
+ return Object.values(this.metadata).find(
500
+ (metadata: AmfMetadataTopic) => {
501
+ return routeMeta.meta === metadata.meta;
502
+ }
503
+ );
504
+ }
505
+
506
+ /**
507
+ * Returns metadata given reference ID and topic amf ID
508
+ */
509
+ protected getMetadataByAmfId(
510
+ referenceId: string,
511
+ amfId: string
512
+ ): AmfMetadataTopic {
513
+ // Lets make a map based on the hash values so we don't need to loop like this.
514
+ return Object.values(this.metadata).find(
515
+ (metadata: AmfMetadataTopic) =>
516
+ referenceId === metadata.referenceId && amfId === metadata.amfId
517
+ );
518
+ }
519
+
520
+ /**
521
+ * Returns metadata given reference ID and topic identifier
522
+ */
523
+ protected getMetadataByIdentifier(
524
+ referenceId: string,
525
+ identifier: string
526
+ ): AmfMetadataTopic {
527
+ // Lets make a map based on the hash values so we don't need to loop like this.
528
+ return Object.values(this.metadata).find(
529
+ (metadata: AmfMetadataTopic) =>
530
+ referenceId === metadata.referenceId &&
531
+ identifier === metadata.identifier
532
+ );
533
+ }
534
+
535
+ /**
536
+ * Returns metadata given reference ID and topic type
537
+ */
538
+ protected getMetadataByType(
539
+ referenceId: string,
540
+ type: string
541
+ ): AmfMetadataTopic {
542
+ // Lets make a map based on the hash values so we don't need to loop like this.
543
+ return Object.values(this.metadata).find(
544
+ (metadata: AmfMetadataTopic) =>
545
+ referenceId === metadata.referenceId && type === metadata.type
546
+ );
547
+ }
548
+
549
+ /**
550
+ * Parses url query params without decoding of params
551
+ */
552
+ private parseParams(path: string): qs.ParsedQuery<string> {
553
+ if (!path) {
554
+ return {};
555
+ }
556
+ return qs.parse(path, {
557
+ decode: false
558
+ });
559
+ }
560
+
561
+ /**
562
+ * Gets the portion from the URL query param 'meta'.
563
+ */
564
+ protected getCurrentRefMeta(
565
+ previousRefMetaInSession?: string
566
+ ): RouteMeta | null {
567
+ const path = window.location.search;
568
+ const urlParams = this.parseParams(path);
569
+ let meta = urlParams.meta as string;
570
+ let routeMeta = null;
571
+ if (previousRefMetaInSession) {
572
+ const refParts = previousRefMetaInSession.split(":");
573
+ const newRefId = refParts.length > 0 ? refParts[0] : null;
574
+ const [, type, topicId] = previousRefMetaInSession.split(":");
575
+ meta = newRefId ? [newRefId, type, topicId].join(":") : null;
576
+ } else if (!meta) {
577
+ // If no `meta` explicitly exists, check the URL hash to see whether this is one we
578
+ // want to redirect (see GUS W-10718771 for one reference where we want hash-based
579
+ // redirects)
580
+ const { hash } = window.location;
581
+ const strippedHash = hash.startsWith("#") ? hash.slice(1) : hash;
582
+ meta = urlHashToMetaRedirectMap[strippedHash];
583
+ }
584
+ if (meta) {
585
+ routeMeta = new RouteMeta(meta);
586
+ }
587
+ return routeMeta;
588
+ }
589
+
590
+ /**
591
+ * Normalizes topic identifier by replacing spaces with '+'
592
+ * and running encodeURI() on it
593
+ * @param identifer raw identifer for a topic as parsed from the spec file
594
+ * @returns normalized and encoded identifier
595
+ */
596
+ protected encodeIdentifier(identifer: string): string {
597
+ let result = identifer.trim();
598
+ result = result.replace(new RegExp(/\s+/, "g"), "+");
599
+ return encodeURI(result);
600
+ }
601
+
602
+ /**
603
+ * Constructs a Reference Topic ID
604
+ */
605
+ protected getFormattedIdentifier(referenceId: string, id: string): string {
606
+ return `${referenceId}:${id}`;
607
+ }
608
+
609
+ protected updateSelectedItemFromUrlQuery(): void {
610
+ const currentMeta: RouteMeta | null = this.getCurrentRefMeta();
611
+ const metadata = currentMeta && this.getMetadataByUrlQuery(currentMeta);
612
+ if (metadata) {
613
+ const {
614
+ referenceId,
615
+ amfId,
616
+ type,
617
+ elementId
618
+ }: AmfMetadataTopic = metadata;
619
+ this.loadContent(
620
+ referenceId,
621
+ amfId,
622
+ type,
623
+ elementId,
624
+ currentMeta.meta
625
+ );
626
+ }
627
+ }
628
+ protected onApiNavigationChanged(): void {
629
+ // The API Navigation event will always intend to navigate within the current reference
630
+ const metadata = this.metadata[
631
+ this.getReferencePathWithMeta(this.selectedTopic.meta)
632
+ ];
633
+ const {
634
+ referenceId,
635
+ amfId,
636
+ type,
637
+ elementId
638
+ }: AmfMetadataTopic = metadata;
639
+ this.loadContent(referenceId, amfId, type, elementId, metadata.meta);
640
+ this.updateUrlWithSelected();
641
+ }
642
+
643
+ protected updateUrlWithSelected(meta?: string): void {
644
+ if (meta) {
645
+ window.history.pushState(
646
+ {},
647
+ "",
648
+ `${window.location.pathname}?meta=${meta}`
649
+ );
650
+ }
651
+ }
652
+
653
+ /**
654
+ * Does a replace on the url meta, so it does not create a history entry.
655
+ */
656
+ protected replaceUrlWithSelected(meta?: string): void {
657
+ if (meta) {
658
+ window.history.replaceState(
659
+ {},
660
+ "",
661
+ `${window.location.pathname}?meta=${meta}`
662
+ );
663
+ }
664
+ }
665
+
666
+ /**
667
+ * Updates the currently selected amf and topic
668
+ */
669
+ protected loadContent(
670
+ referenceId: string,
671
+ amfId: string,
672
+ type: string,
673
+ elementId = "",
674
+ meta = ""
675
+ ): void {
676
+ this.selectedTopic = {
677
+ referenceId,
678
+ amfId,
679
+ elementId,
680
+ type,
681
+ meta
682
+ };
683
+ this.selectedSidebarValue = this.getReferencePathWithMeta(meta);
684
+
685
+ this.handleSelectedItem();
686
+
687
+ // Ensures that the URL always has the meta, that way we don't get two history entries for summary
688
+ this.replaceUrlWithSelected(meta);
689
+ this.updateDocPhase();
690
+ }
691
+
692
+ /**
693
+ * Updates doc phase of selected reference
694
+ */
695
+ updateDocPhase(): void {
696
+ /* If parent level doc phase is enabled, Individual reference level doc phase should not be considered */
697
+
698
+ if (!this.isParentLevelDocPhaseEnabled) {
699
+ const referenceId = this.selectedTopic?.referenceId;
700
+ const selectedReference = this._amfConfig.find(
701
+ (referenceItem: AmfConfig) => {
702
+ return referenceItem.id === referenceId;
703
+ }
704
+ );
705
+ if (selectedReference) {
706
+ this.selectedReferenceDocPhase = JSON.stringify(
707
+ selectedReference.docPhase
708
+ );
709
+ }
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Updates the DOM on first load
715
+ */
716
+ updateView(): void {
717
+ let referenceId: string;
718
+ let topicId = "";
719
+ const previousRefMetaInSession = window.sessionStorage.getItem(
720
+ this.docsReferenceMetaSessionKey
721
+ );
722
+ window.sessionStorage.removeItem(this.docsReferenceMetaSessionKey);
723
+ const currentMeta = this.getCurrentRefMeta(previousRefMetaInSession);
724
+ if (currentMeta) {
725
+ referenceId = currentMeta.referenceId;
726
+ topicId = currentMeta.topicId;
727
+ if (!this.amfFetchPromiseMap[referenceId]) {
728
+ // This could happen if they specify a bad query value.
729
+ // In this case, we'll do the logic below to use the default amf
730
+ referenceId = null;
731
+ }
732
+ }
733
+
734
+ if (!referenceId) {
735
+ referenceId = this._amfConfig[0].id;
736
+ if (!this.amfFetchPromiseMap[referenceId]) {
737
+ // This should never happen.
738
+ referenceId = Object.keys(this.amfFetchPromiseMap)[0];
739
+ }
740
+ }
741
+
742
+ // Wait till the AMF is loaded.
743
+ this.amfFetchPromiseMap[referenceId].then(() => {
744
+ let topic = this.getMetadataByIdentifier(referenceId, topicId);
745
+ if (!topic) {
746
+ // Doesn't exist, let's use the summary.
747
+ topic = this.getMetadataByType(referenceId, "summary");
748
+ }
749
+
750
+ if (topic) {
751
+ this.loadContent(
752
+ topic.referenceId,
753
+ topic.amfId,
754
+ topic.type,
755
+ "",
756
+ topic.meta
757
+ );
758
+ }
759
+ });
760
+ }
761
+
762
+ /**
763
+ * Currently, used to handle the version change and storing the current meta query param to the session storage.
764
+ */
765
+ handleVersionChange(): void {
766
+ const path = window.location.search;
767
+ const urlParams = this.parseParams(path);
768
+ const meta = urlParams.meta as string;
769
+ if (meta) {
770
+ window.sessionStorage.setItem(
771
+ this.docsReferenceMetaSessionKey,
772
+ meta
773
+ );
774
+ }
775
+ }
776
+
777
+ onNavSelect(event: CustomEvent): void {
778
+ const name = event.detail.name;
779
+ const indexOfQueryParam = name.indexOf("?");
780
+ const urlPath = name.substring(
781
+ indexOfQueryParam >= 0 ? indexOfQueryParam : name.length
782
+ );
783
+ const metaVal = this.parseParams(urlPath).meta as string;
784
+ const currentSelectedMeta = this.selectedTopic.meta;
785
+
786
+ if (metaVal === currentSelectedMeta) {
787
+ // selecting the same nav item, skip update
788
+ return;
789
+ }
790
+
791
+ const metadata = this.metadata[metaVal];
792
+ if (metadata) {
793
+ const { referenceId, amfId, type, elementId } = metadata;
794
+ this.loadContent(referenceId, amfId, type, elementId, metaVal);
795
+ this.updateUrlWithSelected(metaVal);
796
+ }
797
+ }
798
+
799
+ handleSelectedItem(): void {
800
+ // update topic view
801
+ const { referenceId, amfId, type } = this.selectedTopic;
802
+
803
+ // This updates the component in the content section.
804
+ this.topicModel = {
805
+ type,
806
+ amf: this.amfMap[referenceId].model,
807
+ parser: this.amfMap[referenceId].parser,
808
+ id: amfId
809
+ };
810
+
811
+ window.scrollTo({ top: 0, behavior: "smooth" });
812
+ }
813
+ }