lucos_search_component 4.0.2 → 4.0.3
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/.github/workflows/code-reviewer-auto-merge.yml +1 -1
- package/.github/workflows/codeql-analysis.yml +1 -1
- package/.github/workflows/convention-check.yml +1 -1
- package/.github/workflows/dependabot-auto-merge.yml +1 -1
- package/README.md +1 -1
- package/dist/index.js +48 -18
- package/example/index.html +20 -0
- package/package.json +1 -1
- package/web-components/lucos-search.js +48 -18
|
@@ -13,7 +13,7 @@ permissions:
|
|
|
13
13
|
|
|
14
14
|
jobs:
|
|
15
15
|
reusable:
|
|
16
|
-
uses: lucas42/.github/.github/workflows/reusable-code-reviewer-auto-merge.yml@v1.
|
|
16
|
+
uses: lucas42/.github/.github/workflows/reusable-code-reviewer-auto-merge.yml@v1.21.0
|
|
17
17
|
secrets:
|
|
18
18
|
LUCOS_CI_APP_ID: ${{ secrets.LUCOS_CI_APP_ID }}
|
|
19
19
|
LUCOS_CI_PRIVATE_KEY: ${{ secrets.LUCOS_CI_PRIVATE_KEY }}
|
|
@@ -9,7 +9,7 @@ permissions:
|
|
|
9
9
|
|
|
10
10
|
jobs:
|
|
11
11
|
dependabot:
|
|
12
|
-
uses: lucas42/.github/.github/workflows/reusable-dependabot-auto-merge.yml@v1.
|
|
12
|
+
uses: lucas42/.github/.github/workflows/reusable-dependabot-auto-merge.yml@v1.21.0
|
|
13
13
|
secrets:
|
|
14
14
|
LUCOS_CI_APP_ID: ${{ secrets.LUCOS_CI_APP_ID }}
|
|
15
15
|
LUCOS_CI_PRIVATE_KEY: ${{ secrets.LUCOS_CI_PRIVATE_KEY }}
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ The following attributes can be added to the lucos-search span:
|
|
|
32
32
|
* **data-api-key** \[required\] — a valid API key for the production instance of [lucos_arachne](https://github.com/lucas42/lucos_arachne), as generated by [lucos_creds](https://github.com/lucas42/lucos_creds).
|
|
33
33
|
* **data-types** — A comma separated list of item types to search for (defaults to all types).
|
|
34
34
|
* **data-exclude_types** — A comma separated list of item types to exclude from the search (ignored if `data-types` is set).
|
|
35
|
-
* **data-is-contact** — Filter results to contacts only (`"true"`) or non-contacts only (`"false"`). Omitting the attribute applies no filter. Composes with `data-types` when both are set.
|
|
35
|
+
* **data-is-contact** — Filter results to contacts only (`"true"`) or non-contacts only (`"false"`). Omitting the attribute applies no filter. Composes with `data-types` when both are set. When `"true"`, selected options use the entity's **`lucos_contacts` URI** (`contact_uri`) as their value, and the selected-item lozenge links through to that same `lucos_contacts` URI instead of the eolas knowledge page — form value and lozenge link stay consistent with each other.
|
|
36
36
|
* **data-label-override-zxx** — Override the displayed label for the `https://eolas.l42.eu/metadata/language/zxx/` entry in both the dropdown and selected lozenge. Has no effect on whether `zxx` appears in the list — that depends on whether `zxx` is present in the search index. Only meaningful when `data-types="Language"`.
|
|
37
37
|
* **data-common** — A comma separated list of item URIs to pin in a "Common" group at the top of the list, above the normal results. Only meaningful when `data-types="Language"`.
|
|
38
38
|
* **data-preload** — If present, all options are loaded upfront rather than fetched on search. Suitable for small, finite datasets such as languages.
|
package/dist/index.js
CHANGED
|
@@ -5890,10 +5890,19 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5890
5890
|
...(component.isLanguageMode || component.getAttribute("data-common") ? { optgroupField: 'lang_family', lockOptgroupOrder: true } : {}),
|
|
5891
5891
|
...(component.hasAttribute("data-create") ? {
|
|
5892
5892
|
create: function(input) {
|
|
5893
|
-
|
|
5893
|
+
// Key must match valueField below (contact_uri in contact mode) — a
|
|
5894
|
+
// hardcoded `id` key here would leave the created option unaddressable
|
|
5895
|
+
// by TomSelect when data-is-contact="true".
|
|
5896
|
+
const created = { pref_label: input, created: true };
|
|
5897
|
+
created[component.valueFieldName] = input;
|
|
5898
|
+
return created;
|
|
5894
5899
|
},
|
|
5895
5900
|
} : {}),
|
|
5896
|
-
|
|
5901
|
+
// In contact mode, the form value, TomSelect's option key, and the lozenge
|
|
5902
|
+
// click-through/link target (render.item below, onItemSelect further down) all
|
|
5903
|
+
// use the lucos_contacts URI instead of the eolas knowledge URI — see
|
|
5904
|
+
// lucos_arachne#712 and the #190 review discussion.
|
|
5905
|
+
valueField: component.valueFieldName,
|
|
5897
5906
|
labelField: 'pref_label',
|
|
5898
5907
|
searchField: [],
|
|
5899
5908
|
closeAfterSelect: true,
|
|
@@ -5907,7 +5916,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5907
5916
|
component._searchAbortController = abortController;
|
|
5908
5917
|
|
|
5909
5918
|
errorMessage.setAttribute('hidden', '');
|
|
5910
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
5919
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
5911
5920
|
// When preloaded, filter locally instead of hitting Typesense
|
|
5912
5921
|
if (component._preloadedOptions) {
|
|
5913
5922
|
const q = query.toLowerCase();
|
|
@@ -5917,7 +5926,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5917
5926
|
(r.labels && r.labels.some(l => l.toLowerCase().includes(q)))
|
|
5918
5927
|
)
|
|
5919
5928
|
: [...component._preloadedOptions];
|
|
5920
|
-
results = results.filter(r => !commonSet.has(r.
|
|
5929
|
+
results = results.filter(r => !commonSet.has(r[component.valueFieldName]));
|
|
5921
5930
|
this.clearOptions();
|
|
5922
5931
|
if (component._commonOptions) {
|
|
5923
5932
|
const filteredCommon = q
|
|
@@ -5947,7 +5956,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5947
5956
|
this.clearOptions();
|
|
5948
5957
|
// Remove common items from results to avoid duplication; filter by query when non-empty
|
|
5949
5958
|
if (component._commonOptions) {
|
|
5950
|
-
results = results.filter(r => !commonSet.has(r.
|
|
5959
|
+
results = results.filter(r => !commonSet.has(r[component.valueFieldName]));
|
|
5951
5960
|
const q = query.toLowerCase();
|
|
5952
5961
|
const filteredCommon = q
|
|
5953
5962
|
? component._commonOptions.filter(o =>
|
|
@@ -5977,14 +5986,14 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5977
5986
|
},
|
|
5978
5987
|
onFocus: function() {
|
|
5979
5988
|
this.clearOptions();
|
|
5980
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
5989
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
5981
5990
|
if (component._commonOptions) {
|
|
5982
5991
|
component._commonOptions.forEach(opt => this.addOption(opt));
|
|
5983
5992
|
}
|
|
5984
5993
|
// Re-add preloaded options (excluding common items which are shown separately)
|
|
5985
5994
|
if (component._preloadedOptions) {
|
|
5986
5995
|
component._preloadedOptions
|
|
5987
|
-
.filter(opt => !commonSet.has(opt.
|
|
5996
|
+
.filter(opt => !commonSet.has(opt[component.valueFieldName]))
|
|
5988
5997
|
.forEach(opt => this.addOption(opt));
|
|
5989
5998
|
}
|
|
5990
5999
|
},
|
|
@@ -5997,7 +6006,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
5997
6006
|
this.addOptionGroup('x-common', { label: 'Common' });
|
|
5998
6007
|
const commonParams = new URLSearchParams({
|
|
5999
6008
|
q: '*',
|
|
6000
|
-
filter_by:
|
|
6009
|
+
filter_by: `${component.valueFieldName}:[${commonIds.join(",")}]`,
|
|
6001
6010
|
per_page: commonIds.length,
|
|
6002
6011
|
});
|
|
6003
6012
|
const commonResults = await component.searchRequest(commonParams);
|
|
@@ -6024,35 +6033,42 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
6024
6033
|
if (filterValue) preloadParams.set("filter_by", filterValue);
|
|
6025
6034
|
const preloaded = await component.searchRequest(preloadParams);
|
|
6026
6035
|
component._preloadedOptions = preloaded;
|
|
6027
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
6028
|
-
preloaded.filter(r => !commonSet.has(r.
|
|
6036
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
6037
|
+
preloaded.filter(r => !commonSet.has(r[component.valueFieldName])).forEach(r => this.addOption(r));
|
|
6029
6038
|
}
|
|
6030
6039
|
if (ids.length < 1) return;
|
|
6031
|
-
// Fetch real options from Typesense, excluding common/preloaded items
|
|
6032
|
-
|
|
6040
|
+
// Fetch real options from Typesense, excluding common/preloaded items.
|
|
6041
|
+
// `ids` are keyed by whatever valueField is configured (contact_uri in
|
|
6042
|
+
// contact mode, id otherwise), so every set/filter/updateOption below must
|
|
6043
|
+
// use the same key — a stray `.id` here is a silent no-op, not an error
|
|
6044
|
+
// (see lucos_search_component#190 review: updateOption(result.id, ...)
|
|
6045
|
+
// looks up tom-select's options map by valueField, not literally `id`).
|
|
6046
|
+
const preloadedIds = component._preloadedOptions ? new Set(component._preloadedOptions.map(r => r[component.valueFieldName])) : new Set();
|
|
6033
6047
|
const excludeIds = new Set([...commonIds, ...preloadedIds]);
|
|
6034
6048
|
const idsToFetch = ids.filter(id => !excludeIds.has(id));
|
|
6035
6049
|
if (idsToFetch.length > 0) {
|
|
6036
6050
|
const searchParams = new URLSearchParams({
|
|
6037
6051
|
q: '*',
|
|
6038
|
-
filter_by:
|
|
6052
|
+
filter_by: `${component.valueFieldName}:[${idsToFetch.join(",")}]`,
|
|
6039
6053
|
per_page: idsToFetch.length,
|
|
6040
6054
|
});
|
|
6041
6055
|
const results = await component.searchRequest(searchParams);
|
|
6042
6056
|
results.forEach(result => {
|
|
6043
|
-
this.updateOption(result.
|
|
6057
|
+
this.updateOption(result[component.valueFieldName], result);
|
|
6044
6058
|
});
|
|
6045
6059
|
}
|
|
6046
6060
|
// Update any pre-selected common items with fresh data
|
|
6047
6061
|
if (component._commonOptions) {
|
|
6048
6062
|
component._commonOptions.forEach(opt => {
|
|
6049
|
-
|
|
6063
|
+
const key = opt[component.valueFieldName];
|
|
6064
|
+
if (ids.includes(key)) this.updateOption(key, opt);
|
|
6050
6065
|
});
|
|
6051
6066
|
}
|
|
6052
6067
|
// Update any pre-selected preloaded items with fresh data
|
|
6053
6068
|
if (component._preloadedOptions) {
|
|
6054
6069
|
component._preloadedOptions.forEach(opt => {
|
|
6055
|
-
|
|
6070
|
+
const key = opt[component.valueFieldName];
|
|
6071
|
+
if (ids.includes(key)) this.updateOption(key, opt);
|
|
6056
6072
|
});
|
|
6057
6073
|
}
|
|
6058
6074
|
},
|
|
@@ -6097,7 +6113,10 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
6097
6113
|
if (data.created) {
|
|
6098
6114
|
return `<div class="lozenge lozenge-pending" data-type="" data-category="">${escape(displayLabel)}</div>`;
|
|
6099
6115
|
}
|
|
6100
|
-
|
|
6116
|
+
// Link target matches the form value (valueFieldName) — in contact mode
|
|
6117
|
+
// that's contact_uri, not the eolas id, per lucas42's review on #190:
|
|
6118
|
+
// everything should link to the same URI for consistency.
|
|
6119
|
+
return `<div class="lozenge" data-type="${escape(data.type)}" data-category="${escape(data.category)}"><a href="${data[component.valueFieldName]}" target="_blank">${escape(displayLabel)}</a></div>`;
|
|
6101
6120
|
},
|
|
6102
6121
|
option_create: function(data, escape) {
|
|
6103
6122
|
const noun = getCreateNoun();
|
|
@@ -6200,6 +6219,17 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
6200
6219
|
if (!types) return false;
|
|
6201
6220
|
return types.split(",").map(t => t.trim()).includes("Language");
|
|
6202
6221
|
}
|
|
6222
|
+
// The document field TomSelect uses as its option key/form value: contact_uri when
|
|
6223
|
+
// data-is-contact="true", id otherwise. Every place that reads/writes TomSelect's
|
|
6224
|
+
// internal options map (keyed by valueField) or filters Typesense by that key must
|
|
6225
|
+
// go through this — hardcoding `.id` breaks in contact mode (lucos_search_component#190
|
|
6226
|
+
// review: `updateOption(result.id, ...)` silently no-op'd because tom-select hashes
|
|
6227
|
+
// on valueField, not literally `id`). Checks the attribute directly rather than via a
|
|
6228
|
+
// separate isContactMode getter — data-is-contact is also read directly elsewhere
|
|
6229
|
+
// (e.g. buildFilterBy's callers), so a getter here would look more reusable than it is.
|
|
6230
|
+
get valueFieldName() {
|
|
6231
|
+
return this.getAttribute("data-is-contact") === "true" ? 'contact_uri' : 'id';
|
|
6232
|
+
}
|
|
6203
6233
|
get commonIds() {
|
|
6204
6234
|
const common = this.getAttribute("data-common");
|
|
6205
6235
|
if (!common) return [];
|
|
@@ -6245,7 +6275,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
6245
6275
|
searchParams.set('query_by_weights', "10,8,3,1");
|
|
6246
6276
|
searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
|
|
6247
6277
|
searchParams.set('prioritize_num_matching_fields', false);
|
|
6248
|
-
searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family");
|
|
6278
|
+
searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family,contact_uri");
|
|
6249
6279
|
searchParams.set('enable_highlight_v1', false);
|
|
6250
6280
|
searchParams.set('highlight_start_tag', '<span class="highlight">');
|
|
6251
6281
|
searchParams.set('highlight_end_tag', '</span>');
|
package/example/index.html
CHANGED
|
@@ -69,6 +69,16 @@
|
|
|
69
69
|
<button type="submit">Submit</button>
|
|
70
70
|
</form>
|
|
71
71
|
<pre id="create-output"></pre>
|
|
72
|
+
<h1>Contact mode (data-is-contact)</h1>
|
|
73
|
+
<p>Person field restricted to <code>lucos_contacts</code> records (<code>data-is-contact="true"</code>). The selected-item lozenge links through to the <code>lucos_contacts</code> URI (<code>contact_uri</code>), and the value serialised on submit is that same URI — not the eolas URI — see lucos_search_component#189.</p>
|
|
74
|
+
<form id="contact-form" onsubmit="handleContactSubmit(event)">
|
|
75
|
+
<label for="linked-contact">Linked contact:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-is-contact="true"><select id="linked-contact" name="linked_contact" multiple></select></span>
|
|
76
|
+
<button type="submit">Submit</button>
|
|
77
|
+
</form>
|
|
78
|
+
<pre id="contact-output"></pre>
|
|
79
|
+
<label for="linked-contact-preselected">Linked contact (pre-selected on page load):</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-is-contact="true"><select id="linked-contact-preselected" multiple>
|
|
80
|
+
<option selected>https://contacts.l42.eu/people/2</option>
|
|
81
|
+
</select></span>
|
|
72
82
|
<script>
|
|
73
83
|
function handleSubmit(event) {
|
|
74
84
|
event.preventDefault();
|
|
@@ -90,6 +100,16 @@
|
|
|
90
100
|
}
|
|
91
101
|
document.getElementById('create-output').textContent = JSON.stringify(output, null, 2);
|
|
92
102
|
}
|
|
103
|
+
function handleContactSubmit(event) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
const data = new FormData(event.target);
|
|
106
|
+
const output = {};
|
|
107
|
+
for (const [key, value] of data.entries()) {
|
|
108
|
+
if (!output[key]) output[key] = [];
|
|
109
|
+
output[key].push(value);
|
|
110
|
+
}
|
|
111
|
+
document.getElementById('contact-output').textContent = JSON.stringify(output, null, 2);
|
|
112
|
+
}
|
|
93
113
|
</script>
|
|
94
114
|
<script src="./built.js"></script>
|
|
95
115
|
</body>
|
package/package.json
CHANGED
|
@@ -144,10 +144,19 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
144
144
|
...(component.isLanguageMode || component.getAttribute("data-common") ? { optgroupField: 'lang_family', lockOptgroupOrder: true } : {}),
|
|
145
145
|
...(component.hasAttribute("data-create") ? {
|
|
146
146
|
create: function(input) {
|
|
147
|
-
|
|
147
|
+
// Key must match valueField below (contact_uri in contact mode) — a
|
|
148
|
+
// hardcoded `id` key here would leave the created option unaddressable
|
|
149
|
+
// by TomSelect when data-is-contact="true".
|
|
150
|
+
const created = { pref_label: input, created: true };
|
|
151
|
+
created[component.valueFieldName] = input;
|
|
152
|
+
return created;
|
|
148
153
|
},
|
|
149
154
|
} : {}),
|
|
150
|
-
|
|
155
|
+
// In contact mode, the form value, TomSelect's option key, and the lozenge
|
|
156
|
+
// click-through/link target (render.item below, onItemSelect further down) all
|
|
157
|
+
// use the lucos_contacts URI instead of the eolas knowledge URI — see
|
|
158
|
+
// lucos_arachne#712 and the #190 review discussion.
|
|
159
|
+
valueField: component.valueFieldName,
|
|
151
160
|
labelField: 'pref_label',
|
|
152
161
|
searchField: [],
|
|
153
162
|
closeAfterSelect: true,
|
|
@@ -161,7 +170,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
161
170
|
component._searchAbortController = abortController;
|
|
162
171
|
|
|
163
172
|
errorMessage.setAttribute('hidden', '');
|
|
164
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
173
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
165
174
|
// When preloaded, filter locally instead of hitting Typesense
|
|
166
175
|
if (component._preloadedOptions) {
|
|
167
176
|
const q = query.toLowerCase();
|
|
@@ -171,7 +180,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
171
180
|
(r.labels && r.labels.some(l => l.toLowerCase().includes(q)))
|
|
172
181
|
)
|
|
173
182
|
: [...component._preloadedOptions];
|
|
174
|
-
results = results.filter(r => !commonSet.has(r.
|
|
183
|
+
results = results.filter(r => !commonSet.has(r[component.valueFieldName]));
|
|
175
184
|
this.clearOptions();
|
|
176
185
|
if (component._commonOptions) {
|
|
177
186
|
const filteredCommon = q
|
|
@@ -201,7 +210,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
201
210
|
this.clearOptions();
|
|
202
211
|
// Remove common items from results to avoid duplication; filter by query when non-empty
|
|
203
212
|
if (component._commonOptions) {
|
|
204
|
-
results = results.filter(r => !commonSet.has(r.
|
|
213
|
+
results = results.filter(r => !commonSet.has(r[component.valueFieldName]));
|
|
205
214
|
const q = query.toLowerCase();
|
|
206
215
|
const filteredCommon = q
|
|
207
216
|
? component._commonOptions.filter(o =>
|
|
@@ -231,14 +240,14 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
231
240
|
},
|
|
232
241
|
onFocus: function() {
|
|
233
242
|
this.clearOptions();
|
|
234
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
243
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
235
244
|
if (component._commonOptions) {
|
|
236
245
|
component._commonOptions.forEach(opt => this.addOption(opt));
|
|
237
246
|
}
|
|
238
247
|
// Re-add preloaded options (excluding common items which are shown separately)
|
|
239
248
|
if (component._preloadedOptions) {
|
|
240
249
|
component._preloadedOptions
|
|
241
|
-
.filter(opt => !commonSet.has(opt.
|
|
250
|
+
.filter(opt => !commonSet.has(opt[component.valueFieldName]))
|
|
242
251
|
.forEach(opt => this.addOption(opt));
|
|
243
252
|
}
|
|
244
253
|
},
|
|
@@ -251,7 +260,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
251
260
|
this.addOptionGroup('x-common', { label: 'Common' });
|
|
252
261
|
const commonParams = new URLSearchParams({
|
|
253
262
|
q: '*',
|
|
254
|
-
filter_by:
|
|
263
|
+
filter_by: `${component.valueFieldName}:[${commonIds.join(",")}]`,
|
|
255
264
|
per_page: commonIds.length,
|
|
256
265
|
});
|
|
257
266
|
const commonResults = await component.searchRequest(commonParams);
|
|
@@ -278,35 +287,42 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
278
287
|
if (filterValue) preloadParams.set("filter_by", filterValue);
|
|
279
288
|
const preloaded = await component.searchRequest(preloadParams);
|
|
280
289
|
component._preloadedOptions = preloaded;
|
|
281
|
-
const commonSet = new Set((component._commonOptions || []).map(o => o.
|
|
282
|
-
preloaded.filter(r => !commonSet.has(r.
|
|
290
|
+
const commonSet = new Set((component._commonOptions || []).map(o => o[component.valueFieldName]));
|
|
291
|
+
preloaded.filter(r => !commonSet.has(r[component.valueFieldName])).forEach(r => this.addOption(r));
|
|
283
292
|
}
|
|
284
293
|
if (ids.length < 1) return;
|
|
285
|
-
// Fetch real options from Typesense, excluding common/preloaded items
|
|
286
|
-
|
|
294
|
+
// Fetch real options from Typesense, excluding common/preloaded items.
|
|
295
|
+
// `ids` are keyed by whatever valueField is configured (contact_uri in
|
|
296
|
+
// contact mode, id otherwise), so every set/filter/updateOption below must
|
|
297
|
+
// use the same key — a stray `.id` here is a silent no-op, not an error
|
|
298
|
+
// (see lucos_search_component#190 review: updateOption(result.id, ...)
|
|
299
|
+
// looks up tom-select's options map by valueField, not literally `id`).
|
|
300
|
+
const preloadedIds = component._preloadedOptions ? new Set(component._preloadedOptions.map(r => r[component.valueFieldName])) : new Set();
|
|
287
301
|
const excludeIds = new Set([...commonIds, ...preloadedIds]);
|
|
288
302
|
const idsToFetch = ids.filter(id => !excludeIds.has(id));
|
|
289
303
|
if (idsToFetch.length > 0) {
|
|
290
304
|
const searchParams = new URLSearchParams({
|
|
291
305
|
q: '*',
|
|
292
|
-
filter_by:
|
|
306
|
+
filter_by: `${component.valueFieldName}:[${idsToFetch.join(",")}]`,
|
|
293
307
|
per_page: idsToFetch.length,
|
|
294
308
|
});
|
|
295
309
|
const results = await component.searchRequest(searchParams);
|
|
296
310
|
results.forEach(result => {
|
|
297
|
-
this.updateOption(result.
|
|
311
|
+
this.updateOption(result[component.valueFieldName], result);
|
|
298
312
|
});
|
|
299
313
|
}
|
|
300
314
|
// Update any pre-selected common items with fresh data
|
|
301
315
|
if (component._commonOptions) {
|
|
302
316
|
component._commonOptions.forEach(opt => {
|
|
303
|
-
|
|
317
|
+
const key = opt[component.valueFieldName];
|
|
318
|
+
if (ids.includes(key)) this.updateOption(key, opt);
|
|
304
319
|
});
|
|
305
320
|
}
|
|
306
321
|
// Update any pre-selected preloaded items with fresh data
|
|
307
322
|
if (component._preloadedOptions) {
|
|
308
323
|
component._preloadedOptions.forEach(opt => {
|
|
309
|
-
|
|
324
|
+
const key = opt[component.valueFieldName];
|
|
325
|
+
if (ids.includes(key)) this.updateOption(key, opt);
|
|
310
326
|
});
|
|
311
327
|
}
|
|
312
328
|
},
|
|
@@ -351,7 +367,10 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
351
367
|
if (data.created) {
|
|
352
368
|
return `<div class="lozenge lozenge-pending" data-type="" data-category="">${escape(displayLabel)}</div>`;
|
|
353
369
|
}
|
|
354
|
-
|
|
370
|
+
// Link target matches the form value (valueFieldName) — in contact mode
|
|
371
|
+
// that's contact_uri, not the eolas id, per lucas42's review on #190:
|
|
372
|
+
// everything should link to the same URI for consistency.
|
|
373
|
+
return `<div class="lozenge" data-type="${escape(data.type)}" data-category="${escape(data.category)}"><a href="${data[component.valueFieldName]}" target="_blank">${escape(displayLabel)}</a></div>`;
|
|
355
374
|
},
|
|
356
375
|
option_create: function(data, escape) {
|
|
357
376
|
const noun = getCreateNoun();
|
|
@@ -454,6 +473,17 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
454
473
|
if (!types) return false;
|
|
455
474
|
return types.split(",").map(t => t.trim()).includes("Language");
|
|
456
475
|
}
|
|
476
|
+
// The document field TomSelect uses as its option key/form value: contact_uri when
|
|
477
|
+
// data-is-contact="true", id otherwise. Every place that reads/writes TomSelect's
|
|
478
|
+
// internal options map (keyed by valueField) or filters Typesense by that key must
|
|
479
|
+
// go through this — hardcoding `.id` breaks in contact mode (lucos_search_component#190
|
|
480
|
+
// review: `updateOption(result.id, ...)` silently no-op'd because tom-select hashes
|
|
481
|
+
// on valueField, not literally `id`). Checks the attribute directly rather than via a
|
|
482
|
+
// separate isContactMode getter — data-is-contact is also read directly elsewhere
|
|
483
|
+
// (e.g. buildFilterBy's callers), so a getter here would look more reusable than it is.
|
|
484
|
+
get valueFieldName() {
|
|
485
|
+
return this.getAttribute("data-is-contact") === "true" ? 'contact_uri' : 'id';
|
|
486
|
+
}
|
|
457
487
|
get commonIds() {
|
|
458
488
|
const common = this.getAttribute("data-common");
|
|
459
489
|
if (!common) return [];
|
|
@@ -499,7 +529,7 @@ class LucosSearchComponent extends HTMLSpanElement {
|
|
|
499
529
|
searchParams.set('query_by_weights', "10,8,3,1");
|
|
500
530
|
searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
|
|
501
531
|
searchParams.set('prioritize_num_matching_fields', false);
|
|
502
|
-
searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family");
|
|
532
|
+
searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family,contact_uri");
|
|
503
533
|
searchParams.set('enable_highlight_v1', false);
|
|
504
534
|
searchParams.set('highlight_start_tag', '<span class="highlight">')
|
|
505
535
|
searchParams.set('highlight_end_tag', '</span>');
|