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.
- package/.github/workflows/codeql-analysis.yml +3 -3
- package/dist/index.js +123 -0
- package/example/index.html +8 -0
- package/index.js +2 -224
- package/package.json +2 -2
- package/web-components/lucos-lang.js +125 -0
- package/web-components/lucos-search.js +224 -0
|
@@ -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@
|
|
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@
|
|
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@
|
|
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" });
|
package/example/index.html
CHANGED
|
@@ -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
|
|
2
|
-
import
|
|
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
|
@@ -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" });
|