hal-search 0.1.0
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/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/HalSearch.d.ts +23 -0
- package/dist/api.d.ts +12 -0
- package/dist/embed.d.ts +21 -0
- package/dist/hal-search.es.js +482 -0
- package/dist/hal-search.es.js.map +1 -0
- package/dist/hal-search.umd.js +254 -0
- package/dist/hal-search.umd.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/levels.d.ts +6 -0
- package/dist/renderer.d.ts +5 -0
- package/dist/styles.d.ts +3 -0
- package/dist/svg-renderer.d.ts +11 -0
- package/dist/types.d.ts +75 -0
- package/package.json +31 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
//#region src/levels.ts
|
|
2
|
+
var e = [
|
|
3
|
+
"docid",
|
|
4
|
+
"label_s",
|
|
5
|
+
"uri_s"
|
|
6
|
+
], t = [
|
|
7
|
+
...e,
|
|
8
|
+
"title_s",
|
|
9
|
+
"authFullName_s",
|
|
10
|
+
"publicationDate_s",
|
|
11
|
+
"docType_s"
|
|
12
|
+
], n = [
|
|
13
|
+
...t,
|
|
14
|
+
"keyword_s",
|
|
15
|
+
"domain_s",
|
|
16
|
+
"openAccess_bool",
|
|
17
|
+
"language_s",
|
|
18
|
+
"peerReviewing_s",
|
|
19
|
+
"conferenceTitle_s"
|
|
20
|
+
], r = {
|
|
21
|
+
0: e.join(","),
|
|
22
|
+
1: t.join(","),
|
|
23
|
+
2: n.join(","),
|
|
24
|
+
3: "*"
|
|
25
|
+
}, i = {
|
|
26
|
+
0: "minimal",
|
|
27
|
+
1: "basic",
|
|
28
|
+
2: "detailed",
|
|
29
|
+
3: "full"
|
|
30
|
+
};
|
|
31
|
+
function a(e) {
|
|
32
|
+
return r[e] ?? r[1];
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/api.ts
|
|
36
|
+
var o = "https://api.archives-ouvertes.fr/search/";
|
|
37
|
+
function s(e, t, n, r, i = o) {
|
|
38
|
+
let s = a(t);
|
|
39
|
+
return `${i}?${new URLSearchParams({
|
|
40
|
+
q: `"${e}"`,
|
|
41
|
+
wt: "json",
|
|
42
|
+
fl: s,
|
|
43
|
+
rows: String(n),
|
|
44
|
+
start: String(r)
|
|
45
|
+
}).toString()}`;
|
|
46
|
+
}
|
|
47
|
+
async function c(e, t, n, r, i = o) {
|
|
48
|
+
let a = s(e, t, n, r, i), c = await fetch(a, { headers: { Accept: "application/json" } });
|
|
49
|
+
if (!c.ok) throw Error(`HAL API error: ${c.status} ${c.statusText}`);
|
|
50
|
+
let l = await c.json();
|
|
51
|
+
if (l.responseHeader?.status !== void 0 && l.responseHeader.status !== 0) throw Error(`HAL API returned non-zero status: ${l.responseHeader.status}`);
|
|
52
|
+
return l;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/renderer.ts
|
|
56
|
+
function l(e, t) {
|
|
57
|
+
let n = document.createElement(e);
|
|
58
|
+
return t && (n.className = t), n;
|
|
59
|
+
}
|
|
60
|
+
function u(e) {
|
|
61
|
+
return document.createTextNode(e);
|
|
62
|
+
}
|
|
63
|
+
function d(e) {
|
|
64
|
+
let t = document.createElement("textarea");
|
|
65
|
+
return t.innerHTML = e, t.value;
|
|
66
|
+
}
|
|
67
|
+
function f(e, t, n) {
|
|
68
|
+
let r = l("a", n);
|
|
69
|
+
return e && (e.startsWith("https://") || e.startsWith("http://")) && (r.href = e), r.target = "_blank", r.rel = "noopener noreferrer", r.textContent = t, r;
|
|
70
|
+
}
|
|
71
|
+
function p(e) {
|
|
72
|
+
e.innerHTML = "";
|
|
73
|
+
let t = l("div", "hal-loading"), n = l("div", "hal-spinner");
|
|
74
|
+
t.appendChild(n), t.appendChild(u("Loading…")), e.appendChild(t);
|
|
75
|
+
}
|
|
76
|
+
function m(e, t) {
|
|
77
|
+
e.innerHTML = "";
|
|
78
|
+
let n = l("div", "hal-error");
|
|
79
|
+
n.textContent = `Error: ${t.message}`, e.appendChild(n);
|
|
80
|
+
}
|
|
81
|
+
function h(e) {
|
|
82
|
+
e.innerHTML = "";
|
|
83
|
+
let t = l("div", "hal-empty");
|
|
84
|
+
t.textContent = "No results found.", e.appendChild(t);
|
|
85
|
+
}
|
|
86
|
+
function g(e, t) {
|
|
87
|
+
let n = l("article", "hal-article");
|
|
88
|
+
e.docid && (n.dataset.docid = e.docid), e.docType_s && (n.dataset.doctype = e.docType_s);
|
|
89
|
+
let r = l("header");
|
|
90
|
+
if (t >= 1) {
|
|
91
|
+
let t = l("h3", "hal-article__title"), n = e.title_s?.[0] ?? e.label_s ?? "Untitled";
|
|
92
|
+
t.appendChild(f(e.uri_s, n)), r.appendChild(t);
|
|
93
|
+
let i = l("div", "hal-article__meta");
|
|
94
|
+
if (e.authFullName_s?.length) {
|
|
95
|
+
let t = l("span", "hal-article__authors");
|
|
96
|
+
t.textContent = e.authFullName_s.join(", "), i.appendChild(t);
|
|
97
|
+
}
|
|
98
|
+
if (e.publicationDate_s) {
|
|
99
|
+
let t = l("span", "hal-article__date");
|
|
100
|
+
t.textContent = e.publicationDate_s.slice(0, 4), i.appendChild(t);
|
|
101
|
+
}
|
|
102
|
+
if (e.docType_s && e.docType_s.toUpperCase() !== "UNDEFINED") {
|
|
103
|
+
let t = l("span", "hal-badge");
|
|
104
|
+
t.textContent = e.docType_s, i.appendChild(t);
|
|
105
|
+
}
|
|
106
|
+
if (e.openAccess_bool === !0) {
|
|
107
|
+
let e = l("span", "hal-badge hal-badge--oa");
|
|
108
|
+
e.textContent = "Open Access", i.appendChild(e);
|
|
109
|
+
}
|
|
110
|
+
r.appendChild(i);
|
|
111
|
+
} else {
|
|
112
|
+
let t = l("div", "hal-article__label");
|
|
113
|
+
t.appendChild(f(e.uri_s, d(e.label_s ?? e.docid ?? ""), "hal-article__link")), r.appendChild(t);
|
|
114
|
+
}
|
|
115
|
+
if (n.appendChild(r), t >= 2) {
|
|
116
|
+
let r = e.keyword_s && e.keyword_s.length > 0, i = e.domain_s && e.domain_s.length > 0, a = !!e.conferenceTitle_s;
|
|
117
|
+
if (r || i || a) {
|
|
118
|
+
let o = l("section", "hal-article__details");
|
|
119
|
+
if (r) {
|
|
120
|
+
let t = l("div", "hal-article__tags");
|
|
121
|
+
for (let n of e.keyword_s) {
|
|
122
|
+
let e = l("span", "hal-tag");
|
|
123
|
+
e.textContent = n, t.appendChild(e);
|
|
124
|
+
}
|
|
125
|
+
o.appendChild(t);
|
|
126
|
+
}
|
|
127
|
+
if (i) {
|
|
128
|
+
let t = l("div", "hal-article__tags");
|
|
129
|
+
for (let n of e.domain_s) {
|
|
130
|
+
let e = l("span", "hal-tag hal-tag--domain");
|
|
131
|
+
e.textContent = n, t.appendChild(e);
|
|
132
|
+
}
|
|
133
|
+
o.appendChild(t);
|
|
134
|
+
}
|
|
135
|
+
if (a) {
|
|
136
|
+
let t = l("div", "hal-article__conference");
|
|
137
|
+
t.textContent = e.conferenceTitle_s, o.appendChild(t);
|
|
138
|
+
}
|
|
139
|
+
if (t === 3 && e.abstract_s?.[0]) {
|
|
140
|
+
let t = l("div", "hal-article__abstract");
|
|
141
|
+
t.textContent = e.abstract_s[0], o.appendChild(t);
|
|
142
|
+
}
|
|
143
|
+
n.appendChild(o);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return n;
|
|
147
|
+
}
|
|
148
|
+
function _(e, t) {
|
|
149
|
+
let n = Math.max(1, Math.ceil(e.totalFound / e.rows)), { currentPage: r } = e, i = l("nav", "hal-pagination");
|
|
150
|
+
i.setAttribute("aria-label", "Search results pages");
|
|
151
|
+
let a = l("button", "hal-pagination__btn");
|
|
152
|
+
a.textContent = "← Previous", a.disabled = r <= 1, a.addEventListener("click", () => t(r - 1)), i.appendChild(a);
|
|
153
|
+
let o = l("span", "hal-pagination__info");
|
|
154
|
+
o.textContent = `Page ${r} of ${n} (${e.totalFound.toLocaleString()} results)`, i.appendChild(o);
|
|
155
|
+
let s = l("button", "hal-pagination__btn");
|
|
156
|
+
return s.textContent = "Next →", s.disabled = r >= n, s.addEventListener("click", () => t(r + 1)), i.appendChild(s), i;
|
|
157
|
+
}
|
|
158
|
+
function v(e, t, n, r, i) {
|
|
159
|
+
if (e.innerHTML = "", t.length === 0) {
|
|
160
|
+
h(e);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
let a = l("div", "hal-results");
|
|
164
|
+
for (let e of t) a.appendChild(g(e, n));
|
|
165
|
+
r.totalFound > r.rows && a.appendChild(_(r, i));
|
|
166
|
+
let o = l("div", "hal-footer"), s = f("https://github.com/JPugetGil/hal-search", "hal-search", "hal-footer__link");
|
|
167
|
+
o.appendChild(s), a.appendChild(o), e.appendChild(a);
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/svg-renderer.ts
|
|
171
|
+
var y = "http://www.w3.org/2000/svg", b = 800, x = 16, S = 8, C = 50, w = 28, T = "https://github.com/JPugetGil/hal-search", E = {
|
|
172
|
+
accent: "#0052cc",
|
|
173
|
+
accentText: "#ffffff",
|
|
174
|
+
bg: "#f2f4f8",
|
|
175
|
+
cardBg: "#ffffff",
|
|
176
|
+
border: "#e0e0e0",
|
|
177
|
+
text: "#1a1a1a",
|
|
178
|
+
muted: "#666666",
|
|
179
|
+
link: "#0052cc",
|
|
180
|
+
oaBg: "#e3f5ee",
|
|
181
|
+
oaColor: "#006644",
|
|
182
|
+
tagBg: "#f0f0f0",
|
|
183
|
+
tagColor: "#444444",
|
|
184
|
+
domainBg: "#dbeafe",
|
|
185
|
+
domainColor: "#1a56db"
|
|
186
|
+
}, D = 11, O = 15, k = 3, A = 18, j = 10;
|
|
187
|
+
function M(e) {
|
|
188
|
+
return document.createElementNS(y, e);
|
|
189
|
+
}
|
|
190
|
+
function N(e, t) {
|
|
191
|
+
for (let [n, r] of Object.entries(t)) e.setAttribute(n, String(r));
|
|
192
|
+
}
|
|
193
|
+
function P(e) {
|
|
194
|
+
let t = document.createElement("textarea");
|
|
195
|
+
return t.innerHTML = e, t.value;
|
|
196
|
+
}
|
|
197
|
+
function F(e, t, n) {
|
|
198
|
+
let r = n * .56, i = Math.floor(t / r);
|
|
199
|
+
return e.length > i ? e.slice(0, i - 1) + "…" : e;
|
|
200
|
+
}
|
|
201
|
+
function I(e, t, n, r, i, a) {
|
|
202
|
+
let o = M("rect");
|
|
203
|
+
return N(o, {
|
|
204
|
+
x: e,
|
|
205
|
+
y: t,
|
|
206
|
+
width: n,
|
|
207
|
+
height: r,
|
|
208
|
+
fill: i,
|
|
209
|
+
...a
|
|
210
|
+
}), o;
|
|
211
|
+
}
|
|
212
|
+
function L(e, t, n, r) {
|
|
213
|
+
let i = M("text");
|
|
214
|
+
return N(i, {
|
|
215
|
+
x: e,
|
|
216
|
+
y: t,
|
|
217
|
+
"font-family": "system-ui,-apple-system,sans-serif",
|
|
218
|
+
...r
|
|
219
|
+
}), i.textContent = n, i;
|
|
220
|
+
}
|
|
221
|
+
function R(e, t) {
|
|
222
|
+
let n = M("a");
|
|
223
|
+
return n.setAttribute("href", e), n.setAttribute("target", "_blank"), n.appendChild(t), n;
|
|
224
|
+
}
|
|
225
|
+
function z(e, t, n, r, i, a) {
|
|
226
|
+
let o = r.length * 11 * .6 + 10;
|
|
227
|
+
return e.appendChild(I(t, n - 11, o, 17, i, { rx: 3 })), e.appendChild(L(t + 5, n - 1, r, {
|
|
228
|
+
"font-size": 11,
|
|
229
|
+
fill: a
|
|
230
|
+
})), o + 4;
|
|
231
|
+
}
|
|
232
|
+
function B(e, t, n, r, i, a, o, s, c = k) {
|
|
233
|
+
let l = Math.floor(i / (a * .52)), u = r.split(/\s+/), d = [], f = "", p = !1;
|
|
234
|
+
for (let e = 0; e < u.length; e++) {
|
|
235
|
+
let t = u[e], n = f ? `${f} ${t}` : t;
|
|
236
|
+
if (n.length <= l) f = n;
|
|
237
|
+
else {
|
|
238
|
+
if (f && d.push(f), d.length >= c) {
|
|
239
|
+
p = !0;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
f = t;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
!p && f ? d.push(f) : p && d.length > 0 && (d[d.length - 1] += "…");
|
|
246
|
+
let m = M("text");
|
|
247
|
+
N(m, {
|
|
248
|
+
x: t,
|
|
249
|
+
y: n,
|
|
250
|
+
"font-size": a,
|
|
251
|
+
fill: s,
|
|
252
|
+
"font-family": "system-ui,-apple-system,sans-serif"
|
|
253
|
+
});
|
|
254
|
+
for (let e = 0; e < d.length; e++) {
|
|
255
|
+
let n = M("tspan");
|
|
256
|
+
n.setAttribute("x", String(t)), e > 0 && n.setAttribute("dy", String(o)), n.textContent = d[e], m.appendChild(n);
|
|
257
|
+
}
|
|
258
|
+
return e.appendChild(m), d.length * o;
|
|
259
|
+
}
|
|
260
|
+
function V(e, t) {
|
|
261
|
+
if (t !== 3 || !e.abstract_s?.[0]) return 0;
|
|
262
|
+
let n = b - x * 4, r = Math.floor(n / (D * .52)), i = e.abstract_s[0].split(/\s+/), a = 1, o = 0;
|
|
263
|
+
for (let e of i) if (o + e.length + (o ? 1 : 0) > r) {
|
|
264
|
+
if (a++, o = e.length, a >= k) break;
|
|
265
|
+
} else o += e.length + (o ? 1 : 0);
|
|
266
|
+
return j + A + Math.min(a, k) * O;
|
|
267
|
+
}
|
|
268
|
+
function H(e, t) {
|
|
269
|
+
return t === 0 ? 44 : (t >= 2 && ((e.keyword_s?.length ?? 0) > 0 || (e.domain_s?.length ?? 0) > 0 || e.conferenceTitle_s) ? 86 : 62) + V(e, t);
|
|
270
|
+
}
|
|
271
|
+
function U(e, t, n) {
|
|
272
|
+
let r = M("g"), i = H(e, t), a = x, o = b - x * 2, s = x * 2;
|
|
273
|
+
if (r.appendChild(I(a, n, o, i, E.cardBg, {
|
|
274
|
+
rx: 6,
|
|
275
|
+
stroke: E.border,
|
|
276
|
+
"stroke-width": 1
|
|
277
|
+
})), t === 0) {
|
|
278
|
+
let t = F(P(e.label_s ?? e.docid ?? ""), o - x * 2, 12), i = L(s, n + 26, t, {
|
|
279
|
+
"font-size": 12,
|
|
280
|
+
fill: E.link
|
|
281
|
+
});
|
|
282
|
+
return e.uri_s?.startsWith("http") ? r.appendChild(R(e.uri_s, i)) : (i.setAttribute("fill", E.text), r.appendChild(i)), r;
|
|
283
|
+
}
|
|
284
|
+
let c = F(e.title_s?.[0] ?? e.label_s ?? "Untitled", o - x * 2, 14), l = L(s, n + 22, c, {
|
|
285
|
+
"font-size": 14,
|
|
286
|
+
"font-weight": "bold",
|
|
287
|
+
fill: e.uri_s?.startsWith("http") ? E.link : E.text
|
|
288
|
+
});
|
|
289
|
+
r.appendChild(e.uri_s?.startsWith("http") ? R(e.uri_s, l) : l);
|
|
290
|
+
let u = n + 44, d = [];
|
|
291
|
+
e.authFullName_s?.length && d.push(F(e.authFullName_s.join(", "), o * .5, 12)), e.publicationDate_s && d.push(e.publicationDate_s.slice(0, 4)), d.length && r.appendChild(L(s, u, d.join(" · "), {
|
|
292
|
+
"font-size": 12,
|
|
293
|
+
fill: E.muted
|
|
294
|
+
}));
|
|
295
|
+
let f = a + o - x;
|
|
296
|
+
if (e.openAccess_bool === !0 && (f -= 86.6, z(r, f, u, "Open Access", E.oaBg, E.oaColor)), e.docType_s && e.docType_s.toUpperCase() !== "UNDEFINED") {
|
|
297
|
+
let t = e.docType_s;
|
|
298
|
+
f -= t.length * 11 * .6 + 10 + 4, z(r, f, u, t, E.tagBg, E.tagColor);
|
|
299
|
+
}
|
|
300
|
+
let p = u + x;
|
|
301
|
+
if (t >= 2) {
|
|
302
|
+
let t = s, i = n + 68, c = a + o - x;
|
|
303
|
+
p = i + x;
|
|
304
|
+
for (let n of e.keyword_s ?? []) {
|
|
305
|
+
let e = n.length * 11 * .6 + 14;
|
|
306
|
+
if (t + e > c) break;
|
|
307
|
+
t += z(r, t, i, n, E.tagBg, E.tagColor);
|
|
308
|
+
}
|
|
309
|
+
for (let n of e.domain_s ?? []) {
|
|
310
|
+
let e = n.length * 11 * .6 + 14;
|
|
311
|
+
if (t + e > c) break;
|
|
312
|
+
t += z(r, t, i, n, E.domainBg, E.domainColor);
|
|
313
|
+
}
|
|
314
|
+
if (e.conferenceTitle_s && t < c) {
|
|
315
|
+
let n = F(e.conferenceTitle_s, c - t, 11);
|
|
316
|
+
r.appendChild(L(t, i, n, {
|
|
317
|
+
"font-size": 11,
|
|
318
|
+
fill: E.muted,
|
|
319
|
+
"font-style": "italic"
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (t === 3 && e.abstract_s?.[0]) {
|
|
324
|
+
let t = p + j;
|
|
325
|
+
r.appendChild(I(s, t - 4, o - x * 2, 1, E.border)), r.appendChild(L(s, t + A - 4, "Abstract", {
|
|
326
|
+
"font-size": 11,
|
|
327
|
+
fill: E.muted,
|
|
328
|
+
"font-style": "italic"
|
|
329
|
+
})), B(r, s, t + A + O - 2, e.abstract_s[0], o - x * 2, D, O, E.text);
|
|
330
|
+
}
|
|
331
|
+
return r;
|
|
332
|
+
}
|
|
333
|
+
function W(e, t, n) {
|
|
334
|
+
let r = e.reduce((e, n) => e + H(n, t) + S, 0), i = C + S + r + w, a = M("svg");
|
|
335
|
+
N(a, {
|
|
336
|
+
width: b,
|
|
337
|
+
height: i,
|
|
338
|
+
viewBox: `0 0 ${b} ${i}`,
|
|
339
|
+
xmlns: y,
|
|
340
|
+
role: "img",
|
|
341
|
+
"aria-label": "HAL Search Results"
|
|
342
|
+
}), a.appendChild(I(0, 0, b, i, E.bg)), a.appendChild(I(0, 0, b, C, E.accent)), a.appendChild(L(x, 32, "HAL Search Results", {
|
|
343
|
+
"font-size": 18,
|
|
344
|
+
"font-weight": "bold",
|
|
345
|
+
fill: E.accentText
|
|
346
|
+
}));
|
|
347
|
+
let { totalFound: o, currentPage: s, rows: c } = n, l = `Page ${s} / ${Math.max(1, Math.ceil(o / c))} · ${o.toLocaleString()} results`;
|
|
348
|
+
a.appendChild(L(b - x, 32, l, {
|
|
349
|
+
"font-size": 12,
|
|
350
|
+
fill: E.accentText,
|
|
351
|
+
"text-anchor": "end"
|
|
352
|
+
}));
|
|
353
|
+
let u = C + S;
|
|
354
|
+
for (let n of e) a.appendChild(U(n, t, u)), u += H(n, t) + S;
|
|
355
|
+
let d = i - w / 2 + 4;
|
|
356
|
+
return a.appendChild(L(x, d, l, {
|
|
357
|
+
"font-size": 11,
|
|
358
|
+
fill: E.muted
|
|
359
|
+
})), a.appendChild(R(T, L(b - x, d, "hal-search", {
|
|
360
|
+
"font-size": 11,
|
|
361
|
+
fill: E.muted,
|
|
362
|
+
"text-anchor": "end"
|
|
363
|
+
}))), a;
|
|
364
|
+
}
|
|
365
|
+
function G(e, t, n, r) {
|
|
366
|
+
if (e.innerHTML = "", t.length === 0) {
|
|
367
|
+
let t = document.createElement("p");
|
|
368
|
+
t.className = "hal-empty", t.textContent = "No results found.", e.appendChild(t);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
e.appendChild(W(t, n, r));
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/styles.ts
|
|
375
|
+
var K = "\n:root {\n --hal-accent: #0052cc;\n --hal-accent-hover: #003d99;\n --hal-bg: #ffffff;\n --hal-bg-article: #fafafa;\n --hal-border: #e0e0e0;\n --hal-text: #1a1a1a;\n --hal-text-muted: #666666;\n --hal-oa-color: #006644;\n --hal-oa-bg: #e3f5ee;\n --hal-tag-bg: #f0f0f0;\n --hal-tag-color: #444444;\n --hal-font: system-ui, -apple-system, sans-serif;\n --hal-radius: 6px;\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n\n.hal-results {\n font-family: var(--hal-font);\n color: var(--hal-text);\n width: 100%;\n}\n\n/* Loading state */\n.hal-loading {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 24px 0;\n color: var(--hal-text-muted);\n font-size: 0.95rem;\n}\n\n.hal-spinner {\n width: 18px;\n height: 18px;\n border: 2px solid var(--hal-border);\n border-top-color: var(--hal-accent);\n border-radius: 50%;\n animation: hal-spin 0.7s linear infinite;\n}\n\n@keyframes hal-spin {\n to { transform: rotate(360deg); }\n}\n\n/* Error and empty states */\n.hal-error,\n.hal-empty {\n padding: 16px;\n border-radius: var(--hal-radius);\n font-size: 0.9rem;\n}\n\n.hal-error {\n background: #fff5f5;\n border: 1px solid #ffc9c9;\n color: #c92a2a;\n}\n\n.hal-empty {\n background: var(--hal-bg-article);\n border: 1px solid var(--hal-border);\n color: var(--hal-text-muted);\n}\n\n/* Article card */\n.hal-article {\n background: var(--hal-bg);\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n padding: 18px 20px;\n margin-bottom: 12px;\n box-shadow: var(--hal-shadow);\n}\n\n.hal-article:last-of-type {\n margin-bottom: 0;\n}\n\n/* Title */\n.hal-article__title {\n margin: 0 0 8px 0;\n font-size: 1rem;\n font-weight: 600;\n line-height: 1.4;\n}\n\n.hal-article__title a {\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__title a:hover {\n color: var(--hal-accent-hover);\n text-decoration: underline;\n}\n\n/* Label (minimal mode) */\n.hal-article__label {\n margin: 0 0 8px 0;\n font-size: 0.9rem;\n line-height: 1.5;\n color: var(--hal-text);\n}\n\n/* Meta row */\n.hal-article__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n margin-bottom: 4px;\n}\n\n.hal-article__authors {\n font-weight: 500;\n color: var(--hal-text);\n}\n\n.hal-article__date::before {\n content: '·';\n margin-right: 6px;\n}\n\n/* Badges */\n.hal-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.75rem;\n font-weight: 600;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n letter-spacing: 0.02em;\n text-transform: uppercase;\n}\n\n.hal-badge--oa {\n background: var(--hal-oa-bg);\n color: var(--hal-oa-color);\n}\n\n/* Details section (lvl >= 2) */\n.hal-article__details {\n margin-top: 10px;\n padding-top: 10px;\n border-top: 1px solid var(--hal-border);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.hal-article__tags {\n display: flex;\n flex-wrap: wrap;\n gap: 5px;\n}\n\n.hal-tag {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 4px;\n font-size: 0.78rem;\n background: var(--hal-tag-bg);\n color: var(--hal-tag-color);\n}\n\n.hal-tag--domain {\n background: #e8f0fe;\n color: #1a56c4;\n}\n\n.hal-article__conference {\n font-size: 0.85rem;\n color: var(--hal-text-muted);\n font-style: italic;\n}\n\n.hal-article__abstract {\n font-size: 0.85rem;\n color: var(--hal-text);\n line-height: 1.55;\n margin-top: 4px;\n}\n\n.hal-article__link {\n font-size: 0.82rem;\n color: var(--hal-accent);\n text-decoration: none;\n}\n\n.hal-article__link:hover {\n text-decoration: underline;\n}\n\n/* Footer credit */\n.hal-footer {\n margin-top: 12px;\n text-align: right;\n font-size: 0.75rem;\n}\n\n.hal-footer__link {\n color: var(--hal-text-muted);\n text-decoration: none;\n}\n\n.hal-footer__link:hover {\n color: var(--hal-accent);\n text-decoration: underline;\n}\n\n/* Pagination */\n.hal-pagination {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-top: 20px;\n padding-top: 16px;\n border-top: 1px solid var(--hal-border);\n font-size: 0.875rem;\n}\n\n.hal-pagination__info {\n color: var(--hal-text-muted);\n}\n\n.hal-pagination__btn {\n padding: 7px 16px;\n border: 1px solid var(--hal-border);\n border-radius: var(--hal-radius);\n background: var(--hal-bg);\n color: var(--hal-accent);\n font-size: 0.875rem;\n cursor: pointer;\n transition: background 0.15s, border-color 0.15s;\n}\n\n.hal-pagination__btn:hover:not(:disabled) {\n background: #f0f4ff;\n border-color: var(--hal-accent);\n}\n\n.hal-pagination__btn:disabled {\n color: var(--hal-text-muted);\n cursor: not-allowed;\n opacity: 0.5;\n}\n";
|
|
376
|
+
function q() {
|
|
377
|
+
let e = "hal-search-styles";
|
|
378
|
+
if (document.getElementById(e)) return;
|
|
379
|
+
let t = document.createElement("style");
|
|
380
|
+
t.id = e, t.textContent = K, document.head.appendChild(t);
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/HalSearch.ts
|
|
384
|
+
var J = {
|
|
385
|
+
lvl: 1,
|
|
386
|
+
rows: 10,
|
|
387
|
+
apiBase: o,
|
|
388
|
+
injectStyles: !0,
|
|
389
|
+
output: "html"
|
|
390
|
+
}, Y = class {
|
|
391
|
+
constructor(e) {
|
|
392
|
+
this.currentUid = "", e.container && (this.container = this._resolveContainer(e.container)), this.options = {
|
|
393
|
+
lvl: e.lvl ?? J.lvl,
|
|
394
|
+
rows: e.rows ?? J.rows,
|
|
395
|
+
apiBase: e.apiBase ?? J.apiBase,
|
|
396
|
+
injectStyles: e.injectStyles ?? J.injectStyles,
|
|
397
|
+
output: e.output ?? J.output,
|
|
398
|
+
onResults: e.onResults,
|
|
399
|
+
onError: e.onError
|
|
400
|
+
}, this.pagination = {
|
|
401
|
+
currentPage: 1,
|
|
402
|
+
totalFound: 0,
|
|
403
|
+
rows: this.options.rows,
|
|
404
|
+
start: 0
|
|
405
|
+
}, this.options.injectStyles && q();
|
|
406
|
+
}
|
|
407
|
+
async search(e) {
|
|
408
|
+
return this.currentUid = e.uid, e.rows !== void 0 && (this.options.rows = e.rows), this.pagination = {
|
|
409
|
+
currentPage: 1,
|
|
410
|
+
totalFound: 0,
|
|
411
|
+
rows: this.options.rows,
|
|
412
|
+
start: e.start ?? 0
|
|
413
|
+
}, this._fetch(this.currentUid, this.pagination.start);
|
|
414
|
+
}
|
|
415
|
+
async goToPage(e) {
|
|
416
|
+
let t = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows)), n = (Math.min(Math.max(1, e), t) - 1) * this.options.rows;
|
|
417
|
+
return this._fetch(this.currentUid, n);
|
|
418
|
+
}
|
|
419
|
+
async nextPage() {
|
|
420
|
+
return this.goToPage(this.pagination.currentPage + 1);
|
|
421
|
+
}
|
|
422
|
+
async prevPage() {
|
|
423
|
+
return this.goToPage(this.pagination.currentPage - 1);
|
|
424
|
+
}
|
|
425
|
+
async setLevel(e) {
|
|
426
|
+
return this.options.lvl = e, this._fetch(this.currentUid, this.pagination.start);
|
|
427
|
+
}
|
|
428
|
+
destroy() {
|
|
429
|
+
this.container && (this.container.innerHTML = "");
|
|
430
|
+
}
|
|
431
|
+
async _fetch(e, t) {
|
|
432
|
+
if (e) {
|
|
433
|
+
this.container && p(this.container);
|
|
434
|
+
try {
|
|
435
|
+
let n = await c(e, this.options.lvl, this.options.rows, t, this.options.apiBase);
|
|
436
|
+
if (this._updatePagination(n, t), this.options.output === "svg") if (this.container) G(this.container, n.response.docs, this.options.lvl, this.pagination);
|
|
437
|
+
else {
|
|
438
|
+
let e = W(n.response.docs, this.options.lvl, this.pagination);
|
|
439
|
+
return this.options.onResults?.(n), e;
|
|
440
|
+
}
|
|
441
|
+
else if (this.container) v(this.container, n.response.docs, this.options.lvl, this.pagination, (e) => {
|
|
442
|
+
this.goToPage(e);
|
|
443
|
+
});
|
|
444
|
+
else throw Error("HalSearch: container is required for HTML output");
|
|
445
|
+
this.options.onResults?.(n);
|
|
446
|
+
} catch (e) {
|
|
447
|
+
let t = e instanceof Error ? e : Error(String(e));
|
|
448
|
+
if (this.container && m(this.container, t), this.options.onError?.(t), !this.container && !this.options.onError) throw t;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
_resolveContainer(e) {
|
|
453
|
+
if (typeof e == "string") {
|
|
454
|
+
let t = document.querySelector(e);
|
|
455
|
+
if (!t) throw Error(`HalSearch: container not found for selector "${e}"`);
|
|
456
|
+
return t;
|
|
457
|
+
}
|
|
458
|
+
return e;
|
|
459
|
+
}
|
|
460
|
+
_updatePagination(e, t) {
|
|
461
|
+
let { numFound: n } = e.response;
|
|
462
|
+
this.pagination = {
|
|
463
|
+
currentPage: Math.floor(t / this.options.rows) + 1,
|
|
464
|
+
totalFound: n,
|
|
465
|
+
rows: this.options.rows,
|
|
466
|
+
start: t
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/embed.ts
|
|
472
|
+
function X(e) {
|
|
473
|
+
let t = new URLSearchParams({ uid: e.uid });
|
|
474
|
+
return e.lvl !== void 0 && t.set("lvl", String(e.lvl)), e.rows !== void 0 && t.set("rows", String(e.rows)), e.type && t.set("type", e.type), `${e.embedBase}/embed.html?${t.toString()}`;
|
|
475
|
+
}
|
|
476
|
+
function Z(e) {
|
|
477
|
+
return `<iframe src="${X(e)}" width="${e.width ?? "100%"}" height="${e.height ?? "600"}"></iframe>`;
|
|
478
|
+
}
|
|
479
|
+
//#endregion
|
|
480
|
+
export { o as DEFAULT_BASE, K as DEFAULT_CSS, Y as HalSearch, r as LEVEL_FIELDS, i as LEVEL_NAMES, W as buildArticlesSvg, Z as buildEmbedSnippet, X as buildEmbedUrl, s as buildUrl, c as fetchArticles, q as injectDefaultStyles, G as renderResultsSvg, a as resolveFields };
|
|
481
|
+
|
|
482
|
+
//# sourceMappingURL=hal-search.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hal-search.es.js","names":[],"sources":["../src/levels.ts","../src/api.ts","../src/renderer.ts","../src/svg-renderer.ts","../src/styles.ts","../src/HalSearch.ts","../src/embed.ts"],"sourcesContent":["import type { DetailLevel, LevelName } from './types';\n\nconst MINIMAL_FIELDS = ['docid', 'label_s', 'uri_s'] as const;\n\nconst BASIC_FIELDS = [\n ...MINIMAL_FIELDS,\n 'title_s',\n 'authFullName_s',\n 'publicationDate_s',\n 'docType_s',\n] as const;\n\nconst DETAILED_FIELDS = [\n ...BASIC_FIELDS,\n 'keyword_s',\n 'domain_s',\n 'openAccess_bool',\n 'language_s',\n 'peerReviewing_s',\n 'conferenceTitle_s',\n] as const;\n\n/** Maps a DetailLevel to its comma-joined `fl` parameter string */\nexport const LEVEL_FIELDS: Record<DetailLevel, string> = {\n 0: MINIMAL_FIELDS.join(','),\n 1: BASIC_FIELDS.join(','),\n 2: DETAILED_FIELDS.join(','),\n 3: '*',\n};\n\nexport const LEVEL_NAMES: Record<DetailLevel, LevelName> = {\n 0: 'minimal',\n 1: 'basic',\n 2: 'detailed',\n 3: 'full',\n};\n\n/** Returns the `fl` field string for the given detail level */\nexport function resolveFields(lvl: DetailLevel): string {\n return LEVEL_FIELDS[lvl] ?? LEVEL_FIELDS[1];\n}\n","import type { HalApiResponse, DetailLevel } from './types';\r\nimport { resolveFields } from './levels';\r\n\r\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\r\n\r\n/**\r\n * Builds a HAL API search URL from the given parameters.\r\n * Uses URLSearchParams to safely encode special characters in uid.\r\n */\r\nexport function buildUrl(\r\n uid: string,\r\n lvl: DetailLevel,\r\n rows: number,\r\n start: number,\r\n base = DEFAULT_BASE,\r\n): string {\r\n const fl = resolveFields(lvl);\r\n const params = new URLSearchParams({\r\n q: `\"${uid}\"`,\r\n wt: 'json',\r\n fl,\r\n rows: String(rows),\r\n start: String(start),\r\n });\r\n return `${base}?${params.toString()}`;\r\n}\r\n\r\n/**\r\n * Fetches articles from the HAL API.\r\n * Throws on HTTP errors or non-zero API status codes.\r\n */\r\nexport async function fetchArticles(\r\n uid: string,\r\n lvl: DetailLevel,\r\n rows: number,\r\n start: number,\r\n base = DEFAULT_BASE,\r\n): Promise<HalApiResponse> {\r\n const url = buildUrl(uid, lvl, rows, start, base);\r\n const res = await fetch(url, {\r\n headers: { Accept: 'application/json' },\r\n });\r\n\r\n if (!res.ok) {\r\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\r\n }\r\n\r\n const data: HalApiResponse = await res.json();\r\n\r\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\r\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\r\n }\r\n\r\n return data;\r\n}\r\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction el<K extends keyof HTMLElementTagNameMap>(\r\n tag: K,\r\n className?: string,\r\n): HTMLElementTagNameMap[K] {\r\n const node = document.createElement(tag);\r\n if (className) node.className = className;\r\n return node;\r\n}\r\n\r\nfunction text(content: string): Text {\r\n return document.createTextNode(content);\r\n}\r\n\r\n/** Decodes HTML entities (e.g. ⟨) into their actual characters. */\r\nfunction decodeEntities(raw: string): string {\r\n const textarea = document.createElement('textarea');\r\n textarea.innerHTML = raw;\r\n return textarea.value;\r\n}\r\n\r\n/** Creates an <a> element with href validated to start with https:// */\r\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\r\n const a = el('a', className);\r\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\r\n a.href = href;\r\n }\r\n a.target = '_blank';\r\n a.rel = 'noopener noreferrer';\r\n a.textContent = label;\r\n return a;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// State renderers\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function renderLoading(container: HTMLElement): void {\r\n container.innerHTML = '';\r\n const wrap = el('div', 'hal-loading');\r\n const spinner = el('div', 'hal-spinner');\r\n wrap.appendChild(spinner);\r\n wrap.appendChild(text('Loading…'));\r\n container.appendChild(wrap);\r\n}\r\n\r\nexport function renderError(container: HTMLElement, err: Error): void {\r\n container.innerHTML = '';\r\n const wrap = el('div', 'hal-error');\r\n wrap.textContent = `Error: ${err.message}`;\r\n container.appendChild(wrap);\r\n}\r\n\r\nexport function renderEmpty(container: HTMLElement): void {\r\n container.innerHTML = '';\r\n const wrap = el('div', 'hal-empty');\r\n wrap.textContent = 'No results found.';\r\n container.appendChild(wrap);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Article card\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\r\n const article = el('article', 'hal-article');\r\n if (doc.docid) article.dataset.docid = doc.docid;\r\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\r\n\r\n // --- Header ---\r\n const header = el('header');\r\n\r\n if (lvl >= 1) {\r\n // Title + link\r\n const h3 = el('h3', 'hal-article__title');\r\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\r\n h3.appendChild(safeLink(doc.uri_s, titleText));\r\n header.appendChild(h3);\r\n\r\n // Meta row\r\n const meta = el('div', 'hal-article__meta');\r\n\r\n if (doc.authFullName_s?.length) {\r\n const authors = el('span', 'hal-article__authors');\r\n authors.textContent = doc.authFullName_s.join(', ');\r\n meta.appendChild(authors);\r\n }\r\n\r\n if (doc.publicationDate_s) {\r\n const date = el('span', 'hal-article__date');\r\n // Show only the year if it's a full date string\r\n date.textContent = doc.publicationDate_s.slice(0, 4);\r\n meta.appendChild(date);\r\n }\r\n\r\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\r\n const badge = el('span', 'hal-badge');\r\n badge.textContent = doc.docType_s;\r\n meta.appendChild(badge);\r\n }\r\n\r\n if (doc.openAccess_bool === true) {\r\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\r\n oaBadge.textContent = 'Open Access';\r\n meta.appendChild(oaBadge);\r\n }\r\n\r\n header.appendChild(meta);\r\n } else {\r\n // lvl 0: just the full citation\r\n const div = el('div', 'hal-article__label');\r\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\r\n header.appendChild(div);\r\n }\r\n\r\n article.appendChild(header);\r\n\r\n // --- Details (lvl >= 2) ---\r\n if (lvl >= 2) {\r\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\r\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\r\n const hasConference = Boolean(doc.conferenceTitle_s);\r\n\r\n if (hasKeywords || hasDomains || hasConference) {\r\n const details = el('section', 'hal-article__details');\r\n\r\n if (hasKeywords) {\r\n const tagsWrap = el('div', 'hal-article__tags');\r\n for (const kw of doc.keyword_s!) {\r\n const tag = el('span', 'hal-tag');\r\n tag.textContent = kw;\r\n tagsWrap.appendChild(tag);\r\n }\r\n details.appendChild(tagsWrap);\r\n }\r\n\r\n if (hasDomains) {\r\n const domainsWrap = el('div', 'hal-article__tags');\r\n for (const domain of doc.domain_s!) {\r\n const tag = el('span', 'hal-tag hal-tag--domain');\r\n tag.textContent = domain;\r\n domainsWrap.appendChild(tag);\r\n }\r\n details.appendChild(domainsWrap);\r\n }\r\n\r\n if (hasConference) {\r\n const conf = el('div', 'hal-article__conference');\r\n conf.textContent = doc.conferenceTitle_s!;\r\n details.appendChild(conf);\r\n }\r\n\r\n if (lvl === 3 && doc.abstract_s?.[0]) {\r\n const abstract = el('div', 'hal-article__abstract');\r\n abstract.textContent = doc.abstract_s[0];\r\n details.appendChild(abstract);\r\n }\r\n\r\n article.appendChild(details);\r\n }\r\n }\r\n\r\n return article;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Pagination bar\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction buildPagination(\r\n pagination: PaginationState,\r\n onPageChange: (page: number) => void,\r\n): HTMLElement {\r\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\r\n const { currentPage } = pagination;\r\n\r\n const nav = el('nav', 'hal-pagination');\r\n nav.setAttribute('aria-label', 'Search results pages');\r\n\r\n const prevBtn = el('button', 'hal-pagination__btn');\r\n prevBtn.textContent = '← Previous';\r\n prevBtn.disabled = currentPage <= 1;\r\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\r\n nav.appendChild(prevBtn);\r\n\r\n const info = el('span', 'hal-pagination__info');\r\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\r\n nav.appendChild(info);\r\n\r\n const nextBtn = el('button', 'hal-pagination__btn');\r\n nextBtn.textContent = 'Next →';\r\n nextBtn.disabled = currentPage >= totalPages;\r\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\r\n nav.appendChild(nextBtn);\r\n\r\n return nav;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Main results renderer\r\n// ---------------------------------------------------------------------------\r\n\r\nexport function renderResults(\r\n container: HTMLElement,\r\n docs: HalDoc[],\r\n lvl: DetailLevel,\r\n pagination: PaginationState,\r\n onPageChange: (page: number) => void,\r\n): void {\r\n container.innerHTML = '';\r\n\r\n if (docs.length === 0) {\r\n renderEmpty(container);\r\n return;\r\n }\r\n\r\n const wrapper = el('div', 'hal-results');\r\n\r\n for (const doc of docs) {\r\n wrapper.appendChild(buildArticleCard(doc, lvl));\r\n }\r\n\r\n if (pagination.totalFound > pagination.rows) {\r\n wrapper.appendChild(buildPagination(pagination, onPageChange));\r\n }\r\n\r\n const footer = el('div', 'hal-footer');\r\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\r\n footer.appendChild(credit);\r\n wrapper.appendChild(footer);\r\n\r\n container.appendChild(wrapper);\r\n}\r\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\r\n\r\nconst NS = 'http://www.w3.org/2000/svg';\r\nconst W = 800;\r\nconst PAD = 16;\r\nconst CARD_GAP = 8;\r\nconst HEADER_H = 50;\r\nconst FOOTER_H = 28;\r\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\r\n\r\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\r\nconst C = {\r\n accent: '#0052cc',\r\n accentText: '#ffffff',\r\n bg: '#f2f4f8',\r\n cardBg: '#ffffff',\r\n border: '#e0e0e0',\r\n text: '#1a1a1a',\r\n muted: '#666666',\r\n link: '#0052cc',\r\n oaBg: '#e3f5ee',\r\n oaColor: '#006644',\r\n tagBg: '#f0f0f0',\r\n tagColor: '#444444',\r\n domainBg: '#dbeafe',\r\n domainColor: '#1a56db',\r\n};\r\n\r\n// Abstract rendering constants\r\nconst ABSTRACT_FS = 11;\r\nconst ABSTRACT_LINE_H = 15;\r\nconst ABSTRACT_MAX_LINES = 3;\r\nconst ABSTRACT_LABEL_H = 18;\r\nconst ABSTRACT_TOP_GAP = 10;\r\n\r\n// ---------------------------------------------------------------------------\r\n// SVG helpers\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\r\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\r\n}\r\n\r\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\r\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\r\n}\r\n\r\nfunction decodeEntities(raw: string): string {\r\n const ta = document.createElement('textarea');\r\n ta.innerHTML = raw;\r\n return ta.value;\r\n}\r\n\r\n/** Approximate truncation (SVG has no native text-measurement API). */\r\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\r\n const charW = fontSize * 0.56;\r\n const max = Math.floor(maxPx / charW);\r\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\r\n}\r\n\r\nfunction mkRect(\r\n x: number, y: number, w: number, h: number,\r\n fill: string,\r\n extra?: Record<string, string | number>,\r\n): SVGRectElement {\r\n const r = svgEl('rect');\r\n set(r, { x, y, width: w, height: h, fill, ...extra });\r\n return r;\r\n}\r\n\r\nfunction mkText(\r\n x: number, y: number, content: string,\r\n extra?: Record<string, string | number>,\r\n): SVGTextElement {\r\n const t = svgEl('text');\r\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\r\n t.textContent = content;\r\n return t;\r\n}\r\n\r\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\r\n const a = svgEl('a');\r\n a.setAttribute('href', href);\r\n a.setAttribute('target', '_blank');\r\n a.appendChild(child);\r\n return a;\r\n}\r\n\r\n/**\r\n * Renders a pill badge anchored at (x, baseline-y).\r\n * Returns the total pixel width consumed, including a 4 px trailing gap.\r\n */\r\nfunction pill(\r\n parent: SVGElement,\r\n x: number, y: number,\r\n label: string,\r\n bg: string, color: string,\r\n): number {\r\n const fs = 11;\r\n const ph = 5, pv = 3;\r\n const bw = label.length * fs * 0.6 + ph * 2;\r\n const bh = fs + pv * 2;\r\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\r\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\r\n return bw + 4;\r\n}\r\n\r\n/**\r\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\r\n * Returns the total pixel height consumed (lines × lineHeight).\r\n */\r\nfunction wrapText(\r\n parent: SVGElement,\r\n x: number, baseY: number,\r\n content: string,\r\n maxPx: number,\r\n fontSize: number,\r\n lineHeight: number,\r\n fill: string,\r\n maxLines = ABSTRACT_MAX_LINES,\r\n): number {\r\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\r\n const words = content.split(/\\s+/);\r\n const lines: string[] = [];\r\n let cur = '';\r\n let truncated = false;\r\n\r\n for (let i = 0; i < words.length; i++) {\r\n const word = words[i]!;\r\n const test = cur ? `${cur} ${word}` : word;\r\n if (test.length <= maxChars) {\r\n cur = test;\r\n } else {\r\n if (cur) lines.push(cur);\r\n if (lines.length >= maxLines) { truncated = true; break; }\r\n cur = word;\r\n }\r\n }\r\n if (!truncated && cur) {\r\n lines.push(cur);\r\n } else if (truncated && lines.length > 0) {\r\n lines[lines.length - 1] += '…';\r\n }\r\n\r\n const t = svgEl('text');\r\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\r\n for (let i = 0; i < lines.length; i++) {\r\n const ts = svgEl('tspan');\r\n ts.setAttribute('x', String(x));\r\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\r\n ts.textContent = lines[i]!;\r\n t.appendChild(ts);\r\n }\r\n parent.appendChild(t);\r\n return lines.length * lineHeight;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Card geometry\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Returns the extra height added by the abstract section, or 0 if absent. */\r\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\r\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\r\n // Pre-estimate the number of wrapped lines\r\n const maxPx = W - PAD * 4;\r\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\r\n const words = doc.abstract_s[0].split(/\\s+/);\r\n let lines = 1;\r\n let chars = 0;\r\n for (const word of words) {\r\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\r\n lines++;\r\n chars = word.length;\r\n if (lines >= ABSTRACT_MAX_LINES) break;\r\n } else {\r\n chars += word.length + (chars ? 1 : 0);\r\n }\r\n }\r\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\r\n}\r\n\r\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\r\n if (lvl === 0) return 44;\r\n const hasTagRow =\r\n lvl >= 2 &&\r\n ((doc.keyword_s?.length ?? 0) > 0 ||\r\n (doc.domain_s?.length ?? 0) > 0 ||\r\n Boolean(doc.conferenceTitle_s));\r\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Card builder\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number): SVGElement {\r\n const g = svgEl('g');\r\n const h = cardHeight(doc, lvl);\r\n const cx = PAD;\r\n const cw = W - PAD * 2;\r\n const ix = PAD * 2; // inner-x (text left margin)\r\n\r\n // Card background\r\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\r\n rx: 6, stroke: C.border, 'stroke-width': 1,\r\n }));\r\n\r\n // ── Level 0: citation label only ──────────────────────────────────────────\r\n if (lvl === 0) {\r\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\r\n const label = truncate(raw, cw - PAD * 2, 12);\r\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\r\n if (doc.uri_s?.startsWith('http')) {\r\n g.appendChild(mkLink(doc.uri_s, t));\r\n } else {\r\n t.setAttribute('fill', C.text);\r\n g.appendChild(t);\r\n }\r\n return g;\r\n }\r\n\r\n // ── Level 1+: title row ──────────────────────────────────────────────────\r\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\r\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\r\n const titleEl = mkText(ix, cardY + 22, titleStr, {\r\n 'font-size': 14,\r\n 'font-weight': 'bold',\r\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\r\n });\r\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\r\n\r\n // ── Meta row ─────────────────────────────────────────────────────────────\r\n const metaY = cardY + 44;\r\n\r\n // Left: \"authors · year\"\r\n const metaParts: string[] = [];\r\n if (doc.authFullName_s?.length) {\r\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\r\n }\r\n if (doc.publicationDate_s) {\r\n metaParts.push(doc.publicationDate_s.slice(0, 4));\r\n }\r\n if (metaParts.length) {\r\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\r\n 'font-size': 12, fill: C.muted,\r\n }));\r\n }\r\n\r\n // Right: badges (rendered right-to-left so order is docType | OA visually)\r\n let badgeRight = cx + cw - PAD;\r\n if (doc.openAccess_bool === true) {\r\n const label = 'Open Access';\r\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\r\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\r\n }\r\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\r\n const label = doc.docType_s;\r\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\r\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\r\n }\r\n\r\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\r\n let nextSectionY = metaY + PAD;\r\n if (lvl >= 2) {\r\n let tagX = ix;\r\n const tagY = cardY + 68;\r\n const tagRight = cx + cw - PAD;\r\n nextSectionY = tagY + PAD;\r\n\r\n for (const kw of (doc.keyword_s ?? [])) {\r\n const bw = kw.length * 11 * 0.6 + 14;\r\n if (tagX + bw > tagRight) break;\r\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\r\n }\r\n for (const domain of (doc.domain_s ?? [])) {\r\n const bw = domain.length * 11 * 0.6 + 14;\r\n if (tagX + bw > tagRight) break;\r\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\r\n }\r\n if (doc.conferenceTitle_s && tagX < tagRight) {\r\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\r\n g.appendChild(mkText(tagX, tagY, label, {\r\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\r\n }));\r\n }\r\n }\r\n\r\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\r\n if (lvl === 3 && doc.abstract_s?.[0]) {\r\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\r\n // Separator line\r\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\r\n // \"Abstract\" label\r\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\r\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\r\n }));\r\n // Wrapped text\r\n wrapText(\r\n g,\r\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\r\n doc.abstract_s[0],\r\n cw - PAD * 2,\r\n ABSTRACT_FS,\r\n ABSTRACT_LINE_H,\r\n C.text,\r\n );\r\n }\r\n\r\n return g;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Public API\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Builds an SVG element representing the article list.\r\n * The SVG is self-contained and can be inserted into the DOM or serialised.\r\n */\r\nexport function buildArticlesSvg(\r\n docs: HalDoc[],\r\n lvl: DetailLevel,\r\n pagination: PaginationState,\r\n): SVGSVGElement {\r\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\r\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\r\n\r\n const svg = svgEl('svg');\r\n set(svg, {\r\n width: W,\r\n height: totalH,\r\n viewBox: `0 0 ${W} ${totalH}`,\r\n xmlns: NS,\r\n role: 'img',\r\n 'aria-label': 'HAL Search Results',\r\n });\r\n\r\n // Background\r\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\r\n\r\n // Header bar\r\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\r\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\r\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\r\n }));\r\n\r\n const { totalFound, currentPage, rows } = pagination;\r\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\r\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\r\n\r\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\r\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\r\n }));\r\n\r\n // Article cards\r\n let y = HEADER_H + CARD_GAP;\r\n for (const doc of docs) {\r\n svg.appendChild(buildCard(doc, lvl, y));\r\n y += cardHeight(doc, lvl) + CARD_GAP;\r\n }\r\n\r\n // Footer: pagination info (left) + GitHub credit (right)\r\n const footerY = totalH - FOOTER_H / 2 + 4;\r\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\r\n 'font-size': 11, fill: C.muted,\r\n }));\r\n svg.appendChild(mkLink(\r\n GITHUB_URL,\r\n mkText(W - PAD, footerY, 'hal-search', {\r\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\r\n }),\r\n ));\r\n\r\n return svg;\r\n}\r\n\r\n/**\r\n * Clears `container` and renders the article list as an inline SVG.\r\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\r\n */\r\nexport function renderResultsSvg(\r\n container: HTMLElement,\r\n docs: HalDoc[],\r\n lvl: DetailLevel,\r\n pagination: PaginationState,\r\n): void {\r\n container.innerHTML = '';\r\n if (docs.length === 0) {\r\n const p = document.createElement('p');\r\n p.className = 'hal-empty';\r\n p.textContent = 'No results found.';\r\n container.appendChild(p);\r\n return;\r\n }\r\n container.appendChild(buildArticlesSvg(docs, lvl, pagination));\r\n}\r\n","export const DEFAULT_CSS = `\r\n:root {\r\n --hal-accent: #0052cc;\r\n --hal-accent-hover: #003d99;\r\n --hal-bg: #ffffff;\r\n --hal-bg-article: #fafafa;\r\n --hal-border: #e0e0e0;\r\n --hal-text: #1a1a1a;\r\n --hal-text-muted: #666666;\r\n --hal-oa-color: #006644;\r\n --hal-oa-bg: #e3f5ee;\r\n --hal-tag-bg: #f0f0f0;\r\n --hal-tag-color: #444444;\r\n --hal-font: system-ui, -apple-system, sans-serif;\r\n --hal-radius: 6px;\r\n --hal-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\r\n}\r\n\r\n.hal-results {\r\n font-family: var(--hal-font);\r\n color: var(--hal-text);\r\n width: 100%;\r\n}\r\n\r\n/* Loading state */\r\n.hal-loading {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n padding: 24px 0;\r\n color: var(--hal-text-muted);\r\n font-size: 0.95rem;\r\n}\r\n\r\n.hal-spinner {\r\n width: 18px;\r\n height: 18px;\r\n border: 2px solid var(--hal-border);\r\n border-top-color: var(--hal-accent);\r\n border-radius: 50%;\r\n animation: hal-spin 0.7s linear infinite;\r\n}\r\n\r\n@keyframes hal-spin {\r\n to { transform: rotate(360deg); }\r\n}\r\n\r\n/* Error and empty states */\r\n.hal-error,\r\n.hal-empty {\r\n padding: 16px;\r\n border-radius: var(--hal-radius);\r\n font-size: 0.9rem;\r\n}\r\n\r\n.hal-error {\r\n background: #fff5f5;\r\n border: 1px solid #ffc9c9;\r\n color: #c92a2a;\r\n}\r\n\r\n.hal-empty {\r\n background: var(--hal-bg-article);\r\n border: 1px solid var(--hal-border);\r\n color: var(--hal-text-muted);\r\n}\r\n\r\n/* Article card */\r\n.hal-article {\r\n background: var(--hal-bg);\r\n border: 1px solid var(--hal-border);\r\n border-radius: var(--hal-radius);\r\n padding: 18px 20px;\r\n margin-bottom: 12px;\r\n box-shadow: var(--hal-shadow);\r\n}\r\n\r\n.hal-article:last-of-type {\r\n margin-bottom: 0;\r\n}\r\n\r\n/* Title */\r\n.hal-article__title {\r\n margin: 0 0 8px 0;\r\n font-size: 1rem;\r\n font-weight: 600;\r\n line-height: 1.4;\r\n}\r\n\r\n.hal-article__title a {\r\n color: var(--hal-accent);\r\n text-decoration: none;\r\n}\r\n\r\n.hal-article__title a:hover {\r\n color: var(--hal-accent-hover);\r\n text-decoration: underline;\r\n}\r\n\r\n/* Label (minimal mode) */\r\n.hal-article__label {\r\n margin: 0 0 8px 0;\r\n font-size: 0.9rem;\r\n line-height: 1.5;\r\n color: var(--hal-text);\r\n}\r\n\r\n/* Meta row */\r\n.hal-article__meta {\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: center;\r\n gap: 6px;\r\n font-size: 0.85rem;\r\n color: var(--hal-text-muted);\r\n margin-bottom: 4px;\r\n}\r\n\r\n.hal-article__authors {\r\n font-weight: 500;\r\n color: var(--hal-text);\r\n}\r\n\r\n.hal-article__date::before {\r\n content: '·';\r\n margin-right: 6px;\r\n}\r\n\r\n/* Badges */\r\n.hal-badge {\r\n display: inline-block;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n background: var(--hal-tag-bg);\r\n color: var(--hal-tag-color);\r\n letter-spacing: 0.02em;\r\n text-transform: uppercase;\r\n}\r\n\r\n.hal-badge--oa {\r\n background: var(--hal-oa-bg);\r\n color: var(--hal-oa-color);\r\n}\r\n\r\n/* Details section (lvl >= 2) */\r\n.hal-article__details {\r\n margin-top: 10px;\r\n padding-top: 10px;\r\n border-top: 1px solid var(--hal-border);\r\n display: flex;\r\n flex-direction: column;\r\n gap: 6px;\r\n}\r\n\r\n.hal-article__tags {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 5px;\r\n}\r\n\r\n.hal-tag {\r\n display: inline-block;\r\n padding: 2px 8px;\r\n border-radius: 4px;\r\n font-size: 0.78rem;\r\n background: var(--hal-tag-bg);\r\n color: var(--hal-tag-color);\r\n}\r\n\r\n.hal-tag--domain {\r\n background: #e8f0fe;\r\n color: #1a56c4;\r\n}\r\n\r\n.hal-article__conference {\r\n font-size: 0.85rem;\r\n color: var(--hal-text-muted);\r\n font-style: italic;\r\n}\r\n\r\n.hal-article__abstract {\r\n font-size: 0.85rem;\r\n color: var(--hal-text);\r\n line-height: 1.55;\r\n margin-top: 4px;\r\n}\r\n\r\n.hal-article__link {\r\n font-size: 0.82rem;\r\n color: var(--hal-accent);\r\n text-decoration: none;\r\n}\r\n\r\n.hal-article__link:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n/* Footer credit */\r\n.hal-footer {\r\n margin-top: 12px;\r\n text-align: right;\r\n font-size: 0.75rem;\r\n}\r\n\r\n.hal-footer__link {\r\n color: var(--hal-text-muted);\r\n text-decoration: none;\r\n}\r\n\r\n.hal-footer__link:hover {\r\n color: var(--hal-accent);\r\n text-decoration: underline;\r\n}\r\n\r\n/* Pagination */\r\n.hal-pagination {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n margin-top: 20px;\r\n padding-top: 16px;\r\n border-top: 1px solid var(--hal-border);\r\n font-size: 0.875rem;\r\n}\r\n\r\n.hal-pagination__info {\r\n color: var(--hal-text-muted);\r\n}\r\n\r\n.hal-pagination__btn {\r\n padding: 7px 16px;\r\n border: 1px solid var(--hal-border);\r\n border-radius: var(--hal-radius);\r\n background: var(--hal-bg);\r\n color: var(--hal-accent);\r\n font-size: 0.875rem;\r\n cursor: pointer;\r\n transition: background 0.15s, border-color 0.15s;\r\n}\r\n\r\n.hal-pagination__btn:hover:not(:disabled) {\r\n background: #f0f4ff;\r\n border-color: var(--hal-accent);\r\n}\r\n\r\n.hal-pagination__btn:disabled {\r\n color: var(--hal-text-muted);\r\n cursor: not-allowed;\r\n opacity: 0.5;\r\n}\r\n`;\r\n\r\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\r\nexport function injectDefaultStyles(): void {\r\n const ID = 'hal-search-styles';\r\n if (document.getElementById(ID)) return;\r\n const style = document.createElement('style');\r\n style.id = ID;\r\n style.textContent = DEFAULT_CSS;\r\n document.head.appendChild(style);\r\n}\r\n","import type {\r\n HalSearchOptions,\r\n SearchParams,\r\n PaginationState,\r\n HalApiResponse,\r\n DetailLevel,\r\n} from './types';\r\nimport { fetchArticles, DEFAULT_BASE } from './api';\r\nimport { renderResults, renderLoading, renderError } from './renderer';\r\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\r\nimport { injectDefaultStyles } from './styles';\r\n\r\nconst DEFAULTS = {\r\n lvl: 1 as DetailLevel,\r\n rows: 10,\r\n apiBase: DEFAULT_BASE,\r\n injectStyles: true,\r\n output: 'html' as 'html' | 'svg',\r\n};\r\n\r\nexport class HalSearch {\r\n private readonly container?: HTMLElement;\r\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError'>> & {\r\n onResults?: HalSearchOptions['onResults'];\r\n onError?: HalSearchOptions['onError'];\r\n };\r\n private pagination: PaginationState;\r\n private currentUid: string = '';\r\n\r\n constructor(options: HalSearchOptions) {\r\n if (options.container) {\r\n this.container = this._resolveContainer(options.container);\r\n }\r\n\r\n this.options = {\r\n lvl: options.lvl ?? DEFAULTS.lvl,\r\n rows: options.rows ?? DEFAULTS.rows,\r\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\r\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\r\n output: options.output ?? DEFAULTS.output,\r\n onResults: options.onResults,\r\n onError: options.onError,\r\n };\r\n\r\n this.pagination = {\r\n currentPage: 1,\r\n totalFound: 0,\r\n rows: this.options.rows,\r\n start: 0,\r\n };\r\n\r\n if (this.options.injectStyles) {\r\n injectDefaultStyles();\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Public API\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Start a new search, resetting to page 1. */\r\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\r\n this.currentUid = params.uid;\r\n if (params.rows !== undefined) this.options.rows = params.rows;\r\n this.pagination = {\r\n currentPage: 1,\r\n totalFound: 0,\r\n rows: this.options.rows,\r\n start: params.start ?? 0,\r\n };\r\n return this._fetch(this.currentUid, this.pagination.start);\r\n }\r\n\r\n /** Navigate to a specific page number (1-based). */\r\n async goToPage(page: number): Promise<SVGSVGElement | void> {\r\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\r\n const clampedPage = Math.min(Math.max(1, page), totalPages);\r\n const start = (clampedPage - 1) * this.options.rows;\r\n return this._fetch(this.currentUid, start);\r\n }\r\n\r\n /** Navigate to the next page. */\r\n async nextPage(): Promise<SVGSVGElement | void> {\r\n return this.goToPage(this.pagination.currentPage + 1);\r\n }\r\n\r\n /** Navigate to the previous page. */\r\n async prevPage(): Promise<SVGSVGElement | void> {\r\n return this.goToPage(this.pagination.currentPage - 1);\r\n }\r\n\r\n /** Change the detail level and re-fetch the current results. */\r\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\r\n this.options.lvl = lvl;\r\n return this._fetch(this.currentUid, this.pagination.start);\r\n }\r\n\r\n /** Clear the container and remove rendered content. */\r\n destroy(): void {\r\n if (this.container) {\r\n this.container.innerHTML = '';\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Private helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\r\n if (!uid) return;\r\n\r\n if (this.container) {\r\n renderLoading(this.container);\r\n }\r\n\r\n try {\r\n const response: HalApiResponse = await fetchArticles(\r\n uid,\r\n this.options.lvl,\r\n this.options.rows,\r\n start,\r\n this.options.apiBase,\r\n );\r\n\r\n this._updatePagination(response, start);\r\n\r\n if (this.options.output === 'svg') {\r\n if (this.container) {\r\n renderResultsSvg(\r\n this.container,\r\n response.response.docs,\r\n this.options.lvl,\r\n this.pagination,\r\n );\r\n } else {\r\n const svg = buildArticlesSvg(\r\n response.response.docs,\r\n this.options.lvl,\r\n this.pagination,\r\n );\r\n this.options.onResults?.(response);\r\n return svg;\r\n }\r\n } else {\r\n if (this.container) {\r\n renderResults(\r\n this.container,\r\n response.response.docs,\r\n this.options.lvl,\r\n this.pagination,\r\n (page) => { void this.goToPage(page); },\r\n );\r\n } else {\r\n throw new Error('HalSearch: container is required for HTML output');\r\n }\r\n }\r\n\r\n this.options.onResults?.(response);\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n if (this.container) {\r\n renderError(this.container, error);\r\n }\r\n this.options.onError?.(error);\r\n if (!this.container && !this.options.onError) {\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\r\n if (typeof target === 'string') {\r\n const found = document.querySelector<HTMLElement>(target);\r\n if (!found) {\r\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\r\n }\r\n return found;\r\n }\r\n return target;\r\n }\r\n\r\n private _updatePagination(response: HalApiResponse, start: number): void {\r\n const { numFound } = response.response;\r\n this.pagination = {\r\n currentPage: Math.floor(start / this.options.rows) + 1,\r\n totalFound: numFound,\r\n rows: this.options.rows,\r\n start,\r\n };\r\n }\r\n}\r\n","import type { DetailLevel } from './types';\r\n\r\nexport interface EmbedOptions {\r\n /** Base URL where embed.html is hosted */\r\n embedBase: string;\r\n /** Search query or author UID */\r\n uid: string;\r\n /** Detail level 0-3 */\r\n lvl?: DetailLevel;\r\n /** Results per page */\r\n rows?: number;\r\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\r\n type?: 'html' | 'svg';\r\n /** iframe width (CSS value) */\r\n width?: string;\r\n /** iframe height (CSS value) */\r\n height?: string;\r\n}\r\n\r\n/** Builds the URL for the embeddable page with query parameters. */\r\nexport function buildEmbedUrl(options: EmbedOptions): string {\r\n const params = new URLSearchParams({ uid: options.uid });\r\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\r\n if (options.rows !== undefined) params.set('rows', String(options.rows));\r\n if (options.type) params.set('type', options.type);\r\n return `${options.embedBase}/embed.html?${params.toString()}`;\r\n}\r\n\r\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\r\nexport function buildEmbedSnippet(options: EmbedOptions): string {\r\n const src = buildEmbedUrl(options);\r\n const width = options.width ?? '100%';\r\n const height = options.height ?? '600';\r\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\r\n}\r\n"],"mappings":"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"}
|