@salesforcedevs/docs-components 1.28.5-node22-1 → 1.28.5-redoc-alpha1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "1.28.5-node22-1",
3
+ "version": "1.28.5-redoc-alpha1",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -42,7 +42,18 @@
42
42
  latest-version={latestVersion}
43
43
  ></doc-version-picker>
44
44
  </div>
45
- <template lwc:if={showSpecBasedReference}>
45
+ <template lwc:if={showRedocReference}>
46
+ <doc-redoc-reference
47
+ reference-config={redocReferenceConfig}
48
+ doc-phase-info={docPhaseInfo}
49
+ project-title={projectTitle}
50
+ spec-title={redocSpecTitle}
51
+ origin={origin}
52
+ >
53
+ <div class="redoc-container"></div>
54
+ </doc-redoc-reference>
55
+ </template>
56
+ <template lwc:elseif={showSpecBasedReference}>
46
57
  <div class="container">
47
58
  <div class="api-documentation">
48
59
  <doc-amf-topic
@@ -24,6 +24,7 @@ import {
24
24
  NAVIGATION_ITEMS,
25
25
  URL_CONFIG,
26
26
  REFERENCE_TYPES,
27
+ RENDER_WITH,
27
28
  oldReferenceIdNewReferenceIdMap
28
29
  } from "./constants";
29
30
  import { restoreScroll } from "dx/scrollManager";
@@ -37,6 +38,7 @@ type NavigationItem = {
37
38
  isExpanded: boolean;
38
39
  children: ParsedMarkdownTopic[];
39
40
  isChildrenLoading: boolean;
41
+ renderWith?: string;
40
42
  };
41
43
 
