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