@salesforcedevs/docs-components 1.17.5 → 1.17.6-redoc1
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 +1 -0
- package/package.json +2 -2
- package/src/modules/doc/docReference/docReference.css +52 -0
- package/src/modules/doc/docReference/docReference.html +62 -0
- package/src/modules/doc/docReference/docReference.ts +1090 -0
- package/src/modules/doc/docReference/types.ts +24 -0
- package/LICENSE +0 -12
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/docs-components",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.6-redoc1",
|
|
4
4
|
"description": "Docs Lightning web components for DSC",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.js",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"@types/lodash.orderby": "4.6.9",
|
|
26
26
|
"@types/lodash.uniqby": "4.7.9"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "4629fdd9ca18a13480044ad43515b91945d16aad"
|
|
29
29
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
@import "docHelpers/amfStyle";
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
--reference-container-margin-top: var(--dx-g-spacing-sm);
|
|
5
|
+
--api-documentation-margin-top: var(--dx-g-spacing-3xl);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 1. We need to scroll to top from the tablet size as side nav bar and content in side by side from tablet size
|
|
10
|
+
* 2. Consider global nav height, doc header height and content margins to scroll to the right position
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
@media screen and (min-width: 769px) {
|
|
14
|
+
.redoc-container {
|
|
15
|
+
scroll-margin-top: calc(
|
|
16
|
+
var(--dx-g-global-header-height) + var(--dx-g-doc-header-height) +
|
|
17
|
+
var(--reference-container-margin-top) +
|
|
18
|
+
var(--api-documentation-margin-top)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.redoc-container {
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
/* Ensure Redoc content is scrollable within the content layout */
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Loading state styles */
|
|
31
|
+
.loading-container {
|
|
32
|
+
display: flex;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
align-items: center;
|
|
35
|
+
height: 200px;
|
|
36
|
+
font-size: 16px;
|
|
37
|
+
color: var(--dx-g-color-text-secondary);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Error state styles */
|
|
41
|
+
.error-container {
|
|
42
|
+
padding: 20px;
|
|
43
|
+
background-color: var(--dx-g-color-background-error);
|
|
44
|
+
border: 1px solid var(--dx-g-color-border-error);
|
|
45
|
+
border-radius: 4px;
|
|
46
|
+
margin: 20px 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.error-message {
|
|
50
|
+
color: var(--dx-g-color-text-error);
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<doc-content-layout
|
|
3
|
+
lwc:if={isVersionFetched}
|
|
4
|
+
use-old-sidebar={useOldSidebar}
|
|
5
|
+
class="content-type content-type-reference"
|
|
6
|
+
coveo-organization-id={coveoOrganizationId}
|
|
7
|
+
coveo-public-access-token={coveoPublicAccessToken}
|
|
8
|
+
coveo-analytics-token={coveoAnalyticsToken}
|
|
9
|
+
coveo-search-hub={coveoSearchHub}
|
|
10
|
+
coveo-advanced-query-config={coveoAdvancedQueryConfig}
|
|
11
|
+
breadcrumbs={breadcrumbs}
|
|
12
|
+
sidebar-header={sidebarHeader}
|
|
13
|
+
sidebar-value={selectedSidebarValue}
|
|
14
|
+
sidebar-content={navigation}
|
|
15
|
+
onselect={onNavSelect}
|
|
16
|
+
onexpandcollapse={onExpandCollapse}
|
|
17
|
+
toc-title={tocTitle}
|
|
18
|
+
toc-options={tocOptions}
|
|
19
|
+
enable-slot-change="true"
|
|
20
|
+
languages={languages}
|
|
21
|
+
language={language}
|
|
22
|
+
show-footer={enableFooter}
|
|
23
|
+
>
|
|
24
|
+
<doc-phase
|
|
25
|
+
slot="doc-phase"
|
|
26
|
+
lwc:if={docPhaseInfo}
|
|
27
|
+
doc-phase-info={docPhaseInfo}
|
|
28
|
+
></doc-phase>
|
|
29
|
+
<doc-phase
|
|
30
|
+
slot="version-banner"
|
|
31
|
+
lwc:if={showVersionBanner}
|
|
32
|
+
doc-phase-info={oldVersionInfo}
|
|
33
|
+
icon-name="warning"
|
|
34
|
+
dismissible="true"
|
|
35
|
+
ondismissphase={handleDismissVersionBanner}
|
|
36
|
+
></doc-phase>
|
|
37
|
+
<div lwc:if={isVersionEnabled} slot="sidebar-header">
|
|
38
|
+
<doc-version-picker
|
|
39
|
+
onchange={handleVersionChange}
|
|
40
|
+
data-type="version"
|
|
41
|
+
versions={versions}
|
|
42
|
+
selected-version={selectedVersion}
|
|
43
|
+
latest-version={latestVersion}
|
|
44
|
+
></doc-version-picker>
|
|
45
|
+
</div>
|
|
46
|
+
<template lwc:if={showSpecBasedReference}>
|
|
47
|
+
<div class="container">
|
|
48
|
+
<div class="api-documentation">
|
|
49
|
+
<!-- Replace doc-amf-topic with Redoc container -->
|
|
50
|
+
<div
|
|
51
|
+
class="redoc-container"
|
|
52
|
+
lwc:dom="manual"
|
|
53
|
+
data-redoc-container
|
|
54
|
+
></div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
<template lwc:else>
|
|
59
|
+
<slot></slot>
|
|
60
|
+
</template>
|
|
61
|
+
</doc-content-layout>
|
|
62
|
+
</template>
|
|
@@ -0,0 +1,1090 @@
|
|
|
1
|
+
import { LightningElement, api, track } from "lwc";
|
|
2
|
+
import { normalizeBoolean, toJson } from "dxUtils/normalizers";
|
|
3
|
+
import type { OptionWithLink } from "typings/custom";
|
|
4
|
+
import type {
|
|
5
|
+
AmfConfig,
|
|
6
|
+
ReferenceVersion,
|
|
7
|
+
ReferenceSetConfig,
|
|
8
|
+
ParsedMarkdownTopic
|
|
9
|
+
} from "../amfReference/types";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
REFERENCE_TYPES,
|
|
13
|
+
oldReferenceIdNewReferenceIdMap
|
|
14
|
+
} from "../amfReference/constants";
|
|
15
|
+
import { restoreScroll } from "dx/scrollManager";
|
|
16
|
+
import { DocPhaseInfo } from "typings/custom";
|
|
17
|
+
import { logCoveoPageView, oldVersionDocInfo } from "docUtils/utils";
|
|
18
|
+
import type { RedocNavigationItem, RedocSidebarData } from "./types";
|
|
19
|
+
import "./types"; // Import types to ensure global declarations are loaded
|
|
20
|
+
|
|
21
|
+
type NavigationItem = {
|
|
22
|
+
label: string;
|
|
23
|
+
name: string;
|
|
24
|
+
isExpanded: boolean;
|
|
25
|
+
children: RedocNavigationItem[];
|
|
26
|
+
isChildrenLoading: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default class DocReference extends LightningElement {
|
|
30
|
+
@api breadcrumbs: string | null = null;
|
|
31
|
+
@api sidebarHeader!: string;
|
|
32
|
+
@api coveoOrganizationId!: string;
|
|
33
|
+
@api coveoPublicAccessToken!: string;
|
|
34
|
+
@api coveoAnalyticsToken!: string;
|
|
35
|
+
@api coveoSearchHub!: string;
|
|
36
|
+
@api useOldSidebar: boolean = false;
|
|
37
|
+
@api tocTitle?: string;
|
|
38
|
+
@api tocOptions?: string;
|
|
39
|
+
@api languages!: OptionWithLink[];
|
|
40
|
+
@api language!: string;
|
|
41
|
+
@api hideFooter = false;
|
|
42
|
+
@track navigation = [] as NavigationItem[];
|
|
43
|
+
@track versions: Array<ReferenceVersion> = [];
|
|
44
|
+
@track showVersionBanner = false;
|
|
45
|
+
@track _coveoAdvancedQueryConfig!: { [key: string]: any };
|
|
46
|
+
|
|
47
|
+
get isVersionEnabled(): boolean {
|
|
48
|
+
return !!this._referenceSetConfig?.versions?.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gives if the currently selected reference is spec based or not
|
|
53
|
+
*/
|
|
54
|
+
get showSpecBasedReference(): boolean {
|
|
55
|
+
return this.isSpecBasedReference(this._currentReferenceId);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@api
|
|
59
|
+
get referenceSetConfig(): ReferenceSetConfig {
|
|
60
|
+
return this._referenceSetConfig;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
set referenceSetConfig(value: ReferenceSetConfig) {
|
|
64
|
+
// No change, do nothing.
|
|
65
|
+
if (value === this._referenceSetConfig) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const refConfig =
|
|
71
|
+
typeof value === "string" ? JSON.parse(value) : value;
|
|
72
|
+
if (!(<ReferenceSetConfig>refConfig).versions) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this._referenceSetConfig = refConfig;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
this._referenceSetConfig = {
|
|
78
|
+
refList: [],
|
|
79
|
+
versions: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this._amfConfigList = this._referenceSetConfig.refList || [];
|
|
84
|
+
|
|
85
|
+
this._amfConfigList.forEach((amfConfig) => {
|
|
86
|
+
this._amfConfigMap.set(amfConfig.id, amfConfig);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (this._amfConfigList.length > 0) {
|
|
90
|
+
this._currentReferenceId =
|
|
91
|
+
this._referenceSetConfig.refId || this._amfConfigList[0].id;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.isVersionEnabled) {
|
|
95
|
+
const selectedVersion = this.getSelectedVersion();
|
|
96
|
+
|
|
97
|
+
if (this.isSpecBasedReference(this._currentReferenceId)) {
|
|
98
|
+
this.versions = this.getVersions();
|
|
99
|
+
}
|
|
100
|
+
this.selectedVersion = selectedVersion;
|
|
101
|
+
if (this.isSpecBasedReference(this._currentReferenceId)) {
|
|
102
|
+
this.isVersionFetched = true;
|
|
103
|
+
if (this.oldVersionInfo) {
|
|
104
|
+
this.showVersionBanner = true;
|
|
105
|
+
} else {
|
|
106
|
+
this.latestVersion = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
this.isVersionFetched = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// This is to check if the url is hash based and redirect if needed
|
|
114
|
+
const redirectUrl = this.getHashBasedRedirectUrl();
|
|
115
|
+
if (redirectUrl) {
|
|
116
|
+
window.location.href = redirectUrl;
|
|
117
|
+
} else {
|
|
118
|
+
this.updateConfigInView();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@api
|
|
123
|
+
get docPhaseInfo(): string | null {
|
|
124
|
+
return this.selectedReferenceDocPhase || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
set docPhaseInfo(value: string) {
|
|
128
|
+
if (value) {
|
|
129
|
+
this.isParentLevelDocPhaseEnabled = true;
|
|
130
|
+
this.selectedReferenceDocPhase = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@api
|
|
135
|
+
get expandChildren() {
|
|
136
|
+
return this._expandChildren;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
set expandChildren(value) {
|
|
140
|
+
this._expandChildren = normalizeBoolean(value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@api
|
|
144
|
+
get coveoAdvancedQueryConfig(): { [key: string]: any } {
|
|
145
|
+
const coveoConfig = this._coveoAdvancedQueryConfig;
|
|
146
|
+
if (this.versions.length > 1 && this.selectedVersion) {
|
|
147
|
+
const currentGAVersionRef = this.versions[0];
|
|
148
|
+
if (this.selectedVersion.id !== currentGAVersionRef.id) {
|
|
149
|
+
const version = this.selectedVersion.id.replace("v", "");
|
|
150
|
+
coveoConfig.version = version;
|
|
151
|
+
this._coveoAdvancedQueryConfig = coveoConfig;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return this._coveoAdvancedQueryConfig;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
set coveoAdvancedQueryConfig(config) {
|
|
158
|
+
this._coveoAdvancedQueryConfig = toJson(config);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private get enableFooter(): boolean {
|
|
162
|
+
return !this.hideFooter;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// model
|
|
166
|
+
protected _amfConfigList: AmfConfig[] = [];
|
|
167
|
+
protected _amfConfigMap: Map<string, AmfConfig> = new Map();
|
|
168
|
+
protected _referenceSetConfig!: ReferenceSetConfig;
|
|
169
|
+
protected _currentReferenceId = "";
|
|
170
|
+
|
|
171
|
+
protected parentReferenceUrls = [] as string[];
|
|
172
|
+
protected redocFetchPromiseMap = {} as any;
|
|
173
|
+
protected selectedSidebarValue: string | undefined = undefined;
|
|
174
|
+
protected selectedVersion: ReferenceVersion | null = null;
|
|
175
|
+
|
|
176
|
+
// Redoc-specific properties
|
|
177
|
+
private redocContainers: Map<string, Element> = new Map();
|
|
178
|
+
private redocSidebarData: Map<string, RedocSidebarData> = new Map();
|
|
179
|
+
private currentActiveSection: string | null = null;
|
|
180
|
+
private isProgrammaticScroll = false;
|
|
181
|
+
private scrollTimeout: number | null = null;
|
|
182
|
+
|
|
183
|
+
private hasRendered = false;
|
|
184
|
+
private isParentLevelDocPhaseEnabled = false;
|
|
185
|
+
private selectedReferenceDocPhase?: string | null = null;
|
|
186
|
+
private _expandChildren?: boolean = false;
|
|
187
|
+
private isVersionFetched = false;
|
|
188
|
+
private latestVersion = false;
|
|
189
|
+
|
|
190
|
+
private readonly docsReferenceUrlSessionKey: string = "docsReferenceUrl";
|
|
191
|
+
|
|
192
|
+
_boundOnApiNavigationChanged;
|
|
193
|
+
_boundUpdateSelectedItemFromUrlQuery;
|
|
194
|
+
_boundHandleRedocScroll;
|
|
195
|
+
|
|
196
|
+
constructor() {
|
|
197
|
+
super();
|
|
198
|
+
|
|
199
|
+
this._boundOnApiNavigationChanged =
|
|
200
|
+
this.onApiNavigationChanged.bind(this);
|
|
201
|
+
this._boundUpdateSelectedItemFromUrlQuery =
|
|
202
|
+
this.updateSelectedItemFromUrlQuery.bind(this);
|
|
203
|
+
this._boundHandleRedocScroll =
|
|
204
|
+
this.handleRedocScroll.bind(this);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
connectedCallback(): void {
|
|
208
|
+
this.addEventListener(
|
|
209
|
+
"api-navigation-selection-changed",
|
|
210
|
+
this._boundOnApiNavigationChanged
|
|
211
|
+
);
|
|
212
|
+
window.addEventListener(
|
|
213
|
+
"popstate",
|
|
214
|
+
this._boundUpdateSelectedItemFromUrlQuery
|
|
215
|
+
);
|
|
216
|
+
window.addEventListener(
|
|
217
|
+
"hashchange",
|
|
218
|
+
this.handleHashNavigation.bind(this)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
disconnectedCallback(): void {
|
|
223
|
+
this.removeEventListener(
|
|
224
|
+
"api-navigation-selection-changed",
|
|
225
|
+
this._boundOnApiNavigationChanged
|
|
226
|
+
);
|
|
227
|
+
window.removeEventListener(
|
|
228
|
+
"popstate",
|
|
229
|
+
this._boundUpdateSelectedItemFromUrlQuery
|
|
230
|
+
);
|
|
231
|
+
window.removeEventListener(
|
|
232
|
+
"hashchange",
|
|
233
|
+
this.handleHashNavigation.bind(this)
|
|
234
|
+
);
|
|
235
|
+
if (this.scrollTimeout) {
|
|
236
|
+
clearTimeout(this.scrollTimeout);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
renderedCallback(): void {
|
|
241
|
+
if (!this.hasRendered) {
|
|
242
|
+
this.hasRendered = true;
|
|
243
|
+
if (this._amfConfigList && this._amfConfigList.length) {
|
|
244
|
+
this.updateView();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if the URL hash to see whether this is one we want to redirect
|
|
251
|
+
*/
|
|
252
|
+
private getHashBasedRedirectUrl(): string | undefined {
|
|
253
|
+
const { hash } = window.location;
|
|
254
|
+
let hashBasedRedirectUrl = "";
|
|
255
|
+
if (hash) {
|
|
256
|
+
const strippedHash = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
257
|
+
const strippedHashItems = strippedHash
|
|
258
|
+
? strippedHash.split(":")
|
|
259
|
+
: [];
|
|
260
|
+
if (strippedHashItems.length) {
|
|
261
|
+
const referenceId = strippedHashItems[0];
|
|
262
|
+
const section = strippedHashItems[1];
|
|
263
|
+
const updatedReferenceId =
|
|
264
|
+
oldReferenceIdNewReferenceIdMap[referenceId];
|
|
265
|
+
const newReferenceId = updatedReferenceId || referenceId;
|
|
266
|
+
const referenceItemConfig =
|
|
267
|
+
this.getAmfConfigWithId(newReferenceId);
|
|
268
|
+
if (referenceItemConfig) {
|
|
269
|
+
hashBasedRedirectUrl = `${referenceItemConfig.href}#${section}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return hashBasedRedirectUrl;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param referenceId
|
|
278
|
+
* @returns AMFConfig with given reference Id
|
|
279
|
+
*/
|
|
280
|
+
private getAmfConfigWithId(referenceId: string): AmfConfig | undefined {
|
|
281
|
+
return this._amfConfigMap.get(referenceId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @param referenceId
|
|
286
|
+
* @returns if the reference is spec based one or not with given referenceId.
|
|
287
|
+
*/
|
|
288
|
+
private isSpecBasedReference(referenceId: string): boolean {
|
|
289
|
+
const selectedReference = this.getAmfConfigWithId(referenceId);
|
|
290
|
+
return selectedReference
|
|
291
|
+
? selectedReference.referenceType !== REFERENCE_TYPES.markdown
|
|
292
|
+
: false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* @param url
|
|
297
|
+
* @returns reference Id from url path / selected sidebar item.
|
|
298
|
+
*/
|
|
299
|
+
private getReferenceIdFromUrl(url: string): string {
|
|
300
|
+
let referenceId = "";
|
|
301
|
+
const urlItems = url.split("/references/");
|
|
302
|
+
if (urlItems.length > 1) {
|
|
303
|
+
const rightSidePart = urlItems[1];
|
|
304
|
+
const slashSeparatorItems = rightSidePart.split("/");
|
|
305
|
+
const querySeparatorItems = slashSeparatorItems[0].split("?");
|
|
306
|
+
referenceId = querySeparatorItems[0];
|
|
307
|
+
}
|
|
308
|
+
return referenceId;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private get oldVersionInfo(): DocPhaseInfo | null {
|
|
312
|
+
let info = null;
|
|
313
|
+
if (this.versions.length > 1 && this.selectedVersion) {
|
|
314
|
+
const currentGAVersionRef = this.versions[0];
|
|
315
|
+
if (this.selectedVersion.id !== currentGAVersionRef.id) {
|
|
316
|
+
info = oldVersionDocInfo(currentGAVersionRef.link.href);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return info;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @returns versions to be shown in the dropdown
|
|
324
|
+
*/
|
|
325
|
+
private getVersions(): Array<ReferenceVersion> {
|
|
326
|
+
const allVersions = this._referenceSetConfig.versions;
|
|
327
|
+
if (!this.isSpecBasedReference(this._currentReferenceId)) {
|
|
328
|
+
// For markdown references, handle differently if needed
|
|
329
|
+
// For now, return as-is since we're focusing on spec-based
|
|
330
|
+
}
|
|
331
|
+
return allVersions;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Returns the selected version or the first available version.
|
|
336
|
+
*/
|
|
337
|
+
private getSelectedVersion(): ReferenceVersion | null {
|
|
338
|
+
const versions = this._referenceSetConfig?.versions || [];
|
|
339
|
+
const selectedVersion = versions.find(
|
|
340
|
+
(v: ReferenceVersion) => v.selected
|
|
341
|
+
);
|
|
342
|
+
return selectedVersion || (versions.length && versions[0]) || null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private updateConfigInView(): void {
|
|
346
|
+
if (this._amfConfigList && this._amfConfigList.length) {
|
|
347
|
+
this.populateReferenceItems();
|
|
348
|
+
if (this.hasRendered) {
|
|
349
|
+
this.updateView();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Fetch spec and generate Redoc structure
|
|
356
|
+
*/
|
|
357
|
+
private async fetchSpec(amfConfig: AmfConfig): Promise<RedocSidebarData | null> {
|
|
358
|
+
const { amf } = amfConfig;
|
|
359
|
+
try {
|
|
360
|
+
// Load Redoc sidebar structure
|
|
361
|
+
if (window.Redoc?.getSpecStructure && amf) {
|
|
362
|
+
return await window.Redoc.getSpecStructure(amf);
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
console.error('Failed to fetch spec structure:', error);
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Populates reference Items and assigns it to navigation for sidebar
|
|
373
|
+
*/
|
|
374
|
+
private populateReferenceItems(): void {
|
|
375
|
+
const navOrder = [] as NavigationItem[];
|
|
376
|
+
for (const [index, amfConfig] of this._amfConfigList.entries()) {
|
|
377
|
+
let navItemChildren = [] as RedocNavigationItem[];
|
|
378
|
+
let isChildrenLoading = false;
|
|
379
|
+
|
|
380
|
+
if (amfConfig.referenceType !== REFERENCE_TYPES.markdown) {
|
|
381
|
+
if (amfConfig.isSelected) {
|
|
382
|
+
const redocPromise = this.fetchSpec(amfConfig).then(
|
|
383
|
+
(redocData) => {
|
|
384
|
+
if (redocData) {
|
|
385
|
+
this.redocSidebarData.set(amfConfig.id, redocData);
|
|
386
|
+
this.assignNavigationItemsFromRedoc(amfConfig, index);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
this.redocFetchPromiseMap[amfConfig.id] = redocPromise;
|
|
391
|
+
}
|
|
392
|
+
isChildrenLoading = true;
|
|
393
|
+
} else {
|
|
394
|
+
const isExpandChildrenEnabled = this.isExpandChildrenEnabled(
|
|
395
|
+
amfConfig.id
|
|
396
|
+
);
|
|
397
|
+
// check whether we should expand all the child nodes, this is required for Coveo to crawl.
|
|
398
|
+
if (isExpandChildrenEnabled && amfConfig.topic) {
|
|
399
|
+
this.expandChildrenForMarkdownReferences(
|
|
400
|
+
amfConfig.topic.children
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
// Convert markdown topics to RedocNavigationItem format
|
|
404
|
+
navItemChildren = this.convertMarkdownTopicsToNavItems(
|
|
405
|
+
amfConfig.topic?.children || []
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
navOrder[index] = {
|
|
410
|
+
label: amfConfig.title,
|
|
411
|
+
name: amfConfig.href,
|
|
412
|
+
isExpanded:
|
|
413
|
+
amfConfig.isSelected ||
|
|
414
|
+
this.isExpandChildrenEnabled(amfConfig.id),
|
|
415
|
+
children: navItemChildren,
|
|
416
|
+
isChildrenLoading
|
|
417
|
+
};
|
|
418
|
+
this.parentReferenceUrls.push(amfConfig.href);
|
|
419
|
+
}
|
|
420
|
+
this.navigation = navOrder;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Returns a boolean indicating whether the children should be expanded or not.
|
|
425
|
+
*/
|
|
426
|
+
private isExpandChildrenEnabled(referenceId: string): boolean {
|
|
427
|
+
return (
|
|
428
|
+
!!this.expandChildren && this._currentReferenceId === referenceId
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Assigns Navigation Items to dx-sidebar from the Redoc structure.
|
|
434
|
+
*/
|
|
435
|
+
private assignNavigationItemsFromRedoc(
|
|
436
|
+
amfConfig: AmfConfig,
|
|
437
|
+
amfIdx: number
|
|
438
|
+
): void {
|
|
439
|
+
const referenceId = amfConfig.id;
|
|
440
|
+
const redocData = this.redocSidebarData.get(referenceId);
|
|
441
|
+
|
|
442
|
+
if (!redocData) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const children: RedocNavigationItem[] = this.convertRedocItemsToNavItems(
|
|
447
|
+
redocData.items,
|
|
448
|
+
amfConfig.href
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
this.navigation[amfIdx] = {
|
|
452
|
+
...this.navigation[amfIdx],
|
|
453
|
+
children,
|
|
454
|
+
isChildrenLoading: false
|
|
455
|
+
};
|
|
456
|
+
this.navigation = [...this.navigation];
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Convert Redoc sidebar items to navigation items with proper hash URLs
|
|
461
|
+
*/
|
|
462
|
+
private convertRedocItemsToNavItems(
|
|
463
|
+
redocItems: RedocNavigationItem[],
|
|
464
|
+
baseHref: string
|
|
465
|
+
): RedocNavigationItem[] {
|
|
466
|
+
return redocItems.map(item => ({
|
|
467
|
+
label: item.label,
|
|
468
|
+
name: `${baseHref}#${item.id}`, // Hash-based navigation
|
|
469
|
+
id: item.id,
|
|
470
|
+
children: item.children ?
|
|
471
|
+
this.convertRedocItemsToNavItems(item.children, baseHref) :
|
|
472
|
+
undefined
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Find a Redoc navigation item by its ID
|
|
478
|
+
*/
|
|
479
|
+
private findRedocItemById(
|
|
480
|
+
items: RedocNavigationItem[],
|
|
481
|
+
targetId: string
|
|
482
|
+
): RedocNavigationItem | null {
|
|
483
|
+
for (const item of items) {
|
|
484
|
+
if (item.id === targetId) {
|
|
485
|
+
return item;
|
|
486
|
+
}
|
|
487
|
+
if (item.children) {
|
|
488
|
+
const found = this.findRedocItemById(item.children, targetId);
|
|
489
|
+
if (found) {
|
|
490
|
+
return found;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Convert markdown topics to RedocNavigationItem format for unified sidebar handling
|
|
499
|
+
*/
|
|
500
|
+
private convertMarkdownTopicsToNavItems(
|
|
501
|
+
markdownTopics: ParsedMarkdownTopic[]
|
|
502
|
+
): RedocNavigationItem[] {
|
|
503
|
+
return markdownTopics.map(topic => ({
|
|
504
|
+
label: topic.label,
|
|
505
|
+
name: topic.link?.href || '',
|
|
506
|
+
id: topic.label.toLowerCase().replace(/\s+/g, '-'),
|
|
507
|
+
children: topic.children ?
|
|
508
|
+
this.convertMarkdownTopicsToNavItems(topic.children) :
|
|
509
|
+
undefined
|
|
510
|
+
}));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Update the DOM on the first load
|
|
515
|
+
*/
|
|
516
|
+
updateView(): void {
|
|
517
|
+
const referenceId = this._currentReferenceId;
|
|
518
|
+
const specBasedReference = this.isSpecBasedReference(referenceId);
|
|
519
|
+
|
|
520
|
+
if (specBasedReference) {
|
|
521
|
+
// Wait till the spec is loaded.
|
|
522
|
+
this.redocFetchPromiseMap[referenceId].then(() => {
|
|
523
|
+
this.loadSpecReferenceContent(referenceId);
|
|
524
|
+
this.updateDocPhase();
|
|
525
|
+
|
|
526
|
+
// Handle hash navigation after content is loaded
|
|
527
|
+
setTimeout(() => {
|
|
528
|
+
this.handleHashNavigation();
|
|
529
|
+
}, 1000);
|
|
530
|
+
});
|
|
531
|
+
} else {
|
|
532
|
+
// Handle markdown references if needed
|
|
533
|
+
this.loadMarkdownBasedReference();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Load and render Redoc content for the selected reference
|
|
539
|
+
*/
|
|
540
|
+
private async loadSpecReferenceContent(referenceId: string): Promise<void> {
|
|
541
|
+
const amfConfig = this.getAmfConfigWithId(referenceId);
|
|
542
|
+
if (!amfConfig) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const redocContainer = this.template.querySelector('.redoc-container');
|
|
547
|
+
if (!redocContainer) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
try {
|
|
552
|
+
// Clear existing content
|
|
553
|
+
while (redocContainer.firstChild) {
|
|
554
|
+
redocContainer.firstChild.remove();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Initialize Redoc
|
|
558
|
+
if (window.Redoc && amfConfig.amf) {
|
|
559
|
+
await window.Redoc.init(amfConfig.amf, {
|
|
560
|
+
expandResponses: '200,400',
|
|
561
|
+
scrollYOffset: 73,
|
|
562
|
+
sidebar: false
|
|
563
|
+
}, redocContainer);
|
|
564
|
+
|
|
565
|
+
// Set up scroll sync after Redoc is loaded
|
|
566
|
+
setTimeout(() => {
|
|
567
|
+
this.setupScrollSync();
|
|
568
|
+
}, 2000);
|
|
569
|
+
}
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error('Failed to load Redoc:', error);
|
|
572
|
+
|
|
573
|
+
// Create error elements properly without innerHTML
|
|
574
|
+
const errorContainer = document.createElement('div');
|
|
575
|
+
errorContainer.className = 'error-container';
|
|
576
|
+
|
|
577
|
+
const errorMessage = document.createElement('div');
|
|
578
|
+
errorMessage.className = 'error-message';
|
|
579
|
+
errorMessage.textContent = 'Failed to load API documentation';
|
|
580
|
+
|
|
581
|
+
errorContainer.appendChild(errorMessage);
|
|
582
|
+
redocContainer.appendChild(errorContainer);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Set up scroll synchronization between sidebar and Redoc content
|
|
588
|
+
*/
|
|
589
|
+
private setupScrollSync(): void {
|
|
590
|
+
const contentArea = this.template.querySelector('.api-documentation');
|
|
591
|
+
if (!contentArea) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
contentArea.addEventListener('scroll', this._boundHandleRedocScroll);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Handle scroll events in Redoc content to update active sidebar item
|
|
600
|
+
*/
|
|
601
|
+
private handleRedocScroll(): void {
|
|
602
|
+
if (this.isProgrammaticScroll) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (this.scrollTimeout) {
|
|
607
|
+
clearTimeout(this.scrollTimeout);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
this.scrollTimeout = window.setTimeout(() => {
|
|
611
|
+
this.updateActiveSidebarItemFromScroll();
|
|
612
|
+
}, 50);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Update active sidebar item based on scroll position
|
|
617
|
+
*/
|
|
618
|
+
private updateActiveSidebarItemFromScroll(): void {
|
|
619
|
+
const redocSections = this.template.querySelectorAll('[data-section-id]');
|
|
620
|
+
if (redocSections.length === 0) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
let activeSection: string | null = null;
|
|
625
|
+
let bestScore = -Infinity;
|
|
626
|
+
const viewportHeight = window.innerHeight;
|
|
627
|
+
const scrollThreshold = viewportHeight * 0.3;
|
|
628
|
+
|
|
629
|
+
redocSections.forEach(section => {
|
|
630
|
+
const sectionId = section.getAttribute('data-section-id');
|
|
631
|
+
if (!sectionId) {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const rect = section.getBoundingClientRect();
|
|
636
|
+
const visibleHeight = Math.max(0, Math.min(viewportHeight, rect.bottom) - Math.max(0, rect.top));
|
|
637
|
+
const visibilityPercentage = visibleHeight / section.clientHeight;
|
|
638
|
+
|
|
639
|
+
let score = 0;
|
|
640
|
+
if (visibilityPercentage > 0.05) {
|
|
641
|
+
score += visibilityPercentage * 100;
|
|
642
|
+
|
|
643
|
+
if (rect.top <= scrollThreshold && rect.top >= -scrollThreshold) {
|
|
644
|
+
score += 100;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (score > bestScore) {
|
|
649
|
+
bestScore = score;
|
|
650
|
+
activeSection = sectionId;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
if (activeSection && activeSection !== this.currentActiveSection) {
|
|
655
|
+
this.currentActiveSection = activeSection;
|
|
656
|
+
this.updateSelectedSidebarItem(activeSection);
|
|
657
|
+
|
|
658
|
+
// Update URL hash
|
|
659
|
+
if (window.location.hash !== `#${activeSection}`) {
|
|
660
|
+
window.history.replaceState(null, '', `#${activeSection}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Update the selected sidebar item based on section ID
|
|
667
|
+
*/
|
|
668
|
+
private updateSelectedSidebarItem(sectionId: string): void {
|
|
669
|
+
const currentRef = this.getAmfConfigWithId(this._currentReferenceId);
|
|
670
|
+
if (currentRef) {
|
|
671
|
+
this.selectedSidebarValue = `${currentRef.href}#${sectionId}`;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Handle hash navigation from URL changes
|
|
677
|
+
*/
|
|
678
|
+
private handleHashNavigation(): void {
|
|
679
|
+
const hash = window.location.hash;
|
|
680
|
+
if (hash) {
|
|
681
|
+
const sectionId = hash.substring(1);
|
|
682
|
+
this.scrollToSection(sectionId);
|
|
683
|
+
this.updateSelectedSidebarItem(sectionId);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Scroll to a specific section in Redoc content
|
|
689
|
+
*/
|
|
690
|
+
private scrollToSection(sectionId: string): void {
|
|
691
|
+
this.isProgrammaticScroll = true;
|
|
692
|
+
|
|
693
|
+
const targetSection = this.template.querySelector(`[data-section-id="${sectionId}"]`);
|
|
694
|
+
if (targetSection) {
|
|
695
|
+
targetSection.scrollIntoView({
|
|
696
|
+
behavior: 'smooth',
|
|
697
|
+
block: 'start'
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
// Reset flag after scroll animation
|
|
701
|
+
setTimeout(() => {
|
|
702
|
+
this.isProgrammaticScroll = false;
|
|
703
|
+
}, 1000);
|
|
704
|
+
} else {
|
|
705
|
+
this.isProgrammaticScroll = false;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Handle navigation selection from sidebar
|
|
711
|
+
*/
|
|
712
|
+
onNavSelect(event: CustomEvent): void {
|
|
713
|
+
const name = event.detail.name;
|
|
714
|
+
if (name) {
|
|
715
|
+
const urlReferenceId = this.getReferenceIdFromUrl(name);
|
|
716
|
+
const specBasedReference = this.isSpecBasedReference(urlReferenceId);
|
|
717
|
+
|
|
718
|
+
if (specBasedReference) {
|
|
719
|
+
// Extract hash from the name (URL)
|
|
720
|
+
const hashIndex = name.indexOf('#');
|
|
721
|
+
if (hashIndex > -1) {
|
|
722
|
+
const sectionId = name.substring(hashIndex + 1);
|
|
723
|
+
|
|
724
|
+
// Update URL hash and scroll to section
|
|
725
|
+
window.location.hash = sectionId;
|
|
726
|
+
|
|
727
|
+
// Update page title with section name
|
|
728
|
+
const redocData = this.redocSidebarData.get(urlReferenceId);
|
|
729
|
+
if (redocData) {
|
|
730
|
+
const sectionItem = this.findRedocItemById(redocData.items, sectionId);
|
|
731
|
+
if (sectionItem) {
|
|
732
|
+
this.updateTags(sectionItem.label);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
logCoveoPageView(
|
|
737
|
+
this.coveoOrganizationId,
|
|
738
|
+
this.coveoAnalyticsToken
|
|
739
|
+
);
|
|
740
|
+
} else {
|
|
741
|
+
// This is a parent reference selection
|
|
742
|
+
if (this.isParentReferencePath(name)) {
|
|
743
|
+
this.loadNewReferenceItem(name);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
this.loadMarkdownBasedReference(name);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
onExpandCollapse(event: CustomEvent): void {
|
|
753
|
+
const { name, isSelectAction, isExpanded } = event.detail;
|
|
754
|
+
if (!isSelectAction && isExpanded) {
|
|
755
|
+
const referenceId = this.getReferenceIdFromUrl(name);
|
|
756
|
+
const currentUrl = window.location.href;
|
|
757
|
+
const currentReferenceId = this.getReferenceIdFromUrl(currentUrl);
|
|
758
|
+
|
|
759
|
+
if (referenceId !== currentReferenceId) {
|
|
760
|
+
const isSpecBasedReference = this.isSpecBasedReference(referenceId);
|
|
761
|
+
if (isSpecBasedReference) {
|
|
762
|
+
this.onNavSelect(event);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* The API Navigation event will always intend to navigate within the current reference
|
|
770
|
+
*/
|
|
771
|
+
protected onApiNavigationChanged(): void {
|
|
772
|
+
// Handle API navigation changes if needed
|
|
773
|
+
restoreScroll();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* This method gets called when the user navigates back and forth using browser arrows
|
|
778
|
+
*/
|
|
779
|
+
protected updateSelectedItemFromUrlQuery(): void {
|
|
780
|
+
const specBasedReference = this.isSpecBasedReference(this._currentReferenceId);
|
|
781
|
+
if (specBasedReference) {
|
|
782
|
+
this.handleHashNavigation();
|
|
783
|
+
} else {
|
|
784
|
+
this.loadMarkdownBasedReference();
|
|
785
|
+
}
|
|
786
|
+
restoreScroll();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Returns whether given url is parent reference path
|
|
791
|
+
*/
|
|
792
|
+
private isParentReferencePath(urlPath?: string | null): boolean {
|
|
793
|
+
if (!urlPath) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
const parentReferenceIndex = this.parentReferenceUrls.findIndex(
|
|
797
|
+
(referenceUrl: string) => {
|
|
798
|
+
return urlPath.endsWith(referenceUrl);
|
|
799
|
+
}
|
|
800
|
+
);
|
|
801
|
+
return parentReferenceIndex !== -1;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Navigates to reference of the given URL
|
|
806
|
+
*/
|
|
807
|
+
private loadNewReferenceItem(url: string): void {
|
|
808
|
+
const referenceId = this.getReferenceIdFromUrl(url);
|
|
809
|
+
const referenceItem = this.getAmfConfigWithId(referenceId);
|
|
810
|
+
if (referenceItem) {
|
|
811
|
+
window.location.href = referenceItem.href;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Load markdown-based reference
|
|
817
|
+
*/
|
|
818
|
+
private loadMarkdownBasedReference(referenceUrl?: string | null): void {
|
|
819
|
+
let referenceId = "";
|
|
820
|
+
const currentUrl = window.location.href;
|
|
821
|
+
|
|
822
|
+
if (this.isProjectRootPath()) {
|
|
823
|
+
/**
|
|
824
|
+
* CASE1: This case is to consider when the user navigates to references by clicking a project card
|
|
825
|
+
* Ex: /docs/example-project/references should navigate to the first topic in the first reference
|
|
826
|
+
*/
|
|
827
|
+
referenceId = this._currentReferenceId;
|
|
828
|
+
} else if (this.isParentReferencePath(referenceUrl)) {
|
|
829
|
+
/**
|
|
830
|
+
* CASE2: This case is to navigate to respective reference when the user clicked on root item
|
|
831
|
+
* Ex: .../references/markdown-ref should navigate to first topic.
|
|
832
|
+
*/
|
|
833
|
+
referenceId = this.getReferenceIdFromUrl(referenceUrl!);
|
|
834
|
+
} else if (this.isParentReferencePath(currentUrl)) {
|
|
835
|
+
/**
|
|
836
|
+
* CASE3: This case is to navigate to respective reference when the user entered url with reference id
|
|
837
|
+
* Ex: .../references/markdown-ref should navigate to first topic.
|
|
838
|
+
*/
|
|
839
|
+
referenceId = this.getReferenceIdFromUrl(currentUrl);
|
|
840
|
+
} else if (referenceUrl) {
|
|
841
|
+
/**
|
|
842
|
+
* CASE4: This case is to navigate to first item when we don't have topic in the selected version
|
|
843
|
+
* Ex: .../references/markdown-ref/not-existed-topic-url should navigate to first topic.
|
|
844
|
+
*/
|
|
845
|
+
referenceId = this.getReferenceIdFromUrl(referenceUrl);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
let isRedirecting = false;
|
|
849
|
+
if (referenceId) {
|
|
850
|
+
const amfConfig = this.getAmfConfigWithId(referenceId);
|
|
851
|
+
let redirectReferenceUrl = "";
|
|
852
|
+
if (amfConfig && amfConfig.topic) {
|
|
853
|
+
const childrenItems = amfConfig.topic.children;
|
|
854
|
+
if (childrenItems && childrenItems.length > 0) {
|
|
855
|
+
redirectReferenceUrl = childrenItems[0].link!.href;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (redirectReferenceUrl) {
|
|
859
|
+
if (this.isParentReferencePath(referenceUrl)) {
|
|
860
|
+
// This is for CASE2 mentioned above, Where we need to navigate user to respective href
|
|
861
|
+
isRedirecting = true;
|
|
862
|
+
window.location.href = redirectReferenceUrl;
|
|
863
|
+
} else {
|
|
864
|
+
// This is for CASE 1,3 and 4 mentioned above, Where we need to update the browser history
|
|
865
|
+
window.history.replaceState(
|
|
866
|
+
window.history.state,
|
|
867
|
+
"",
|
|
868
|
+
redirectReferenceUrl
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (!isRedirecting) {
|
|
875
|
+
// Update title for markdown references
|
|
876
|
+
// For markdown, we could extract title from the page or use a default
|
|
877
|
+
this.updateTags("Reference Documentation");
|
|
878
|
+
|
|
879
|
+
this.versions = this.getVersions();
|
|
880
|
+
if (this.oldVersionInfo) {
|
|
881
|
+
this.showVersionBanner = true;
|
|
882
|
+
} else {
|
|
883
|
+
this.latestVersion = true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
this.isVersionFetched = true;
|
|
887
|
+
this.updateDocPhase();
|
|
888
|
+
this.selectedSidebarValue = window.location.pathname;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Handle version change
|
|
894
|
+
*/
|
|
895
|
+
handleVersionChange(): void {
|
|
896
|
+
const currentUrl = window.location.href;
|
|
897
|
+
window.sessionStorage.setItem(
|
|
898
|
+
this.docsReferenceUrlSessionKey,
|
|
899
|
+
currentUrl
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
handleDismissVersionBanner() {
|
|
904
|
+
this.showVersionBanner = false;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Updates doc phase of selected reference
|
|
909
|
+
*/
|
|
910
|
+
updateDocPhase(): void {
|
|
911
|
+
/* If parent level doc phase is enabled, Individual reference level doc phase should not be considered */
|
|
912
|
+
|
|
913
|
+
if (!this.isParentLevelDocPhaseEnabled) {
|
|
914
|
+
const selectedReference = this._amfConfigList.find(
|
|
915
|
+
(referenceItem: AmfConfig) => {
|
|
916
|
+
return referenceItem.id === this._currentReferenceId;
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
if (selectedReference) {
|
|
920
|
+
this.selectedReferenceDocPhase = JSON.stringify(
|
|
921
|
+
selectedReference.docPhase
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Normalizes topic identifier by replacing spaces with '+'
|
|
929
|
+
* and running encodeURI() on it
|
|
930
|
+
* @param identifier raw identifier for a topic as parsed from the spec file
|
|
931
|
+
* @returns normalized and encoded identifier
|
|
932
|
+
*/
|
|
933
|
+
protected encodeIdentifier(identifier: string): string {
|
|
934
|
+
let result = identifier.trim();
|
|
935
|
+
result = result.replace(new RegExp(/\s+/, "g"), "+");
|
|
936
|
+
return encodeURI(result);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Gets the encoded url.
|
|
941
|
+
* This method will return the encoded url for 2 cases,
|
|
942
|
+
* 1. If the url is encoded already
|
|
943
|
+
* 2. If the url is decoded
|
|
944
|
+
*/
|
|
945
|
+
getUrlEncoded(url: string): string {
|
|
946
|
+
// if url matches, then return the encoded url.
|
|
947
|
+
if (decodeURIComponent(url) === url) {
|
|
948
|
+
return encodeURIComponent(url);
|
|
949
|
+
}
|
|
950
|
+
// return the encoded url.
|
|
951
|
+
return this.getUrlEncoded(decodeURIComponent(url));
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Constructs a Reference Topic ID
|
|
956
|
+
*/
|
|
957
|
+
protected getFormattedIdentifier(referenceId: string, id: string): string {
|
|
958
|
+
return `${referenceId}:${id}`;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Update browser URL with hash-based navigation
|
|
963
|
+
*/
|
|
964
|
+
protected updateUrlWithHash(hash: string): void {
|
|
965
|
+
if (hash) {
|
|
966
|
+
window.history.pushState({}, "", `#${hash}`);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Replace browser URL with hash-based navigation (no history entry)
|
|
972
|
+
*/
|
|
973
|
+
protected replaceUrlWithHash(hash: string): void {
|
|
974
|
+
if (hash) {
|
|
975
|
+
window.history.replaceState(null, '', `#${hash}`);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Update page title and meta tags
|
|
981
|
+
*/
|
|
982
|
+
private updateTags(navTitle = ""): void {
|
|
983
|
+
if (!navTitle) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// this is required to update the nav title meta tag.
|
|
988
|
+
// eslint-disable-next-line @lwc/lwc/no-document-query
|
|
989
|
+
const metaNavTitle = document.querySelector('meta[name="nav-title"]');
|
|
990
|
+
// eslint-disable-next-line @lwc/lwc/no-document-query
|
|
991
|
+
const titleTag = document.querySelector("title");
|
|
992
|
+
const TITLE_SEPARATOR = " | ";
|
|
993
|
+
|
|
994
|
+
if (metaNavTitle) {
|
|
995
|
+
metaNavTitle.setAttribute("content", navTitle);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Right now, the title tag only changes when you pick a Ref spec,
|
|
1000
|
+
* not every time you choose a subsection of the Ref spec.
|
|
1001
|
+
* This update aims to refresh the title tag with each selection.
|
|
1002
|
+
* If a Ref spec is chosen, we add the value of the <selected topic> to the title.
|
|
1003
|
+
* If a subsection is selected, we update the first part of the current
|
|
1004
|
+
* title with the new <selected topic>.
|
|
1005
|
+
* Example: Following is a sample project structure.
|
|
1006
|
+
* - Project Name
|
|
1007
|
+
* - Ref Spec1
|
|
1008
|
+
* - Summary
|
|
1009
|
+
* - Endpoints
|
|
1010
|
+
* - E1
|
|
1011
|
+
* - E2
|
|
1012
|
+
* - Ref Spec2
|
|
1013
|
+
* - Summary
|
|
1014
|
+
* - Endpoints
|
|
1015
|
+
* - E1 (Selected)
|
|
1016
|
+
* - E2
|
|
1017
|
+
* Previous Title: Ref Spec2 | Project Name | Salesforce Developer
|
|
1018
|
+
* New Title: E1 | Ref Spec2 | Project Name | Salesforce Developer
|
|
1019
|
+
*
|
|
1020
|
+
*/
|
|
1021
|
+
if (titleTag) {
|
|
1022
|
+
let titleTagValue = titleTag.textContent;
|
|
1023
|
+
const titleTagSectionValues: string[] =
|
|
1024
|
+
titleTagValue?.split(TITLE_SEPARATOR) || [];
|
|
1025
|
+
if (titleTagSectionValues.length > 0) {
|
|
1026
|
+
if (titleTagSectionValues.length <= 3) {
|
|
1027
|
+
titleTagValue = navTitle + TITLE_SEPARATOR + titleTagValue;
|
|
1028
|
+
} else {
|
|
1029
|
+
titleTagSectionValues[0] = navTitle;
|
|
1030
|
+
titleTagValue = titleTagSectionValues.join(TITLE_SEPARATOR);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
titleTag.textContent = titleTagValue;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Expands the children of Markdown-based References.
|
|
1039
|
+
*/
|
|
1040
|
+
private expandChildrenForMarkdownReferences(
|
|
1041
|
+
children: ParsedMarkdownTopic[]
|
|
1042
|
+
): void {
|
|
1043
|
+
if (!children) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
for (const childNode of children) {
|
|
1047
|
+
childNode.isExpanded = true;
|
|
1048
|
+
this.expandChildrenForMarkdownReferences(childNode.children);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Returns whether current location is project root path like ../example-project/references
|
|
1054
|
+
*/
|
|
1055
|
+
private isProjectRootPath(): boolean {
|
|
1056
|
+
return this.getReferenceIdFromUrl(window.location.href) === "";
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Returns the current hash from URL (for hash-based navigation)
|
|
1061
|
+
* Note: This replaces the old meta query param logic
|
|
1062
|
+
*/
|
|
1063
|
+
getMetaFromUrl(): string {
|
|
1064
|
+
// For hash-based navigation, extract from hash instead
|
|
1065
|
+
const hash = window.location.hash;
|
|
1066
|
+
if (hash) {
|
|
1067
|
+
return hash.substring(1); // Remove '#' prefix
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
return "";
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Convert any case to a readable Title
|
|
1075
|
+
* ex: snake_case => Snake case
|
|
1076
|
+
* ex: camelCase => Camel case
|
|
1077
|
+
* ex: PascalCase => Pascal case
|
|
1078
|
+
* @param label
|
|
1079
|
+
* @returns string
|
|
1080
|
+
*/
|
|
1081
|
+
private getTitleForLabel(label: string): string {
|
|
1082
|
+
// Simple sentence case conversion without external dependencies
|
|
1083
|
+
return label
|
|
1084
|
+
.replace(/([A-Z])/g, ' $1') // Add space before capitals
|
|
1085
|
+
.replace(/[-_]/g, ' ') // Replace dashes and underscores with spaces
|
|
1086
|
+
.trim()
|
|
1087
|
+
.toLowerCase()
|
|
1088
|
+
.replace(/^\w/, c => c.toUpperCase()); // Capitalize first letter
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Redoc type declarations for window object
|
|
2
|
+
export interface RedocNavigationItem {
|
|
3
|
+
label: string;
|
|
4
|
+
name: string; // Hash-based URL (#section-id)
|
|
5
|
+
id: string; // Section ID for scroll targeting
|
|
6
|
+
children?: RedocNavigationItem[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface RedocSidebarData {
|
|
10
|
+
spec: {
|
|
11
|
+
title: string;
|
|
12
|
+
version: string;
|
|
13
|
+
};
|
|
14
|
+
items: RedocNavigationItem[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare global {
|
|
18
|
+
interface Window {
|
|
19
|
+
Redoc?: {
|
|
20
|
+
init: (specUrl: string, options: any, container: Element) => Promise<void>;
|
|
21
|
+
getSpecStructure?: (specUrl: string) => Promise<RedocSidebarData>;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
Copyright (c) 2020, Salesforce.com, Inc.
|
|
2
|
-
All rights reserved.
|
|
3
|
-
|
|
4
|
-
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
-
|
|
6
|
-
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
-
|
|
8
|
-
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
9
|
-
|
|
10
|
-
* Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
11
|
-
|
|
12
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|