@salesforcedevs/dx-components 1.28.7-alpha.12 → 1.28.7-alpha.14

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 CHANGED
@@ -127,7 +127,7 @@
127
127
  "dxUtils/analytics",
128
128
  "dxUtils/async",
129
129
  "dxUtils/constants",
130
- "dxUtils/dataCloudSearch",
130
+ "dxUtils/data360Search",
131
131
  "dxUtils/contentTypes",
132
132
  "dxUtils/coveo",
133
133
  "dxUtils/dates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/dx-components",
3
- "version": "1.28.7-alpha.12",
3
+ "version": "1.28.7-alpha.14",
4
4
  "description": "DX Lightning web components",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -44,5 +44,5 @@
44
44
  "luxon": "3.4.4",
45
45
  "msw": "^2.12.4"
46
46
  },
47
- "gitHead": "d90a01c3e0b1366d6f262d986bfa0ef6ede99137"
47
+ "gitHead": "01442763b9d41a4eb0f50911dba1bb1f5b94f5c8"
48
48
  }
@@ -2,10 +2,10 @@ import { LightningElement, api, track } from "lwc";
2
2
  import debounce from "debounce";
3
3
  import { track as trackGTM } from "dxUtils/analytics";
4
4
  import {
5
- type DataCloudSearchResultItem,
5
+ type Data360SearchResultItem,
6
6
  fetchSearch,
7
7
  getQueryFromUrl
8
- } from "dxUtils/dataCloudSearch";
8
+ } from "dxUtils/data360Search";
9
9
 
10
10
  const SEARCH_DEBOUNCE_DELAY = 400;
11
11
 
