@salesforcedevs/dx-components 1.28.0 → 1.28.1-banner-1

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.
Files changed (32) hide show
  1. package/lwc.config.json +5 -1
  2. package/package.json +2 -2
  3. package/src/modules/dx/alert/alert.css +0 -1
  4. package/src/modules/dx/codeBlock/codeBlock.ts +6 -6
  5. package/src/modules/dx/emptyState/emptyState.html +1 -1
  6. package/src/modules/dx/emptyState/emptyState.ts +2 -2
  7. package/src/modules/dx/error/error.css +3 -3
  8. package/src/modules/dx/errorFallback/errorFallback.html +1 -1
  9. package/src/modules/dx/feature/feature.css +2 -2
  10. package/src/modules/dx/feature/feature.html +1 -1
  11. package/src/modules/dx/featuredContentHeader/featuredContentHeader.css +5 -5
  12. package/src/modules/dx/featuredContentHeader/svgs.ts +2 -2
  13. package/src/modules/dx/footer/footer.css +176 -4
  14. package/src/modules/dx/footer/footer.html +91 -3
  15. package/src/modules/dx/footer/footer.ts +276 -1
  16. package/src/modules/dx/footerMfe/footerMfe.ts +2 -0
  17. package/src/modules/dx/input/input.ts +1 -1
  18. package/src/modules/dx/logo/logo.ts +1 -1
  19. package/src/modules/dx/searchResults/resultsTemplate.js +1 -1
  20. package/src/modules/dx/searchResults/searchResults.css +4 -4
  21. package/src/modules/dx/sectionBanner/sectionBanner.html +1 -1
  22. package/src/modules/dx/sidebar/sidebar.html +1 -1
  23. package/src/modules/dx/toc/toc.css +21 -0
  24. package/src/modules/dx/toc/toc.ts +2 -0
  25. package/src/modules/dx/treeItem/treeItem.html +1 -1
  26. package/src/modules/dxUtils/shiki/__mocks__/shiki.ts +2 -1
  27. package/src/modules/dxUtils/shiki/__mocks__/shikijs.ts +6 -0
  28. package/src/modules/dxUtils/shiki/shiki.ts +26 -176
  29. package/src/modules/dxUtils/shikiCore/shikiCore.ts +188 -0
  30. package/src/modules/dxUtils/shikiGrammars/shikiGrammars.ts +151 -41
  31. package/src/modules/dxUtils/shikiStatic/shikiStatic.html +3 -0
  32. package/src/modules/dxUtils/shikiStatic/shikiStatic.ts +46 -0
@@ -4,15 +4,21 @@ import { FooterVariant, LightningSlotElement } from "typings/custom";
4
4
  import { track } from "dxUtils/analytics";
5
5
  import { isSlotEmpty } from "dxUtils/slot";
6
6
 
