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