lucos_search_component 1.0.50 → 1.0.51

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/dist/index.js CHANGED
@@ -5646,7 +5646,7 @@ var tomSelectStylesheet = "/**\n * tom-select.css (v2.5.2)\n * Copyright (c) con
5646
5646
 
5647
5647
  class LucosSearchComponent extends HTMLSpanElement {
5648
5648
  static get observedAttributes() {
5649
- return ['data-api-key','data-types','data-exclude-types'];
5649
+ return ['data-api-key','data-types','data-exclude-types','data-no-lang'];
5650
5650
  }
5651
5651
  constructor() {
5652
5652
  super();
@@ -5794,6 +5794,7 @@ class LucosSearchComponent extends HTMLSpanElement {
5794
5794
  if (!selector) throw new Error("Can't find select element in lucos-search");
5795
5795
  selector.setAttribute("multiple", "multiple");
5796
5796
  new TomSelect(selector, {
5797
+ ...(component.isLanguageMode ? { optgroupField: 'lang_family', lockOptgroupOrder: true } : {}),
5797
5798
  valueField: 'id',
5798
5799
  labelField: 'pref_label',
5799
5800
  searchField: [],
@@ -5812,6 +5813,11 @@ class LucosSearchComponent extends HTMLSpanElement {
5812
5813
  try {
5813
5814
  const results = await component.searchRequest(queryParams);
5814
5815
  this.clearOptions();
5816
+ if (component.isLanguageMode) {
5817
+ results.forEach(r => { if (!r.lang_family) r.lang_family = 'qli'; });
5818
+ }
5819
+ const noLang = component.noLangOption;
5820
+ if (noLang) results.unshift(noLang);
5815
5821
  callback(results);
5816
5822
  } catch(err) {
5817
5823
  callback([]);
@@ -5831,20 +5837,42 @@ class LucosSearchComponent extends HTMLSpanElement {
5831
5837
  },
5832
5838
  onFocus: function() {
5833
5839
  this.clearOptions();
5840
+ const noLang = component.noLangOption;
5841
+ if (noLang) this.addOption(noLang);
5834
5842
  },
5835
5843
  // On startup, update any existing options with latest data from search
5836
5844
  onInitialize: async function() {
5837
5845
  const ids = Object.keys(this.options);
5846
+ const noLang = component.noLangOption;
5847
+ // Always make the no-lang option available for new selections
5848
+ if (noLang) this.addOption(noLang);
5849
+ // In language mode, fetch families and register option groups
5850
+ if (component.isLanguageMode) {
5851
+ const families = await component.getLanguageFamilies();
5852
+ this.addOptionGroup('qli', { label: 'Language Isolate' });
5853
+ families.forEach(family => {
5854
+ this.addOptionGroup(family.code, { label: family.label });
5855
+ });
5856
+ }
5838
5857
  if (ids.length < 1) return;
5839
- const searchParams = new URLSearchParams({
5840
- q: '*',
5841
- filter_by: `id:[${ids.join(",")}]`,
5842
- per_page: ids.length,
5843
- });
5844
- const results = await component.searchRequest(searchParams);
5845
- results.forEach(result => {
5846
- this.updateOption(result.id, result);
5847
- });
5858
+ // Fetch real options from Typesense, excluding the synthetic no-lang option
5859
+ const idsToFetch = noLang ? ids.filter(id => id !== noLang.id) : ids;
5860
+ if (idsToFetch.length > 0) {
5861
+ const searchParams = new URLSearchParams({
5862
+ q: '*',
5863
+ filter_by: `id:[${idsToFetch.join(",")}]`,
5864
+ per_page: idsToFetch.length,
5865
+ });
5866
+ const results = await component.searchRequest(searchParams);
5867
+ results.forEach(result => {
5868
+ if (component.isLanguageMode && !result.lang_family) result.lang_family = 'qli';
5869
+ this.updateOption(result.id, result);
5870
+ });
5871
+ }
5872
+ // Update any pre-selected no-lang option with the synthetic data
5873
+ if (noLang && ids.includes(noLang.id)) {
5874
+ this.updateOption(noLang.id, noLang);
5875
+ }
5848
5876
  },
5849
5877
  onItemSelect: function (item) {
5850
5878
  // Tom-select prevents clicking on link in an item to work as normal, so force it here
@@ -5874,6 +5902,56 @@ class LucosSearchComponent extends HTMLSpanElement {
5874
5902
  shadow.append(selector.nextElementSibling);
5875
5903
  }
5876
5904
  }
5905
+ get noLangOption() {
5906
+ const label = this.getAttribute("data-no-lang");
5907
+ if (!label) return null;
5908
+ return {
5909
+ id: 'https://eolas.l42.eu/metadata/language/zxx/',
5910
+ pref_label: label,
5911
+ type: 'Language',
5912
+ category: 'Anthropological',
5913
+ labels: [],
5914
+ highlight: {},
5915
+ };
5916
+ }
5917
+ get isLanguageMode() {
5918
+ const types = this.getAttribute("data-types");
5919
+ if (!types) return false;
5920
+ return types.split(",").map(t => t.trim()).includes("Language");
5921
+ }
5922
+ async getLanguageFamilies() {
5923
+ if (this._langFamilies) return this._langFamilies;
5924
+ const key = this.getAttribute("data-api-key");
5925
+ if (!key) { this._langFamilies = []; return []; }
5926
+ const searchParams = new URLSearchParams({
5927
+ q: '*',
5928
+ filter_by: 'type:=Language Family',
5929
+ query_by: 'pref_label',
5930
+ include_fields: 'id,pref_label',
5931
+ sort_by: 'pref_label:asc',
5932
+ enable_highlight_v1: false,
5933
+ per_page: 250,
5934
+ });
5935
+ try {
5936
+ const response = await fetch("https://arachne.l42.eu/search?" + searchParams.toString(), {
5937
+ headers: { 'X-TYPESENSE-API-KEY': key },
5938
+ signal: AbortSignal.timeout(8000),
5939
+ });
5940
+ if (!response.ok) { this._langFamilies = []; return []; }
5941
+ const data = await response.json();
5942
+ this._langFamilies = data.hits.map(hit => ({
5943
+ // The lang_family field on Language documents in Typesense stores the last
5944
+ // path segment of the Language Family URI (e.g. "gmw" for West Germanic at
5945
+ // http://id.loc.gov/vocabulary/iso639-5/gmw). filter(Boolean) strips any
5946
+ // trailing slash. This is consistent with lucos-lang's family code extraction.
5947
+ code: hit.document.id.split("/").filter(Boolean).pop(),
5948
+ label: hit.document.pref_label,
5949
+ }));
5950
+ } catch (_) {
5951
+ this._langFamilies = [];
5952
+ }
5953
+ return this._langFamilies;
5954
+ }
5877
5955
  async searchRequest(searchParams) {
5878
5956
  const key = this.getAttribute("data-api-key");
5879
5957
  if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
@@ -5881,7 +5959,7 @@ class LucosSearchComponent extends HTMLSpanElement {
5881
5959
  searchParams.set('query_by_weights', "10,8,3,1");
5882
5960
  searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
5883
5961
  searchParams.set('prioritize_num_matching_fields', false);
5884
- searchParams.set('include_fields', "id,pref_label,type,category,labels");
5962
+ searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family");
5885
5963
  searchParams.set('enable_highlight_v1', false);
5886
5964
  searchParams.set('highlight_start_tag', '<span class="highlight">');
5887
5965
  searchParams.set('highlight_end_tag', '</span>');
@@ -13,6 +13,9 @@
13
13
  <option selected>https://media-metadata.l42.eu/tracks/13713</option>
14
14
  <option selected>https://eolas.l42.eu/metadata/language/ain/</option>
15
15
  </select></span>
16
+ <label for="search8">Languages grouped by family:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-types="Language"><select id="search8"></select></span>
17
+ <label for="search6">Languages with no-lang option:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-types="Language" data-no-lang="No Language"><select id="search6"></select></span>
18
+ <label for="search7">Languages with no-lang pre-selected:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-types="Language" data-no-lang="No Language"><select id="search7" multiple><option selected>https://eolas.l42.eu/metadata/language/zxx/</option></select></span>
16
19
  <label for="search5">More than 10:</label>
17
20
  <span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-exclude_types="Track"><select id="search5" multiple>
18
21
  <option selected>https://eolas.l42.eu/metadata/place/125/</option>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lucos_search_component",
3
- "version": "1.0.50",
3
+ "version": "1.0.51",
4
4
  "description": "Web Components for searching lucOS data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3,7 +3,7 @@ import tomSelectStylesheet from 'tom-select/dist/css/tom-select.default.css';
3
3
 
4
4
  class LucosSearchComponent extends HTMLSpanElement {
5
5
  static get observedAttributes() {
6
- return ['data-api-key','data-types','data-exclude-types'];
6
+ return ['data-api-key','data-types','data-exclude-types','data-no-lang'];
7
7
  }
8
8
  constructor() {
9
9
  super();
@@ -151,6 +151,7 @@ class LucosSearchComponent extends HTMLSpanElement {
151
151
  if (!selector) throw new Error("Can't find select element in lucos-search");
152
152
  selector.setAttribute("multiple", "multiple");
153
153
  new TomSelect(selector, {
154
+ ...(component.isLanguageMode ? { optgroupField: 'lang_family', lockOptgroupOrder: true } : {}),
154
155
  valueField: 'id',
155
156
  labelField: 'pref_label',
156
157
  searchField: [],
@@ -169,6 +170,11 @@ class LucosSearchComponent extends HTMLSpanElement {
169
170
  try {
170
171
  const results = await component.searchRequest(queryParams);
171
172
  this.clearOptions();
173
+ if (component.isLanguageMode) {
174
+ results.forEach(r => { if (!r.lang_family) r.lang_family = 'qli'; });
175
+ }
176
+ const noLang = component.noLangOption;
177
+ if (noLang) results.unshift(noLang);
172
178
  callback(results);
173
179
  } catch(err) {
174
180
  callback([]);
@@ -188,20 +194,42 @@ class LucosSearchComponent extends HTMLSpanElement {
188
194
  },
189
195
  onFocus: function() {
190
196
  this.clearOptions();
197
+ const noLang = component.noLangOption;
198
+ if (noLang) this.addOption(noLang);
191
199
  },
192
200
  // On startup, update any existing options with latest data from search
193
201
  onInitialize: async function() {
194
202
  const ids = Object.keys(this.options);
203
+ const noLang = component.noLangOption;
204
+ // Always make the no-lang option available for new selections
205
+ if (noLang) this.addOption(noLang);
206
+ // In language mode, fetch families and register option groups
207
+ if (component.isLanguageMode) {
208
+ const families = await component.getLanguageFamilies();
209
+ this.addOptionGroup('qli', { label: 'Language Isolate' });
210
+ families.forEach(family => {
211
+ this.addOptionGroup(family.code, { label: family.label });
212
+ });
213
+ }
195
214
  if (ids.length < 1) return;
196
- const searchParams = new URLSearchParams({
197
- q: '*',
198
- filter_by: `id:[${ids.join(",")}]`,
199
- per_page: ids.length,
200
- });
201
- const results = await component.searchRequest(searchParams);
202
- results.forEach(result => {
203
- this.updateOption(result.id, result);
204
- });
215
+ // Fetch real options from Typesense, excluding the synthetic no-lang option
216
+ const idsToFetch = noLang ? ids.filter(id => id !== noLang.id) : ids;
217
+ if (idsToFetch.length > 0) {
218
+ const searchParams = new URLSearchParams({
219
+ q: '*',
220
+ filter_by: `id:[${idsToFetch.join(",")}]`,
221
+ per_page: idsToFetch.length,
222
+ });
223
+ const results = await component.searchRequest(searchParams);
224
+ results.forEach(result => {
225
+ if (component.isLanguageMode && !result.lang_family) result.lang_family = 'qli';
226
+ this.updateOption(result.id, result);
227
+ });
228
+ }
229
+ // Update any pre-selected no-lang option with the synthetic data
230
+ if (noLang && ids.includes(noLang.id)) {
231
+ this.updateOption(noLang.id, noLang);
232
+ }
205
233
  },
206
234
  onItemSelect: function (item) {
207
235
  // Tom-select prevents clicking on link in an item to work as normal, so force it here
@@ -231,6 +259,56 @@ class LucosSearchComponent extends HTMLSpanElement {
231
259
  shadow.append(selector.nextElementSibling);
232
260
  }
233
261
  }
262
+ get noLangOption() {
263
+ const label = this.getAttribute("data-no-lang");
264
+ if (!label) return null;
265
+ return {
266
+ id: 'https://eolas.l42.eu/metadata/language/zxx/',
267
+ pref_label: label,
268
+ type: 'Language',
269
+ category: 'Anthropological',
270
+ labels: [],
271
+ highlight: {},
272
+ };
273
+ }
274
+ get isLanguageMode() {
275
+ const types = this.getAttribute("data-types");
276
+ if (!types) return false;
277
+ return types.split(",").map(t => t.trim()).includes("Language");
278
+ }
279
+ async getLanguageFamilies() {
280
+ if (this._langFamilies) return this._langFamilies;
281
+ const key = this.getAttribute("data-api-key");
282
+ if (!key) { this._langFamilies = []; return []; }
283
+ const searchParams = new URLSearchParams({
284
+ q: '*',
285
+ filter_by: 'type:=Language Family',
286
+ query_by: 'pref_label',
287
+ include_fields: 'id,pref_label',
288
+ sort_by: 'pref_label:asc',
289
+ enable_highlight_v1: false,
290
+ per_page: 250,
291
+ });
292
+ try {
293
+ const response = await fetch("https://arachne.l42.eu/search?" + searchParams.toString(), {
294
+ headers: { 'X-TYPESENSE-API-KEY': key },
295
+ signal: AbortSignal.timeout(8000),
296
+ });
297
+ if (!response.ok) { this._langFamilies = []; return []; }
298
+ const data = await response.json();
299
+ this._langFamilies = data.hits.map(hit => ({
300
+ // The lang_family field on Language documents in Typesense stores the last
301
+ // path segment of the Language Family URI (e.g. "gmw" for West Germanic at
302
+ // http://id.loc.gov/vocabulary/iso639-5/gmw). filter(Boolean) strips any
303
+ // trailing slash. This is consistent with lucos-lang's family code extraction.
304
+ code: hit.document.id.split("/").filter(Boolean).pop(),
305
+ label: hit.document.pref_label,
306
+ }));
307
+ } catch (_) {
308
+ this._langFamilies = [];
309
+ }
310
+ return this._langFamilies;
311
+ }
234
312
  async searchRequest(searchParams) {
235
313
  const key = this.getAttribute("data-api-key");
236
314
  if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
@@ -238,7 +316,7 @@ class LucosSearchComponent extends HTMLSpanElement {
238
316
  searchParams.set('query_by_weights', "10,8,3,1");
239
317
  searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
240
318
  searchParams.set('prioritize_num_matching_fields', false);
241
- searchParams.set('include_fields', "id,pref_label,type,category,labels");
319
+ searchParams.set('include_fields', "id,pref_label,type,category,labels,lang_family");
242
320
  searchParams.set('enable_highlight_v1', false);
243
321
  searchParams.set('highlight_start_tag', '<span class="highlight">')
244
322
  searchParams.set('highlight_end_tag', '</span>');