@salesforcedevs/docs-components 0.55.9-docs-more-locales-1 → 0.56.2-comp-flex-ref-1

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.
@@ -3,7 +3,6 @@ import { noCase } from "no-case";
3
3
  import { sentenceCase } from "sentence-case";
4
4
  import qs from "query-string";
5
5
  import { AmfModelParser } from "./utils";
6
- import { RouteMeta } from "./route-meta";
7
6
  import type {
8
7
  AmfConfig,
9
8
  AmfMetadataTopic,
@@ -14,94 +13,46 @@ import type {
14
13
  TopicModel,
15
14
  ReferenceVersion,
16
15
  ReferenceSetConfig,
17
- AmfMetaTopicType
16
+ AmfMetaTopicType,
17
+ RouteMeta,
18
+ ParsedMarkdownTopic
18
19
  } from "./types";
19
20
 
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
- };
21
+ import {
22
+ NAVIGATION_ITEMS,
23
+ URL_CONFIG,
24
+ REFERENCE_TYPES,
25
+ oldReferenceIdNewReferenceIdMap
26
+ } from "./constants";
87
27
 
88
28
  export default class AmfReference extends LightningElement {
89
- @api breadcrumbs?: string = null;
90
- @api sidebarHeader: string;
29
+ @api breadcrumbs?: string | null | undefined = null;
30
+ @api sidebarHeader!: string;
91
31
  @api coveoOrganizationId!: string;
92
32
  @api coveoPublicAccessToken!: string;
93
33
  @api coveoAdvancedQueryConfig!: string;
94
34
  @api coveoSearchHub!: string;
95
35
  @api useOldSidebar?: boolean = false;
36
+ @api tocTitle?: string;
37
+ @api tocOptions?: string;
38
+ @track navigation = [];
39
+ @track versions: Array<ReferenceVersion> = [];
96
40
 
97
41
  // Update this to update what component gets rendered in the content block
98
42
  @track
99
- protected topicModel: TopicModel;
43
+ protected topicModel!: TopicModel;
100
44
 
101
45
  get isVersionEnabled(): boolean {
102
46
  return !!this._referenceSetConfig?.versions?.length;
103
47
  }
104
48
 
49
+ /**
50
+ * Gives if the currently selected reference is spec based or not
51
+ */
52
+ get showSpecBasedReference(): boolean {
53
+ return this.isSpecBasedReference(this._currentReferenceId);
54
+ }
55
+
105
56
  @api
106
57
  get referenceSetConfig(): ReferenceSetConfig {
107
58
  return this._referenceSetConfig;
@@ -122,22 +73,42 @@ export default class AmfReference extends LightningElement {
122
73
  this._referenceSetConfig = refConfig;
123
74
  } catch (e) {
124
75
  this._referenceSetConfig = {
125
- refList: []
76
+ refList: [],
77
+ versions: []
126
78
  };
127
79
  }
128
80
 
81
+ this._amfConfigList = this._referenceSetConfig.refList || [];
82
+
83
+ this._amfConfigList.forEach((amfConfig) => {
84
+ this._amfConfigMap.set(amfConfig.id, amfConfig);
85
+ });
86
+
87
+ if (this._amfConfigList.length > 0) {
88
+ this._currentReferenceId =
89
+ this._referenceSetConfig.refId || this._amfConfigList[0].id;
90
+ }
91
+
129
92
  if (this.isVersionEnabled) {
130
93
  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;
94
+
95
+ /**
96
+ * If current selected is markdown based reference,
97
+ * We will assign versions once the selected item url is updated
98
+ */
99
+ if (this.isSpecBasedReference(this._currentReferenceId)) {
100
+ this.versions = this.getVersions();
101
+ }
135
102
  this.selectedVersion = selectedVersion;
136
- } else {
137
- this._amfConfig = this._referenceSetConfig.refList;
138
103
  }
139
104
 
140
- this.updateAmfConfigInView();
105
+ // This is to check if the url is hash based and redirect if needed
106
+ const redirectUrl = this.getHashBasedRedirectUrl();
107
+ if (redirectUrl) {
108
+ location.href = redirectUrl;
109
+ } else {
110
+ this.updateAmfConfigInView();
111
+ }
141
112
  }
142
113
 
143
114
  @api
@@ -153,52 +124,40 @@ export default class AmfReference extends LightningElement {
153
124
  }
154
125
 
155
126
  // model
156
- protected _amfConfig: AmfConfig[] = [];
157
- protected _referenceSetConfig: ReferenceSetConfig;
127
+ protected _amfConfigList: AmfConfig[] = [];
128
+ protected _amfConfigMap: Map<string, AmfConfig> = new Map();
129
+ protected _referenceSetConfig!: ReferenceSetConfig;
158
130
  protected _currentReferenceId = "";
159
131
 
132
+ protected parentReferenceUrls = [];
160
133
  protected amfMap: Record<string, AmfModelRecord> = {};
161
134
  protected amfFetchPromiseMap = {};
162
135
  protected metadata: { [key: string]: AmfMetadataTopic } = {};
163
- protected selectedTopic: AmfMetaTopicType = undefined;
164
- protected navigation = [];
136
+ protected selectedTopic?: AmfMetaTopicType = undefined;
165
137
  protected selectedSidebarValue = undefined;
166
- protected versions: Array<ReferenceVersion> = [];
167
- protected selectedVersion: ReferenceVersion = null;
168
138
 
169
- private hasRendered = false;
170
- private navAmfOrder = [];
139
+ protected selectedVersion: ReferenceVersion | null = null;
171
140
 
172
- private versionToRefMap: Map<string, Array<AmfConfig>>;
141
+ private hasRendered = false;
173
142
 
174
143
  private isParentLevelDocPhaseEnabled = false;
175
- private selectedReferenceDocPhase?: string = null;
144
+ private selectedReferenceDocPhase?: string | null = null;
176
145
 
177
146
  /**
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.
147
+ * Key for storing the currently selected reference url. This will be used to save the
148
+ * previously selected reference url and restoring it when changing between reference versions.
180
149
  */
181
- private readonly docsReferenceMetaSessionKey: string = "docsReferenceMeta";
150
+ private readonly docsReferenceUrlSessionKey: string = "docsReferenceUrl";
182
151
 
183
152
  _boundOnApiNavigationChanged;
184
153
  _boundUpdateSelectedItemFromUrlQuery;
185
154
 
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
155
  constructor() {
195
156
  super();
196
- this._boundOnApiNavigationChanged = this.onApiNavigationChanged.bind(
197
- this
198
- );
199
- this._boundUpdateSelectedItemFromUrlQuery = this.updateSelectedItemFromUrlQuery.bind(
200
- this
201
- );
157
+ this._boundOnApiNavigationChanged =
158
+ this.onApiNavigationChanged.bind(this);
159
+ this._boundUpdateSelectedItemFromUrlQuery =
160
+ this.updateSelectedItemFromUrlQuery.bind(this);
202
161
  }
203
162
 
204
163
  connectedCallback(): void {
@@ -226,7 +185,7 @@ export default class AmfReference extends LightningElement {
226
185
  renderedCallback(): void {
227
186
  if (!this.hasRendered) {
228
187
  this.hasRendered = true;
229
- if (this._amfConfig && this._amfConfig.length) {
188
+ if (this._amfConfigList && this._amfConfigList.length) {
230
189
  // If amfConfig has a value and length, it is assumed that fetch
231
190
  // has already been called and promises stored.
232
191
  this.updateView();
@@ -234,6 +193,108 @@ export default class AmfReference extends LightningElement {
234
193
  }
235
194
  }
236
195
 
196
+ /**
197
+ * Check if the URL hash to see whether this is one we want to redirect
198
+ * See GUS W-10718771 for references where we want hash-based redirects
199
+ * Return if we needs to redirect url to updated url
200
+ */
201
+ private getHashBasedRedirectUrl(): string | undefined {
202
+ const { hash } = window.location;
203
+ let hashBasedRedirectUrl = "";
204
+ if (hash) {
205
+ const strippedHash = hash.startsWith("#") ? hash.slice(1) : hash;
206
+ const strippedHashItems = strippedHash
207
+ ? strippedHash.split(":")
208
+ : [];
209
+ if (strippedHashItems.length) {
210
+ const referenceId = strippedHashItems[0];
211
+ const meta = strippedHashItems[1];
212
+ const updatedReferenceId =
213
+ oldReferenceIdNewReferenceIdMap[referenceId];
214
+ const newReferenceId = updatedReferenceId || referenceId;
215
+ const referenceItemConfig =
216
+ this.getAmfConfigWithId(newReferenceId);
217
+ if (referenceItemConfig) {
218
+ hashBasedRedirectUrl = `${referenceItemConfig.href}?meta=${meta}`;
219
+ }
220
+ }
221
+ }
222
+ return hashBasedRedirectUrl;
223
+ }
224
+
225
+ /**
226
+ * @param referenceId
227
+ * @returns AMFConfig with given reference Id
228
+ */
229
+ private getAmfConfigWithId(referenceId: string): AmfConfig | undefined {
230
+ return this._amfConfigMap.get(referenceId);
231
+ }
232
+
233
+ /**
234
+ * @param referenceId
235
+ * @returns if the reference is spec based one or not with given referenceId.
236
+ */
237
+ private isSpecBasedReference(referenceId: string): boolean {
238
+ const selectedReference = this.getAmfConfigWithId(referenceId);
239
+ return selectedReference
240
+ ? selectedReference.referenceType !== REFERENCE_TYPES.markdown
241
+ : false;
242
+ }
243
+
244
+ /*
245
+ * Refactor below method when sidebar allows sending extraData along with the name for each item.
246
+ * See if we can refactor the below method using regex.
247
+ */
248
+
249
+ /**
250
+ * @param url
251
+ * @returns reference Id from url path / selected sidebar item.
252
+ */
253
+ private getReferenceIdFromUrl(url: string): string {
254
+ let referenceId = "";
255
+ const urlItems = url.split("/references/");
256
+ if (urlItems.length > 1) {
257
+ const rightSidePart = urlItems[1];
258
+
259
+ //This covers urls like "/project-name/references/reference-id/..."
260
+ const slashSeparatorItems = rightSidePart.split("/");
261
+
262
+ //This covers urls like "/project-name/references/reference-id?meta=Summary"
263
+ const querySeparatorItems = slashSeparatorItems[0].split("?");
264
+
265
+ referenceId = querySeparatorItems[0];
266
+ }
267
+
268
+ return referenceId;
269
+ }
270
+
271
+ /**
272
+ * @returns versions to be shown in the dropdown
273
+ * For markdown based specs, Adds selected markdown topic url to same references
274
+ */
275
+ private getVersions(): Array<ReferenceVersion> {
276
+ const allVersions = this._referenceSetConfig.versions;
277
+ if (!this.isSpecBasedReference(this._currentReferenceId)) {
278
+ const currentRefMeta = this.getMarkdownReferenceMeta(
279
+ window.location.href
280
+ );
281
+ if (currentRefMeta) {
282
+ for (let i = 0; i < allVersions.length; i++) {
283
+ const versionItem = allVersions[i];
284
+ const referenceLink = versionItem.link.href;
285
+ const referenceId =
286
+ this.getReferenceIdFromUrl(referenceLink);
287
+ if (this._currentReferenceId === referenceId) {
288
+ // This is to navigate to respective topic in the changed version
289
+ versionItem.link.href = `${referenceLink}/${currentRefMeta}`;
290
+ allVersions[i] = versionItem;
291
+ }
292
+ }
293
+ }
294
+ }
295
+ return allVersions;
296
+ }
297
+
237
298
  /**
238
299
  * Returns the selected version or the first available version.
239
300
  */
@@ -247,9 +308,9 @@ export default class AmfReference extends LightningElement {
247
308
  }
248
309
 
249
310
  private updateAmfConfigInView(): void {
250
- if (this._amfConfig && this._amfConfig.length) {
311
+ if (this._amfConfigList && this._amfConfigList.length) {
251
312
  // fetch AMF Json as soon as config is set
252
- this.fetchAllAmf();
313
+ this.populateReferenceItems();
253
314
  // update() must be called after renderedCallback.
254
315
  if (this.hasRendered) {
255
316
  this.updateView();
@@ -269,17 +330,60 @@ export default class AmfReference extends LightningElement {
269
330
  }
270
331
 
271
332
  /**
272
- * Calls the fetch for each AMF in the config.
273
- * Stores each fetch promise for handling after renderCallback.
333
+ * Returns whether current location is project root path like ../example-project/references
274
334
  */
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;
335
+ private isProjectRootPath(): boolean {
336
+ return this.getReferenceIdFromUrl(window.location.href) === "";
337
+ }
338
+
339
+ /**
340
+ * Returns whether given url is parent reference path like ../example-project/references/reference-id
341
+ */
342
+ private isParentReferencePath(urlPath: string): boolean {
343
+ if (!urlPath) {
344
+ return false;
282
345
  }
346
+ const parentReferenceIndex = this.parentReferenceUrls.findIndex(
347
+ (referenceUrl: string) => {
348
+ return urlPath.endsWith(referenceUrl);
349
+ }
350
+ );
351
+ return parentReferenceIndex !== -1;
352
+ }
353
+
354
+ /**
355
+ * Populates reference Items from amfConfigList and assigns it to navigation for sidebar
356
+ */
357
+ private populateReferenceItems(): void {
358
+ const navAmfOrder = [];
359
+ for (const [index, amfConfig] of this._amfConfigList.entries()) {
360
+ let navItemChildren = [];
361
+ let isChildrenLoading = false;
362
+ if (amfConfig.referenceType !== REFERENCE_TYPES.markdown) {
363
+ if (amfConfig.isSelected) {
364
+ const amfPromise = this.fetchAmf(amfConfig).then(
365
+ (amfJson) => {
366
+ this.updateModel(amfConfig.id, amfJson);
367
+ this.assignNavigationItemsFromAmf(amfConfig, index);
368
+ }
369
+ );
370
+ this.amfFetchPromiseMap[amfConfig.id] = amfPromise;
371
+ }
372
+ isChildrenLoading = true;
373
+ } else {
374
+ navItemChildren = amfConfig.topic.children;
375
+ }
376
+ // store nav items for each spec in order
377
+ navAmfOrder[index] = {
378
+ label: amfConfig.title,
379
+ name: amfConfig.href,
380
+ isExpanded: amfConfig.isSelected,
381
+ children: navItemChildren,
382
+ isChildrenLoading
383
+ };
384
+ this.parentReferenceUrls.push(amfConfig.href);
385
+ }
386
+ this.navigation = navAmfOrder;
283
387
  }
284
388
 
285
389
  /**
@@ -311,7 +415,7 @@ export default class AmfReference extends LightningElement {
311
415
 
312
416
  /**
313
417
  * Transforms a list of model data for endpoints into corresponding
314
- * navigation list items that is compatible with dx-sidebar.
418
+ * navigation list items that are compatible with dx-sidebar.
315
419
  * Compatible with transforming AMF data parsed from both RAML and OAS spec.
316
420
  * Transforms a flat list of endpoints into a nested list based on indentation level
317
421
  * for RAML spec.
@@ -319,6 +423,7 @@ export default class AmfReference extends LightningElement {
319
423
  * @returns {array<Object>} List of navigation items
320
424
  */
321
425
  private assignEndpointNavItems(
426
+ parentReferencePath: string,
322
427
  referenceId: string,
323
428
  items: ParsedTopicModel[]
324
429
  ): NavItem[] {
@@ -326,22 +431,35 @@ export default class AmfReference extends LightningElement {
326
431
 
327
432
  items.forEach((item) => {
328
433
  item.methods?.forEach((method) => {
329
- const meta = this.addToMetadata(referenceId, "method", method);
434
+ const meta = this.addToMetadata(
435
+ parentReferencePath,
436
+ referenceId,
437
+ "method",
438
+ method
439
+ );
330
440
  methodList.push(
331
441
  Object.assign(method, {
332
- name: this.getReferencePathWithMeta(meta),
442
+ name: this.getReferencePathWithMeta(
443
+ parentReferencePath,
444
+ meta
445
+ ),
333
446
  label:
334
447
  this.getTitleForLabel(method.label) || method.method
335
448
  })
336
449
  );
337
450
  });
338
451
  });
339
-
340
452
  return methodList;
341
453
  }
342
454
 
343
- private getReferencePathWithMeta(meta: string): string {
344
- return meta ? `${window.location.pathname}?meta=${meta}` : "";
455
+ /**
456
+ * Gives URL path for reference items, Which can be used to push to the history
457
+ */
458
+ private getReferencePathWithMeta(
459
+ parentReferencePath: string,
460
+ meta: string
461
+ ): string {
462
+ return meta ? `${parentReferencePath}?meta=${meta}` : "";
345
463
  }
346
464
 
347
465
  /**
@@ -351,9 +469,11 @@ export default class AmfReference extends LightningElement {
351
469
  * The 'endpoint' nav item may have nested children.
352
470
  */
353
471
  private assignNavigationItemsFromAmf(
354
- referenceId: string,
472
+ amfConfig: AmfConfig,
355
473
  amfIdx: number
356
474
  ): void {
475
+ const referenceId = amfConfig.id;
476
+ const parentReferencePath = amfConfig.href;
357
477
  const model = this.amfMap[referenceId].parser.parsedModel;
358
478
 
359
479
  const children = [];
@@ -365,13 +485,17 @@ export default class AmfReference extends LightningElement {
365
485
  case "summary": {
366
486
  const summary = model[type];
367
487
  const meta = this.addToMetadata(
488
+ parentReferencePath,
368
489
  referenceId,
369
490
  type,
370
491
  summary
371
492
  );
372
493
  children.push({
373
494
  label,
374
- name: this.getReferencePathWithMeta(meta)
495
+ name: this.getReferencePathWithMeta(
496
+ parentReferencePath,
497
+ meta
498
+ )
375
499
  });
376
500
  break;
377
501
  }
@@ -385,12 +509,14 @@ export default class AmfReference extends LightningElement {
385
509
  indexedName
386
510
  );
387
511
  const childTopics = this.assignEndpointNavItems(
512
+ parentReferencePath,
388
513
  referenceId,
389
514
  model[childrenPropertyName]
390
515
  );
391
516
  children.push({
392
517
  label,
393
518
  name: this.getReferencePathWithMeta(
519
+ parentReferencePath,
394
520
  this.metadata[amfTopicId]?.meta
395
521
  ),
396
522
  isExpanded: false,
@@ -425,12 +551,14 @@ export default class AmfReference extends LightningElement {
425
551
  children.push({
426
552
  label,
427
553
  name: this.getReferencePathWithMeta(
554
+ parentReferencePath,
428
555
  this.metadata[amfTopicId]?.meta
429
556
  ),
430
557
  isExpanded: false,
431
558
  children: model[childrenPropertyName].map(
432
559
  (topic) => {
433
560
  const meta = this.addToMetadata(
561
+ parentReferencePath,
434
562
  referenceId,
435
563
  type,
436
564
  topic
@@ -438,6 +566,7 @@ export default class AmfReference extends LightningElement {
438
566
  return {
439
567
  label: topic.label,
440
568
  name: this.getReferencePathWithMeta(
569
+ parentReferencePath,
441
570
  meta
442
571
  )
443
572
  };
@@ -449,26 +578,16 @@ export default class AmfReference extends LightningElement {
449
578
  }
450
579
  );
451
580
 
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
581
+ this.navigation[amfIdx] = {
582
+ ...this.navigation[amfIdx],
583
+ children,
584
+ isChildrenLoading: false
458
585
  };
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;
586
+ this.navigation = [...this.navigation];
469
587
  }
470
588
 
471
589
  protected addToMetadata(
590
+ parentReferencePath: string,
472
591
  referenceId: string,
473
592
  type: string,
474
593
  topic: { id: string; domId: string }
@@ -478,18 +597,21 @@ export default class AmfReference extends LightningElement {
478
597
  // encodeURI to avoid special characters in the URL meta.
479
598
  const identifier =
480
599
  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;
600
+
601
+ // Assuming that there will be an identifier always
602
+ if (identifier) {
603
+ const meta = prefix ? `${prefix}${identifier}` : `${identifier}`;
604
+ this.metadata[meta] = {
605
+ parentReferencePath,
606
+ meta,
607
+ referenceId,
608
+ amfId: topic.id,
609
+ elementId: topic.domId,
610
+ identifier,
611
+ type
612
+ };
613
+ return meta;
614
+ }
493
615
  }
494
616
 
495
617
  /**
@@ -547,54 +669,25 @@ export default class AmfReference extends LightningElement {
547
669
  }
548
670
 
549
671
  /**
550
- * Parses url query params without decoding of params
672
+ * Parses URL query params without decoding of params
551
673
  */
552
- private parseParams(path: string): qs.ParsedQuery<string> {
553
- if (!path) {
674
+ private parseParams(params: string): qs.ParsedQuery<string> {
675
+ if (!params) {
554
676
  return {};
555
677
  }
556
- return qs.parse(path, {
678
+ return qs.parse(params, {
557
679
  decode: false
558
680
  });
559
681
  }
560
682
 
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
683
  /**
591
684
  * Normalizes topic identifier by replacing spaces with '+'
592
685
  * and running encodeURI() on it
593
- * @param identifer raw identifer for a topic as parsed from the spec file
686
+ * @param identifier raw identifier for a topic as parsed from the spec file
594
687
  * @returns normalized and encoded identifier
595
688
  */
596
- protected encodeIdentifier(identifer: string): string {
597
- let result = identifer.trim();
689
+ protected encodeIdentifier(identifier: string): string {
690
+ let result = identifier.trim();
598
691
  result = result.replace(new RegExp(/\s+/, "g"), "+");
599
692
  return encodeURI(result);
600
693
  }
@@ -606,86 +699,130 @@ export default class AmfReference extends LightningElement {
606
699
  return `${referenceId}:${id}`;
607
700
  }
608
701
 
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 {
702
+ protected updateUrlWithSelected(
703
+ parentReferencePath: string,
704
+ meta?: string
705
+ ): void {
644
706
  if (meta) {
645
707
  window.history.pushState(
646
708
  {},
647
709
  "",
648
- `${window.location.pathname}?meta=${meta}`
710
+ `${parentReferencePath}?meta=${meta}`
649
711
  );
650
712
  }
651
713
  }
652
714
 
653
715
  /**
654
- * Does a replace on the url meta, so it does not create a history entry.
716
+ * Does a replace on the URL meta, so it does not create a history entry.
655
717
  */
656
- protected replaceUrlWithSelected(meta?: string): void {
718
+ protected replaceUrlWithSelected(
719
+ parentReferencePath: string,
720
+ meta?: string
721
+ ): void {
657
722
  if (meta) {
658
723
  window.history.replaceState(
659
724
  {},
660
725
  "",
661
- `${window.location.pathname}?meta=${meta}`
726
+ `${parentReferencePath}?meta=${meta}`
662
727
  );
663
728
  }
664
729
  }
665
730
 
731
+ /**
732
+ * This method gets called when the user navigates back and forth using browser arrows
733
+ * Updates content depending on the type of reference - spec based or markdown
734
+ */
735
+ protected updateSelectedItemFromUrlQuery(): void {
736
+ const specBasedReference = this.isSpecBasedReference(
737
+ this._currentReferenceId
738
+ );
739
+ if (specBasedReference) {
740
+ const currentMeta: RouteMeta | null = this.getReferenceMetaInfo(
741
+ window.location.href
742
+ );
743
+ const metadata =
744
+ currentMeta && this.getMetadataByUrlQuery(currentMeta);
745
+ if (metadata) {
746
+ const {
747
+ parentReferencePath,
748
+ referenceId,
749
+ amfId,
750
+ type,
751
+ elementId
752
+ }: AmfMetadataTopic = metadata;
753
+ this.loadSpecReferenceContent(
754
+ parentReferencePath,
755
+ referenceId,
756
+ amfId,
757
+ type,
758
+ elementId,
759
+ currentMeta.meta
760
+ );
761
+ }
762
+ } else {
763
+ this.loadMarkdownBasedReference();
764
+ }
765
+ }
766
+
767
+ /**
768
+ * The API Navigation event will always intend to navigate within the current reference
769
+ * @param event
770
+ */
771
+ protected onApiNavigationChanged(event: CustomEvent): void {
772
+ const specBasedReference = this.isSpecBasedReference(
773
+ this._currentReferenceId
774
+ );
775
+ if (specBasedReference) {
776
+ const { meta } = this.selectedTopic;
777
+ const metadata = this.metadata[meta];
778
+ if (metadata) {
779
+ const {
780
+ parentReferencePath,
781
+ referenceId,
782
+ amfId,
783
+ type,
784
+ elementId
785
+ }: AmfMetadataTopic = metadata;
786
+ this.loadSpecReferenceContent(
787
+ parentReferencePath,
788
+ referenceId,
789
+ amfId,
790
+ type,
791
+ elementId,
792
+ metadata.meta
793
+ );
794
+ }
795
+ } else {
796
+ this.loadMarkdownBasedReference();
797
+ }
798
+ }
799
+
666
800
  /**
667
801
  * Updates the currently selected amf and topic
668
802
  */
669
- protected loadContent(
803
+ protected loadSpecReferenceContent(
804
+ parentReferencePath: string,
670
805
  referenceId: string,
671
806
  amfId: string,
672
807
  type: string,
673
- elementId = "",
674
- meta = ""
808
+ elementId: string,
809
+ meta: string
675
810
  ): void {
676
811
  this.selectedTopic = {
677
812
  referenceId,
813
+ parentReferencePath,
678
814
  amfId,
679
815
  elementId,
680
816
  type,
681
817
  meta
682
818
  };
683
- this.selectedSidebarValue = this.getReferencePathWithMeta(meta);
819
+ this.selectedSidebarValue = this.getReferencePathWithMeta(
820
+ parentReferencePath,
821
+ meta
822
+ );
684
823
 
685
824
  this.handleSelectedItem();
686
825
 
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
826
  this.updateDocPhase();
690
827
  }
691
828
 
@@ -696,10 +833,9 @@ export default class AmfReference extends LightningElement {
696
833
  /* If parent level doc phase is enabled, Individual reference level doc phase should not be considered */
697
834
 
698
835
  if (!this.isParentLevelDocPhaseEnabled) {
699
- const referenceId = this.selectedTopic?.referenceId;
700
- const selectedReference = this._amfConfig.find(
836
+ const selectedReference = this._amfConfigList.find(
701
837
  (referenceItem: AmfConfig) => {
702
- return referenceItem.id === referenceId;
838
+ return referenceItem.id === this._currentReferenceId;
703
839
  }
704
840
  );
705
841
  if (selectedReference) {
@@ -711,88 +847,349 @@ export default class AmfReference extends LightningElement {
711
847
  }
712
848
 
713
849
  /**
714
- * Updates the DOM on first load
850
+ * @returns meta query param from given Url
715
851
  */
716
- updateView(): void {
717
- let referenceId: string;
718
- let topicId = "";
719
- const previousRefMetaInSession = window.sessionStorage.getItem(
720
- this.docsReferenceMetaSessionKey
852
+ getMetaFromUrl(referenceUrl: string): string {
853
+ const indexOfQueryParam = referenceUrl.indexOf("?");
854
+ const urlPath = referenceUrl.substring(
855
+ indexOfQueryParam >= 0 ? indexOfQueryParam : referenceUrl.length
721
856
  );
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;
857
+ return this.parseParams(urlPath)["meta"] as string;
858
+ }
859
+
860
+ /**
861
+ *
862
+ * @returns meta for given markdown based referenceUrl
863
+ * Consider last topic url in ../references/reference-name/example.html
864
+ */
865
+ getMarkdownReferenceMeta(referenceUrl: string): string {
866
+ let meta = "";
867
+ if (referenceUrl) {
868
+ const slashSeparatorItems = referenceUrl.split("/");
869
+ const lastItem =
870
+ slashSeparatorItems[slashSeparatorItems.length - 1];
871
+ if (lastItem.endsWith(".html")) {
872
+ meta = lastItem;
731
873
  }
732
874
  }
875
+ return meta;
876
+ }
733
877
 
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];
878
+ /**
879
+ *
880
+ * @returns RouteMeta object for given referenceUrl
881
+ * referenceId - gets referenceId from url
882
+ * For spec based references gets meta parm from url and then topicId & type from meta
883
+ * For markdown based references gets topicId as last html path in the name, meta & type will be empty
884
+ */
885
+ getReferenceMetaInfo(referenceUrl: string): RouteMeta | undefined {
886
+ if (referenceUrl) {
887
+ const referenceId = this.getReferenceIdFromUrl(referenceUrl);
888
+ let meta = "";
889
+ let topicId = "";
890
+ let type = "";
891
+ if (this.isSpecBasedReference(referenceId)) {
892
+ meta = this.getMetaFromUrl(referenceUrl);
893
+ if (meta) {
894
+ if (meta.includes(":")) {
895
+ const metaInfo = meta.split(":");
896
+ type = metaInfo[0];
897
+ topicId = metaInfo[1] || type;
898
+ } else {
899
+ topicId = meta;
900
+ }
901
+ }
902
+ } else {
903
+ topicId = this.getMarkdownReferenceMeta(referenceUrl);
739
904
  }
905
+ return {
906
+ referenceId,
907
+ meta,
908
+ topicId,
909
+ type
910
+ };
740
911
  }
912
+ }
913
+
914
+ /**
915
+ * Finds and returns referenceUrl if given topic url matches
916
+ */
917
+ getReferenceUrlInGivenTopics(
918
+ topics: ParsedMarkdownTopic[],
919
+ topicMeta: string
920
+ ): string {
921
+ let referenceUrl = "";
922
+ for (let i = 0; i < topics.length; i++) {
923
+ const topic = topics[i];
924
+ const meta = this.getMarkdownReferenceMeta(topic.link.href);
925
+ const childTopics = topic.children;
926
+ if (meta === topicMeta) {
927
+ referenceUrl = topic.link.href;
928
+ } else if (childTopics && childTopics.length) {
929
+ referenceUrl = this.getReferenceUrlInGivenTopics(
930
+ childTopics,
931
+ topicMeta
932
+ );
933
+ }
934
+ if (referenceUrl) {
935
+ break;
936
+ }
937
+ }
938
+ return referenceUrl;
939
+ }
940
+
941
+ /**
942
+ * Gives referenceUrl for given markdown topic url
943
+ */
944
+ getRefLinkForGivenTopicMeta(
945
+ referenceId: string,
946
+ topicMeta: string
947
+ ): string | undefined {
948
+ const amfConfig = this.getAmfConfigWithId(referenceId);
949
+ if (amfConfig) {
950
+ const topics = amfConfig.topic?.children || [];
951
+ return this.getReferenceUrlInGivenTopics(topics, topicMeta);
952
+ }
953
+ }
954
+
955
+ /**
956
+ * Updates the DOM on the first load
957
+ */
958
+ updateView(): void {
959
+ const previousRefUrlInSession = window.sessionStorage.getItem(
960
+ this.docsReferenceUrlSessionKey
961
+ );
962
+ window.sessionStorage.removeItem(this.docsReferenceUrlSessionKey);
963
+ let previousRefInfo = this.getReferenceMetaInfo(
964
+ previousRefUrlInSession
965
+ );
741
966
 
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");
967
+ // For spec based reference, We should consider urlData to show same topic when user reloads after navigating to specific topic
968
+ if (!previousRefInfo) {
969
+ const currentUrl = window.location.href;
970
+ const urlReferenceId = this.getReferenceIdFromUrl(currentUrl);
971
+ if (urlReferenceId && this.isSpecBasedReference(urlReferenceId)) {
972
+ if (
973
+ !this.isProjectRootPath() &&
974
+ !this.isParentReferencePath(currentUrl)
975
+ ) {
976
+ previousRefInfo = this.getReferenceMetaInfo(currentUrl);
977
+ }
748
978
  }
979
+ }
980
+
981
+ let referenceId: string;
982
+ let topicId = "";
749
983
 
750
- if (topic) {
751
- this.loadContent(
752
- topic.referenceId,
753
- topic.amfId,
754
- topic.type,
755
- "",
756
- topic.meta
984
+ if (
985
+ previousRefInfo &&
986
+ this._amfConfigMap.has(previousRefInfo.referenceId)
987
+ ) {
988
+ referenceId = previousRefInfo.referenceId;
989
+ topicId = previousRefInfo.topicId;
990
+ } else {
991
+ referenceId = this._currentReferenceId;
992
+ }
993
+
994
+ const specBasedReference = this.isSpecBasedReference(referenceId);
995
+ if (specBasedReference) {
996
+ // Wait till the AMF is loaded.
997
+ this.amfFetchPromiseMap[referenceId].then(() => {
998
+ let selectedItemMetaData = this.getMetadataByIdentifier(
999
+ referenceId,
1000
+ topicId
757
1001
  );
1002
+ if (!selectedItemMetaData) {
1003
+ // Doesn't exist, let's use the summary.
1004
+ selectedItemMetaData = this.getMetadataByType(
1005
+ referenceId,
1006
+ "summary"
1007
+ );
1008
+ }
1009
+
1010
+ if (selectedItemMetaData) {
1011
+ this.loadSpecReferenceContent(
1012
+ selectedItemMetaData.parentReferencePath,
1013
+ selectedItemMetaData.referenceId,
1014
+ selectedItemMetaData.amfId,
1015
+ selectedItemMetaData.type,
1016
+ "",
1017
+ selectedItemMetaData.meta
1018
+ );
1019
+ this.updateUrlWithSelected(
1020
+ selectedItemMetaData.parentReferencePath,
1021
+ selectedItemMetaData.meta
1022
+ );
1023
+ }
1024
+ });
1025
+ } else {
1026
+ let invalidTopicReferenceUrl = "";
1027
+ if (topicId) {
1028
+ const selectedItemUrl = this.getRefLinkForGivenTopicMeta(
1029
+ referenceId,
1030
+ topicId
1031
+ );
1032
+ if (!selectedItemUrl) {
1033
+ invalidTopicReferenceUrl = previousRefUrlInSession;
1034
+ }
758
1035
  }
759
- });
1036
+ this.loadMarkdownBasedReference(invalidTopicReferenceUrl);
1037
+ }
760
1038
  }
761
1039
 
762
1040
  /**
763
- * Currently, used to handle the version change and storing the current meta query param to the session storage.
1041
+ * Navigates to reference of the given URL
1042
+ * @param url
764
1043
  */
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
1044
+ private loadNewReferenceItem(url: string): void {
1045
+ const referenceId = this.getReferenceIdFromUrl(url);
1046
+ const referenceItem = this.getAmfConfigWithId(referenceId);
1047
+ if (referenceItem) {
1048
+ location.href = referenceItem.href;
1049
+ }
1050
+ }
1051
+
1052
+ /**
1053
+ * @param referenceUrl to which user wants to navigate
1054
+ * Redirect to first sub item if it's root level item, otherwise content will be loaded
1055
+ * Push the history as a first child item
1056
+ * set selected sidebar value as a pathname
1057
+ */
1058
+
1059
+ private loadMarkdownBasedReference(referenceUrl?: string): void {
1060
+ let referenceId = "";
1061
+ const currentUrl = window.location.href;
1062
+ if (this.isProjectRootPath()) {
1063
+ /**
1064
+ * CASE1: This case is to consider when the user navigates to references by clicking a project card
1065
+ * Ex: /docs/example-project/references should navigate to the first topic in the first reference
1066
+ */
1067
+ referenceId = this._currentReferenceId;
1068
+ } else if (this.isParentReferencePath(referenceUrl)) {
1069
+ /**
1070
+ * CASE2: This case is to navigate to respective reference when the user clicked on root item
1071
+ * Ex: .../references/markdown-ref should navigate to first topic.
1072
+ */
1073
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1074
+ } else if (this.isParentReferencePath(currentUrl)) {
1075
+ /**
1076
+ * CASE3: This case is to navigate to respective reference when the user entered url with reference id
1077
+ * Ex: .../references/markdown-ref should navigate to first topic.
1078
+ */
1079
+ referenceId = this.getReferenceIdFromUrl(currentUrl);
1080
+ } else if (referenceUrl) {
1081
+ /**
1082
+ * CASE4: This case is to navigate to first item when we don't have topic in the selected version
1083
+ * Ex: .../references/markdown-ref/not-existed-topic-url should navigate to first topic.
1084
+ */
1085
+ const referenceMeta = this.getMarkdownReferenceMeta(referenceUrl);
1086
+ const selectedItemRefId = this.getReferenceIdFromUrl(referenceUrl);
1087
+ const selectedItemUrl = this.getRefLinkForGivenTopicMeta(
1088
+ selectedItemRefId,
1089
+ referenceMeta
773
1090
  );
1091
+ if (!selectedItemUrl) {
1092
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1093
+ }
1094
+ }
1095
+
1096
+ if (referenceId) {
1097
+ const amfConfig = this.getAmfConfigWithId(referenceId);
1098
+ let redirectReferenceUrl = "";
1099
+ if (amfConfig) {
1100
+ const childrenItems = amfConfig.topic.children;
1101
+ if (childrenItems.length > 0) {
1102
+ redirectReferenceUrl = childrenItems[0].link.href;
1103
+ }
1104
+ }
1105
+ if (redirectReferenceUrl) {
1106
+ if (this.isParentReferencePath(referenceUrl)) {
1107
+ // This is for CASE2 mentioned above, Where we need to navigate user to respective href
1108
+ location.href = redirectReferenceUrl;
1109
+ } else {
1110
+ // This is for CASE 1,3 and 4 mentioned above, Where we need to update the browser history
1111
+ window.history.replaceState({}, "", redirectReferenceUrl);
1112
+ }
1113
+ }
774
1114
  }
1115
+
1116
+ this.versions = this.getVersions();
1117
+
1118
+ this.updateDocPhase();
1119
+ this.selectedSidebarValue = window.location.pathname;
1120
+ }
1121
+
1122
+ /**
1123
+ * Currently, used to handle the version change and store the current reference Url.
1124
+ */
1125
+ handleVersionChange(): void {
1126
+ const currentUrl = window.location.href;
1127
+ window.sessionStorage.setItem(
1128
+ this.docsReferenceUrlSessionKey,
1129
+ currentUrl
1130
+ );
775
1131
  }
776
1132
 
777
1133
  onNavSelect(event: CustomEvent): void {
778
1134
  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;
1135
+ if (name) {
1136
+ const referenceId = this.getReferenceIdFromUrl(name);
1137
+ const specBasedReference = this.isSpecBasedReference(referenceId);
1138
+ if (specBasedReference) {
1139
+ const metaVal = this.getMetaFromUrl(name);
1140
+ const currentSelectedMeta = this.selectedTopic
1141
+ ? this.selectedTopic.meta
1142
+ : "";
1143
+
1144
+ if (metaVal && metaVal === currentSelectedMeta) {
1145
+ // selecting the same nav item, skip update
1146
+ return;
1147
+ }
785
1148
 
786
- if (metaVal === currentSelectedMeta) {
787
- // selecting the same nav item, skip update
788
- return;
1149
+ const metadata = this.metadata[metaVal];
1150
+ if (metadata) {
1151
+ const {
1152
+ parentReferencePath,
1153
+ referenceId,
1154
+ amfId,
1155
+ type,
1156
+ elementId
1157
+ } = metadata;
1158
+ this.loadSpecReferenceContent(
1159
+ parentReferencePath,
1160
+ referenceId,
1161
+ amfId,
1162
+ type,
1163
+ elementId,
1164
+ metaVal
1165
+ );
1166
+ this.updateUrlWithSelected(parentReferencePath, metaVal);
1167
+ } else {
1168
+ if (this.isParentReferencePath(name)) {
1169
+ this.loadNewReferenceItem(name);
1170
+ }
1171
+ }
1172
+ } else {
1173
+ this.loadMarkdownBasedReference(name);
1174
+ }
789
1175
  }
1176
+ }
790
1177
 
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);
1178
+ onExpandCollapse(event: CustomEvent): void {
1179
+ const { name, isSelectAction, isExpanded } = event.detail;
1180
+ if (!isSelectAction && isExpanded) {
1181
+ const referenceId = this.getReferenceIdFromUrl(name);
1182
+ const currentUrl = window.location.href;
1183
+ const currentReferenceId = this.getReferenceIdFromUrl(currentUrl);
1184
+ //No need to do anything if user is expanding currently selected reference
1185
+ if (referenceId !== currentReferenceId) {
1186
+ const isSpecBasedReference =
1187
+ this.isSpecBasedReference(referenceId);
1188
+ if (isSpecBasedReference) {
1189
+ // Perform functionality same as item selection
1190
+ this.onNavSelect(event);
1191
+ }
1192
+ }
796
1193
  }
797
1194
  }
798
1195