@@ -99,12 +99,12 @@ export default class SearchResults extends LightningElement {
99
99
  try {
100
100
  const raw = await fetchSearch(query);
101
101
  this.results = raw.map(
102
- (item: DataCloudSearchResultItem, index: number) =>
102
+ (item: Data360SearchResultItem, index: number) =>
103
103
  this.normalizeResult(item, index)
104
104
  );
105
105
  this.trackSearchResultsOnce(query, this.results.length);
106
106
  } catch (err) {
107
- console.error("Data Cloud Search request failed", err);
107
+ console.error("Data 360 Search request failed", err);
108
108
  this.results = [];
109
109
  } finally {
110
110
  this.isLoading = false;
@@ -116,7 +116,7 @@ export default class SearchResults extends LightningElement {
116
116
  }, SEARCH_DEBOUNCE_DELAY);
117
117
 
118
118
  private normalizeResult(
119
- item: DataCloudSearchResultItem,
119
+ item: Data360SearchResultItem,
120
120
  index: number
121
121
  ): SearchResultDisplay {
122
122
  const href = item.url ?? "";
@@ -1,9 +1,14 @@
1
1
  import { LightningElement, api } from "lwc";
2
2
  import debounce from "debounce";
3
3
  import {
4
- type DataCloudSearchResultItem,
5
- fetchSearch
6
- } from "dxUtils/dataCloudSearch";
4
+ type Data360SearchCacheItem,
5
+ type Data360SearchResultItem,
6
+ fetchSearch,
7
+ getBaseUrlPath,
8
+ getStoredSearch,
9
+ isCacheStale,
10
+ setStoredSearch
11
+ } from "dxUtils/data360Search";
7
12
  import { createSearchRegExp } from "dxUtils/regexps";
8
13
  import { RecentSearches } from "dxUtils/recentSearches";
9
14
  import {
@@ -38,13 +43,28 @@ function getHighlightRanges(
38
43
 
39
44
  const UserRecentSearches = new RecentSearches();
40
45
 
46
+ function resultPathFromHref(href: string): string {
47
+ return href.startsWith("http") ? new URL(href).pathname : href;
48
+ }
49
+
50
+ function applySelectedAndSelect(
51
+ items: Data360SearchCacheItem[]
52
+ ): SidebarSearchResult[] {
53
+ const currentPath = window.location.pathname;
54
+ return items.map((item) => ({
55
+ ...item,
56
+ selected: !!item.href && resultPathFromHref(item.href) === currentPath,
57
+ select: () => {}
58
+ }));
59
+ }
60
+
41
61
  const getSearchQueryParam = (): string =>
42
62
  new URLSearchParams(window.location.search).get("q") ?? "";
43
63
 
44
64
  export default class SidebarSearch extends LightningElement {
45
65
  @api
46
66
  public fetchMoreResults() {
47
- // Data Cloud Search API does not expose pagination in the same way; no-op
67
+ // Data 360 Search API does not expose pagination in the same way; no-op
48
68
  }
49
69
 
50
70
  @api
@@ -64,7 +84,7 @@ export default class SidebarSearch extends LightningElement {
64
84
  private recentSearches: Option[] = [];
65
85
  private value: string = "";
66
86
  private didRender = false;
67
- private dataCloudSearchInitialized: boolean = false;
87
+ private data360SearchInitialized: boolean = false;
68
88
 
69
89
  private get isDropdownOpen() {
70
90
  return (
@@ -81,16 +101,42 @@ export default class SidebarSearch extends LightningElement {
81
101
  }
82
102
 
83
103
  renderedCallback() {
84
- if (!this.dataCloudSearchInitialized) {
85
- this.dataCloudSearchInitialized = true;
104
+ if (!this.data360SearchInitialized) {
105
+ this.data360SearchInitialized = true;
86
106
  if (this.value) {
87
- // Page load with ?q= in URL: run search so results and loading state resolve
88
- if (!this.didRender) {
89
- this.dispatchHighlightedTermChange();
107
+ const baseUrlPath = getBaseUrlPath();
108
+ const cached = getStoredSearch(baseUrlPath);
109
+ const queryTrimmed = this.value.trim();
110
+ if (
111
+ cached?.query === queryTrimmed &&
112
+ Array.isArray(cached.results) &&
113
+ !isCacheStale(cached)
114
+ ) {
115
+ if (!this.didRender) {
116
+ this.dispatchHighlightedTermChange();
117
+ }
118
+ const results = applySelectedAndSelect(cached.results);
119
+ this.dispatchChange(results);
120
+ this.dispatchOnLoading(false);
121
+ } else {
122
+ if (!this.didRender) {
123
+ this.dispatchHighlightedTermChange();
124
+ }
125
+ this.dispatchOnLoading(true);
126
+ this.fetchDataCloudSearch();
90
127
  }
91
- this.dispatchOnLoading(true);
92
- this.fetchDataCloudSearch();
93
128
  } else {
129
+ // No URL query: restore search bar and results from localStorage for this path
130
+ const baseUrlPath = getBaseUrlPath();
131
+ const cached = getStoredSearch(baseUrlPath);
132
+ if (cached?.query && !isCacheStale(cached)) {
133
+ this.value = cached.query;
134
+ const results = applySelectedAndSelect(
135
+ cached.results ?? []
136
+ );
137
+ this.dispatchChange(results);
138
+ this.dispatchHighlightedTermChange();
139
+ }
94
140
  this.dispatchOnLoading(false);
95
141
  }
96
142
  }
@@ -101,7 +147,7 @@ export default class SidebarSearch extends LightningElement {
101
147
  }
102
148
 
103
149
  private normalizeDataCloudResult = (
104
- item: DataCloudSearchResultItem,
150
+ item: Data360SearchResultItem,
105
151
  index: number
106
152
  ): SidebarSearchResult => {
107
153
  const href = item.url ?? "";
@@ -132,8 +178,20 @@ export default class SidebarSearch extends LightningElement {
132
178
  this.normalizeDataCloudResult
133
179
  );
134
180
  this.dispatchChange(results);
181
+ const cacheItems: Data360SearchCacheItem[] = results.map((r) => ({
182
+ title: r.title,
183
+ titleHighlights: r.titleHighlights,
184
+ excerpt: r.excerpt,
185
+ excerptHighlights: r.excerptHighlights,
186
+ uniqueId: r.uniqueId,
187
+ href: r.href
188
+ }));
189
+ setStoredSearch(getBaseUrlPath(), {
190
+ query: this.value.trim(),
191
+ results: cacheItems
192
+ });
135
193
  } catch (err) {
136
- console.error("Data Cloud Search request failed", err);
194
+ console.error("Data 360 Search request failed", err);
137
195
  this.dispatchChange([]);
138
196
  } finally {
139
197
  this.dispatchOnLoading(false);
@@ -227,6 +285,7 @@ export default class SidebarSearch extends LightningElement {
227
285
  this.dispatchOnLoading(false);
228
286
  this.dispatchChange([]);
229
287
  this.dispatchSidebarSearchChange(this.value);
288
+ setStoredSearch(getBaseUrlPath(), { query: "", results: [] });
230
289
  }
231
290
  }
232
291
  }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Data 360 search API – shared endpoints and helpers for sidebar search,
3
+ * search results page, and has-results check.
4
+ */
5
+
6
+ const STORAGE_KEY_PREFIX = "data360-search:";
7
+
8
+ /** Data 360 Search API result item (title, url, matchedText) */
9
+ export interface Data360SearchResultItem {
10
+ title?: string;
11
+ url?: string;
12
+ matchedText?: string;
13
+ }
14
+
15
+ /** Normalized result shape stored in cache (no selected/select; those are applied on restore). */
16
+ export interface Data360SearchCacheItem {
17
+ title: string;
18
+ titleHighlights: Array<{ length: number; offset: number }>;
19
+ excerpt: string;
20
+ excerptHighlights: Array<{ length: number; offset: number }>;
21
+ uniqueId: string;
22
+ href: string;
23
+ }
24
+
25
+ export interface Data360SearchCache {
26
+ query: string;
27
+ results: Data360SearchCacheItem[];
28
+ /** Timestamp when stored (Date.now()); missing on older entries (treated as stale). */
29
+ storedAt?: number;
30
+ }
31
+
32
+ const CACHE_MAX_AGE_MS = 2 * 60 * 60 * 1000; // 2 hours
33
+
34
+ export function isCacheStale(cache: Data360SearchCache | null): boolean {
35
+ if (!cache?.storedAt) {
36
+ return true;
37
+ }
38
+ return Date.now() - cache.storedAt > CACHE_MAX_AGE_MS;
39
+ }
40
+
41
+ export function getStoredSearch(
42
+ baseUrlPath: string
43
+ ): Data360SearchCache | null {
44
+ try {
45
+ const raw = localStorage.getItem(`${STORAGE_KEY_PREFIX}${baseUrlPath}`);
46
+ if (!raw) {
47
+ return null;
48
+ }
49
+ const parsed = JSON.parse(raw) as Data360SearchCache;
50
+ if (
51
+ !parsed ||
52
+ typeof parsed.query !== "string" ||
53
+ !Array.isArray(parsed.results)
54
+ ) {
55
+ return null;
56
+ }
57
+ return parsed;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ export function setStoredSearch(
64
+ baseUrlPath: string,
65
+ data: Data360SearchCache
66
+ ): void {
67
+ try {
68
+ const toStore: Data360SearchCache = {
69
+ ...data,
70
+ storedAt: Date.now()
71
+ };
72
+ localStorage.setItem(
73
+ `${STORAGE_KEY_PREFIX}${baseUrlPath}`,
74
+ JSON.stringify(toStore)
75
+ );
76
+ } catch {
77
+ /* ignore quota or disabled localStorage */
78
+ }
79
+ }
80
+
81
+ export const DATA_360_SEARCH_PATH = "/data-360-search/search";
82
+ export const DATA_360_HAS_RESULTS_PATH = "/data-360-search/has-results";
83
+ export const DATA_360_SEARCH_ORIGIN = "https://developer.salesforce.com";
84
+
85
+ export function getBaseUrlPath(): string {
86
+ const url = DATA_360_SEARCH_ORIGIN + window.location.pathname;
87
+ const lastSlash = url.lastIndexOf("/");
88
+ return lastSlash > 0 ? url.substring(0, lastSlash) : url;
89
+ }
90
+
91
+ /**
92
+ * Read search query from URL: ?q=, ?keywords= (migrated to q), or #q=.
93
+ */
94
+ export function getQueryFromUrl(): string {
95
+ const params = new URLSearchParams(window.location.search);
96
+ const keywords = params.get("keywords");
97
+ if (keywords) {
98
+ const url = new URL(window.location.href);
99
+ url.searchParams.delete("keywords");
100
+ url.searchParams.set("q", keywords);
101
+ window.history.replaceState(null, "", url.href);
102
+ return keywords;
103
+ }
104
+ const qFromSearch = params.get("q");
105
+ if (qFromSearch) {
106
+ return qFromSearch;
107
+ }
108
+ const hash = window.location.hash.slice(1);
109
+ if (hash) {
110
+ try {
111
+ const qFromHash = new URLSearchParams(hash).get("q");
112
+ if (qFromHash) {
113
+ return qFromHash;
114
+ }
115
+ } catch {
116
+ /* ignore */
117
+ }
118
+ }
119
+ return "";
120
+ }
121
+
122
+ /**
123
+ * Quick check if the current page has searchable results.
124
+ * Only when this returns true should the Data 360 sidebar be shown.
125
+ */
126
+ export async function fetchHasResults(): Promise<boolean> {
127
+ try {
128
+ const res = await fetch(DATA_360_HAS_RESULTS_PATH, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify({ baseUrlPath: getBaseUrlPath() })
132
+ });
133
+ if (!res.ok) {
134
+ return false;
135
+ }
136
+ const data = await res.json();
137
+ return data.hasResults === true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Run a Data 360 search. Returns the raw result items; callers normalize for UI.
145
+ */
146
+ export async function fetchSearch(
147
+ searchQuery: string
148
+ ): Promise<Data360SearchResultItem[]> {
149
+ const query = searchQuery.trim();
150
+ if (!query) {
151
+ return [];
152
+ }
153
+ const res = await fetch(DATA_360_SEARCH_PATH, {
154
+ method: "POST",
155
+ headers: { "Content-Type": "application/json" },
156
+ body: JSON.stringify({
157
+ searchQuery: query,
158
+ baseUrlPath: getBaseUrlPath()
159
+ })
160
+ });
161
+ if (!res.ok) {
162
+ throw new Error(`Search API error: ${res.status}`);
163
+ }
164
+ const data = await res.json();
165
+ return Array.isArray(data)
166
+ ? data
167
+ : data?.results ?? data?.data?.results ?? [];
168
+ }
@@ -1,100 +0,0 @@
1
- /**
2
- * Data Cloud search API – shared endpoints and helpers for sidebar search,
3
- * search results page, and has-results check.
4
- */
5
-
6
- export const DATA_CLOUD_SEARCH_PATH = "/data-cloud-search/search";
7
- export const DATA_CLOUD_HAS_RESULTS_PATH = "/data-cloud-search/has-results";
8
- export const DATA_CLOUD_SEARCH_ORIGIN = "https://developer.salesforce.com";
9
-
10
- /** Data Cloud Search API result item (title, url, matchedText) */
11
- export interface DataCloudSearchResultItem {
12
- title?: string;
13
- url?: string;
14
- matchedText?: string;
15
- }
16
-
17
- export function getBaseUrlPath(): string {
18
- const url = DATA_CLOUD_SEARCH_ORIGIN + window.location.pathname;
19
- const lastSlash = url.lastIndexOf("/");
20
- return lastSlash > 0 ? url.substring(0, lastSlash) : url;
21
- }
22
-
23
- /**
24
- * Read search query from URL: ?q=, ?keywords= (migrated to q), or #q=.
25
- */
26
- export function getQueryFromUrl(): string {
27
- const params = new URLSearchParams(window.location.search);
28
- const keywords = params.get("keywords");
29
- if (keywords) {
30
- const url = new URL(window.location.href);
31
- url.searchParams.delete("keywords");
32
- url.searchParams.set("q", keywords);
33
- window.history.replaceState(null, "", url.href);
34
- return keywords;
35
- }
36
- const qFromSearch = params.get("q");
37
- if (qFromSearch) {
38
- return qFromSearch;
39
- }
40
- const hash = window.location.hash.slice(1);
41
- if (hash) {
42
- try {
43
- const qFromHash = new URLSearchParams(hash).get("q");
44
- if (qFromHash) {
45
- return qFromHash;
46
- }
47
- } catch {
48
- /* ignore */
49
- }
50
- }
51
- return "";
52
- }
53
-
54
- /**
55
- * Quick check if the current page has searchable results.
56
- * Only when this returns true should the Data Cloud sidebar be shown.
57
- */
58
- export async function fetchHasResults(): Promise<boolean> {
59
- try {
60
- const res = await fetch(DATA_CLOUD_HAS_RESULTS_PATH, {
61
- method: "POST",
62
- headers: { "Content-Type": "application/json" },
63
- body: JSON.stringify({ baseUrlPath: getBaseUrlPath() })
64
- });
65
- if (!res.ok) {
66
- return false;
67
- }
68
- const data = await res.json();
69
- return data.hasResults === true;
70
- } catch {
71
- return false;
72
- }
73
- }
74
-
75
- /**
76
- * Run a Data Cloud search. Returns the raw result items; callers normalize for UI.
77
- */
78
- export async function fetchSearch(
79
- searchQuery: string
80
- ): Promise<DataCloudSearchResultItem[]> {
81
- const query = searchQuery.trim();
82
- if (!query) {
83
- return [];
84
- }
85
- const res = await fetch(DATA_CLOUD_SEARCH_PATH, {
86
- method: "POST",
87
- headers: { "Content-Type": "application/json" },
88
- body: JSON.stringify({
89
- searchQuery: query,
90
- baseUrlPath: getBaseUrlPath()
91
- })
92
- });
93
- if (!res.ok) {
94
- throw new Error(`Search API error: ${res.status}`);
95
- }
96
- const data = await res.json();
97
- return Array.isArray(data)
98
- ? data
99
- : data?.results ?? data?.data?.results ?? [];
100
- }