lucos_search_component 0.0.16 → 0.1.1

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.
@@ -43,7 +43,7 @@ jobs:
43
43
 
44
44
  # Initializes the CodeQL tools for scanning.
45
45
  - name: Initialize CodeQL
46
- uses: github/codeql-action/init@v3
46
+ uses: github/codeql-action/init@v4
47
47
  with:
48
48
  languages: ${{ matrix.language }}
49
49
  # If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
54
54
  # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55
55
  # If this step fails, then you should remove it and run the build manually (see below)
56
56
  - name: Autobuild
57
- uses: github/codeql-action/autobuild@v3
57
+ uses: github/codeql-action/autobuild@v4
58
58
 
59
59
  # ℹ️ Command-line programs to run using the OS shell.
60
60
  # 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
68
68
  # make release
69
69
 
70
70
  - name: Perform CodeQL Analysis
71
- uses: github/codeql-action/analyze@v3
71
+ uses: github/codeql-action/analyze@v4
package/dist/index.js CHANGED
@@ -5820,3 +5820,126 @@ class LucosSearchComponent extends HTMLSpanElement {
5820
5820
  }
5821
5821
  }
5822
5822
  customElements.define('lucos-search', LucosSearchComponent, { extends: "span" });
5823
+
5824
+ class LucosLangComponent extends HTMLSpanElement {
5825
+ static get observedAttributes() {
5826
+ return ['data-api-key'];
5827
+ }
5828
+ constructor() {
5829
+ super();
5830
+ const component = this;
5831
+ const shadow = component.attachShadow({mode: 'open'});
5832
+
5833
+ {
5834
+ const tomStyle = document.createElement('style');
5835
+ tomStyle.textContent = tomSelectStylesheet;
5836
+ shadow.appendChild(tomStyle);
5837
+ }
5838
+
5839
+ const mainStyle = document.createElement('style');
5840
+ mainStyle.textContent = `
5841
+ .lozenge {
5842
+ align-items: center;
5843
+ vertical-align: baseline;
5844
+ border-radius: 3px;
5845
+ background-repeat: repeat-x;
5846
+ border-style: solid;
5847
+ border-width: 1px;
5848
+ text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
5849
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03);
5850
+
5851
+ /** Make the colour settings !important so they override the tom-select default style **/
5852
+ background-image: linear-gradient(to bottom, #ffffff63, #24232347) !important;
5853
+ background-color: var(--lozenge-background) !important;
5854
+ border-color: var(--lozenge-border) !important;
5855
+ color: var(--lozenge-text) !important;
5856
+ }
5857
+ .lozenge .remove {
5858
+ border-left-color: var(--lozenge-border) !important;
5859
+ }
5860
+ .lozenge {
5861
+ --lozenge-background: #8affe7;
5862
+ --lozenge-border: #068900;
5863
+ --lozenge-text: #000000;
5864
+ }
5865
+ .ts-dropdown {
5866
+ margin: 0;
5867
+ }
5868
+ .lozenge a {
5869
+ color: inherit;
5870
+ text-decoration: none;
5871
+ }
5872
+ `;
5873
+ shadow.appendChild(mainStyle);
5874
+
5875
+
5876
+ const selector = component.querySelector("select");
5877
+ if (!selector) throw new Error("Can't find select element in lucos-lang");
5878
+ selector.setAttribute("multiple", "multiple");
5879
+ new TomSelect(selector, {
5880
+ valueField: 'code',
5881
+ labelField: 'label',
5882
+ searchField: ['code','label'],
5883
+ closeAfterSelect: true,
5884
+ plugins: {
5885
+ remove_button:{
5886
+ title:'Remove this language',
5887
+ },
5888
+ drag_drop: {},
5889
+ },
5890
+ onItemAdd: function() { // Workaround until https://github.com/orchidjs/tom-select/pull/945 is merged/released
5891
+ this.setTextboxValue('');
5892
+ this.refreshOptions();
5893
+ },
5894
+ // On startup, update any existing options with latest data from search
5895
+ onInitialize: async function() {
5896
+ const results = await component.searchRequest();
5897
+ results.forEach(result => {
5898
+ this.updateOption(result.code, result); // Updates any existing options which are selected with the correct label
5899
+ this.addOption(result); // Makes the option available for new selections
5900
+ });
5901
+ },
5902
+ onItemSelect: function (item) {
5903
+ // Tom-select prevents clicking on link in an item to work as normal, so force it here
5904
+ window.open(item.dataset.url, '_blank').focus();
5905
+ },
5906
+ render:{
5907
+ item: function(data, escape) {
5908
+ return `<div class="lozenge" data-url="${escape(data.url)}"><a href="${escape(data.url)}" target="_blank">${escape(data.label)}</a></div>`;
5909
+ },
5910
+ },
5911
+ });
5912
+ if (selector.nextElementSibling) {
5913
+ shadow.append(selector.nextElementSibling);
5914
+ }
5915
+ }
5916
+ async searchRequest() {
5917
+ const key = this.getAttribute("data-api-key");
5918
+ if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
5919
+ const searchParams = new URLSearchParams({
5920
+ filter_by: 'type:=Language',
5921
+ query_by: "pref_label",
5922
+ include_fields: "id,pref_label",
5923
+ sort_by: "pref_label:asc",
5924
+ enable_highlight_v1: false,
5925
+ per_page: 200,
5926
+ });
5927
+ const response = await fetch("https://arachne.l42.eu/search?"+searchParams.toString(), {
5928
+ headers: { 'X-TYPESENSE-API-KEY': key },
5929
+ signal: AbortSignal.timeout(900),
5930
+ });
5931
+ const data = await response.json();
5932
+ if (!response.ok) {
5933
+ throw new Error(`Recieved ${response.status} error from search endpoint: ${data["message"]}`);
5934
+ }
5935
+ const results = data.hits.map(result => {
5936
+ return {
5937
+ code: result.document.id.split("/").reverse()[1],
5938
+ label: result.document.pref_label,
5939
+ url: result.document.id,
5940
+ }
5941
+ });
5942
+ return results;
5943
+ }
5944
+ }
5945
+ customElements.define('lucos-lang', LucosLangComponent, { extends: "span" });
@@ -3,6 +3,7 @@
3
3
  <title>Hello World Search Component</title>