42
44
  export default class AmfReference extends LightningElement {
@@ -44,6 +46,12 @@ export default class AmfReference extends LightningElement {
44
46
  /** Optional Twitter "via" handle (e.g. SalesforceDevs) for social share; passed to doc-content-layout. */
45
47
  @api twitterVia: string | null = null;
46
48
  @api sidebarHeader!: string;
49
+ /**
50
+ * Project title shown by `<doc-header>` (as its `subtitle`). Forwarded to
51
+ * `<doc-redoc-reference>` so it can surface the same project title inside
52
+ * the Redoc-rendered UI.
53
+ */
54
+ @api projectTitle: string | null = null;
47
55
  @api tocTitle?: string;
48
56
  @api tocOptions?: string;
49
57
  @api tocAriaLevel?: string;
@@ -69,7 +77,48 @@ export default class AmfReference extends LightningElement {
69
77
  * Gives if the currently selected reference is spec based or not
70
78
  */
71
79
  get showSpecBasedReference(): boolean {
72
- return this.isSpecBasedReference(this._currentReferenceId);
80
+ return (
81
+ this.isSpecBasedReference(this._currentReferenceId) &&
82
+ !this.showRedocReference
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Whether the currently selected reference is rendered with Redoc.
88
+ * Driven by the backend-supplied `renderWith` attribute on the AmfConfig.
89
+ */
90
+ get showRedocReference(): boolean {
91
+ return this.isRedocReference(this._currentReferenceId);
92
+ }
93
+
94
+ /**
95
+ * Serialized config consumed by `<doc-redoc-reference>`. Built from the
96
+ * currently selected redoc reference so Redoc has a single spec to render.
97
+ */
98
+ get redocReferenceConfig(): string {
99
+ const ref = this.getAmfConfigWithId(this._currentReferenceId);
100
+ if (!ref) {
101
+ return JSON.stringify({ refList: [] });
102
+ }
103
+ return JSON.stringify({
104
+ refList: [
105
+ {
106
+ source: ref.source || ref.amf || "",
107
+ href: ref.href,
108
+ isSelected: true,
109
+ docPhase: ref.docPhase ?? null
110
+ }
111
+ ]
112
+ });
113
+ }
114
+
115
+ /**
116
+ * Title of the currently selected spec. Forwarded to `<doc-redoc-reference>`
117
+ * so it can label the spec inside the Redoc UI.
118
+ */
119
+ get redocSpecTitle(): string {
120
+ const ref = this.getAmfConfigWithId(this._currentReferenceId);
121
+ return ref?.title ?? "";
73
122
  }
74
123
 
75
124
  @api
@@ -99,6 +148,19 @@ export default class AmfReference extends LightningElement {
99
148
 
100
149
  this._amfConfigList = this._referenceSetConfig.refList || [];
101
150
 
151
+ // If the framework didn't tag the reference, infer redoc rendering
152
+ // from the config shape: spec-based references with no AMF URL are
153
+ // shipped as raw spec sources for Redoc to render directly.
154
+ this._amfConfigList.forEach((amfConfig) => {
155
+ if (
156
+ !amfConfig.renderWith &&
157
+ amfConfig.referenceType !== REFERENCE_TYPES.markdown &&
158
+ !amfConfig.amf
159
+ ) {
160
+ amfConfig.renderWith = RENDER_WITH.redoc;
161
+ }
162
+ });
163
+
102
164
  this._amfConfigList.forEach((amfConfig) => {
103
165
  this._amfConfigMap.set(amfConfig.id, amfConfig);
104
166
  });
@@ -204,6 +266,7 @@ export default class AmfReference extends LightningElement {
204
266
 
205
267
  _boundOnApiNavigationChanged;
206
268
  _boundUpdateSelectedItemFromUrlQuery;
269
+ _boundOnPageShow;
207
270
 
208
271
  constructor() {
209
272
  super();
@@ -212,6 +275,7 @@ export default class AmfReference extends LightningElement {
212
275
  this.onApiNavigationChanged.bind(this);
213
276
  this._boundUpdateSelectedItemFromUrlQuery =
214
277
  this.updateSelectedItemFromUrlQuery.bind(this);
278
+ this._boundOnPageShow = this.onPageShow.bind(this);
215
279
  }
216
280
 
217
281
  connectedCallback(): void {
@@ -223,6 +287,7 @@ export default class AmfReference extends LightningElement {
223
287
  "popstate",
224
288
  this._boundUpdateSelectedItemFromUrlQuery
225
289
  );
290
+ window.addEventListener("pageshow", this._boundOnPageShow);
226
291
  }
227
292
 
228
293
  disconnectedCallback(): void {
@@ -234,6 +299,22 @@ export default class AmfReference extends LightningElement {
234
299
  "popstate",
235
300
  this._boundUpdateSelectedItemFromUrlQuery
236
301
  );
302
+ window.removeEventListener("pageshow", this._boundOnPageShow);
303
+ }
304
+
305
+ /**
306
+ * On bfcache restore, reset the sidebar selection so the tree re-syncs
307
+ * its highlighted tile with the current URL.
308
+ */
309
+ protected onPageShow(event: PageTransitionEvent): void {
310
+ if (!event.persisted) {
311
+ return;
312
+ }
313
+ const currentPath = window.location.pathname;
314
+ this.selectedSidebarValue = "";
315
+ Promise.resolve().then(() => {
316
+ this.selectedSidebarValue = currentPath;
317
+ });
237
318
  }
238
319
 
239
320
  renderedCallback(): void {
@@ -296,6 +377,16 @@ export default class AmfReference extends LightningElement {
296
377
  : false;
297
378
  }
298
379
 
380
+ /**
381
+ * @param referenceId
382
+ * @returns whether the reference should be rendered with Redoc rather than
383
+ * the AMF-based topic renderer.
384
+ */
385
+ private isRedocReference(referenceId: string): boolean {
386
+ const selectedReference = this.getAmfConfigWithId(referenceId);
387
+ return selectedReference?.renderWith === RENDER_WITH.redoc;
388
+ }
389
+
299
390
  /*
300
391
  * Refactor below method when sidebar allows sending extraData along with the name for each item.
301
392
  * See if we can refactor the below method using regex.
@@ -442,7 +533,11 @@ export default class AmfReference extends LightningElement {
442
533
  for (const [index, amfConfig] of this._amfConfigList.entries()) {
443
534
  let navItemChildren = [] as ParsedMarkdownTopic[];
444
535
  let isChildrenLoading = false;
445
- if (amfConfig.referenceType !== REFERENCE_TYPES.markdown) {
536
+ if (amfConfig.renderWith === RENDER_WITH.redoc) {
537
+ // Redoc-rendered specs have no AMF model and no sub-tree; they
538
+ // appear as leaf items in the sidebar and open the Redoc UI on
539
+ // selection.
540
+ } else if (amfConfig.referenceType !== REFERENCE_TYPES.markdown) {
446
541
  if (amfConfig.isSelected) {
447
542
  const amfPromise = this.fetchAmf(amfConfig).then(
448
543
  (amfJson) => {
@@ -473,7 +568,8 @@ export default class AmfReference extends LightningElement {
473
568
  amfConfig.isSelected ||
474
569
  this.isExpandChildrenEnabled(amfConfig.id),
475
570
  children: navItemChildren,
476
- isChildrenLoading
571
+ isChildrenLoading,
572
+ renderWith: amfConfig.renderWith
477
573
  };
478
574
  this.parentReferenceUrls.push(amfConfig.href);
479
575
  }
@@ -862,6 +958,12 @@ export default class AmfReference extends LightningElement {
862
958
  const specBasedReference = this.isSpecBasedReference(
863
959
  this._currentReferenceId
864
960
  );
961
+ if (this.isRedocReference(this._currentReferenceId)) {
962
+ // Redoc reads its own state from referenceConfig + window.location;
963
+ // no metadata to sync from URL on our side.
964
+ restoreScroll();
965
+ return;
966
+ }
865
967
  if (specBasedReference) {
866
968
  const currentMeta: RouteMeta | undefined =
867
969
  this.getReferenceMetaInfo(window.location.href);
@@ -896,6 +998,9 @@ export default class AmfReference extends LightningElement {
896
998
  * @param event
897
999
  */
898
1000
  protected onApiNavigationChanged(): void {
1001
+ if (this.isRedocReference(this._currentReferenceId)) {
1002
+ return;
1003
+ }
899
1004
  const specBasedReference = this.isSpecBasedReference(
900
1005
  this._currentReferenceId
901
1006
  );
@@ -1165,6 +1270,12 @@ export default class AmfReference extends LightningElement {
1165
1270
  }
1166
1271
 
1167
1272
  const specBasedReference = this.isSpecBasedReference(referenceId);
1273
+ const redocReference = this.isRedocReference(referenceId);
1274
+ if (redocReference) {
1275
+ // Redoc-rendered references have no AMF model; the redoc component
1276
+ // handles its own URL/spec resolution from referenceConfig.
1277
+ return;
1278
+ }
1168
1279
  if (specBasedReference) {
1169
1280
  // Wait till the AMF is loaded.
1170
1281
  this.amfFetchPromiseMap[referenceId].then(() => {
@@ -61,6 +61,10 @@ export const REFERENCE_TYPES = {
61
61
  oa3: "rest-oa3"
62
62
  };
63
63
 
64
+ export const RENDER_WITH = {
65
+ redoc: "redoc"
66
+ };
67
+
64
68
  const oldReferenceIdNewReferenceIdMap: Record<string, string> = {
65
69
  "commerce-api-assignments": "assignments",
66
70
  "commerce-api-campaigns": "campaigns",
@@ -78,6 +78,19 @@ export interface AmfConfig {
78
78
 
79
79
  // required for markdown based references
80
80
  topic?: ParsedMarkdownTopic;
81
+
82
+ /**
83
+ * Optional renderer override sent by the backend.
84
+ * When "redoc", spec-based references are rendered with Redoc instead of
85
+ * the AMF-based topic view. Other values fall back to the default pipeline.
86
+ */
87
+ renderWith?: string;
88
+
89
+ /**
90
+ * Spec URL consumed by alternate renderers (e.g. Redoc). For redoc-rendered
91
+ * references this is the OpenAPI document URL.
92
+ */
93
+ source?: string;
81
94
  }
82
95
 
83
96
  export interface ParsedTopicModel {
@@ -2,6 +2,7 @@
2
2
  import { createElement, LightningElement, api } from "lwc";
3
3
  import DocPhase from "doc/phase";
4
4
  import DxFooter from "dx/footer";
5
+ import DxIcon from "dx/icon";
5
6
  import SprigSurvey from "doc/sprigSurvey";
6
7
  import { throttle } from "throttle-debounce";
7
8
  import { pollUntil } from "dxUtils/async";
@@ -17,6 +18,7 @@ declare const Sprig: (eventType: string, eventName: string) => void;
17
18
  type ReferenceItem = {
18
19
  source: string;
19
20
  href: string;
21
+ title?: string;
20
22
  isSelected?: boolean;
21
23
  docPhase?: string | null;
22
24
  };
@@ -71,6 +73,45 @@ export default class RedocReference extends LightningElement {
71
73
  /** Optional origin URL for the footer MFE (e.g. wp-json endpoint). Passed through to dx-footer. */
72
74
  @api origin: string | null = null;
73
75
 
76
+ /**
77
+ * Project title (same value passed to `<doc-header>` as `subtitle`). Used
78
+ * inside the Redoc-rendered UI to label the parent project.
79
+ */
80
+ @api projectTitle: string | null = null;
81
+
82
+ private _specTitle: string | null = null;
83
+
84
+ /** Title of the currently selected spec, shown beneath the project title. */
85
+ @api
86
+ get specTitle(): string | null {
87
+ if (this._specTitle) {
88
+ return this._specTitle;
89
+ }
90
+ return this.getSelectedReference()?.title ?? null;
91
+ }
92
+
93
+ set specTitle(value: string | null) {
94
+ this._specTitle = value;
95
+ }
96
+
97
+ /**
98
+ * Whether to show the project header (only for multi-spec reference sets).
99
+ */
100
+ get showRedocHeader(): boolean {
101
+ const refCount = this._referenceConfig?.refList?.length ?? 0;
102
+ const isMultiSpecSet = refCount > 1;
103
+ return isMultiSpecSet && !!(this.projectTitle || this.specTitle);
104
+ }
105
+
106
+ /**
107
+ * Navigates back to the previous history entry when the user clicks the
108
+ * project-title back link rendered above the Redoc UI.
109
+ */
110
+ private onBackClick(event: Event): void {
111
+ event.preventDefault();
112
+ window.history.back();
113
+ }
114
+
74
115
  /** When origin is provided, pass it to the footer; otherwise use dx-footer's default. */
75
116
  get effectiveFooterOrigin(): string {
76
117
  return (
@@ -108,7 +149,13 @@ export default class RedocReference extends LightningElement {
108
149
  }
109
150
 
110
151
  private getRedocContainer(): HTMLElement | null {
111
- return document.querySelector(".redoc-container");
152
+ // Prefer the slotted container in the consumer's light DOM so this
153
+ // component composes inside shadow trees; fall back to a global
154
+ // `.redoc-container` for legacy page-level integrations.
155
+ return (
156
+ this.querySelector<HTMLElement>(".redoc-container") ||
157
+ document.querySelector<HTMLElement>(".redoc-container")
158
+ );
112
159
  }
113
160
 
114
161
  private getSelectedReference(): ReferenceItem | null {
@@ -304,6 +351,9 @@ export default class RedocReference extends LightningElement {
304
351
 
305
352
  this.appendFooterItems(apiContentDiv);
306
353
 
354
+ // Inject the multi-spec project header into Redoc's left menu only.
355
+ this.insertProjectHeaderInMenu(redocContainer);
356
+
307
357
  // Wait for footer to be rendered before updating styles
308
358
  requestAnimationFrame(() => {
309
359
  this.updateRedocThirdColumnStyle(redocContainer);
@@ -316,6 +366,80 @@ export default class RedocReference extends LightningElement {
316
366
  }
317
367
  }
318
368
 
369
+ /**
370
+ * Inserts the project header into Redoc for multi-spec reference sets.
371
+ */
372
+ private insertProjectHeaderInMenu(redocContainer: HTMLElement): void {
373
+ if (!this.showRedocHeader) {
374
+ return;
375
+ }
376
+ const menuContent =
377
+ redocContainer.querySelector<HTMLElement>(".menu-content");
378
+ if (
379
+ menuContent &&
380
+ !menuContent.querySelector(":scope > .redoc-project-header")
381
+ ) {
382
+ menuContent.insertBefore(
383
+ this.buildProjectHeaderDom(),
384
+ menuContent.firstChild
385
+ );
386
+ }
387
+
388
+ const apiContent =
389
+ redocContainer.querySelector<HTMLElement>(".api-content");
390
+ if (
391
+ apiContent &&
392
+ !apiContent.querySelector(":scope > .redoc-project-header")
393
+ ) {
394
+ apiContent.insertBefore(
395
+ this.buildProjectHeaderDom(),
396
+ apiContent.firstChild
397
+ );
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Builds a fresh project-title/spec-title header DOM node.
403
+ */
404
+ private buildProjectHeaderDom(): HTMLElement {
405
+ const wrapper = document.createElement("div");
406
+ wrapper.className = "redoc-project-header";
407
+
408
+ if (this.projectTitle) {
409
+ const backLink = document.createElement("a");
410
+ backLink.className = "redoc-project-back";
411
+ backLink.href = "#";
412
+ backLink.addEventListener("click", (event) => {
413
+ event.preventDefault();
414
+ window.history.back();
415
+ });
416
+ // 16x16 utility/back icon. dx-icon size="medium" maps to
417
+ // --dx-g-icon-size-md (16px).
418
+ const iconEl = createElement("dx-icon", { is: DxIcon });
419
+ Object.assign(iconEl, {
420
+ sprite: "utility",
421
+ symbol: "back",
422
+ size: "medium"
423
+ });
424
+ iconEl.classList.add("redoc-project-back-arrow");
425
+ const label = document.createElement("span");
426
+ label.className = "redoc-project-title";
427
+ label.textContent = this.projectTitle;
428
+ backLink.appendChild(iconEl);
429
+ backLink.appendChild(label);
430
+ wrapper.appendChild(backLink);
431
+ }
432
+
433
+ if (this.specTitle) {
434
+ const specEl = document.createElement("h2");
435
+ specEl.className = "redoc-spec-title";
436
+ specEl.textContent = this.specTitle;
437
+ wrapper.appendChild(specEl);
438
+ }
439
+
440
+ return wrapper;
441
+ }
442
+
319
443
  // Waits for Redoc's API content element to be rendered
320
444
  private async waitForApiContent(
321
445
  container: HTMLElement
package/.npmrc DELETED
@@ -1 +0,0 @@
1
- //registry.npmjs.org/:_authToken=${SFDOCS_NPM_AUTH_TOKEN}