@salesforcedevs/docs-components 1.30.1-locale-picker-2 → 1.30.1-locale-picker-final

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.30.1-locale-picker-2",
3
+ "version": "1.30.1-locale-picker-final",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -0,0 +1,233 @@
1
+ # Redoc "← Project Title" Back Button
2
+
3
+ How the back link in the Redoc Left Nav behaves, and why each piece exists.
4
+
5
+ ## Why this is not a plain `<a href>`
6
+
7
+ Switching locale in the Redoc UI is a **full page reload** (the locale picker is
8
+ an `<a href>` that navigates to the same page in another locale). That has two
9
+ consequences the back link has to deal with:
10
+
11
+ 1. `document.referrer` only ever remembers **one** previous page, and each locale
12
+ switch overwrites it. So after a couple of locale switches the referrer no
13
+ longer points at the real page you came from.
14
+ 2. The page you should go "back" to must be shown in **whatever locale you are
15
+ currently viewing** — not the locale it was in when you first visited it.
16
+
17
+ To solve (1) we remember the back target ourselves in `sessionStorage` (survives
18
+ reloads, per browser tab). To solve (2) we rewrite the stored URL's locale to the
19
+ current one right before navigating.
20
+
21
+ ## URL shape assumption
22
+
23
+ The locale segment sits in a **different position** depending on the content
24
+ type:
25
+
26
+ ```
27
+ docs content type
28
+ en-us (default): /docs/bot-api ← no locale segment
29
+ ja-jp: /docs/ja-jp/bot-api ← locale right after "docs" (index 1)
30
+
31
+ references content type
32
+ en-us (default): /docs/service/multi-oas-redoc/references/reference-sample.html
33
+ ja-jp: /docs/service/ja-jp/multi-oas-redoc/references/reference-sample.html
34
+ ↑ locale after the product segment (index 2)
35
+ ```
36
+
37
+ So: paths always start with `/docs/`, and a non-default locale is an extra
38
+ segment — after `docs` for docs pages, after the product segment for
39
+ references. The default locale `en-us` has no segment. `translateToCurrentLocale`
40
+ picks the index with `isDocContentType` (docs → 1, references → 2).
41
+
42
+ ---
43
+
44
+ ## The two halves of the feature
45
+
46
+ ### Half 1 — Remembering where to go back to (`trackBackTarget`)
47
+
48
+ Runs once per page load, inside `connectedCallback`.
49
+
50
+ ```ts
51
+ private trackBackTarget(): void {
52
+ const referrerHref = this.getSameOriginReferrerHref(); // (a)
53
+ if (!referrerHref) {
54
+ return; // (b)
55
+ }
56
+
57
+ const referrerUrl = new URL(referrerHref);
58
+ if (this.isLocaleHref(referrerUrl.pathname)) { // (c)
59
+ return;
60
+ }
61
+
62
+ this.setBackTargetInSession(referrerHref); // (d)
63
+ }
64
+ ```
65
+
66
+ - **(a)** Get the page we navigated here from — but only if it's a real in-tab,
67
+ same-origin navigation (see `getSameOriginReferrerHref` below).
68
+ - **(b)** No usable referrer (fresh tab, opened a shared link, came from another
69
+ site) → store nothing. The stored target, if any, is left untouched.
70
+ - **(c)** If the referrer is just **this same page in another locale** (a locale
71
+ switch), do **not** overwrite the stored target. This is the key line: it lets
72
+ the real "previous page" survive across any number of locale switches.
73
+ - **(d)** Otherwise this was a normal navigation — remember it as the back target.
74
+
75
+ #### `getSameOriginReferrerHref`
76
+
77
+ ```ts
78
+ private getSameOriginReferrerHref(): string | null {
79
+ if (this.initialHistoryLength <= 1 || !document.referrer) {
80
+ return null; // fresh tab / no referrer
81
+ }
82
+ try {
83
+ const referrerUrl = new URL(document.referrer);
84
+ if (referrerUrl.origin !== window.location.origin) {
85
+ return null; // came from another site
86
+ }
87
+ return referrerUrl.href;
88
+ } catch {
89
+ return null; // malformed referrer
90
+ }
91
+ }
92
+ ```
93
+
94
+ `initialHistoryLength` is captured at mount (before Redoc pushes its own hash
95
+ entries). `<= 1` means this is the first entry in the tab — there is no real
96
+ in-tab "back".
97
+
98
+ #### `isLocaleHref`
99
+
100
+ ```ts
101
+ private isLocaleHref(pathname: string): boolean {
102
+ return this.languages.some((locale) => {
103
+ const href = locale?.link?.href;
104
+ return (
105
+ !!href &&
106
+ new URL(href, window.location.origin).pathname === pathname
107
+ );
108
+ });
109
+ }
110
+ ```
111
+
112
+ `this.languages` is the list the locale picker uses — each entry's `link.href` is
113
+ **this page's URL in that locale**. So a pathname is a "locale sibling" if it
114
+ exactly matches one of those hrefs.
115
+
116
+ ### Half 2 — Going back (`onBackClick` → `getTranslatedBackTarget` → `translateToCurrentLocale`)
117
+
118
+ ```ts
119
+ private onBackClick = (event: Event): void => {
120
+ event.preventDefault();
121
+
122
+ const target = this.getTranslatedBackTarget() ?? this.headerHref; // (1)
123
+
124
+ if (target) {
125
+ window.location.assign(target); // (2)
126
+ }
127
+ };
128
+ ```
129
+
130
+ - **(1)** Work out the back target; if none, fall back to `headerHref` (the
131
+ parent reference page passed in by the consumer).
132
+ - **(2)** Navigate there.
133
+
134
+ ```ts
135
+ private getTranslatedBackTarget(): string | null {
136
+ const stored = this.getBackTargetFromSession(); // (3)
137
+ if (stored) {
138
+ return this.language
139
+ ? this.translateToCurrentLocale(stored) // (4)
140
+ : stored;
141
+ }
142
+
143
+ const referrer = this.getSameOriginReferrerHref(); // (5)
144
+ return referrer && !this.isLocaleHref(new URL(referrer).pathname)
145
+ ? referrer
146
+ : null; // (6)
147
+ }
148
+ ```
149
+
150
+ - **(3)** Prefer the target we stored in `sessionStorage`.
151
+ - **(4)** Rewrite its locale to match the locale we're viewing right now.
152
+ - **(5)** No stored target → fall back to the referrer.
153
+ - **(6)** But if that referrer is just this page in another locale, reject it
154
+ (returning `null`) so `onBackClick` uses `headerHref` instead. Without this we
155
+ could ping-pong between locales.
156
+
157
+ ```ts
158
+ private translateToCurrentLocale(storedHref: string): string {
159
+ const url = new URL(storedHref, window.location.origin);
160
+ const localeIds = this.languages.map((loc) => loc.id);
161
+ const segments = url.pathname.split("/").filter(Boolean);
162
+
163
+ if (segments[0] === "docs") {
164
+ // Drop the existing locale segment, if any
165
+ if (localeIds.includes(segments[1])) {
166
+ segments.splice(1, 1);
167
+ }
168
+ // Add the current locale (en-us is the default, so it has none)
169
+ if (this.language && this.language !== "en-us") {
170
+ segments.splice(1, 0, this.language);
171
+ }
172
+ }
173
+
174
+ url.pathname = "/" + segments.join("/");
175
+ return url.href;
176
+ }
177
+ ```
178
+
179
+ Step by step, for a stored `/docs/intro.html` while viewing `ja-jp`:
180
+
181
+ | step | `segments` |
182
+ |------|-----------|
183
+ | split path | `["docs", "intro.html"]` |
184
+ | drop locale (`"intro.html"` is not a locale → no change) | `["docs", "intro.html"]` |
185
+ | add `ja-jp` after `docs` | `["docs", "ja-jp", "intro.html"]` |
186
+ | join | `/docs/ja-jp/intro.html` |
187
+
188
+ All four locale combinations:
189
+
190
+ | current locale | stored URL | result |
191
+ |---|---|---|
192
+ | ja-jp | `/docs/intro.html` | `/docs/ja-jp/intro.html` |
193
+ | en-us | `/docs/ja-jp/intro.html` | `/docs/intro.html` |
194
+ | en-us | `/docs/intro.html` | `/docs/intro.html` (unchanged) |
195
+ | ja-jp | `/docs/ja-jp/intro.html` | `/docs/ja-jp/intro.html` (unchanged) |
196
+
197
+ ---
198
+
199
+ ## Worked example — the original QA bug
200
+
201
+ > On `advanced-setup` → open `bot-api` → switch ja-jp → switch en → click back.
202
+
203
+ | step | action | `document.referrer` | `sessionStorage[redoc-back-target]` |
204
+ |------|--------|---------------------|--------------------------------------|
205
+ | 1 | land on `advanced-setup` (en) | — | (empty) |
206
+ | 2 | navigate to `bot-api` (en) | `advanced-setup` | **`advanced-setup`** ← stored, not a locale sibling |
207
+ | 3 | switch to ja-jp → `bot-api` (ja) | `bot-api` (en) | `advanced-setup` ← **kept** (referrer is a locale sibling) |
208
+ | 4 | switch to en → `bot-api` (en) | `bot-api` (ja) | `advanced-setup` ← **kept** again |
209
+ | 5 | click **back** | — | reads `advanced-setup`, current locale en-us → no rewrite → navigate to `advanced-setup` ✓ |
210
+
211
+ If at step 4 you had **stayed** in ja-jp and clicked back, step 5 would rewrite
212
+ `advanced-setup` → `/docs/ja-jp/.../advanced-setup`, so you land on the page in
213
+ the locale you're actually viewing.
214
+
215
+ ---
216
+
217
+ ## All cases at a glance
218
+
219
+ | situation | what happens |
220
+ |---|---|
221
+ | Stored target exists | Go to it, re-localed to the current locale |
222
+ | No stored target, normal same-origin referrer | Go to the referrer |
223
+ | No stored target, referrer is a locale sibling | Ignore it → go to `headerHref` (parent) |
224
+ | No stored target, no referrer (fresh tab / shared link / external) | Go to `headerHref` |
225
+ | Multiple locale switches in a row | Original page is preserved (locale-sibling referrers never overwrite it) |
226
+
227
+ ## Tests
228
+
229
+ See `__tests__/redocReference.test.ts`:
230
+
231
+ - *adds the locale to the back target on click (en-us → ja-jp)*
232
+ - *removes the locale from the back target on click (ja-jp → en-us)*
233
+ - *falls back to headerHref when nothing is stored*
@@ -42,6 +42,8 @@ const ELEMENT_TIMEOUT = 10000;
42
42
  const ELEMENT_CHECK_INTERVAL = 100;
