@salesforcedevs/dx-components 0.10.1-alpha.13 → 0.10.1-alpha.133
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 +3 -1
- package/package.json +2 -2
- package/src/assets/svg/docs-empty-state-small.svg +72 -0
- package/src/assets/svg/sidebar-loading.svg +34 -0
- package/src/modules/dx/cardGrid/__tests__/cardMockProps.ts +78 -148
- package/src/modules/dx/codeBlock/codeBlock.stories.ts +2 -69
- package/src/modules/dx/codeBlock/codeBlock.ts +5 -1
- package/src/modules/dx/codeBlock/darkTheme.css +0 -1
- package/src/modules/dx/codeBlock/lightTheme.css +0 -1
- package/src/modules/dx/emptyState/__tests__/emptyState.test.ts +29 -9
- package/src/modules/dx/emptyState/emptyState.css +14 -0
- package/src/modules/dx/emptyState/emptyState.html +2 -2
- package/src/modules/dx/emptyState/emptyState.stories.ts +8 -0
- package/src/modules/dx/emptyState/emptyState.ts +14 -0
- package/src/modules/dx/footer/links.ts +3 -5
- package/src/modules/dx/headerNav/__tests__/headerNav.test.ts +2 -4
- package/src/modules/dx/headerNav/headerNav.html +8 -6
- package/src/modules/dx/headerNav/headerNav.ts +2 -2
- package/src/modules/dx/lazy/lazy.ts +0 -7
- package/src/modules/dx/newsletterForm/newsletterForm.html +5 -3
- package/src/modules/dx/searchResults/coveo.css +314 -111
- package/src/modules/dx/sidebar/__tests__/mockDocContent.ts +48 -0
- package/src/modules/dx/sidebar/__tests__/mockPageReference.ts +8 -0
- package/src/modules/dx/sidebar/sidebar.css +38 -17
- package/src/modules/dx/sidebar/sidebar.html +62 -58
- package/src/modules/dx/sidebar/sidebar.stories.ts +53 -54
- package/src/modules/dx/sidebar/sidebar.ts +99 -10
- package/src/modules/dx/sidebarSearch/__tests__/mockPreloadedState.ts +8843 -0
- package/src/modules/dx/sidebarSearch/__tests__/sidebarSearch.test.ts +121 -0
- package/src/modules/dx/sidebarSearch/sidebarSearch.ts +282 -120
- package/src/modules/dx/sidebarSearch/utils.ts +2 -0
- package/src/modules/dx/sidebarSearchResult/__tests__/sidebarSearchResult.test.ts +55 -0
- package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.css +12 -2
- package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.html +1 -1
- package/src/modules/dx/sidebarSearchResult/sidebarSearchResult.ts +8 -2
- package/src/modules/dx/spinner/__tests__/spinner.test.ts +37 -0
- package/src/modules/dx/spinner/spinner.css +750 -0
- package/src/modules/dx/spinner/spinner.html +6 -0
- package/src/modules/dx/spinner/spinner.stories.ts +69 -0
- package/src/modules/dx/spinner/spinner.ts +20 -0
- package/src/modules/dx/{headerNavOption/__tests__/headerNavOption.test.ts → tab/__tests__/tab.test.ts} +40 -24
- package/src/modules/dx/{headerNavOption/headerNavOption.css → tab/tab.css} +24 -9
- package/src/modules/dx/{headerNavOption/headerNavOption.html → tab/tab.html} +5 -1
- package/src/modules/dx/tab/tab.stories.ts +57 -0
- package/src/modules/dx/{headerNavOption/headerNavOption.ts → tab/tab.ts} +26 -5
- package/src/modules/helpers/table/table.css +23 -7
- package/src/modules/utils/coveo/coveo.ts +50 -12
- package/src/modules/utils/highlight/__tests__/highlight.test.ts +154 -0
- package/src/modules/utils/highlight/highlight.ts +32 -0
- package/src/modules/utils/jest/jest.ts +35 -0
- package/src/modules/utils/normalizers/normalizers.ts +3 -0
- package/src/modules/utils/recentSearches/__tests__/recentSearches.test.ts +79 -0
- package/src/modules/utils/recentSearches/recentSearches.ts +12 -17
- package/src/modules/utils/regexps/regexps.ts +5 -0
- package/src/modules/utils/tests/tests.ts +18 -11
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createElement } from "lwc";
|
|
2
|
+
import SidebarSearch from "dx/sidebarSearch";
|
|
3
|
+
import { createRenderComponent, renderLightningElement } from "utils/tests";
|
|
4
|
+
import { coveoXMLDocsConfig } from "utils/coveo";
|
|
5
|
+
import { mockQueryParameter, unmockQueryParameter } from "utils/jest";
|
|
6
|
+
import mockPreloadedState from "./mockPreloadedState";
|
|
7
|
+
import { SESSION_KEY } from "../sidebarSearch";
|
|
8
|
+
|
|
9
|
+
const TAG = "dx-sidebar-search";
|
|
10
|
+
const render = createRenderComponent(TAG, SidebarSearch);
|
|
11
|
+
|
|
12
|
+
const PROPS = coveoXMLDocsConfig;
|
|
13
|
+
const QUERY = "test";
|
|
14
|
+
|
|
15
|
+
describe(TAG, () => {
|
|
16
|
+
afterEach(unmockQueryParameter);
|
|
17
|
+
|
|
18
|
+
it("renders with the correct initial empty query", () => {
|
|
19
|
+
const element = render(PROPS);
|
|
20
|
+
|
|
21
|
+
const input = element.shadowRoot.querySelector("dx-input");
|
|
22
|
+
|
|
23
|
+
expect(input.value).toEqual("");
|
|
24
|
+
|
|
25
|
+
return expect(element).toBeAccessible();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders with the correct initial query", () => {
|
|
29
|
+
mockQueryParameter(QUERY);
|
|
30
|
+
|
|
31
|
+
const element = render(PROPS);
|
|
32
|
+
|
|
33
|
+
const input = element.shadowRoot.querySelector("dx-input");
|
|
34
|
+
|
|
35
|
+
expect(input.value).toEqual(QUERY);
|
|
36
|
+
|
|
37
|
+
return expect(element).toBeAccessible();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("correctly consumes a valid session storage object", () => {
|
|
41
|
+
mockQueryParameter(mockPreloadedState.query.q);
|
|
42
|
+
|
|
43
|
+
window.sessionStorage.setItem(
|
|
44
|
+
SESSION_KEY,
|
|
45
|
+
JSON.stringify(mockPreloadedState)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const mockOnChange = jest.fn();
|
|
49
|
+
|
|
50
|
+
const element = render(PROPS);
|
|
51
|
+
element.addEventListener("change", mockOnChange);
|
|
52
|
+
|
|
53
|
+
return Promise.resolve().then(() => {
|
|
54
|
+
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
|
55
|
+
|
|
56
|
+
expect(element).toBeAccessible();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("highlighting", () => {
|
|
61
|
+
afterEach(unmockQueryParameter);
|
|
62
|
+
|
|
63
|
+
it("triggers on initial render when a search value exists", () => {
|
|
64
|
+
mockQueryParameter(QUERY);
|
|
65
|
+
const element = createElement(TAG, { is: SidebarSearch });
|
|
66
|
+
const dispatchEventSpy = jest.spyOn(element, "dispatchEvent");
|
|
67
|
+
renderLightningElement(element, PROPS);
|
|
68
|
+
expect(dispatchEventSpy).toHaveBeenCalledWith(
|
|
69
|
+
expect.objectContaining({
|
|
70
|
+
type: "highlightedtermchange",
|
|
71
|
+
detail: QUERY
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("does not trigger on initial render when a search value does not exist", () => {
|
|
77
|
+
const element = createElement(TAG, { is: SidebarSearch });
|
|
78
|
+
const dispatchEventSpy = jest.spyOn(element, "dispatchEvent");
|
|
79
|
+
renderLightningElement(element, PROPS);
|
|
80
|
+
expect(dispatchEventSpy).not.toHaveBeenCalledWith(
|
|
81
|
+
expect.objectContaining({ type: "highlightedtermchange" })
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("triggers on input change with non-empty value", () => {
|
|
86
|
+
const element = render(PROPS);
|
|
87
|
+
const dxInput = element.shadowRoot.querySelector("dx-input");
|
|
88
|
+
const inputEl = dxInput.shadowRoot.querySelector("input");
|
|
89
|
+
const dispatchEventSpy = jest.spyOn(element, "dispatchEvent");
|
|
90
|
+
|
|
91
|
+
inputEl.focus();
|
|
92
|
+
inputEl.value = QUERY;
|
|
93
|
+
inputEl.dispatchEvent(new KeyboardEvent("input"));
|
|
94
|
+
|
|
95
|
+
expect(dispatchEventSpy).toHaveBeenCalledWith(
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
type: "highlightedtermchange",
|
|
98
|
+
detail: QUERY
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("triggers on input change with empty value", () => {
|
|
104
|
+
const element = render(PROPS);
|
|
105
|
+
const dxInput = element.shadowRoot.querySelector("dx-input");
|
|
106
|
+
const inputEl = dxInput.shadowRoot.querySelector("input");
|
|
107
|
+
const dispatchEventSpy = jest.spyOn(element, "dispatchEvent");
|
|
108
|
+
|
|
109
|
+
inputEl.focus();
|
|
110
|
+
inputEl.value = "";
|
|
111
|
+
inputEl.dispatchEvent(new KeyboardEvent("input"));
|
|
112
|
+
|
|
113
|
+
expect(dispatchEventSpy).toHaveBeenCalledWith(
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
type: "highlightedtermchange",
|
|
116
|
+
detail: ""
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -1,207 +1,364 @@
|
|
|
1
1
|
import { LightningElement, api } from "lwc";
|
|
2
2
|
import debounce from "debounce";
|
|
3
|
-
import { buildSearchEngine } from "utils/coveo";
|
|
4
|
-
import { RecentSearches } from "utils/recentSearches";
|
|
5
3
|
import {
|
|
6
4
|
Result,
|
|
7
5
|
ResultList,
|
|
6
|
+
ResultsPerPage,
|
|
8
7
|
SearchBox,
|
|
9
|
-
SearchBoxOptions,
|
|
10
8
|
buildResultList,
|
|
9
|
+
buildResultsPerPage,
|
|
11
10
|
buildSearchBox,
|
|
12
11
|
loadAdvancedSearchQueryActions,
|
|
13
|
-
SearchEngine
|
|
12
|
+
SearchEngine,
|
|
13
|
+
SearchAppState,
|
|
14
|
+
loadQueryActions
|
|
14
15
|
} from "@coveo/headless";
|
|
16
|
+
import { buildSearchEngine, getSidebarSearchParams } from "utils/coveo";
|
|
17
|
+
import { RecentSearches } from "utils/recentSearches";
|
|
18
|
+
import { toJson } from "utils/normalizers";
|
|
15
19
|
import { Option, PopoverRequestCloseType } from "typings/custom";
|
|
16
20
|
|
|
21
|
+
export const SESSION_KEY = "dx-sidebar-search-state";
|
|
22
|
+
|
|
23
|
+
const DEFAULT_RESULT_COUNT = 20;
|
|
24
|
+
const SEARCH_DEBOUNCE_DELAY = 780;
|
|
25
|
+
|
|
17
26
|
const UserRecentSearches = new RecentSearches();
|
|
18
27
|
|
|
19
|
-
const
|
|
28
|
+
const normalizeCoveoAdvancedQueryValue = (version: string): string =>
|
|
20
29
|
version.split(".").join("\\.");
|
|
21
30
|
|
|
22
|
-
const normalizeCoveoResult = ({
|
|
23
|
-
title,
|
|
24
|
-
titleHighlights,
|
|
25
|
-
excerpt,
|
|
26
|
-
excerptHighlights,
|
|
27
|
-
uniqueId,
|
|
28
|
-
raw: { sfhtml_url__c }
|
|
29
|
-
}: Result) => ({
|
|
30
|
-
title,
|
|
31
|
-
titleHighlights,
|
|
32
|
-
excerpt,
|
|
33
|
-
excerptHighlights,
|
|
34
|
-
uniqueId,
|
|
35
|
-
href: `/docs/${sfhtml_url__c}`
|
|
36
|
-
});
|
|
37
|
-
|
|
38
31
|
export default class SidebarSearch extends LightningElement {
|
|
39
32
|
@api coveoOrganizationId!: string;
|
|
40
33
|
@api coveoPublicAccessToken!: string;
|
|
41
34
|
@api coveoSearchHub!: string;
|
|
42
|
-
@api
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
@api
|
|
36
|
+
get coveoAdvancedQueryConfig(): { [key: string]: any } {
|
|
37
|
+
return this._coveoAdvancedQueryConfig;
|
|
38
|
+
}
|
|
39
|
+
set coveoAdvancedQueryConfig(value) {
|
|
40
|
+
console.log('set coveoAdvancedQueryConfig', value.sfrelease__c, value.sflanguage)
|
|
41
|
+
this._coveoAdvancedQueryConfig = toJson(value);
|
|
42
|
+
}
|
|
46
43
|
|
|
44
|
+
@api
|
|
45
|
+
public fetchMoreResults() {
|
|
46
|
+
if (this.resultList?.state?.moreResultsAvailable) {
|
|
47
|
+
this.fetchingMoreResults = true;
|
|
48
|
+
this.resultList.fetchMoreResults();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@api
|
|
53
|
+
setInputValue(searchTerm: string) {
|
|
54
|
+
this.value = searchTerm || "";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private _coveoAdvancedQueryConfig!: object;
|
|
47
58
|
private dropdownRequestedOpen: boolean = false;
|
|
48
59
|
private recentSearches: Option[] = [];
|
|
49
60
|
private resultList: ResultList | null = null;
|
|
61
|
+
private resultsPerPage: ResultsPerPage | null = null;
|
|
62
|
+
private preloadedState?: SearchAppState;
|
|
50
63
|
private searchBox: SearchBox | null = null;
|
|
51
|
-
private value?: string =
|
|
64
|
+
private value?: string = "";
|
|
52
65
|
private engine: SearchEngine | null = null;
|
|
66
|
+
private fetchingMoreResults: boolean = false;
|
|
67
|
+
private didRender = false;
|
|
68
|
+
|
|
69
|
+
private get hasCorrectCoveoConfig(): boolean {
|
|
70
|
+
const coveoConfigurations = [
|
|
71
|
+
this.coveoOrganizationId,
|
|
72
|
+
this.coveoPublicAccessToken,
|
|
73
|
+
this.coveoSearchHub,
|
|
74
|
+
this.coveoAdvancedQueryConfig
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return coveoConfigurations.every(
|
|
78
|
+
(val) =>
|
|
79
|
+
!!val && (typeof val === "string" || typeof val === "object")
|
|
80
|
+
);
|
|
81
|
+
}
|
|
53
82
|
|
|
54
83
|
private get isDropdownOpen() {
|
|
55
84
|
return (
|
|
56
85
|
this.dropdownRequestedOpen &&
|
|
57
86
|
this.recentSearches &&
|
|
58
87
|
this.recentSearches.length > 0 &&
|
|
59
|
-
this.
|
|
88
|
+
!this.value
|
|
60
89
|
);
|
|
61
90
|
}
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
92
|
+
constructor() {
|
|
93
|
+
super();
|
|
66
94
|
|
|
67
|
-
|
|
68
|
-
this.
|
|
95
|
+
this.updateRecentSearches();
|
|
96
|
+
this.value = getSidebarSearchParams() || "";
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
const coveoConfigurations = [
|
|
73
|
-
this.coveoOrganizationId,
|
|
74
|
-
this.coveoPublicAccessToken,
|
|
75
|
-
this.coveoSearchHub,
|
|
76
|
-
this.coveoObjectType,
|
|
77
|
-
this.coveoLanguage,
|
|
78
|
-
this.coveoRelease,
|
|
79
|
-
this.coveoDeliverable
|
|
80
|
-
];
|
|
99
|
+
queryConfigValue: string | null = null;
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
-
} else {
|
|
101
|
+
renderedCallback() {
|
|
102
|
+
// in prod some of the critical coveo configuration attributes seem to arrive later
|
|
103
|
+
// so I needed to put this coveo startup flow here where we can wait for them to arrive
|
|
104
|
+
if (this.hasCorrectCoveoConfig && !this.engine) {
|
|
105
|
+
console.log("coveo config is good");
|
|
106
|
+
this.initializeUserSession();
|
|
107
|
+
this.initializeCoveo();
|
|
108
|
+
} else if (!this.hasCorrectCoveoConfig) {
|
|
90
109
|
console.error(
|
|
91
|
-
"Incorrect coveo configuration provided. All coveo configuration attributes must be defined
|
|
110
|
+
"Incorrect coveo search configuration provided. All coveo configuration attributes must be defined.",
|
|
92
111
|
{
|
|
93
112
|
coveoOrganizationId: this.coveoOrganizationId,
|
|
94
113
|
coveoPublicAccessToken: this.coveoPublicAccessToken,
|
|
95
114
|
coveoSearchHub: this.coveoSearchHub,
|
|
96
|
-
|
|
97
|
-
coveoLanguage: this.coveoLanguage,
|
|
98
|
-
coveoRelease: this.coveoRelease,
|
|
99
|
-
coveoDeliverable: this.coveoDeliverable
|
|
115
|
+
coveoAdvancedQueryConfig: this.coveoAdvancedQueryConfig
|
|
100
116
|
}
|
|
101
117
|
);
|
|
102
118
|
}
|
|
119
|
+
|
|
120
|
+
const queryConfigValue = JSON.stringify(this.coveoAdvancedQueryConfig);
|
|
121
|
+
console.log('query config value', queryConfigValue);
|
|
122
|
+
if (this.queryConfigValue !== queryConfigValue) {
|
|
123
|
+
if (this.queryConfigValue && this.engine) {
|
|
124
|
+
console.log('setting query config again')
|
|
125
|
+
this.setAdvancedFilters(this.engine);
|
|
126
|
+
}
|
|
127
|
+
this.queryConfigValue = queryConfigValue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If this is the first render and there is a search value, trigger
|
|
131
|
+
// term highlighting
|
|
132
|
+
if (!this.didRender && this.value) {
|
|
133
|
+
this.dispatchHighlightedTermChange();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
this.didRender = true;
|
|
103
137
|
}
|
|
104
138
|
|
|
105
|
-
private
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
139
|
+
private initializeUserSession() {
|
|
140
|
+
const stringified = window.sessionStorage.getItem(SESSION_KEY);
|
|
141
|
+
if (!stringified) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
109
144
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
try {
|
|
146
|
+
const json = JSON.parse(stringified);
|
|
147
|
+
const hasSameConfig = Object.entries(
|
|
148
|
+
json.coveoAdvancedQueryConfig
|
|
149
|
+
).every(
|
|
150
|
+
([key, value]: [string, any]) =>
|
|
151
|
+
this.coveoAdvancedQueryConfig[key] === value
|
|
152
|
+
);
|
|
153
|
+
const hasSameQuery = json?.state?.query?.q === this.value;
|
|
154
|
+
if (hasSameConfig && hasSameQuery) {
|
|
155
|
+
console.log(
|
|
156
|
+
"valid session storage found for value =",
|
|
157
|
+
this.value
|
|
158
|
+
);
|
|
159
|
+
this.preloadedState = json?.state;
|
|
160
|
+
} else {
|
|
161
|
+
window.sessionStorage.removeItem(SESSION_KEY);
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
console.error(e);
|
|
165
|
+
window.sessionStorage.removeItem(SESSION_KEY);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
115
168
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
169
|
+
private initializeCoveo = () => {
|
|
170
|
+
try {
|
|
171
|
+
this.engine = buildSearchEngine({
|
|
172
|
+
organizationId: this.coveoOrganizationId,
|
|
173
|
+
publicAccessToken: this.coveoPublicAccessToken,
|
|
174
|
+
searchHub: this.coveoSearchHub,
|
|
175
|
+
preloadedState: this.preloadedState
|
|
119
176
|
});
|
|
120
|
-
|
|
177
|
+
} catch (ex) {
|
|
178
|
+
console.error(`Coveo engine failed to initialize (${ex})`);
|
|
121
179
|
}
|
|
122
180
|
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
181
|
+
if (!this.engine) {
|
|
182
|
+
console.error("Coveo engine failed to initialize!");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
127
185
|
|
|
128
|
-
|
|
186
|
+
if (this.preloadedState) {
|
|
187
|
+
const actions = loadQueryActions(this.engine);
|
|
188
|
+
this.engine.dispatch(actions.updateQuery({ q: this.value || "" }));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
this.resultList = buildResultList(this.engine, {
|
|
192
|
+
options: { fieldsToInclude: ["sfhtml_url__c"] }
|
|
193
|
+
});
|
|
129
194
|
|
|
130
|
-
|
|
131
|
-
|
|
195
|
+
this.resultsPerPage = buildResultsPerPage(this.engine, {
|
|
196
|
+
initialState: {
|
|
197
|
+
numberOfResults: DEFAULT_RESULT_COUNT
|
|
132
198
|
}
|
|
199
|
+
});
|
|
133
200
|
|
|
134
|
-
|
|
201
|
+
this.searchBox = buildSearchBox(this.engine, {
|
|
202
|
+
options: { numberOfSuggestions: 3 }
|
|
135
203
|
});
|
|
136
|
-
}
|
|
137
204
|
|
|
138
|
-
|
|
139
|
-
|
|
205
|
+
this.setAdvancedFilters(this.engine);
|
|
206
|
+
|
|
207
|
+
this.resultList?.subscribe(this.onResultListChange);
|
|
208
|
+
|
|
209
|
+
if (this.value) {
|
|
210
|
+
this.dispatchHighlightedTermChange();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!this.preloadedState) {
|
|
214
|
+
if (this.value) {
|
|
215
|
+
this.submitSearch();
|
|
216
|
+
} else {
|
|
217
|
+
this.dispatchOnLoading(false);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
private onResultListChange = () => {
|
|
223
|
+
if (!this.resultList) {
|
|
140
224
|
return;
|
|
141
225
|
}
|
|
142
226
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
227
|
+
const { isLoading, firstSearchExecuted, results } =
|
|
228
|
+
this.resultList.state;
|
|
229
|
+
|
|
230
|
+
if ((!firstSearchExecuted && !isLoading) || !this.value) {
|
|
231
|
+
// coveo search is firing off some unwanted payloads on startup
|
|
232
|
+
console.log(
|
|
233
|
+
"returning in results list change",
|
|
234
|
+
this.value,
|
|
235
|
+
!firstSearchExecuted && !isLoading
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!isLoading) {
|
|
241
|
+
console.log("dispatching on change event");
|
|
242
|
+
this.dispatchOnChangeEvent(results);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!this.fetchingMoreResults) {
|
|
246
|
+
this.dispatchOnLoading(isLoading);
|
|
247
|
+
}
|
|
149
248
|
|
|
150
|
-
|
|
151
|
-
|
|
249
|
+
if (!isLoading && this.fetchingMoreResults) {
|
|
250
|
+
this.fetchingMoreResults = false;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
private setAdvancedFilters(engine: SearchEngine) {
|
|
255
|
+
const aq = Object.entries(this.coveoAdvancedQueryConfig).reduce(
|
|
256
|
+
(result, [key, value]) =>
|
|
257
|
+
`${result}(@${key}=="${normalizeCoveoAdvancedQueryValue(
|
|
258
|
+
value
|
|
259
|
+
)}")`,
|
|
152
260
|
""
|
|
153
261
|
);
|
|
154
|
-
const advancedSearchQueryActions = loadAdvancedSearchQueryActions(
|
|
155
|
-
this.engine
|
|
156
|
-
);
|
|
157
262
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
263
|
+
const registerSearchQueryAction = loadAdvancedSearchQueryActions(
|
|
264
|
+
engine
|
|
265
|
+
).registerAdvancedSearchQueries({
|
|
266
|
+
aq: aq
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
engine.dispatch(registerSearchQueryAction);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private normalizeCoveoResult = ({
|
|
273
|
+
clickUri,
|
|
274
|
+
excerpt,
|
|
275
|
+
excerptHighlights,
|
|
276
|
+
raw: { sfhtml_url__c },
|
|
277
|
+
title,
|
|
278
|
+
titleHighlights,
|
|
279
|
+
uniqueId
|
|
280
|
+
}: Result) => {
|
|
281
|
+
const pathname = sfhtml_url__c
|
|
282
|
+
? `/docs/${sfhtml_url__c}`
|
|
283
|
+
: `${new URL(clickUri).pathname}.html`;
|
|
284
|
+
const href = `${pathname}?q=${this.value}`;
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
title,
|
|
288
|
+
titleHighlights,
|
|
289
|
+
excerpt,
|
|
290
|
+
excerptHighlights,
|
|
291
|
+
uniqueId,
|
|
292
|
+
href,
|
|
293
|
+
selected: pathname === window.location.pathname
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
private updateSessionStorage() {
|
|
298
|
+
console.log("updating session storage");
|
|
299
|
+
window.sessionStorage.setItem(
|
|
300
|
+
SESSION_KEY,
|
|
301
|
+
JSON.stringify({
|
|
302
|
+
coveoAdvancedQueryConfig: this.coveoAdvancedQueryConfig,
|
|
303
|
+
state: this.engine?.state
|
|
161
304
|
})
|
|
162
305
|
);
|
|
163
306
|
}
|
|
164
307
|
|
|
165
308
|
private dispatchOnChangeEvent(results: Result[]) {
|
|
309
|
+
this.updateSessionStorage();
|
|
166
310
|
this.dispatchEvent(
|
|
167
311
|
new CustomEvent("change", {
|
|
168
312
|
detail: {
|
|
169
|
-
results: results.map(normalizeCoveoResult),
|
|
313
|
+
results: results.map(this.normalizeCoveoResult),
|
|
170
314
|
value: this.value
|
|
171
315
|
}
|
|
172
316
|
})
|
|
173
317
|
);
|
|
174
318
|
}
|
|
175
319
|
|
|
176
|
-
private dispatchOnLoading(
|
|
320
|
+
private dispatchOnLoading(loading: boolean) {
|
|
321
|
+
console.log("dispatching onloading", loading);
|
|
322
|
+
this.dispatchEvent(new CustomEvent("loading", { detail: loading }));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private dispatchHighlightedTermChange() {
|
|
177
326
|
this.dispatchEvent(
|
|
178
|
-
new CustomEvent("
|
|
179
|
-
detail:
|
|
327
|
+
new CustomEvent("highlightedtermchange", {
|
|
328
|
+
detail: this.value,
|
|
329
|
+
composed: true,
|
|
330
|
+
bubbles: true
|
|
180
331
|
})
|
|
181
332
|
);
|
|
182
333
|
}
|
|
183
334
|
|
|
184
|
-
private
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
335
|
+
private updateRecentSearches = () =>
|
|
336
|
+
(this.recentSearches = UserRecentSearches.get().map(
|
|
337
|
+
(item: { id: string; label: string }) => ({
|
|
338
|
+
id: item,
|
|
339
|
+
label: item
|
|
340
|
+
})
|
|
341
|
+
));
|
|
192
342
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
343
|
+
private dispatchSidebarSearchChange(searchTerm: string | undefined): void {
|
|
344
|
+
this.dispatchEvent(
|
|
345
|
+
new CustomEvent("sidebarsearchchange", {
|
|
346
|
+
detail: searchTerm ?? "",
|
|
347
|
+
bubbles: true,
|
|
348
|
+
composed: true
|
|
349
|
+
})
|
|
350
|
+
);
|
|
197
351
|
}
|
|
198
352
|
|
|
199
353
|
private submitSearch = debounce(() => {
|
|
200
|
-
this.searchBox
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
354
|
+
if (this.searchBox) {
|
|
355
|
+
UserRecentSearches.add(this.value);
|
|
356
|
+
this.updateRecentSearches();
|
|
357
|
+
|
|
358
|
+
this.searchBox.updateText(this.value || "");
|
|
359
|
+
this.searchBox.submit();
|
|
360
|
+
}
|
|
361
|
+
}, SEARCH_DEBOUNCE_DELAY);
|
|
205
362
|
|
|
206
363
|
private onClickRecentSearch(e: CustomEvent) {
|
|
207
364
|
this.value = e.detail;
|
|
@@ -222,15 +379,20 @@ export default class SidebarSearch extends LightningElement {
|
|
|
222
379
|
private onInputChange(e: CustomEvent) {
|
|
223
380
|
this.value = e.detail;
|
|
224
381
|
|
|
225
|
-
if (this.
|
|
226
|
-
this.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
382
|
+
if (this.value) {
|
|
383
|
+
this.dispatchOnLoading(true);
|
|
384
|
+
if (this.searchBox) {
|
|
385
|
+
this.submitSearch();
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
// coveo's reaction to an empty value triggers a return of results
|
|
389
|
+
// bootlegging our own ux here`
|
|
390
|
+
this.dispatchOnLoading(false);
|
|
230
391
|
this.dispatchOnChangeEvent([]);
|
|
231
392
|
}
|
|
232
393
|
|
|
233
|
-
this.
|
|
394
|
+
this.dispatchHighlightedTermChange();
|
|
395
|
+
this.dispatchSidebarSearchChange(this.value);
|
|
234
396
|
}
|
|
235
397
|
|
|
236
398
|
private onInputFocus() {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import SidebarSearchResult from "dx/sidebarSearchResult";
|
|
2
|
+
import { createRenderComponent } from "utils/tests";
|
|
3
|
+
|
|
4
|
+
const TAG = "dx-sidebar-search-result";
|
|
5
|
+
const render = createRenderComponent(TAG, SidebarSearchResult);
|
|
6
|
+
|
|
7
|
+
const PROPS = {
|
|
8
|
+
description: "test description here",
|
|
9
|
+
descriptionHighlights: [{ length: 5, offset: 5 }],
|
|
10
|
+
href: "/test",
|
|
11
|
+
selected: false,
|
|
12
|
+
title: "test title here",
|
|
13
|
+
titleHighlights: [{ length: 5, offset: 5 }]
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe(TAG, () => {
|
|
17
|
+
it("renders a result with the correct highlighted content", () => {
|
|
18
|
+
const element = render(PROPS);
|
|
19
|
+
|
|
20
|
+
const titleBolds = element.shadowRoot.querySelectorAll(".title .bold");
|
|
21
|
+
expect(titleBolds.length).toEqual(1);
|
|
22
|
+
expect(titleBolds[0].textContent).toEqual("title");
|
|
23
|
+
|
|
24
|
+
const descriptionBolds =
|
|
25
|
+
element.shadowRoot.querySelectorAll(".description .bold");
|
|
26
|
+
expect(descriptionBolds.length).toEqual(1);
|
|
27
|
+
expect(descriptionBolds[0].textContent).toEqual("descr");
|
|
28
|
+
|
|
29
|
+
return expect(element).toBeAccessible();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("renders a result as selected when specified", () => {
|
|
33
|
+
const element = render({ ...PROPS, selected: true });
|
|
34
|
+
|
|
35
|
+
const selected = element.shadowRoot.querySelector(
|
|
36
|
+
".sidebar-item-selected"
|
|
37
|
+
);
|
|
38
|
+
expect(selected).not.toBeNull();
|
|
39
|
+
|
|
40
|
+
return expect(element).toBeAccessible();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("doesn't add bold class when there is no highlighted term", () => {
|
|
44
|
+
const element = render({
|
|
45
|
+
...PROPS,
|
|
46
|
+
descriptionHighlights: [],
|
|
47
|
+
titleHighlights: []
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const selected = element.shadowRoot.querySelector(".bold");
|
|
51
|
+
expect(selected).toBeNull();
|
|
52
|
+
|
|
53
|
+
return expect(element).toBeAccessible();
|
|
54
|
+
});
|
|
55
|
+
});
|