4
4
  </head>
5
5
  <body>
6
+ <h1>lucos-search</h1>
6
7
  <label for="search1">Everything:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}"><select id="search1"></select></span>
7
8
  <label for="search2">No Tracks:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-exclude_types="Track"><select id="search2"></select></span>
8
9
  <label for="search3">Only Cites and Rivers:</label><span is="lucos-search" data-api-key="${KEY_LUCOS_ARACHNE}" data-types="City,River"><select id="search3"></select></span>
@@ -27,6 +28,13 @@
27
28
  <option value="https://eolas.l42.eu/metadata/place/316/" selected>https://eolas.l42.eu/metadata/place/316/</option>
28
29
  <option value="https://eolas.l42.eu/metadata/place/317/" selected>https://eolas.l42.eu/metadata/place/317/</option>
29
30
  </select></span>
31
+ <h1>lucos-lang</h1>
32
+ <label for="lang1">Blank:</label><span is="lucos-lang" data-api-key="${KEY_LUCOS_ARACHNE}"><select id="lang1"></select></span>
33
+ <label for="lang2">Load with existing:</label><span is="lucos-lang" data-api-key="${KEY_LUCOS_ARACHNE}"><select id="lang2" multiple>
34
+ <option id="ga" selected>ga</option>
35
+ <option id="emen" selected>emen</option>
36
+ <option id="art-x-simlish" selected>art-x-simlish</option>
37
+ </select></span>
30
38
  <script src="./built.js"></script>
31
39
  </body>
32
40
  </html>
