@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 +1 -1
- package/package.json +2 -2
- package/src/modules/dx/searchResults/searchResults.ts +5 -5
- package/src/modules/dx/sidebarSearch/sidebarSearch.ts +73 -14
- package/src/modules/dxUtils/data360Search/data360Search.ts +168 -0
- package/src/modules/dxUtils/dataCloudSearch/dataCloudSearch.ts +0 -100
package/lwc.config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforcedevs/dx-components",
|
|
3
|
-
"version": "1.28.7-alpha.
|
|
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": "
|
|
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
|
|
5
|
+
type Data360SearchResultItem,
|
|
6
6
|
fetchSearch,
|
|
7
7
|
getQueryFromUrl
|
|
8
|
-
} from "dxUtils/
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
|
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.
|
|
85
|
-
this.
|
|
104
|
+
if (!this.data360SearchInitialized) {
|
|
105
|
+
this.data360SearchInitialized = true;
|
|
86
106
|
if (this.value) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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:
|
|
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
|
|
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
|
-
}
|