@salesforcedevs/docs-components 0.56.0 → 0.56.2-comp-flex-ref-2

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
+ window.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,46 +578,39 @@ 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 }
475
- ): string {
594
+ ): string | undefined {
476
595
  const { urlIdentifer, prefix } = URL_CONFIG[type];
477
596
 
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
- };
600
+ let meta;
601
+ // Assuming that there will be an identifier always
602
+ if (identifier) {
603
+ 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
+ }
492
614
  return meta;
493
615
  }
494
616
 
@@ -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}`
727
+ );
728
+ }
729
+ }
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
662
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(): 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();
663
797
  }
664
798
  }
665
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,353 @@ 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
+ let metaReferenceInfo;
887
+ if (referenceUrl) {
888
+ const referenceId = this.getReferenceIdFromUrl(referenceUrl);
889
+ let meta = "";
890
+ let topicId = "";
891
+ let type = "";
892
+ if (this.isSpecBasedReference(referenceId)) {
893
+ meta = this.getMetaFromUrl(referenceUrl);
894
+ if (meta) {
895
+ if (meta.includes(":")) {
896
+ const metaInfo = meta.split(":");
897
+ type = metaInfo[0];
898
+ topicId = metaInfo[1] || type;
899
+ } else {
900
+ topicId = meta;
901
+ }
902
+ }
903
+ } else {
904
+ topicId = this.getMarkdownReferenceMeta(referenceUrl);
739
905
  }
906
+ metaReferenceInfo = {
907
+ referenceId,
908
+ meta,
909
+ topicId,
910
+ type
911
+ };
740
912
  }
913
+ return metaReferenceInfo;
914
+ }
741
915
 
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");
916
+ /**
917
+ * Finds and returns referenceUrl if given topic url matches
918
+ */
919
+ getReferenceUrlInGivenTopics(
920
+ topics: ParsedMarkdownTopic[],
921
+ topicMeta: string
922
+ ): string {
923
+ let referenceUrl = "";
924
+ for (let i = 0; i < topics.length; i++) {
925
+ const topic = topics[i];
926
+ const meta = this.getMarkdownReferenceMeta(topic.link.href);
927
+ const childTopics = topic.children;
928
+ if (meta === topicMeta) {
929
+ referenceUrl = topic.link.href;
930
+ } else if (childTopics && childTopics.length) {
931
+ referenceUrl = this.getReferenceUrlInGivenTopics(
932
+ childTopics,
933
+ topicMeta
934
+ );
748
935
  }
936
+ if (referenceUrl) {
937
+ break;
938
+ }
939
+ }
940
+ return referenceUrl;
941
+ }
749
942
 
750
- if (topic) {
751
- this.loadContent(
752
- topic.referenceId,
753
- topic.amfId,
754
- topic.type,
755
- "",
756
- topic.meta
943
+ /**
944
+ * Gives referenceUrl for given markdown topic url
945
+ */
946
+ getRefLinkForGivenTopicMeta(
947
+ referenceId: string,
948
+ topicMeta: string
949
+ ): string | undefined {
950
+ const amfConfig = this.getAmfConfigWithId(referenceId);
951
+ let referenceUrl;
952
+ if (amfConfig) {
953
+ const topics = amfConfig.topic?.children || [];
954
+ referenceUrl = this.getReferenceUrlInGivenTopics(topics, topicMeta);
955
+ }
956
+ return referenceUrl;
957
+ }
958
+
959
+ /**
960
+ * Updates the DOM on the first load
961
+ */
962
+ updateView(): void {
963
+ const previousRefUrlInSession = window.sessionStorage.getItem(
964
+ this.docsReferenceUrlSessionKey
965
+ );
966
+ window.sessionStorage.removeItem(this.docsReferenceUrlSessionKey);
967
+ let previousRefInfo = this.getReferenceMetaInfo(
968
+ previousRefUrlInSession
969
+ );
970
+
971
+ // For spec based reference, We should consider urlData to show same topic when user reloads after navigating to specific topic
972
+ if (!previousRefInfo) {
973
+ const currentUrl = window.location.href;
974
+ const urlReferenceId = this.getReferenceIdFromUrl(currentUrl);
975
+ if (urlReferenceId && this.isSpecBasedReference(urlReferenceId)) {
976
+ if (
977
+ !this.isProjectRootPath() &&
978
+ !this.isParentReferencePath(currentUrl)
979
+ ) {
980
+ previousRefInfo = this.getReferenceMetaInfo(currentUrl);
981
+ }
982
+ }
983
+ }
984
+
985
+ let referenceId: string;
986
+ let topicId = "";
987
+
988
+ if (
989
+ previousRefInfo &&
990
+ this._amfConfigMap.has(previousRefInfo.referenceId)
991
+ ) {
992
+ referenceId = previousRefInfo.referenceId;
993
+ topicId = previousRefInfo.topicId;
994
+ } else {
995
+ referenceId = this._currentReferenceId;
996
+ }
997
+
998
+ const specBasedReference = this.isSpecBasedReference(referenceId);
999
+ if (specBasedReference) {
1000
+ // Wait till the AMF is loaded.
1001
+ this.amfFetchPromiseMap[referenceId].then(() => {
1002
+ let selectedItemMetaData = this.getMetadataByIdentifier(
1003
+ referenceId,
1004
+ topicId
757
1005
  );
1006
+ if (!selectedItemMetaData) {
1007
+ // Doesn't exist, let's use the summary.
1008
+ selectedItemMetaData = this.getMetadataByType(
1009
+ referenceId,
1010
+ "summary"
1011
+ );
1012
+ }
1013
+
1014
+ if (selectedItemMetaData) {
1015
+ this.loadSpecReferenceContent(
1016
+ selectedItemMetaData.parentReferencePath,
1017
+ selectedItemMetaData.referenceId,
1018
+ selectedItemMetaData.amfId,
1019
+ selectedItemMetaData.type,
1020
+ "",
1021
+ selectedItemMetaData.meta
1022
+ );
1023
+ this.updateUrlWithSelected(
1024
+ selectedItemMetaData.parentReferencePath,
1025
+ selectedItemMetaData.meta
1026
+ );
1027
+ }
1028
+ });
1029
+ } else {
1030
+ let invalidTopicReferenceUrl = "";
1031
+ if (topicId) {
1032
+ const selectedItemUrl = this.getRefLinkForGivenTopicMeta(
1033
+ referenceId,
1034
+ topicId
1035
+ );
1036
+ if (!selectedItemUrl) {
1037
+ invalidTopicReferenceUrl = previousRefUrlInSession;
1038
+ }
758
1039
  }
759
- });
1040
+ this.loadMarkdownBasedReference(invalidTopicReferenceUrl);
1041
+ }
760
1042
  }
761
1043
 
762
1044
  /**
763
- * Currently, used to handle the version change and storing the current meta query param to the session storage.
1045
+ * Navigates to reference of the given URL
1046
+ * @param url
764
1047
  */
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
1048
+ private loadNewReferenceItem(url: string): void {
1049
+ const referenceId = this.getReferenceIdFromUrl(url);
1050
+ const referenceItem = this.getAmfConfigWithId(referenceId);
1051
+ if (referenceItem) {
1052
+ window.location.href = referenceItem.href;
1053
+ }
1054
+ }
1055
+
1056
+ /**
1057
+ * @param referenceUrl to which user wants to navigate
1058
+ * Redirect to first sub item if it's root level item, otherwise content will be loaded
1059
+ * Push the history as a first child item
1060
+ * set selected sidebar value as a pathname
1061
+ */
1062
+
1063
+ private loadMarkdownBasedReference(referenceUrl?: string): void {
1064
+ let referenceId = "";
1065
+ const currentUrl = window.location.href;
1066
+ if (this.isProjectRootPath()) {
1067
+ /**
1068
+ * CASE1: This case is to consider when the user navigates to references by clicking a project card
1069
+ * Ex: /docs/example-project/references should navigate to the first topic in the first reference
1070
+ */
1071
+ referenceId = this._currentReferenceId;
1072
+ } else if (this.isParentReferencePath(referenceUrl)) {
1073
+ /**
1074
+ * CASE2: This case is to navigate to respective reference when the user clicked on root item
1075
+ * Ex: .../references/markdown-ref should navigate to first topic.
1076
+ */
1077
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1078
+ } else if (this.isParentReferencePath(currentUrl)) {
1079
+ /**
1080
+ * CASE3: This case is to navigate to respective reference when the user entered url with reference id
1081
+ * Ex: .../references/markdown-ref should navigate to first topic.
1082
+ */
1083
+ referenceId = this.getReferenceIdFromUrl(currentUrl);
1084
+ } else if (referenceUrl) {
1085
+ /**
1086
+ * CASE4: This case is to navigate to first item when we don't have topic in the selected version
1087
+ * Ex: .../references/markdown-ref/not-existed-topic-url should navigate to first topic.
1088
+ */
1089
+ const referenceMeta = this.getMarkdownReferenceMeta(referenceUrl);
1090
+ const selectedItemRefId = this.getReferenceIdFromUrl(referenceUrl);
1091
+ const selectedItemUrl = this.getRefLinkForGivenTopicMeta(
1092
+ selectedItemRefId,
1093
+ referenceMeta
773
1094
  );
1095
+ if (!selectedItemUrl) {
1096
+ referenceId = this.getReferenceIdFromUrl(referenceUrl);
1097
+ }
1098
+ }
1099
+
1100
+ if (referenceId) {
1101
+ const amfConfig = this.getAmfConfigWithId(referenceId);
1102
+ let redirectReferenceUrl = "";
1103
+ if (amfConfig) {
1104
+ const childrenItems = amfConfig.topic.children;
1105
+ if (childrenItems.length > 0) {
1106
+ redirectReferenceUrl = childrenItems[0].link.href;
1107
+ }
1108
+ }
1109
+ if (redirectReferenceUrl) {
1110
+ if (this.isParentReferencePath(referenceUrl)) {
1111
+ // This is for CASE2 mentioned above, Where we need to navigate user to respective href
1112
+ window.location.href = redirectReferenceUrl;
1113
+ } else {
1114
+ // This is for CASE 1,3 and 4 mentioned above, Where we need to update the browser history
1115
+ window.history.replaceState({}, "", redirectReferenceUrl);
1116
+ }
1117
+ }
774
1118
  }
1119
+
1120
+ this.versions = this.getVersions();
1121
+
1122
+ this.updateDocPhase();
1123
+ this.selectedSidebarValue = window.location.pathname;
1124
+ }
1125
+
1126
+ /**
1127
+ * Currently, used to handle the version change and store the current reference Url.
1128
+ */
1129
+ handleVersionChange(): void {
1130
+ const currentUrl = window.location.href;
1131
+ window.sessionStorage.setItem(
1132
+ this.docsReferenceUrlSessionKey,
1133
+ currentUrl
1134
+ );
775
1135
  }
776
1136
 
777
1137
  onNavSelect(event: CustomEvent): void {
778
1138
  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;
1139
+ if (name) {
1140
+ const urlReferenceId = this.getReferenceIdFromUrl(name);
1141
+ const specBasedReference = this.isSpecBasedReference(urlReferenceId);
1142
+ if (specBasedReference) {
1143
+ const metaVal = this.getMetaFromUrl(name);
1144
+ const currentSelectedMeta = this.selectedTopic
1145
+ ? this.selectedTopic.meta
1146
+ : "";
1147
+
1148
+ if (metaVal && metaVal === currentSelectedMeta) {
1149
+ // selecting the same nav item, skip update
1150
+ return;
1151
+ }
785
1152
 
786
- if (metaVal === currentSelectedMeta) {
787
- // selecting the same nav item, skip update
788
- return;
1153
+ const metadata = this.metadata[metaVal];
1154
+ if (metadata) {
1155
+ const {
1156
+ parentReferencePath,
1157
+ referenceId,
1158
+ amfId,
1159
+ type,
1160
+ elementId
1161
+ } = metadata;
1162
+ this.loadSpecReferenceContent(
1163
+ parentReferencePath,
1164
+ referenceId,
1165
+ amfId,
1166
+ type,
1167
+ elementId,
1168
+ metaVal
1169
+ );
1170
+ this.updateUrlWithSelected(parentReferencePath, metaVal);
1171
+ } else {
1172
+ if (this.isParentReferencePath(name)) {
1173
+ this.loadNewReferenceItem(name);
1174
+ }
1175
+ }
1176
+ } else {
1177
+ this.loadMarkdownBasedReference(name);
1178
+ }
789
1179
  }
1180
+ }
790
1181
 
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);
1182
+ onExpandCollapse(event: CustomEvent): void {
1183
+ const { name, isSelectAction, isExpanded } = event.detail;
1184
+ if (!isSelectAction && isExpanded) {
1185
+ const referenceId = this.getReferenceIdFromUrl(name);
1186
+ const currentUrl = window.location.href;
1187
+ const currentReferenceId = this.getReferenceIdFromUrl(currentUrl);
1188
+ //No need to do anything if user is expanding currently selected reference
1189
+ if (referenceId !== currentReferenceId) {
1190
+ const isSpecBasedReference =
1191
+ this.isSpecBasedReference(referenceId);
1192
+ if (isSpecBasedReference) {
1193
+ // Perform functionality same as item selection
1194
+ this.onNavSelect(event);
1195
+ }
1196
+ }
796
1197
  }
797
1198
  }
798
1199