7
- export default class Footer extends LightningElement {
7
+ class Footer extends LightningElement {
8
8
  private _variant: FooterVariant = "small-signup";
9
9
  private isSlotEmpty = true;
10
10
  private signupUrl =
11
11
  "https://www.salesforce.com/form/other/role-based-newsletter/?Developer=true";
12
12
 
13
+ @api
14
+ mfeHomeHref: string = `/${window.location.host}`; // ugly hack: ideally this wouldn't be necessary, but the only way to remove the "See all ways to contact us" link from the footer MFE is to set this to a non-empty value other than "us"; and given the way that the footer works, the non-empty value needs to be something that can be appended to `/` and work correctly
15
+
13
16
  @api
14
17
  mfeConfigOrigin: string = `${window.location.origin}/developer/en-us/wp-json`;
15
18
 
19
+ @api
20
+ useMfe: boolean = false; // TODO: Probably just remove this once the footer MFE supports rendering in non-full-width contexts
21
+
16
22
  @api
17
23
  set variant(value: FooterVariant) {
18
24
  if (!this.isFooterVariant(value)) {
@@ -93,3 +99,272 @@ export default class Footer extends LightningElement {
93
99
  });
94
100
  }
95
101
  }
102
+
103
+ // This encapsulates the old non-MFE footer functionality that should be removed once the footer MFE supports rendering in non-full-width contexts.
104
+ // It's encapsulated like this solely for ease of deletion later.
105
+ function augmentWithNonMFEFooterFunctionality(FooterClass: typeof Footer) {
106
+ const baseSocialIconUrl = `/assets/icons/brand-sprite/svg/symbols.svg`;
107
+
108
+ return class NonMFEFooter extends FooterClass {
109
+ private didRequestFooterConfig = false;
110
+ private footerConfig: any = []; // NOTE: See mockProps.ts for the expected format of the footer config
111
+ private configItemTitleToItemLookup: Map<string, any> = new Map();
112
+ private configItemParentToChildrenLookup: Map<number, any[]> =
113
+ new Map();
114
+ private generalLinks: any = [];
115
+ private socialLinks: any = [];
116
+ private termsLinks: any = [];
117
+
118
+ async renderedCallback() {
119
+ super.renderedCallback?.();
120
+
121
+ if (this.didRequestFooterConfig || this.useMfe) {
122
+ return;
123
+ }
124
+
125
+ this.didRequestFooterConfig = true;
126
+ this.footerConfig = await this.requestFooterConfig();
127
+ this.buildFooterConfigLookupTables(this.footerConfig);
128
+ this.setSocialLinks();
129
+ this.setGeneralLinks();
130
+ this.setLegalFooter();
131
+ }
132
+
133
+ get showContainerMiddle() {
134
+ return !this.showLegalOnly;
135
+ }
136
+
137
+ private async requestFooterConfig() {
138
+ let config: any = [];
139
+ try {
140
+ const response = await fetch(
141
+ `${this.mfeConfigOrigin}/c360/experience/v1/navigation`
142
+ );
143
+ const data = await response.json();
144
+ config = data["footer-navigation"] || [];
145
+ } catch (error) {
146
+ console.error("Error requesting footer config:", error);
147
+ }
148
+ return config;
149
+ }
150
+
151
+ private buildFooterConfigLookupTables(config: any[]) {
152
+ config.forEach((item: any) => {
153
+ this.configItemTitleToItemLookup.set(item.title, item);
154
+ if (item.menu_item_parent) {
155
+ const parentId = parseInt(item.menu_item_parent, 10);
156
+ const children =
157
+ this.configItemParentToChildrenLookup.get(parentId) ||
158
+ [];
159
+ children.push(item);
160
+ this.configItemParentToChildrenLookup.set(
161
+ parentId,
162
+ children
163
+ );
164
+ }
165
+ });
166
+ }
167
+
168
+ private setSocialLinks() {
169
+ const socialLinksParentItem =
170
+ this.configItemTitleToItemLookup.get("social-icons");
171
+
172
+ if (!socialLinksParentItem || !socialLinksParentItem.ID) {
173
+ console.error(
174
+ "Social links parent item not found in footer config"
175
+ );
176
+ return;
177
+ }
178
+
179
+ const socialLinksItems = this.configItemParentToChildrenLookup.get(
180
+ socialLinksParentItem.ID
181
+ );
182
+
183
+ if (!socialLinksItems) {
184
+ console.error(
185
+ "Social links children items not found in footer config"
186
+ );
187
+ return;
188
+ }
189
+
190
+ this.socialLinks =
191
+ socialLinksItems.map((child: any) => {
192
+ const iconSymbol =
193
+ child.title === "LinkedIn"
194
+ ? "linked-in"
195
+ : child.title.toLowerCase();
196
+ const iconUrlHash =
197
+ iconSymbol === "twitter"
198
+ ? "#twitter-x"
199
+ : `#themed-${iconSymbol}`;
200
+
201
+ return {
202
+ href: child.url,
203
+ iconSprite: "brand",
204
+ iconURL: `${baseSocialIconUrl}${iconUrlHash}`,
205
+ label: child.title,
206
+ iconSymbol
207
+ };
208
+ });
209
+ }
210
+
211
+ private setGeneralLinks() {
212
+ const topFooterItem =
213
+ this.configItemTitleToItemLookup.get("top-footer");
214
+ const mediaItem = this.configItemTitleToItemLookup.get("media");
215
+
216
+ if (!topFooterItem || !topFooterItem.ID) {
217
+ console.error("Top footer item not found in footer config");
218
+ return;
219
+ }
220
+
221
+ const generalLinksHeadingsItems =
222
+ this.configItemParentToChildrenLookup
223
+ .get(topFooterItem.ID)
224
+ ?.filter((item: any) => item.ID !== mediaItem?.ID);
225
+
226
+ if (!generalLinksHeadingsItems) {
227
+ console.error(
228
+ "General links children items not found in footer config"
229
+ );
230
+ return;
231
+ }
232
+
233
+ const generalLinks: any[] = [];
234
+ generalLinksHeadingsItems.forEach((item: any) => {
235
+ const childrenItems = this.configItemParentToChildrenLookup.get(item.ID);
236
+
237
+ if (!childrenItems) {
238
+ console.error(`General links children items not found for item with title "${item.title}"`);
239
+ return;
240
+ }
241
+
242
+ generalLinks.push({
243
+ id: item.title,
244
+ label: item.title,
245
+ options: childrenItems.map((option: any) => {
246
+ return {
247
+ id: option.title,
248
+ label: option.title,
249
+ link: {
250
+ href: option.url,
251
+ target: option.target
252
+ }
253
+ };
254
+ })
255
+ });
256
+ });
257
+ this.generalLinks = generalLinks;
258
+ }
259
+
260
+ private setLegalFooter() {
261
+ const copyrightNoticeItem = this.configItemTitleToItemLookup.get("All rights reserved");
262
+
263
+ if (!copyrightNoticeItem) {
264
+ console.error("All rights reserved item not found in footer config");
265
+ return;
266
+ }
267
+
268
+ const { url, description } = copyrightNoticeItem;
269
+ const copyrightNoticeInnerHtml = description.replace("{{All rights reserved}}", `<a href="${url}">All rights reserved</a>`);
270
+ const copyrightNoticeEl = this.template.querySelector(".copyright-notice")!;
271
+ copyrightNoticeEl.innerHTML = copyrightNoticeInnerHtml;
272
+ const copyrightLink = copyrightNoticeEl.querySelector("a");
273
+ if (copyrightLink) {
274
+ copyrightLink.addEventListener("click", (e) =>
275
+ this.handleCopyrightLinkClick(e as Event)
276
+ );
277
+ }
278
+
279
+ const legalLinksItems = this.configItemParentToChildrenLookup.get(copyrightNoticeItem.ID);
280
+
281
+ if (!legalLinksItems) {
282
+ console.error("Legal links children items not found in footer config");
283
+ return;
284
+ }
285
+
286
+ this.termsLinks = legalLinksItems.map((item: any) => {
287
+ const link: any = {
288
+ label: item.title,
289
+ href: item.url,
290
+ target: item.target,
291
+ rel: item.rel
292
+ };
293
+
294
+ if (item.title === "Cookie Preferences") {
295
+ link.href = "#";
296
+ link.onclick = this.handleCookiePreferencesClick;
297
+ } else if (item.title === "Your Privacy Choices") {
298
+ link.img = "https://developer.salesforce.com/ns-assets/privacyoptions.svg";
299
+ }
300
+
301
+ return link;
302
+ });
303
+ }
304
+
305
+ private openOneTrustInfoDisplay() {
306
+ if ((window as any).OneTrust && (window as any).OneTrust.ToggleInfoDisplay) {
307
+ (window as any).OneTrust.ToggleInfoDisplay();
308
+ }
309
+ }
310
+
311
+ private trackFooterLinkClick(
312
+ e: Event,
313
+ options?: {
314
+ label?: string;
315
+ elementTitle?: string;
316
+ clickUrl?: string;
317
+ }
318
+ ) {
319
+ const anchor = e.currentTarget as HTMLAnchorElement;
320
+ const clickText =
321
+ options?.label ??
322
+ anchor.textContent?.trim() ??
323
+ anchor.getAttribute("aria-label") ??
324
+ anchor.href;
325
+ const elementTitle = options?.elementTitle ?? clickText;
326
+ const clickUrl = options?.clickUrl ?? anchor.href;
327
+
328
+ track(anchor, "custEv_linkClick", {
329
+ click_text: clickText,
330
+ click_url: clickUrl,
331
+ element_type: "link",
332
+ element_title: elementTitle,
333
+ content_category: "footer",
334
+ nav_item: clickText,
335
+ nav_level: "1",
336
+ nav_type: "footer"
337
+ });
338
+ }
339
+
340
+ private handleLogoClick(e: Event) {
341
+ this.trackFooterLinkClick(e, {
342
+ label: "Salesforce",
343
+ elementTitle: "Salesforce logo",
344
+ clickUrl: `${window.location.origin}/`
345
+ });
346
+ }
347
+
348
+ private handleSocialLinkClick(e: Event) {
349
+ this.trackFooterLinkClick(e);
350
+ }
351
+
352
+ private handleCookiePreferencesClick(e: Event) {
353
+ this.trackFooterLinkClick(e, {
354
+ label: "Cookie Preferences",
355
+ clickUrl: (e.currentTarget as HTMLAnchorElement).href || "#"
356
+ });
357
+ this.openOneTrustInfoDisplay();
358
+ }
359
+
360
+ private handleTermsClick(e: Event) {
361
+ this.trackFooterLinkClick(e);
362
+ }
363
+
364
+ private handleCopyrightLinkClick(e: Event) {
365
+ this.trackFooterLinkClick(e, { label: "All rights reserved" });
366
+ }
367
+ };
368
+ }
369
+
370
+ export default augmentWithNonMFEFooterFunctionality(Footer);
@@ -4,6 +4,7 @@ export default class FooterMfe extends LightningElement {
4
4
  private footerElement: HTMLElement | null = null;
5
5
 
6
6
  @api legalOnly = false;
7
+ @api homeHref: string = `/${window.location.host}`; // ugly hack by default: ideally this wouldn't be necessary, but the only way to remove the "See all ways to contact us" link from the footer MFE is to set this to a non-empty value other than "us"; and given the way that the footer works, the non-empty value needs to be something that can be appended to `/` and work correctly
7
8
  @api origin: string = `${window.location.origin}/developer/en-us/wp-json`;
8
9
 
9
10
  renderedCallback() {
@@ -14,6 +15,7 @@ export default class FooterMfe extends LightningElement {
14
15
  this.footerElement = document.createElement("hgf-footer");
15
16
  this.footerElement.setAttribute("origin", this.origin);
16
17
  this.footerElement.setAttribute("hide-language-selector", "true");
18
+ this.footerElement.setAttribute("home-href", this.homeHref);
17
19
  if (this.legalOnly) {
18
20
  this.footerElement.setAttribute("legal-only", "true");
19
21
  }
@@ -195,7 +195,7 @@ export default class Input extends LightningElement {
195
195
  }
196
196
 
197
197
  private get shortcutImgSrc() {
198
- return `https://a.sfdcstatic.com/developer-website/prod/images/${this.commandKey}.svg`;
198
+ return `https://developer.salesforce.com/ns-assets/${this.commandKey}.svg`;
199
199
  }
200
200
 
201
201
  private get shortcutImgAlt() {
@@ -3,7 +3,7 @@ import { LightningElement, api } from "lwc";
3
3
  export default class Logo extends LightningElement {
4
4
  @api href: string = "/";
5
5
  @api imgSrc: string =
6
- "https://a.sfdcstatic.com/developer-website/prod/images/salesforce-cloud.svg";
6
+ "https://developer.salesforce.com/ns-assets/salesforce-cloud.svg";
7
7
  @api imgAlt: string = "Salesforce logo";
8
8
  @api label!: string;
9
9
  }
@@ -9,7 +9,7 @@ export default html`
9
9
  <div class="coveo-show-if-no-results">
10
10
  <div class="no-results">
11
11
  <img
12
- src="https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-circle-small.svg"
12
+ src="https://developer.salesforce.com/ns-assets/binary-cloud-circle-small.svg"
13
13
  alt="purple cloud with floating binary numbers above"
14
14
  />
15
15
  <div class="no-results-info">
@@ -311,7 +311,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
311
311
 
312
312
  .dx-search-header {
313
313
  padding: var(--dx-g-spacing-xl);
314
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-trees.svg")
314
+ background: url("https://developer.salesforce.com/ns-assets/binary-cloud-trees.svg")
315
315
  no-repeat 100% 0,
316
316
  linear-gradient(
317
317
  77deg,
@@ -352,7 +352,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
352
352
 
353
353
  @media (min-width: 928px) and (max-width: 1024px) {
354
354
  .dx-search-header {
355
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-cloud-trees.svg")
355
+ background: url("https://developer.salesforce.com/ns-assets/binary-cloud-trees.svg")
356
356
  no-repeat 200% 0,
357
357
  linear-gradient(
358
358
  77deg,
@@ -380,7 +380,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
380
380
 
381
381
  @media (min-width: 768px) and (max-width: 928px) {
382
382
  .dx-search-header {
383
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary-trees.svg")
383
+ background: url("https://developer.salesforce.com/ns-assets/binary-trees.svg")
384
384
  no-repeat 90% 50%,
385
385
  linear-gradient(
386
386
  77deg,
@@ -392,7 +392,7 @@ li.coveo-dynamic-facet-breadcrumb-value-list-item {
392
392
 
393
393
  @media (max-width: 768px) {
394
394
  .dx-search-header {
395
- background: url("https://a.sfdcstatic.com/developer-website/prod/images/binary.svg")
395
+ background: url("https://developer.salesforce.com/ns-assets/binary.svg")
396
396
  no-repeat 90% 50%,
397
397
  linear-gradient(
398
398
  77deg,
@@ -3,7 +3,7 @@
3
3
  <img
4
4
  if:false={hideTopGraphic}
5
5
  class="graphic"
6
- src="https://a.sfdcstatic.com/developer-website/prod/images/dx-section-banner-graphic-1.svg"
6
+ src="https://developer.salesforce.com/ns-assets/dx-section-banner-graphic-1.svg"
7
7
  alt=""
8
8
  />
9
9
  <div class="content">
@@ -69,7 +69,7 @@
69
69
  <img
70
70
  lwc:if={isSearchLoading}
71
71
  class="loading-skeleton padding-horizontal"
72
- src="https://a.sfdcstatic.com/developer-website/prod/images/sidebar-loading.svg"
72
+ src="https://developer.salesforce.com/ns-assets/sidebar-loading.svg"
73
73
  alt="loading"
74
74
  />
75
75
  <template
@@ -69,6 +69,27 @@
69
69
  transition: var(--dx-g-transition-hue-1x);
70
70
  }
71
71
 
72
+ /* Indent TOC items by heading level: H2 base, H3+ progressively indented */
73
+ .toc-level-2 {
74
+ padding-left: 11px;
75
+ }
76
+
77
+ .toc-level-3 {
78
+ padding-left: calc(11px + var(--dx-g-spacing-md, 16px));
79
+ }
80
+
81
+ .toc-level-4 {
82
+ padding-left: calc(11px + 2 * var(--dx-g-spacing-md, 16px));
83
+ }
84
+
85
+ .toc-level-5 {
86
+ padding-left: calc(11px + 3 * var(--dx-g-spacing-md, 16px));
87
+ }
88
+
89
+ .toc-level-6 {
90
+ padding-left: calc(11px + 4 * var(--dx-g-spacing-md, 16px));
91
+ }
92
+
72
93
  .content-button.selected {
73
94
  border-color: var(--dx-g-blue-vibrant-50);
74
95
  color: var(--dx-g-blue-vibrant-50);
@@ -32,10 +32,12 @@ export default class Toc extends LightningElement {
32
32
  this.value ||
33
33
  (this._options && this._options.length > 0 && this._options[0].id);
34
34
  return this._options.map((option) => {
35
+ const level = option.level ?? 2;
35
36
  return {
36
37
  ...option,
37
38
  className: cx(
38
39
  "content-button",
40
+ `toc-level-${level}`,
39
41
  option.id === actualValue && "selected"
40
42
  )
41
43
  };
@@ -16,7 +16,7 @@
16
16
  <img
17
17
  if:true={showChildrenLoading}
18
18
  class="tree-children-loading"
19
- src="https://a.sfdcstatic.com/developer-website/prod/images/sidebar-item-loading.svg"
19
+ src="https://developer.salesforce.com/ns-assets/sidebar-item-loading.svg"
20
20
  alt="childrenLoading"
21
21
  />
22
22
  <template if:true={showChildren} tabindex="0">
@@ -34,7 +34,8 @@ export const createHighlighter = jest.fn().mockImplementation(() => {
34
34
  "apex",
35
35
  "text",
36
36
  "handlebars"
37
- ])
37
+ ]),
38
+ loadLanguage: jest.fn().mockResolvedValue(undefined)
38
39
  });
39
40
  });
40
41
 
@@ -21,3 +21,9 @@ export default mockLanguage;
21
21
  // Also export as named export for themes
22
22
  export const theme = mockTheme;
23
23
  export const lang = mockLanguage;
24
+
25
+ // Mock for @shikijs/colorized-brackets
26
+ export const transformerColorizedBrackets = () => ({
27
+ name: "colorized-brackets",
28
+ preprocess: (code: string) => code
29
+ });