package/index.js CHANGED
@@ -1,224 +1,2 @@
1
- import TomSelect from 'tom-select';
2
- import tomSelectStylesheet from 'tom-select/dist/css/tom-select.default.css';
3
-
4
- class LucosSearchComponent extends HTMLSpanElement {
5
- static get observedAttributes() {
6
- return ['data-api-key','data-types','data-exclude-types'];
7
- }
8
- constructor() {
9
- super();
10
- const component = this;
11
- const shadow = component.attachShadow({mode: 'open'});
12
-
13
- if (tomSelectStylesheet) {
14
- const tomStyle = document.createElement('style');
15
- tomStyle.textContent = tomSelectStylesheet;
16
- shadow.appendChild(tomStyle);
17
- }
18
-
19
- const mainStyle = document.createElement('style');
20
- mainStyle.textContent = `
21
- .lozenge {
22
- align-items: center;
23
- vertical-align: baseline;
24
- border-radius: 3px;
25
- background-repeat: repeat-x;
26
- border-style: solid;
27
- border-width: 1px;
28
- text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
29
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03);
30
-
31
- /** Make the colour settings !important so they override the tom-select default style **/
32
- background-image: linear-gradient(to bottom, #ffffff63, #24232347) !important;
33
- background-color: var(--lozenge-background) !important;
34
- border-color: var(--lozenge-border) !important;
35
- color: var(--lozenge-text) !important;
36
- }
37
- .lozenge .remove {
38
- border-left-color: var(--lozenge-border) !important;
39
- }
40
-
41
- /* Default colour to greys, but override based on type */
42
- .lozenge {
43
- --lozenge-background: #555;
44
- --lozenge-border: #6d6d6d;
45
- --lozenge-text: #fff;
46
- }
47
- /* Items from lucos_eolas have many types. For now, count any type which isn't specified later as part of eolas. */
48
- .lozenge[data-type] {
49
- --lozenge-background: #6a00c2;
50
- --lozenge-border: #44265d;
51
- }
52
- .lozenge[data-type="Track"] {
53
- --lozenge-background: #000060;
54
- --lozenge-border: #000020;
55
- }
56
- .lozenge[data-type="Person"] {
57
- --lozenge-background: #044E00;
58
- --lozenge-border: #033100;
59
- }
60
- /** Aquatic Places **/
61
- .lozenge[data-type="Ocean"], .lozenge[data-type="Sea"], .lozenge[data-type="Sea Inlet"], .lozenge[data-type="River"], .lozenge[data-type="Lake"] {
62
- --lozenge-background: #0085fe;
63
- --lozenge-border: #0036b1;
64
- }
65
- /** Terrestrial Places **/
66
- .lozenge[data-type="Archipelago"], .lozenge[data-type="Area Of Outstanding Natural Beauty"], .lozenge[data-type="Continent"], .lozenge[data-type="Historical Site"], .lozenge[data-type="Island"], .lozenge[data-type="Mountain"] {
67
- --lozenge-background: #652c17;
68
- --lozenge-border: #321200;
69
- }
70
- /** Cosmic Places **/
71
- .lozenge[data-type="Galaxy"], .lozenge[data-type="Planetary System"], .lozenge[data-type="Star"], .lozenge[data-type="Planet"], .lozenge[data-type="Natural Satellite"] {
72
- --lozenge-background: #15163a;
73
- --lozenge-border: #000000;
74
- --lozenge-text: #feffe8;
75
- }
76
- /** Human Places **/
77
- .lozenge[data-type="Airport"], .lozenge[data-type="Autonomous Area"], .lozenge[data-type="City"], .lozenge[data-type="Country"], .lozenge[data-type="County"], .lozenge[data-type="Dependent Territory"], .lozenge[data-type="Historical Site"], .lozenge[data-type="Neighbourhood"], .lozenge[data-type="Province"], .lozenge[data-type="Region"], .lozenge[data-type="Road"], .lozenge[data-type="State"], .lozenge[data-type="Town"], .lozenge[data-type="Tribal Nation"], .lozenge[data-type="Village"] {
78
- --lozenge-background: #aed0db;
79
- --lozenge-border: #3f6674;
80
- --lozenge-text: #0c1a1b;
81
- }
82
- /** Supernatural Places **/
83
- .lozenge[data-type="Supernatural Realm"] {
84
- --lozenge-background: #f1ff5f;
85
- --lozenge-border: #674800;
86
- --lozenge-text: #352005;
87
- }
88
- .lozenge[data-type="Historical Event"] {
89
- --lozenge-background: #740909;
90
- --lozenge-border: #470202;
91
- }
92
- .lozenge[data-type="Number"] {
93
- --lozenge-background: #0000ff;
94
- --lozenge-border: #000083;
95
- }
96
- /** Temporal Types **/
97
- .lozenge[data-type="Calendar"], .lozenge[data-type="Festival"], .lozenge[data-type="Month of Year"], .lozenge[data-type="Day of Week"] {
98
- --lozenge-background: #fffc33;
99
- --lozenge-border: #7f7e00;
100
- --lozenge-text: #0f0f00;
101
- }
102
-
103
-
104
- .lozenge.active {
105
- --lozenge-border: #b00;
106
- }
107
- .type {
108
- margin: 0 3px;
109
- padding: 2px 6px;
110
- }
111
- .ts-dropdown {
112
- margin: 0;
113
- }
114
- .lozenge a {
115
- color: inherit;
116
- text-decoration: none;
117
- }
118
- `;
119
- shadow.appendChild(mainStyle);
120
-
121
-
122
- const selector = component.querySelector("select");
123
- if (!selector) throw new Error("Can't find select element in lucos-search");
124
- selector.setAttribute("multiple", "multiple");
125
- new TomSelect(selector, {
126
- valueField: 'id',
127
- labelField: 'pref_label',
128
- searchField: [],
129
- closeAfterSelect: true,
130
- highlight: false, // Will use typesense's hightlight (as it can consider other fields)
131
- load: async function(query, callback) {
132
- const queryParams = new URLSearchParams({
133
- q: query,
134
- });
135
- if (component.getAttribute("data-types")) {
136
- queryParams.set("filter_by",`type:[${component.getAttribute("data-types")}]`);
137
- } else if (component.getAttribute("data-exclude_types")) {
138
- queryParams.set("filter_by",`type:![${component.getAttribute("data-exclude_types")}]`);
139
- }
140
- const results = await component.searchRequest(queryParams);
141
- this.clearOptions();
142
- callback(results);
143
- },
144
- plugins: {
145
- remove_button:{
146
- title:'Remove this item',
147
- },
148
- drag_drop: {},
149
- },
150
- onItemAdd: function() { // Workaround until https://github.com/orchidjs/tom-select/pull/945 is merged/released
151
- this.setTextboxValue('');
152
- this.refreshOptions();
153
- },
154
- onFocus: function() {
155
- this.clearOptions();
156
- },
157
- // On startup, update any existing options with latest data from search
158
- onInitialize: async function() {
159
- const ids = Object.keys(this.options);
160
- if (ids.length < 1) return;
161
- const searchParams = new URLSearchParams({
162
- q: '*',
163
- filter_by: `id:[${ids.join(",")}]`,
164
- per_page: ids.length,
165
- });
166
- const results = await component.searchRequest(searchParams);
167
- results.forEach(result => {
168
- this.updateOption(result.id, result);
169
- });
170
- },
171
- onItemSelect: function (item) {
172
- // Tom-select prevents clicking on link in an item to work as normal, so force it here
173
- window.open(item.dataset.value, '_blank').focus();
174
- },
175
- render:{
176
- option: function(data, escape) {
177
- let label = escape(data.pref_label);
178
- let alt_label = "";
179
- if (data.highlight.pref_label) {
180
- label = data.highlight.pref_label.snippet;
181
- } else if (data.highlight.labels) {
182
- const matched_label = data.highlight.labels.find(l => l.matched_tokens.length > 0);
183
- if (matched_label) {
184
- alt_label = ` <span class="alt-label">(${matched_label.snippet})</span>`;
185
- }
186
- }
187
- label = label.replace(` (${data.type})`,""); // No need to include any type disambiguation in label, as types are always shown
188
- return `<div>${label}${alt_label}<span class="type lozenge" data-type="${escape(data.type)}">${escape(data.type)}</span></div>`;
189
- },
190
- item: function(data, escape) {
191
- return `<div class="lozenge" data-type="${escape(data.type)}"><a href="${data.id}" target="_blank">${escape(data.pref_label)}</a></div>`;
192
- },
193
- },
194
- });
195
- if (selector.nextElementSibling) {
196
- shadow.append(selector.nextElementSibling);
197
- }
198
- }
199
- async searchRequest(searchParams) {
200
- const key = this.getAttribute("data-api-key");
201
- if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
202
- searchParams.set('query_by', "pref_label,labels,description,lyrics");
203
- searchParams.set('query_by_weights', "10,8,3,1");
204
- searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
205
- searchParams.set('prioritize_num_matching_fields', false);
206
- searchParams.set('include_fields', "id,pref_label,type,labels");
207
- searchParams.set('enable_highlight_v1', false);
208
- searchParams.set('highlight_start_tag', '<span class="highlight">')
209
- searchParams.set('highlight_end_tag', '</span>');
210
- const response = await fetch("https://arachne.l42.eu/search?"+searchParams.toString(), {
211
- headers: { 'X-TYPESENSE-API-KEY': key },
212
- signal: AbortSignal.timeout(900),
213
- });
214
- const data = await response.json();
215
- if (!response.ok) {
216
- throw new Error(`Recieved ${response.status} error from search endpoint: ${data["message"]}`);
217
- }
218
- const results = data.hits.map(result => {
219
- return {...result, ...result.document}
220
- });
221
- return results;
222
- }
223
- }
224
- customElements.define('lucos-search', LucosSearchComponent, { extends: "span" });
1
+ import './web-components/lucos-search.js';
2
+ import './web-components/lucos-lang.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "lucos_search_component",
3
- "version": "0.0.16",
4
- "description": "Web Component for searching lucOS data",
3
+ "version": "0.1.1",
4
+ "description": "Web Components for searching lucOS data",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
@@ -0,0 +1,125 @@
1
+ import TomSelect from 'tom-select';
2
+ import tomSelectStylesheet from 'tom-select/dist/css/tom-select.default.css';
3
+
4
+ class LucosLangComponent extends HTMLSpanElement {
5
+ static get observedAttributes() {
6
+ return ['data-api-key'];
7
+ }
8
+ constructor() {
9
+ super();
10
+ const component = this;
11
+ const shadow = component.attachShadow({mode: 'open'});
12
+
13
+ if (tomSelectStylesheet) {
14
+ const tomStyle = document.createElement('style');
15
+ tomStyle.textContent = tomSelectStylesheet;
16
+ shadow.appendChild(tomStyle);
17
+ }
18
+
19
+ const mainStyle = document.createElement('style');
20
+ mainStyle.textContent = `
21
+ .lozenge {
22
+ align-items: center;
23
+ vertical-align: baseline;
24
+ border-radius: 3px;
25
+ background-repeat: repeat-x;
26
+ border-style: solid;
27
+ border-width: 1px;
28
+ text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
29
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03);
30
+
31
+ /** Make the colour settings !important so they override the tom-select default style **/
32
+ background-image: linear-gradient(to bottom, #ffffff63, #24232347) !important;
33
+ background-color: var(--lozenge-background) !important;
34
+ border-color: var(--lozenge-border) !important;
35
+ color: var(--lozenge-text) !important;
36
+ }
37
+ .lozenge .remove {
38
+ border-left-color: var(--lozenge-border) !important;
39
+ }
40
+ .lozenge {
41
+ --lozenge-background: #8affe7;
42
+ --lozenge-border: #068900;
43
+ --lozenge-text: #000000;
44
+ }
45
+ .ts-dropdown {
46
+ margin: 0;
47
+ }
48
+ .lozenge a {
49
+ color: inherit;
50
+ text-decoration: none;
51
+ }
52
+ `;
53
+ shadow.appendChild(mainStyle);
54
+
55
+
56
+ const selector = component.querySelector("select");
57
+ if (!selector) throw new Error("Can't find select element in lucos-lang");
58
+ selector.setAttribute("multiple", "multiple");
59
+ new TomSelect(selector, {
60
+ valueField: 'code',
61
+ labelField: 'label',
62
+ searchField: ['code','label'],
63
+ closeAfterSelect: true,
64
+ plugins: {
65
+ remove_button:{
66
+ title:'Remove this language',
67
+ },
68
+ drag_drop: {},
69
+ },
70
+ onItemAdd: function() { // Workaround until https://github.com/orchidjs/tom-select/pull/945 is merged/released
71
+ this.setTextboxValue('');
72
+ this.refreshOptions();
73
+ },
74
+ // On startup, update any existing options with latest data from search
75
+ onInitialize: async function() {
76
+ const results = await component.searchRequest();
77
+ results.forEach(result => {
78
+ this.updateOption(result.code, result); // Updates any existing options which are selected with the correct label
79
+ this.addOption(result); // Makes the option available for new selections
80
+ });
81
+ },
82
+ onItemSelect: function (item) {
83
+ // Tom-select prevents clicking on link in an item to work as normal, so force it here
84
+ window.open(item.dataset.url, '_blank').focus();
85
+ },
86
+ render:{
87
+ item: function(data, escape) {
88
+ return `<div class="lozenge" data-url="${escape(data.url)}"><a href="${escape(data.url)}" target="_blank">${escape(data.label)}</a></div>`;
89
+ },
90
+ },
91
+ });
92
+ if (selector.nextElementSibling) {
93
+ shadow.append(selector.nextElementSibling);
94
+ }
95
+ }
96
+ async searchRequest() {
97
+ const key = this.getAttribute("data-api-key");
98
+ if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
99
+ const searchParams = new URLSearchParams({
100
+ filter_by: 'type:=Language',
101
+ query_by: "pref_label",
102
+ include_fields: "id,pref_label",
103
+ sort_by: "pref_label:asc",
104
+ enable_highlight_v1: false,
105
+ per_page: 200,
106
+ });
107
+ const response = await fetch("https://arachne.l42.eu/search?"+searchParams.toString(), {
108
+ headers: { 'X-TYPESENSE-API-KEY': key },
109
+ signal: AbortSignal.timeout(900),
110
+ });
111
+ const data = await response.json();
112
+ if (!response.ok) {
113
+ throw new Error(`Recieved ${response.status} error from search endpoint: ${data["message"]}`);
114
+ }
115
+ const results = data.hits.map(result => {
116
+ return {
117
+ code: result.document.id.split("/").reverse()[1],
118
+ label: result.document.pref_label,
119
+ url: result.document.id,
120
+ }
121
+ });
122
+ return results;
123
+ }
124
+ }
125
+ customElements.define('lucos-lang', LucosLangComponent, { extends: "span" });
@@ -0,0 +1,224 @@
1
+ import TomSelect from 'tom-select';
2
+ import tomSelectStylesheet from 'tom-select/dist/css/tom-select.default.css';
3
+
4
+ class LucosSearchComponent extends HTMLSpanElement {
5
+ static get observedAttributes() {
6
+ return ['data-api-key','data-types','data-exclude-types'];
7
+ }
8
+ constructor() {
9
+ super();
10
+ const component = this;
11
+ const shadow = component.attachShadow({mode: 'open'});
12
+
13
+ if (tomSelectStylesheet) {
14
+ const tomStyle = document.createElement('style');
15
+ tomStyle.textContent = tomSelectStylesheet;
16
+ shadow.appendChild(tomStyle);
17
+ }
18
+
19
+ const mainStyle = document.createElement('style');
20
+ mainStyle.textContent = `
21
+ .lozenge {
22
+ align-items: center;
23
+ vertical-align: baseline;
24
+ border-radius: 3px;
25
+ background-repeat: repeat-x;
26
+ border-style: solid;
27
+ border-width: 1px;
28
+ text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
29
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 1px rgba(255, 255, 255, 0.03);
30
+
31
+ /** Make the colour settings !important so they override the tom-select default style **/
32
+ background-image: linear-gradient(to bottom, #ffffff63, #24232347) !important;
33
+ background-color: var(--lozenge-background) !important;
34
+ border-color: var(--lozenge-border) !important;
35
+ color: var(--lozenge-text) !important;
36
+ }
37
+ .lozenge .remove {
38
+ border-left-color: var(--lozenge-border) !important;
39
+ }
40
+
41
+ /* Default colour to greys, but override based on type */
42
+ .lozenge {
43
+ --lozenge-background: #555;
44
+ --lozenge-border: #6d6d6d;
45
+ --lozenge-text: #fff;
46
+ }
47
+ /* Items from lucos_eolas have many types. For now, count any type which isn't specified later as part of eolas. */
48
+ .lozenge[data-type] {
49
+ --lozenge-background: #6a00c2;
50
+ --lozenge-border: #44265d;
51
+ }
52
+ .lozenge[data-type="Track"] {
53
+ --lozenge-background: #000060;
54
+ --lozenge-border: #000020;
55
+ }
56
+ .lozenge[data-type="Person"] {
57
+ --lozenge-background: #044E00;
58
+ --lozenge-border: #033100;
59
+ }
60
+ /** Aquatic Places **/
61
+ .lozenge[data-type="Ocean"], .lozenge[data-type="Sea"], .lozenge[data-type="Sea Inlet"], .lozenge[data-type="River"], .lozenge[data-type="Lake"] {
62
+ --lozenge-background: #0085fe;
63
+ --lozenge-border: #0036b1;
64
+ }
65
+ /** Terrestrial Places **/
66
+ .lozenge[data-type="Archipelago"], .lozenge[data-type="Area Of Outstanding Natural Beauty"], .lozenge[data-type="Continent"], .lozenge[data-type="Historical Site"], .lozenge[data-type="Island"], .lozenge[data-type="Mountain"] {
67
+ --lozenge-background: #652c17;
68
+ --lozenge-border: #321200;
69
+ }
70
+ /** Cosmic Places **/
71
+ .lozenge[data-type="Galaxy"], .lozenge[data-type="Planetary System"], .lozenge[data-type="Star"], .lozenge[data-type="Planet"], .lozenge[data-type="Natural Satellite"] {
72
+ --lozenge-background: #15163a;
73
+ --lozenge-border: #000000;
74
+ --lozenge-text: #feffe8;
75
+ }
76
+ /** Human Places **/
77
+ .lozenge[data-type="Airport"], .lozenge[data-type="Autonomous Area"], .lozenge[data-type="City"], .lozenge[data-type="Country"], .lozenge[data-type="County"], .lozenge[data-type="Dependent Territory"], .lozenge[data-type="Historical Site"], .lozenge[data-type="Neighbourhood"], .lozenge[data-type="Province"], .lozenge[data-type="Region"], .lozenge[data-type="Road"], .lozenge[data-type="State"], .lozenge[data-type="Town"], .lozenge[data-type="Tribal Nation"], .lozenge[data-type="Village"] {
78
+ --lozenge-background: #aed0db;
79
+ --lozenge-border: #3f6674;
80
+ --lozenge-text: #0c1a1b;
81
+ }
82
+ /** Supernatural Places **/
83
+ .lozenge[data-type="Supernatural Realm"] {
84
+ --lozenge-background: #f1ff5f;
85
+ --lozenge-border: #674800;
86
+ --lozenge-text: #352005;
87
+ }
88
+ .lozenge[data-type="Historical Event"] {
89
+ --lozenge-background: #740909;
90
+ --lozenge-border: #470202;
91
+ }
92
+ .lozenge[data-type="Number"] {
93
+ --lozenge-background: #0000ff;
94
+ --lozenge-border: #000083;
95
+ }
96
+ /** Temporal Types **/
97
+ .lozenge[data-type="Calendar"], .lozenge[data-type="Festival"], .lozenge[data-type="Month of Year"], .lozenge[data-type="Day of Week"] {
98
+ --lozenge-background: #fffc33;
99
+ --lozenge-border: #7f7e00;
100
+ --lozenge-text: #0f0f00;
101
+ }
102
+
103
+
104
+ .lozenge.active {
105
+ --lozenge-border: #b00;
106
+ }
107
+ .type {
108
+ margin: 0 3px;
109
+ padding: 2px 6px;
110
+ }
111
+ .ts-dropdown {
112
+ margin: 0;
113
+ }
114
+ .lozenge a {
115
+ color: inherit;
116
+ text-decoration: none;
117
+ }
118
+ `;
119
+ shadow.appendChild(mainStyle);
120
+
121
+
122
+ const selector = component.querySelector("select");
123
+ if (!selector) throw new Error("Can't find select element in lucos-search");
124
+ selector.setAttribute("multiple", "multiple");
125
+ new TomSelect(selector, {
126
+ valueField: 'id',
127
+ labelField: 'pref_label',
128
+ searchField: [],
129
+ closeAfterSelect: true,
130
+ highlight: false, // Will use typesense's hightlight (as it can consider other fields)
131
+ load: async function(query, callback) {
132
+ const queryParams = new URLSearchParams({
133
+ q: query,
134
+ });
135
+ if (component.getAttribute("data-types")) {
136
+ queryParams.set("filter_by",`type:[${component.getAttribute("data-types")}]`);
137
+ } else if (component.getAttribute("data-exclude_types")) {
138
+ queryParams.set("filter_by",`type:![${component.getAttribute("data-exclude_types")}]`);
139
+ }
140
+ const results = await component.searchRequest(queryParams);
141
+ this.clearOptions();
142
+ callback(results);
143
+ },
144
+ plugins: {
145
+ remove_button:{
146
+ title:'Remove this item',
147
+ },
148
+ drag_drop: {},
149
+ },
150
+ onItemAdd: function() { // Workaround until https://github.com/orchidjs/tom-select/pull/945 is merged/released
151
+ this.setTextboxValue('');
152
+ this.refreshOptions();
153
+ },
154
+ onFocus: function() {
155
+ this.clearOptions();
156
+ },
157
+ // On startup, update any existing options with latest data from search
158
+ onInitialize: async function() {
159
+ const ids = Object.keys(this.options);
160
+ if (ids.length < 1) return;
161
+ const searchParams = new URLSearchParams({
162
+ q: '*',
163
+ filter_by: `id:[${ids.join(",")}]`,
164
+ per_page: ids.length,
165
+ });
166
+ const results = await component.searchRequest(searchParams);
167
+ results.forEach(result => {
168
+ this.updateOption(result.id, result);
169
+ });
170
+ },
171
+ onItemSelect: function (item) {
172
+ // Tom-select prevents clicking on link in an item to work as normal, so force it here
173
+ window.open(item.dataset.value, '_blank').focus();
174
+ },
175
+ render:{
176
+ option: function(data, escape) {
177
+ let label = escape(data.pref_label);
178
+ let alt_label = "";
179
+ if (data.highlight.pref_label) {
180
+ label = data.highlight.pref_label.snippet;
181
+ } else if (data.highlight.labels) {
182
+ const matched_label = data.highlight.labels.find(l => l.matched_tokens.length > 0);
183
+ if (matched_label) {
184
+ alt_label = ` <span class="alt-label">(${matched_label.snippet})</span>`;
185
+ }
186
+ }
187
+ label = label.replace(` (${data.type})`,""); // No need to include any type disambiguation in label, as types are always shown
188
+ return `<div>${label}${alt_label}<span class="type lozenge" data-type="${escape(data.type)}">${escape(data.type)}</span></div>`;
189
+ },
190
+ item: function(data, escape) {
191
+ return `<div class="lozenge" data-type="${escape(data.type)}"><a href="${data.id}" target="_blank">${escape(data.pref_label)}</a></div>`;
192
+ },
193
+ },
194
+ });
195
+ if (selector.nextElementSibling) {
196
+ shadow.append(selector.nextElementSibling);
197
+ }
198
+ }
199
+ async searchRequest(searchParams) {
200
+ const key = this.getAttribute("data-api-key");
201
+ if (!key) throw new Error("No `data-api-key` attribute set on `lucos-search` component");
202
+ searchParams.set('query_by', "pref_label,labels,description,lyrics");
203
+ searchParams.set('query_by_weights', "10,8,3,1");
204
+ searchParams.set('sort_by', "_text_match:desc,pref_label:asc");
205
+ searchParams.set('prioritize_num_matching_fields', false);
206
+ searchParams.set('include_fields', "id,pref_label,type,labels");
207
+ searchParams.set('enable_highlight_v1', false);
208
+ searchParams.set('highlight_start_tag', '<span class="highlight">')
209
+ searchParams.set('highlight_end_tag', '</span>');
210
+ const response = await fetch("https://arachne.l42.eu/search?"+searchParams.toString(), {
211
+ headers: { 'X-TYPESENSE-API-KEY': key },
212
+ signal: AbortSignal.timeout(900),
213
+ });
214
+ const data = await response.json();
215
+ if (!response.ok) {
216
+ throw new Error(`Recieved ${response.status} error from search endpoint: ${data["message"]}`);
217
+ }
218
+ const results = data.hits.map(result => {
219
+ return {...result, ...result.document}
220
+ });
221
+ return results;
222
+ }
223
+ }
224
+ customElements.define('lucos-search', LucosSearchComponent, { extends: "span" });