43
43
  const DEFAULT_PROJECT_TITLE = "All Reference";
44
44
  const BACK_TARGET_STORAGE_KEY = "redoc-back-target";
45
+ const DOCS_PATH_SEGMENT = "docs";
46
+ const DEFAULT_LOCALE = "en-us";
45
47
 
46
48
  export default class RedocReference extends LightningElement {
47
49
  private _referenceConfig: ReferenceConfig = { refList: [] };
@@ -205,22 +207,25 @@ export default class RedocReference extends LightningElement {
205
207
  : null;
206
208
  }
207
209
 
208
- /* Rewrites the stored URL's locale to the current one. Paths are
209
- * /docs/locale/.., except the default en-us which has no locale segment.
210
+ /* The locale segment sits after "docs" for docs pages,
211
+ * and after the product segment for references.
210
212
  */
211
213
  private translateToCurrentLocale(storedHref: string): string {
212
214
  const url = new URL(storedHref, window.location.origin);
213
215
  const localeIds = this.languages.map((loc) => loc.id);
214
216
  const segments = url.pathname.split("/").filter(Boolean);
215
217
 
216
- if (segments[0] === "docs") {
218
+ // docs -> right after "docs"; references -> after the product segment.
219
+ const localeIndex = this.isDocContentType ? 1 : 2;
220
+
221
+ if (segments[0] === DOCS_PATH_SEGMENT) {
217
222
  // Drop the existing locale segment, if any
218
- if (localeIds.includes(segments[1])) {
219
- segments.splice(1, 1);
223
+ if (localeIds.includes(segments[localeIndex])) {
224
+ segments.splice(localeIndex, 1);
220
225
  }
221
226
  // Add the current locale
222
- if (this.language && this.language !== "en-us") {
223
- segments.splice(1, 0, this.language);
227
+ if (this.language && this.language !== DEFAULT_LOCALE) {
228
+ segments.splice(localeIndex, 0, this.language);
224
229
  }
225
230
  }
226
231
 
@@ -533,22 +538,20 @@ export default class RedocReference extends LightningElement {
533
538
  }
534
539
 
535
540
  /**
536
- * Inserts the project header into Redoc for multi-spec reference sets.
541
+ * Inserts the project header and LNB footer into Redoc.
537
542
  */
538
543
  private insertSidebarNav(redocContainer: HTMLElement): void {
539
- if (!this.showRedocHeader) {
540
- return;
541
- }
542
-
543
544
  // Select the LNB and content area of Redoc and insert the requried header.
544
- redocContainer
545
- .querySelectorAll<HTMLElement>(".menu-content, .api-content")
546
- .forEach((target) => {
547
- target.insertBefore(
548
- this.buildProjectHeaderDom(),
549
- target.firstChild
550
- );
551
- });
545
+ if (this.showRedocHeader) {
546
+ redocContainer
547
+ .querySelectorAll<HTMLElement>(".menu-content, .api-content")
548
+ .forEach((target) => {
549
+ target.insertBefore(
550
+ this.buildProjectHeaderDom(),
551
+ target.firstChild
552
+ );
553
+ });
554
+ }
552
555
 
553
556
  // Locale picker
554
557
  if (this.hasLocalePicker) {
@@ -15,6 +15,8 @@
15
15
  language={language}
16
16
  show-footer={enableFooter}
17
17
  origin={origin}
18
+ dev-center={devCenter}
19
+ brand={brand}
18
20
  >
19
21
  <doc-phase
20
22
  slot="doc-phase"
@@ -44,6 +44,8 @@ export default class UnifiedContentLayout extends LightningElement {
44
44
  @api tocAriaLevel?: string;
45
45
  @api languages?: OptionWithLink[];
46
46
  @api language?: string;
47
+ @api devCenter: any = null;
48
+ @api brand: any = null;
47
49
 
48
50
  /** Optional origin URL for the footer MFE (e.g. wp-json endpoint). */
49
51
  @api origin: string | null = null;