hal-search 0.1.0 → 0.2.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 -21
- package/README.md +241 -214
- package/dist/HalSearch.d.ts +7 -0
- package/dist/embed.d.ts +6 -0
- package/dist/hal-search.es.js +8 -2
- package/dist/hal-search.es.js.map +1 -1
- package/dist/hal-search.umd.js +1 -1
- package/dist/hal-search.umd.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/package.json +34 -31
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Jey Puget Gil
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jey Puget Gil
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,214 +1,241 @@
|
|
|
1
|
-
# hal-search
|
|
2
|
-
|
|
3
|
-
A zero-dependency TypeScript library for querying and displaying articles from the [HAL Open Archive](https://hal.science/) API.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Goal
|
|
8
|
-
|
|
9
|
-
**hal-search** lets you embed a live, paginated list of academic publications from the HAL API into any web page with a single class instantiation. It handles:
|
|
10
|
-
|
|
11
|
-
- Building the correct HAL API query from a user ID or free-text query
|
|
12
|
-
- Fetching results at a configurable level of detail (minimal → full)
|
|
13
|
-
- Rendering styled article cards with pagination
|
|
14
|
-
- Exposing callbacks for results and errors
|
|
15
|
-
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# from npm (once published)
|
|
22
|
-
npm install hal-search
|
|
23
|
-
|
|
24
|
-
# or use the built files directly
|
|
25
|
-
dist/hal-search.es.js # ES module
|
|
26
|
-
dist/hal-search.umd.js # UMD (browser global: window.HalSearch)
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
---
|
|
30
|
-
|
|
31
|
-
## Usage
|
|
32
|
-
|
|
33
|
-
### Basic example
|
|
34
|
-
|
|
35
|
-
```html
|
|
36
|
-
<div id="publications"></div>
|
|
37
|
-
|
|
38
|
-
<script type="module">
|
|
39
|
-
import { HalSearch } from './dist/hal-search.es.js';
|
|
40
|
-
|
|
41
|
-
const hs = new HalSearch({
|
|
42
|
-
container: '#publications',
|
|
43
|
-
lvl: 1,
|
|
44
|
-
rows: 10,
|
|
45
|
-
onResults: (res) => console.log(`${res.response.numFound} results`),
|
|
46
|
-
onError: (err) => console.error(err.message),
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
hs.search({ uid: 'authIdHal_s:jdupont' });
|
|
50
|
-
</script>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Constructor options
|
|
54
|
-
|
|
55
|
-
| Option | Type | Default | Description |
|
|
56
|
-
|----------------|-----------------------------------|---------|-------------|
|
|
57
|
-
| `container`
|
|
58
|
-
| `lvl`
|
|
59
|
-
| `rows`
|
|
60
|
-
| `apiBase`
|
|
61
|
-
| `injectStyles`
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
example
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
1
|
+
# hal-search
|
|
2
|
+
|
|
3
|
+
A zero-dependency TypeScript library for querying and displaying articles from the [HAL Open Archive](https://hal.science/) API.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
|
|
9
|
+
**hal-search** lets you embed a live, paginated list of academic publications from the HAL API into any web page with a single class instantiation. It handles:
|
|
10
|
+
|
|
11
|
+
- Building the correct HAL API query from a user ID or free-text query
|
|
12
|
+
- Fetching results at a configurable level of detail (minimal → full)
|
|
13
|
+
- Rendering styled article cards with pagination
|
|
14
|
+
- Exposing callbacks for results and errors
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# from npm (once published)
|
|
22
|
+
npm install hal-search
|
|
23
|
+
|
|
24
|
+
# or use the built files directly
|
|
25
|
+
dist/hal-search.es.js # ES module
|
|
26
|
+
dist/hal-search.umd.js # UMD (browser global: window.HalSearch)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
### Basic example
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<div id="publications"></div>
|
|
37
|
+
|
|
38
|
+
<script type="module">
|
|
39
|
+
import { HalSearch } from './dist/hal-search.es.js';
|
|
40
|
+
|
|
41
|
+
const hs = new HalSearch({
|
|
42
|
+
container: '#publications',
|
|
43
|
+
lvl: 1,
|
|
44
|
+
rows: 10,
|
|
45
|
+
onResults: (res) => console.log(`${res.response.numFound} results`),
|
|
46
|
+
onError: (err) => console.error(err.message),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
hs.search({ uid: 'authIdHal_s:jdupont' });
|
|
50
|
+
</script>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Constructor options
|
|
54
|
+
|
|
55
|
+
| Option | Type | Default | Description |
|
|
56
|
+
|----------------|-----------------------------------|---------|-------------|
|
|
57
|
+
| `container` | `HTMLElement \| string` | — | Target DOM element or CSS selector (optional if output='svg') |
|
|
58
|
+
| `lvl` | `0 \| 1 \| 2 \| 3` | `1` | Level of detail (see below) |
|
|
59
|
+
| `rows` | `number` | `10` | Results per page |
|
|
60
|
+
| `apiBase` | `string` | HAL URL | Override the API base URL |
|
|
61
|
+
| `injectStyles` | `boolean` | `true` | Auto-inject the default stylesheet |
|
|
62
|
+
| `backgroundColor` | `string` | `#ffffff` | Background color for article cards (sets `--hal-bg`) |
|
|
63
|
+
| `textColor` | `string` | `#1a1a1a` | Text color for article content (sets `--hal-text`) |
|
|
64
|
+
| `mainColor` | `string` | `#0052cc` | Accent color for links, buttons, and highlights (sets `--hal-accent`) |
|
|
65
|
+
| `onResults` | `(res: HalApiResponse) => void` | — | Called on every successful fetch |
|
|
66
|
+
| `onError` | `(err: Error) => void` | — | Called when the fetch fails |
|
|
67
|
+
|
|
68
|
+
### Methods
|
|
69
|
+
|
|
70
|
+
| Method | Description |
|
|
71
|
+
|--------|-------------|
|
|
72
|
+
| `search({ uid, rows?, start? })` | Start a new search. Returns `Promise<SVGSVGElement>` if headless SVG mode. |
|
|
73
|
+
| `setLevel(lvl)` | Change detail level and re-fetch. Returns `Promise<SVGSVGElement>` if headless SVG mode. |
|
|
74
|
+
| `goToPage(n)` | Jump to page `n` (1-based). Returns `Promise<SVGSVGElement>` if headless SVG mode. |
|
|
75
|
+
| `nextPage()` | Go to the next page. Returns `Promise<SVGSVGElement>` if headless SVG mode. |
|
|
76
|
+
| `prevPage()` | Go to the previous page. Returns `Promise<SVGSVGElement>` if headless SVG mode. |
|
|
77
|
+
| `setColors({ backgroundColor?, textColor?, mainColor? })` | Update colors at runtime. Only provided colors are changed. |
|
|
78
|
+
| `destroy()` | Clear the container (if one was provided) |
|
|
79
|
+
|
|
80
|
+
### Headless SVG generation
|
|
81
|
+
|
|
82
|
+
You can generate a standalone SVG without attaching it to the DOM by omitting the `container` option and setting `output: 'svg'`. The `search()` method (and pagination methods) will resolve with the SVG element.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
const hs = new HalSearch({
|
|
86
|
+
output: 'svg',
|
|
87
|
+
lvl: 2,
|
|
88
|
+
rows: 5,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const svgElement = await hs.search({ uid: 'authIdHal_s:jdupont' });
|
|
92
|
+
document.body.appendChild(svgElement);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Detail levels
|
|
96
|
+
|
|
97
|
+
The `lvl` parameter controls which HAL fields are requested and how much information is shown per article.
|
|
98
|
+
|
|
99
|
+
| Level | Name | Fields fetched | What is displayed |
|
|
100
|
+
|-------|------|---------------|-------------------|
|
|
101
|
+
| `0` | Minimal | `docid`, `label_s`, `uri_s` | Citation label + link |
|
|
102
|
+
| `1` | Basic *(default)* | + `title_s`, `authFullName_s`, `publicationDate_s`, `docType_s` | Title, authors, year, document type |
|
|
103
|
+
| `2` | Detailed | + `keyword_s`, `domain_s`, `openAccess_bool`, `language_s`, `conferenceTitle_s` | All of the above + tags, OA badge, conference |
|
|
104
|
+
| `3` | Full | `*` (all fields) | Adds the abstract of the paper |
|
|
105
|
+
|
|
106
|
+
### Color customization
|
|
107
|
+
|
|
108
|
+
You can set the three main colors directly via constructor options:
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const hs = new HalSearch({
|
|
112
|
+
container: '#publications',
|
|
113
|
+
backgroundColor: '#1a1a2e', // dark background
|
|
114
|
+
textColor: '#e0e0e0', // light text
|
|
115
|
+
mainColor: '#e63946', // red accent
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Colors can also be changed at runtime without re-fetching:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
hs.setColors({
|
|
123
|
+
backgroundColor: '#ffffff',
|
|
124
|
+
textColor: '#1a1a1a',
|
|
125
|
+
mainColor: '#0052cc',
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Theming
|
|
130
|
+
|
|
131
|
+
For finer control, default styles use CSS custom properties. Override any of them on the container or globally:
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
#publications {
|
|
135
|
+
--hal-accent: #e63946;
|
|
136
|
+
--hal-bg-article: #fff8f0;
|
|
137
|
+
--hal-radius: 4px;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Variable | Default |
|
|
142
|
+
|----------|---------|
|
|
143
|
+
| `--hal-accent` | `#0052cc` |
|
|
144
|
+
| `--hal-accent-hover` | `#003d99` |
|
|
145
|
+
| `--hal-bg` | `#ffffff` |
|
|
146
|
+
| `--hal-bg-article` | `#fafafa` |
|
|
147
|
+
| `--hal-border` | `#e0e0e0` |
|
|
148
|
+
| `--hal-text` | `#1a1a1a` |
|
|
149
|
+
| `--hal-text-muted` | `#666666` |
|
|
150
|
+
| `--hal-radius` | `6px` |
|
|
151
|
+
|
|
152
|
+
Disable auto-injection with `injectStyles: false` and provide your own stylesheet.
|
|
153
|
+
|
|
154
|
+
### Building a query
|
|
155
|
+
|
|
156
|
+
The `uid` field passed to `search()` maps directly to the `q` parameter of the HAL Solr API, so any valid Solr query works:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
// Free-text
|
|
160
|
+
hs.search({ uid: 'machine learning' });
|
|
161
|
+
|
|
162
|
+
// Author by HAL identifier
|
|
163
|
+
hs.search({ uid: 'authIdHal_s:jdupont' });
|
|
164
|
+
|
|
165
|
+
// By lab structure
|
|
166
|
+
hs.search({ uid: 'structId_i:123456' });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
See the [HAL API documentation](https://api.archives-ouvertes.fr/docs/search) for the full query syntax.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Running the example
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm run example
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This builds the library and serves the interactive demo at **http://localhost:8080/example/**. The demo lets you switch queries, levels, and page size in real time.
|
|
180
|
+
|
|
181
|
+
To run the development sandbox (requires Vite):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm run dev
|
|
185
|
+
# open http://localhost:5173
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
### Project structure
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
src/
|
|
196
|
+
index.ts # Public exports
|
|
197
|
+
HalSearch.ts # Main class
|
|
198
|
+
api.ts # URL builder + fetch logic
|
|
199
|
+
levels.ts # Field lists per detail level
|
|
200
|
+
renderer.ts # DOM rendering
|
|
201
|
+
styles.ts # Default CSS
|
|
202
|
+
types.ts # TypeScript interfaces
|
|
203
|
+
example/
|
|
204
|
+
index.html # Interactive demo
|
|
205
|
+
dist/ # Built output (generated)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Development setup
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
git clone <repo>
|
|
212
|
+
cd hal-search
|
|
213
|
+
npm install
|
|
214
|
+
npm run dev # live dev server at localhost:5173
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Building
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
npm run build
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Produces:
|
|
224
|
+
|
|
225
|
+
| File | Format | Use case |
|
|
226
|
+
|------|--------|----------|
|
|
227
|
+
| `dist/hal-search.es.js` | ES module | Bundlers, `<script type="module">` |
|
|
228
|
+
| `dist/hal-search.umd.js` | UMD | `<script>` tag, CommonJS |
|
|
229
|
+
| `dist/index.d.ts` | TypeScript types | TypeScript consumers |
|
|
230
|
+
|
|
231
|
+
### Guidelines
|
|
232
|
+
|
|
233
|
+
- **TypeScript strict mode** is enabled — all code must type-check cleanly.
|
|
234
|
+
- Keep the library **zero-dependency** at runtime; dev dependencies are fine.
|
|
235
|
+
- Rendering lives in `renderer.ts`, API logic in `api.ts` — keep concerns separated.
|
|
236
|
+
- Test new features against both the dev sandbox (`index.html`) and the example page (`example/index.html`).
|
|
237
|
+
- Follow the existing naming conventions for CSS classes (`.hal-*`).
|
|
238
|
+
|
|
239
|
+
### Useful link
|
|
240
|
+
|
|
241
|
+
- [HAL API documentation](https://api.archives-ouvertes.fr/docs/search)
|
package/dist/HalSearch.d.ts
CHANGED
|
@@ -15,8 +15,15 @@ export declare class HalSearch {
|
|
|
15
15
|
prevPage(): Promise<SVGSVGElement | void>;
|
|
16
16
|
/** Change the detail level and re-fetch the current results. */
|
|
17
17
|
setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void>;
|
|
18
|
+
/** Update the color theme at runtime. Only provided colors are changed. */
|
|
19
|
+
setColors(colors: {
|
|
20
|
+
backgroundColor?: string;
|
|
21
|
+
textColor?: string;
|
|
22
|
+
mainColor?: string;
|
|
23
|
+
}): void;
|
|
18
24
|
/** Clear the container and remove rendered content. */
|
|
19
25
|
destroy(): void;
|
|
26
|
+
private _applyColors;
|
|
20
27
|
private _fetch;
|
|
21
28
|
private _resolveContainer;
|
|
22
29
|
private _updatePagination;
|
package/dist/embed.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface EmbedOptions {
|
|
|
14
14
|
width?: string;
|
|
15
15
|
/** iframe height (CSS value) */
|
|
16
16
|
height?: string;
|
|
17
|
+
/** Background color for article cards */
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
/** Text color for article content */
|
|
20
|
+
textColor?: string;
|
|
21
|
+
/** Main accent color for links and buttons */
|
|
22
|
+
mainColor?: string;
|
|
17
23
|
}
|
|
18
24
|
/** Builds the URL for the embeddable page with query parameters. */
|
|
19
25
|
export declare function buildEmbedUrl(options: EmbedOptions): string;
|
package/dist/hal-search.es.js
CHANGED
|
@@ -402,7 +402,7 @@ var J = {
|
|
|
402
402
|
totalFound: 0,
|
|
403
403
|
rows: this.options.rows,
|
|
404
404
|
start: 0
|
|
405
|
-
}, this.options.injectStyles && q();
|
|
405
|
+
}, this.options.injectStyles && q(), this._applyColors(e);
|
|
406
406
|
}
|
|
407
407
|
async search(e) {
|
|
408
408
|
return this.currentUid = e.uid, e.rows !== void 0 && (this.options.rows = e.rows), this.pagination = {
|
|
@@ -425,9 +425,15 @@ var J = {
|
|
|
425
425
|
async setLevel(e) {
|
|
426
426
|
return this.options.lvl = e, this._fetch(this.currentUid, this.pagination.start);
|
|
427
427
|
}
|
|
428
|
+
setColors(e) {
|
|
429
|
+
this._applyColors(e);
|
|
430
|
+
}
|
|
428
431
|
destroy() {
|
|
429
432
|
this.container && (this.container.innerHTML = "");
|
|
430
433
|
}
|
|
434
|
+
_applyColors(e) {
|
|
435
|
+
this.container && (e.backgroundColor && this.container.style.setProperty("--hal-bg", e.backgroundColor), e.textColor && this.container.style.setProperty("--hal-text", e.textColor), e.mainColor && this.container.style.setProperty("--hal-accent", e.mainColor));
|
|
436
|
+
}
|
|
431
437
|
async _fetch(e, t) {
|
|
432
438
|
if (e) {
|
|
433
439
|
this.container && p(this.container);
|
|
@@ -471,7 +477,7 @@ var J = {
|
|
|
471
477
|
//#region src/embed.ts
|
|
472
478
|
function X(e) {
|
|
473
479
|
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()}`;
|
|
480
|
+
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.backgroundColor && t.set("bg", e.backgroundColor), e.textColor && t.set("text", e.textColor), e.mainColor && t.set("main", e.mainColor), `${e.embedBase}/embed.html?${t.toString()}`;
|
|
475
481
|
}
|
|
476
482
|
function Z(e) {
|
|
477
483
|
return `<iframe src="${X(e)}" width="${e.width ?? "100%"}" height="${e.height ?? "600"}"></iframe>`;
|
|
@@ -1 +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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"}
|
|
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';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. ⟨) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): SVGSVGElement {\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination));\n}\n","export const DEFAULT_CSS = `\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`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this._applyColors(options);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
|
package/dist/hal-search.umd.js
CHANGED
|
@@ -250,5 +250,5 @@
|
|
|
250
250
|
cursor: not-allowed;
|
|
251
251
|
opacity: 0.5;
|
|
252
252
|
}
|
|
253
|
-
`;function J(){let e=`hal-search-styles`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=q,document.head.appendChild(t)}var Y={lvl:1,rows:10,apiBase:s,injectStyles:!0,output:`html`},X=class{constructor(e){this.currentUid=``,e.container&&(this.container=this._resolveContainer(e.container)),this.options={lvl:e.lvl??Y.lvl,rows:e.rows??Y.rows,apiBase:e.apiBase??Y.apiBase,injectStyles:e.injectStyles??Y.injectStyles,output:e.output??Y.output,onResults:e.onResults,onError:e.onError},this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:0},this.options.injectStyles&&J()}async search(e){return this.currentUid=e.uid,e.rows!==void 0&&(this.options.rows=e.rows),this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:e.start??0},this._fetch(this.currentUid,this.pagination.start)}async goToPage(e){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;return this._fetch(this.currentUid,n)}async nextPage(){return this.goToPage(this.pagination.currentPage+1)}async prevPage(){return this.goToPage(this.pagination.currentPage-1)}async setLevel(e){return this.options.lvl=e,this._fetch(this.currentUid,this.pagination.start)}destroy(){this.container&&(this.container.innerHTML=``)}async _fetch(e,t){if(e){this.container&&m(this.container);try{let n=await l(e,this.options.lvl,this.options.rows,t,this.options.apiBase);if(this._updatePagination(n,t),this.options.output===`svg`)if(this.container)K(this.container,n.response.docs,this.options.lvl,this.pagination);else{let e=G(n.response.docs,this.options.lvl,this.pagination);return this.options.onResults?.(n),e}else if(this.container)y(this.container,n.response.docs,this.options.lvl,this.pagination,e=>{this.goToPage(e)});else throw Error(`HalSearch: container is required for HTML output`);this.options.onResults?.(n)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.container&&h(this.container,t),this.options.onError?.(t),!this.container&&!this.options.onError)throw t}}}_resolveContainer(e){if(typeof e==`string`){let t=document.querySelector(e);if(!t)throw Error(`HalSearch: container not found for selector "${e}"`);return t}return e}_updatePagination(e,t){let{numFound:n}=e.response;this.pagination={currentPage:Math.floor(t/this.options.rows)+1,totalFound:n,rows:this.options.rows,start:t}}};function Z(e){let t=new URLSearchParams({uid:e.uid});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()}`}function Q(e){return`<iframe src="${Z(e)}" width="${e.width??`100%`}" height="${e.height??`600`}"></iframe>`}e.DEFAULT_BASE=s,e.DEFAULT_CSS=q,e.HalSearch=X,e.LEVEL_FIELDS=i,e.LEVEL_NAMES=a,e.buildArticlesSvg=G,e.buildEmbedSnippet=Q,e.buildEmbedUrl=Z,e.buildUrl=c,e.fetchArticles=l,e.injectDefaultStyles=J,e.renderResultsSvg=K,e.resolveFields=o});
|
|
253
|
+
`;function J(){let e=`hal-search-styles`;if(document.getElementById(e))return;let t=document.createElement(`style`);t.id=e,t.textContent=q,document.head.appendChild(t)}var Y={lvl:1,rows:10,apiBase:s,injectStyles:!0,output:`html`},X=class{constructor(e){this.currentUid=``,e.container&&(this.container=this._resolveContainer(e.container)),this.options={lvl:e.lvl??Y.lvl,rows:e.rows??Y.rows,apiBase:e.apiBase??Y.apiBase,injectStyles:e.injectStyles??Y.injectStyles,output:e.output??Y.output,onResults:e.onResults,onError:e.onError},this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:0},this.options.injectStyles&&J(),this._applyColors(e)}async search(e){return this.currentUid=e.uid,e.rows!==void 0&&(this.options.rows=e.rows),this.pagination={currentPage:1,totalFound:0,rows:this.options.rows,start:e.start??0},this._fetch(this.currentUid,this.pagination.start)}async goToPage(e){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;return this._fetch(this.currentUid,n)}async nextPage(){return this.goToPage(this.pagination.currentPage+1)}async prevPage(){return this.goToPage(this.pagination.currentPage-1)}async setLevel(e){return this.options.lvl=e,this._fetch(this.currentUid,this.pagination.start)}setColors(e){this._applyColors(e)}destroy(){this.container&&(this.container.innerHTML=``)}_applyColors(e){this.container&&(e.backgroundColor&&this.container.style.setProperty(`--hal-bg`,e.backgroundColor),e.textColor&&this.container.style.setProperty(`--hal-text`,e.textColor),e.mainColor&&this.container.style.setProperty(`--hal-accent`,e.mainColor))}async _fetch(e,t){if(e){this.container&&m(this.container);try{let n=await l(e,this.options.lvl,this.options.rows,t,this.options.apiBase);if(this._updatePagination(n,t),this.options.output===`svg`)if(this.container)K(this.container,n.response.docs,this.options.lvl,this.pagination);else{let e=G(n.response.docs,this.options.lvl,this.pagination);return this.options.onResults?.(n),e}else if(this.container)y(this.container,n.response.docs,this.options.lvl,this.pagination,e=>{this.goToPage(e)});else throw Error(`HalSearch: container is required for HTML output`);this.options.onResults?.(n)}catch(e){let t=e instanceof Error?e:Error(String(e));if(this.container&&h(this.container,t),this.options.onError?.(t),!this.container&&!this.options.onError)throw t}}}_resolveContainer(e){if(typeof e==`string`){let t=document.querySelector(e);if(!t)throw Error(`HalSearch: container not found for selector "${e}"`);return t}return e}_updatePagination(e,t){let{numFound:n}=e.response;this.pagination={currentPage:Math.floor(t/this.options.rows)+1,totalFound:n,rows:this.options.rows,start:t}}};function Z(e){let t=new URLSearchParams({uid:e.uid});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.backgroundColor&&t.set(`bg`,e.backgroundColor),e.textColor&&t.set(`text`,e.textColor),e.mainColor&&t.set(`main`,e.mainColor),`${e.embedBase}/embed.html?${t.toString()}`}function Q(e){return`<iframe src="${Z(e)}" width="${e.width??`100%`}" height="${e.height??`600`}"></iframe>`}e.DEFAULT_BASE=s,e.DEFAULT_CSS=q,e.HalSearch=X,e.LEVEL_FIELDS=i,e.LEVEL_NAMES=a,e.buildArticlesSvg=G,e.buildEmbedSnippet=Q,e.buildEmbedUrl=Z,e.buildUrl=c,e.fetchArticles=l,e.injectDefaultStyles=J,e.renderResultsSvg=K,e.resolveFields=o});
|
|
254
254
|
//# sourceMappingURL=hal-search.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hal-search.umd.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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"}
|
|
1
|
+
{"version":3,"file":"hal-search.umd.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';\nimport { resolveFields } from './levels';\n\nexport const DEFAULT_BASE = 'https://api.archives-ouvertes.fr/search/';\n\n/**\n * Builds a HAL API search URL from the given parameters.\n * Uses URLSearchParams to safely encode special characters in uid.\n */\nexport function buildUrl(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): string {\n const fl = resolveFields(lvl);\n const params = new URLSearchParams({\n q: `\"${uid}\"`,\n wt: 'json',\n fl,\n rows: String(rows),\n start: String(start),\n });\n return `${base}?${params.toString()}`;\n}\n\n/**\n * Fetches articles from the HAL API.\n * Throws on HTTP errors or non-zero API status codes.\n */\nexport async function fetchArticles(\n uid: string,\n lvl: DetailLevel,\n rows: number,\n start: number,\n base = DEFAULT_BASE,\n): Promise<HalApiResponse> {\n const url = buildUrl(uid, lvl, rows, start, base);\n const res = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`HAL API error: ${res.status} ${res.statusText}`);\n }\n\n const data: HalApiResponse = await res.json();\n\n if (data.responseHeader?.status !== undefined && data.responseHeader.status !== 0) {\n throw new Error(`HAL API returned non-zero status: ${data.responseHeader.status}`);\n }\n\n return data;\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className) node.className = className;\n return node;\n}\n\nfunction text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/** Decodes HTML entities (e.g. ⟨) into their actual characters. */\nfunction decodeEntities(raw: string): string {\n const textarea = document.createElement('textarea');\n textarea.innerHTML = raw;\n return textarea.value;\n}\n\n/** Creates an <a> element with href validated to start with https:// */\nfunction safeLink(href: string | undefined, label: string, className?: string): HTMLAnchorElement {\n const a = el('a', className);\n if (href && (href.startsWith('https://') || href.startsWith('http://'))) {\n a.href = href;\n }\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.textContent = label;\n return a;\n}\n\n// ---------------------------------------------------------------------------\n// State renderers\n// ---------------------------------------------------------------------------\n\nexport function renderLoading(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-loading');\n const spinner = el('div', 'hal-spinner');\n wrap.appendChild(spinner);\n wrap.appendChild(text('Loading…'));\n container.appendChild(wrap);\n}\n\nexport function renderError(container: HTMLElement, err: Error): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-error');\n wrap.textContent = `Error: ${err.message}`;\n container.appendChild(wrap);\n}\n\nexport function renderEmpty(container: HTMLElement): void {\n container.innerHTML = '';\n const wrap = el('div', 'hal-empty');\n wrap.textContent = 'No results found.';\n container.appendChild(wrap);\n}\n\n// ---------------------------------------------------------------------------\n// Article card\n// ---------------------------------------------------------------------------\n\nfunction buildArticleCard(doc: HalDoc, lvl: DetailLevel): HTMLElement {\n const article = el('article', 'hal-article');\n if (doc.docid) article.dataset.docid = doc.docid;\n if (doc.docType_s) article.dataset.doctype = doc.docType_s;\n\n // --- Header ---\n const header = el('header');\n\n if (lvl >= 1) {\n // Title + link\n const h3 = el('h3', 'hal-article__title');\n const titleText = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n h3.appendChild(safeLink(doc.uri_s, titleText));\n header.appendChild(h3);\n\n // Meta row\n const meta = el('div', 'hal-article__meta');\n\n if (doc.authFullName_s?.length) {\n const authors = el('span', 'hal-article__authors');\n authors.textContent = doc.authFullName_s.join(', ');\n meta.appendChild(authors);\n }\n\n if (doc.publicationDate_s) {\n const date = el('span', 'hal-article__date');\n // Show only the year if it's a full date string\n date.textContent = doc.publicationDate_s.slice(0, 4);\n meta.appendChild(date);\n }\n\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const badge = el('span', 'hal-badge');\n badge.textContent = doc.docType_s;\n meta.appendChild(badge);\n }\n\n if (doc.openAccess_bool === true) {\n const oaBadge = el('span', 'hal-badge hal-badge--oa');\n oaBadge.textContent = 'Open Access';\n meta.appendChild(oaBadge);\n }\n\n header.appendChild(meta);\n } else {\n // lvl 0: just the full citation\n const div = el('div', 'hal-article__label');\n div.appendChild(safeLink(doc.uri_s, decodeEntities(doc.label_s ?? doc.docid ?? ''), 'hal-article__link'));\n header.appendChild(div);\n }\n\n article.appendChild(header);\n\n // --- Details (lvl >= 2) ---\n if (lvl >= 2) {\n const hasKeywords = doc.keyword_s && doc.keyword_s.length > 0;\n const hasDomains = doc.domain_s && doc.domain_s.length > 0;\n const hasConference = Boolean(doc.conferenceTitle_s);\n\n if (hasKeywords || hasDomains || hasConference) {\n const details = el('section', 'hal-article__details');\n\n if (hasKeywords) {\n const tagsWrap = el('div', 'hal-article__tags');\n for (const kw of doc.keyword_s!) {\n const tag = el('span', 'hal-tag');\n tag.textContent = kw;\n tagsWrap.appendChild(tag);\n }\n details.appendChild(tagsWrap);\n }\n\n if (hasDomains) {\n const domainsWrap = el('div', 'hal-article__tags');\n for (const domain of doc.domain_s!) {\n const tag = el('span', 'hal-tag hal-tag--domain');\n tag.textContent = domain;\n domainsWrap.appendChild(tag);\n }\n details.appendChild(domainsWrap);\n }\n\n if (hasConference) {\n const conf = el('div', 'hal-article__conference');\n conf.textContent = doc.conferenceTitle_s!;\n details.appendChild(conf);\n }\n\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const abstract = el('div', 'hal-article__abstract');\n abstract.textContent = doc.abstract_s[0];\n details.appendChild(abstract);\n }\n\n article.appendChild(details);\n }\n }\n\n return article;\n}\n\n// ---------------------------------------------------------------------------\n// Pagination bar\n// ---------------------------------------------------------------------------\n\nfunction buildPagination(\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): HTMLElement {\n const totalPages = Math.max(1, Math.ceil(pagination.totalFound / pagination.rows));\n const { currentPage } = pagination;\n\n const nav = el('nav', 'hal-pagination');\n nav.setAttribute('aria-label', 'Search results pages');\n\n const prevBtn = el('button', 'hal-pagination__btn');\n prevBtn.textContent = '← Previous';\n prevBtn.disabled = currentPage <= 1;\n prevBtn.addEventListener('click', () => onPageChange(currentPage - 1));\n nav.appendChild(prevBtn);\n\n const info = el('span', 'hal-pagination__info');\n info.textContent = `Page ${currentPage} of ${totalPages} (${pagination.totalFound.toLocaleString()} results)`;\n nav.appendChild(info);\n\n const nextBtn = el('button', 'hal-pagination__btn');\n nextBtn.textContent = 'Next →';\n nextBtn.disabled = currentPage >= totalPages;\n nextBtn.addEventListener('click', () => onPageChange(currentPage + 1));\n nav.appendChild(nextBtn);\n\n return nav;\n}\n\n// ---------------------------------------------------------------------------\n// Main results renderer\n// ---------------------------------------------------------------------------\n\nexport function renderResults(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n onPageChange: (page: number) => void,\n): void {\n container.innerHTML = '';\n\n if (docs.length === 0) {\n renderEmpty(container);\n return;\n }\n\n const wrapper = el('div', 'hal-results');\n\n for (const doc of docs) {\n wrapper.appendChild(buildArticleCard(doc, lvl));\n }\n\n if (pagination.totalFound > pagination.rows) {\n wrapper.appendChild(buildPagination(pagination, onPageChange));\n }\n\n const footer = el('div', 'hal-footer');\n const credit = safeLink('https://github.com/JPugetGil/hal-search', 'hal-search', 'hal-footer__link');\n footer.appendChild(credit);\n wrapper.appendChild(footer);\n\n container.appendChild(wrapper);\n}\n","import type { HalDoc, DetailLevel, PaginationState } from './types';\n\nconst NS = 'http://www.w3.org/2000/svg';\nconst W = 800;\nconst PAD = 16;\nconst CARD_GAP = 8;\nconst HEADER_H = 50;\nconst FOOTER_H = 28;\nconst GITHUB_URL = 'https://github.com/JPugetGil/hal-search';\n\n/** Default colour palette — mirrors CSS-variable defaults from styles.ts */\nconst C = {\n accent: '#0052cc',\n accentText: '#ffffff',\n bg: '#f2f4f8',\n cardBg: '#ffffff',\n border: '#e0e0e0',\n text: '#1a1a1a',\n muted: '#666666',\n link: '#0052cc',\n oaBg: '#e3f5ee',\n oaColor: '#006644',\n tagBg: '#f0f0f0',\n tagColor: '#444444',\n domainBg: '#dbeafe',\n domainColor: '#1a56db',\n};\n\n// Abstract rendering constants\nconst ABSTRACT_FS = 11;\nconst ABSTRACT_LINE_H = 15;\nconst ABSTRACT_MAX_LINES = 3;\nconst ABSTRACT_LABEL_H = 18;\nconst ABSTRACT_TOP_GAP = 10;\n\n// ---------------------------------------------------------------------------\n// SVG helpers\n// ---------------------------------------------------------------------------\n\nfunction svgEl<K extends keyof SVGElementTagNameMap>(tag: K): SVGElementTagNameMap[K] {\n return document.createElementNS(NS, tag) as SVGElementTagNameMap[K];\n}\n\nfunction set(el: SVGElement, attrs: Record<string, string | number>): void {\n for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));\n}\n\nfunction decodeEntities(raw: string): string {\n const ta = document.createElement('textarea');\n ta.innerHTML = raw;\n return ta.value;\n}\n\n/** Approximate truncation (SVG has no native text-measurement API). */\nfunction truncate(text: string, maxPx: number, fontSize: number): string {\n const charW = fontSize * 0.56;\n const max = Math.floor(maxPx / charW);\n return text.length > max ? text.slice(0, max - 1) + '…' : text;\n}\n\nfunction mkRect(\n x: number, y: number, w: number, h: number,\n fill: string,\n extra?: Record<string, string | number>,\n): SVGRectElement {\n const r = svgEl('rect');\n set(r, { x, y, width: w, height: h, fill, ...extra });\n return r;\n}\n\nfunction mkText(\n x: number, y: number, content: string,\n extra?: Record<string, string | number>,\n): SVGTextElement {\n const t = svgEl('text');\n set(t, { x, y, 'font-family': 'system-ui,-apple-system,sans-serif', ...extra });\n t.textContent = content;\n return t;\n}\n\nfunction mkLink(href: string, child: SVGElement): SVGAElement {\n const a = svgEl('a');\n a.setAttribute('href', href);\n a.setAttribute('target', '_blank');\n a.appendChild(child);\n return a;\n}\n\n/**\n * Renders a pill badge anchored at (x, baseline-y).\n * Returns the total pixel width consumed, including a 4 px trailing gap.\n */\nfunction pill(\n parent: SVGElement,\n x: number, y: number,\n label: string,\n bg: string, color: string,\n): number {\n const fs = 11;\n const ph = 5, pv = 3;\n const bw = label.length * fs * 0.6 + ph * 2;\n const bh = fs + pv * 2;\n parent.appendChild(mkRect(x, y - fs, bw, bh, bg, { rx: 3 }));\n parent.appendChild(mkText(x + ph, y - 1, label, { 'font-size': fs, fill: color }));\n return bw + 4;\n}\n\n/**\n * Word-wraps `content` into SVG tspan elements appended to a <text> node.\n * Returns the total pixel height consumed (lines × lineHeight).\n */\nfunction wrapText(\n parent: SVGElement,\n x: number, baseY: number,\n content: string,\n maxPx: number,\n fontSize: number,\n lineHeight: number,\n fill: string,\n maxLines = ABSTRACT_MAX_LINES,\n): number {\n const maxChars = Math.floor(maxPx / (fontSize * 0.52));\n const words = content.split(/\\s+/);\n const lines: string[] = [];\n let cur = '';\n let truncated = false;\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i]!;\n const test = cur ? `${cur} ${word}` : word;\n if (test.length <= maxChars) {\n cur = test;\n } else {\n if (cur) lines.push(cur);\n if (lines.length >= maxLines) { truncated = true; break; }\n cur = word;\n }\n }\n if (!truncated && cur) {\n lines.push(cur);\n } else if (truncated && lines.length > 0) {\n lines[lines.length - 1] += '…';\n }\n\n const t = svgEl('text');\n set(t, { x, y: baseY, 'font-size': fontSize, fill, 'font-family': 'system-ui,-apple-system,sans-serif' });\n for (let i = 0; i < lines.length; i++) {\n const ts = svgEl('tspan');\n ts.setAttribute('x', String(x));\n if (i > 0) ts.setAttribute('dy', String(lineHeight));\n ts.textContent = lines[i]!;\n t.appendChild(ts);\n }\n parent.appendChild(t);\n return lines.length * lineHeight;\n}\n\n// ---------------------------------------------------------------------------\n// Card geometry\n// ---------------------------------------------------------------------------\n\n/** Returns the extra height added by the abstract section, or 0 if absent. */\nfunction abstractExtraHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl !== 3 || !doc.abstract_s?.[0]) return 0;\n // Pre-estimate the number of wrapped lines\n const maxPx = W - PAD * 4;\n const maxChars = Math.floor(maxPx / (ABSTRACT_FS * 0.52));\n const words = doc.abstract_s[0].split(/\\s+/);\n let lines = 1;\n let chars = 0;\n for (const word of words) {\n if (chars + word.length + (chars ? 1 : 0) > maxChars) {\n lines++;\n chars = word.length;\n if (lines >= ABSTRACT_MAX_LINES) break;\n } else {\n chars += word.length + (chars ? 1 : 0);\n }\n }\n return ABSTRACT_TOP_GAP + ABSTRACT_LABEL_H + Math.min(lines, ABSTRACT_MAX_LINES) * ABSTRACT_LINE_H;\n}\n\nfunction cardHeight(doc: HalDoc, lvl: DetailLevel): number {\n if (lvl === 0) return 44;\n const hasTagRow =\n lvl >= 2 &&\n ((doc.keyword_s?.length ?? 0) > 0 ||\n (doc.domain_s?.length ?? 0) > 0 ||\n Boolean(doc.conferenceTitle_s));\n return (hasTagRow ? 86 : 62) + abstractExtraHeight(doc, lvl);\n}\n\n// ---------------------------------------------------------------------------\n// Card builder\n// ---------------------------------------------------------------------------\n\nfunction buildCard(doc: HalDoc, lvl: DetailLevel, cardY: number): SVGElement {\n const g = svgEl('g');\n const h = cardHeight(doc, lvl);\n const cx = PAD;\n const cw = W - PAD * 2;\n const ix = PAD * 2; // inner-x (text left margin)\n\n // Card background\n g.appendChild(mkRect(cx, cardY, cw, h, C.cardBg, {\n rx: 6, stroke: C.border, 'stroke-width': 1,\n }));\n\n // ── Level 0: citation label only ──────────────────────────────────────────\n if (lvl === 0) {\n const raw = decodeEntities(doc.label_s ?? doc.docid ?? '');\n const label = truncate(raw, cw - PAD * 2, 12);\n const t = mkText(ix, cardY + 26, label, { 'font-size': 12, fill: C.link });\n if (doc.uri_s?.startsWith('http')) {\n g.appendChild(mkLink(doc.uri_s, t));\n } else {\n t.setAttribute('fill', C.text);\n g.appendChild(t);\n }\n return g;\n }\n\n // ── Level 1+: title row ──────────────────────────────────────────────────\n const titleRaw = doc.title_s?.[0] ?? doc.label_s ?? 'Untitled';\n const titleStr = truncate(titleRaw, cw - PAD * 2, 14);\n const titleEl = mkText(ix, cardY + 22, titleStr, {\n 'font-size': 14,\n 'font-weight': 'bold',\n fill: doc.uri_s?.startsWith('http') ? C.link : C.text,\n });\n g.appendChild(doc.uri_s?.startsWith('http') ? mkLink(doc.uri_s, titleEl) : titleEl);\n\n // ── Meta row ─────────────────────────────────────────────────────────────\n const metaY = cardY + 44;\n\n // Left: \"authors · year\"\n const metaParts: string[] = [];\n if (doc.authFullName_s?.length) {\n metaParts.push(truncate(doc.authFullName_s.join(', '), cw * 0.5, 12));\n }\n if (doc.publicationDate_s) {\n metaParts.push(doc.publicationDate_s.slice(0, 4));\n }\n if (metaParts.length) {\n g.appendChild(mkText(ix, metaY, metaParts.join(' · '), {\n 'font-size': 12, fill: C.muted,\n }));\n }\n\n // Right: badges (rendered right-to-left so order is docType | OA visually)\n let badgeRight = cx + cw - PAD;\n if (doc.openAccess_bool === true) {\n const label = 'Open Access';\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.oaBg, C.oaColor);\n }\n if (doc.docType_s && doc.docType_s.toUpperCase() !== 'UNDEFINED') {\n const label = doc.docType_s;\n badgeRight -= label.length * 11 * 0.6 + 10 + 4;\n pill(g, badgeRight, metaY, label, C.tagBg, C.tagColor);\n }\n\n // ── Tags row (lvl ≥ 2) ───────────────────────────────────────────────────\n let nextSectionY = metaY + PAD;\n if (lvl >= 2) {\n let tagX = ix;\n const tagY = cardY + 68;\n const tagRight = cx + cw - PAD;\n nextSectionY = tagY + PAD;\n\n for (const kw of (doc.keyword_s ?? [])) {\n const bw = kw.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, kw, C.tagBg, C.tagColor);\n }\n for (const domain of (doc.domain_s ?? [])) {\n const bw = domain.length * 11 * 0.6 + 14;\n if (tagX + bw > tagRight) break;\n tagX += pill(g, tagX, tagY, domain, C.domainBg, C.domainColor);\n }\n if (doc.conferenceTitle_s && tagX < tagRight) {\n const label = truncate(doc.conferenceTitle_s, tagRight - tagX, 11);\n g.appendChild(mkText(tagX, tagY, label, {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n }\n }\n\n // ── Abstract (lvl 3 only) ─────────────────────────────────────────────────\n if (lvl === 3 && doc.abstract_s?.[0]) {\n const absTop = nextSectionY + ABSTRACT_TOP_GAP;\n // Separator line\n g.appendChild(mkRect(ix, absTop - 4, cw - PAD * 2, 1, C.border));\n // \"Abstract\" label\n g.appendChild(mkText(ix, absTop + ABSTRACT_LABEL_H - 4, 'Abstract', {\n 'font-size': 11, fill: C.muted, 'font-style': 'italic',\n }));\n // Wrapped text\n wrapText(\n g,\n ix, absTop + ABSTRACT_LABEL_H + ABSTRACT_LINE_H - 2,\n doc.abstract_s[0],\n cw - PAD * 2,\n ABSTRACT_FS,\n ABSTRACT_LINE_H,\n C.text,\n );\n }\n\n return g;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an SVG element representing the article list.\n * The SVG is self-contained and can be inserted into the DOM or serialised.\n */\nexport function buildArticlesSvg(\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): SVGSVGElement {\n const cardsH = docs.reduce((s, d) => s + cardHeight(d, lvl) + CARD_GAP, 0);\n const totalH = HEADER_H + CARD_GAP + cardsH + FOOTER_H;\n\n const svg = svgEl('svg');\n set(svg, {\n width: W,\n height: totalH,\n viewBox: `0 0 ${W} ${totalH}`,\n xmlns: NS,\n role: 'img',\n 'aria-label': 'HAL Search Results',\n });\n\n // Background\n svg.appendChild(mkRect(0, 0, W, totalH, C.bg));\n\n // Header bar\n svg.appendChild(mkRect(0, 0, W, HEADER_H, C.accent));\n svg.appendChild(mkText(PAD, 32, 'HAL Search Results', {\n 'font-size': 18, 'font-weight': 'bold', fill: C.accentText,\n }));\n\n const { totalFound, currentPage, rows } = pagination;\n const totalPages = Math.max(1, Math.ceil(totalFound / rows));\n const pageInfo = `Page ${currentPage} / ${totalPages} · ${totalFound.toLocaleString()} results`;\n\n svg.appendChild(mkText(W - PAD, 32, pageInfo, {\n 'font-size': 12, fill: C.accentText, 'text-anchor': 'end',\n }));\n\n // Article cards\n let y = HEADER_H + CARD_GAP;\n for (const doc of docs) {\n svg.appendChild(buildCard(doc, lvl, y));\n y += cardHeight(doc, lvl) + CARD_GAP;\n }\n\n // Footer: pagination info (left) + GitHub credit (right)\n const footerY = totalH - FOOTER_H / 2 + 4;\n svg.appendChild(mkText(PAD, footerY, pageInfo, {\n 'font-size': 11, fill: C.muted,\n }));\n svg.appendChild(mkLink(\n GITHUB_URL,\n mkText(W - PAD, footerY, 'hal-search', {\n 'font-size': 11, fill: C.muted, 'text-anchor': 'end',\n }),\n ));\n\n return svg;\n}\n\n/**\n * Clears `container` and renders the article list as an inline SVG.\n * Falls back to the standard `.hal-empty` paragraph when `docs` is empty.\n */\nexport function renderResultsSvg(\n container: HTMLElement,\n docs: HalDoc[],\n lvl: DetailLevel,\n pagination: PaginationState,\n): void {\n container.innerHTML = '';\n if (docs.length === 0) {\n const p = document.createElement('p');\n p.className = 'hal-empty';\n p.textContent = 'No results found.';\n container.appendChild(p);\n return;\n }\n container.appendChild(buildArticlesSvg(docs, lvl, pagination));\n}\n","export const DEFAULT_CSS = `\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`;\n\n/** Injects the default CSS into <head> once. Idempotent — safe to call multiple times. */\nexport function injectDefaultStyles(): void {\n const ID = 'hal-search-styles';\n if (document.getElementById(ID)) return;\n const style = document.createElement('style');\n style.id = ID;\n style.textContent = DEFAULT_CSS;\n document.head.appendChild(style);\n}\n","import type {\n HalSearchOptions,\n SearchParams,\n PaginationState,\n HalApiResponse,\n DetailLevel,\n} from './types';\nimport { fetchArticles, DEFAULT_BASE } from './api';\nimport { renderResults, renderLoading, renderError } from './renderer';\nimport { renderResultsSvg, buildArticlesSvg } from './svg-renderer';\nimport { injectDefaultStyles } from './styles';\n\nconst DEFAULTS = {\n lvl: 1 as DetailLevel,\n rows: 10,\n apiBase: DEFAULT_BASE,\n injectStyles: true,\n output: 'html' as 'html' | 'svg',\n};\n\nexport class HalSearch {\n private readonly container?: HTMLElement;\n private options: Required<Omit<HalSearchOptions, 'container' | 'onResults' | 'onError' | 'backgroundColor' | 'textColor' | 'mainColor'>> & {\n onResults?: HalSearchOptions['onResults'];\n onError?: HalSearchOptions['onError'];\n };\n private pagination: PaginationState;\n private currentUid: string = '';\n\n constructor(options: HalSearchOptions) {\n if (options.container) {\n this.container = this._resolveContainer(options.container);\n }\n\n this.options = {\n lvl: options.lvl ?? DEFAULTS.lvl,\n rows: options.rows ?? DEFAULTS.rows,\n apiBase: options.apiBase ?? DEFAULTS.apiBase,\n injectStyles: options.injectStyles ?? DEFAULTS.injectStyles,\n output: options.output ?? DEFAULTS.output,\n onResults: options.onResults,\n onError: options.onError,\n };\n\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: 0,\n };\n\n if (this.options.injectStyles) {\n injectDefaultStyles();\n }\n\n this._applyColors(options);\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start a new search, resetting to page 1. */\n async search(params: SearchParams): Promise<SVGSVGElement | void> {\n this.currentUid = params.uid;\n if (params.rows !== undefined) this.options.rows = params.rows;\n this.pagination = {\n currentPage: 1,\n totalFound: 0,\n rows: this.options.rows,\n start: params.start ?? 0,\n };\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Navigate to a specific page number (1-based). */\n async goToPage(page: number): Promise<SVGSVGElement | void> {\n const totalPages = Math.max(1, Math.ceil(this.pagination.totalFound / this.options.rows));\n const clampedPage = Math.min(Math.max(1, page), totalPages);\n const start = (clampedPage - 1) * this.options.rows;\n return this._fetch(this.currentUid, start);\n }\n\n /** Navigate to the next page. */\n async nextPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage + 1);\n }\n\n /** Navigate to the previous page. */\n async prevPage(): Promise<SVGSVGElement | void> {\n return this.goToPage(this.pagination.currentPage - 1);\n }\n\n /** Change the detail level and re-fetch the current results. */\n async setLevel(lvl: DetailLevel): Promise<SVGSVGElement | void> {\n this.options.lvl = lvl;\n return this._fetch(this.currentUid, this.pagination.start);\n }\n\n /** Update the color theme at runtime. Only provided colors are changed. */\n setColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n this._applyColors(colors);\n }\n\n /** Clear the container and remove rendered content. */\n destroy(): void {\n if (this.container) {\n this.container.innerHTML = '';\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private _applyColors(colors: { backgroundColor?: string; textColor?: string; mainColor?: string }): void {\n if (!this.container) return;\n if (colors.backgroundColor) {\n this.container.style.setProperty('--hal-bg', colors.backgroundColor);\n }\n if (colors.textColor) {\n this.container.style.setProperty('--hal-text', colors.textColor);\n }\n if (colors.mainColor) {\n this.container.style.setProperty('--hal-accent', colors.mainColor);\n }\n }\n\n private async _fetch(uid: string, start: number): Promise<SVGSVGElement | void> {\n if (!uid) return;\n\n if (this.container) {\n renderLoading(this.container);\n }\n\n try {\n const response: HalApiResponse = await fetchArticles(\n uid,\n this.options.lvl,\n this.options.rows,\n start,\n this.options.apiBase,\n );\n\n this._updatePagination(response, start);\n\n if (this.options.output === 'svg') {\n if (this.container) {\n renderResultsSvg(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n } else {\n const svg = buildArticlesSvg(\n response.response.docs,\n this.options.lvl,\n this.pagination,\n );\n this.options.onResults?.(response);\n return svg;\n }\n } else {\n if (this.container) {\n renderResults(\n this.container,\n response.response.docs,\n this.options.lvl,\n this.pagination,\n (page) => { void this.goToPage(page); },\n );\n } else {\n throw new Error('HalSearch: container is required for HTML output');\n }\n }\n\n this.options.onResults?.(response);\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n if (this.container) {\n renderError(this.container, error);\n }\n this.options.onError?.(error);\n if (!this.container && !this.options.onError) {\n throw error;\n }\n }\n }\n\n private _resolveContainer(target: HTMLElement | string): HTMLElement {\n if (typeof target === 'string') {\n const found = document.querySelector<HTMLElement>(target);\n if (!found) {\n throw new Error(`HalSearch: container not found for selector \"${target}\"`);\n }\n return found;\n }\n return target;\n }\n\n private _updatePagination(response: HalApiResponse, start: number): void {\n const { numFound } = response.response;\n this.pagination = {\n currentPage: Math.floor(start / this.options.rows) + 1,\n totalFound: numFound,\n rows: this.options.rows,\n start,\n };\n }\n}\n","import type { DetailLevel } from './types';\n\nexport interface EmbedOptions {\n /** Base URL where embed.html is hosted */\n embedBase: string;\n /** Search query or author UID */\n uid: string;\n /** Detail level 0-3 */\n lvl?: DetailLevel;\n /** Results per page */\n rows?: number;\n /** Render mode: 'html' (default) for interactive cards, 'svg' for a static SVG */\n type?: 'html' | 'svg';\n /** iframe width (CSS value) */\n width?: string;\n /** iframe height (CSS value) */\n height?: string;\n /** Background color for article cards */\n backgroundColor?: string;\n /** Text color for article content */\n textColor?: string;\n /** Main accent color for links and buttons */\n mainColor?: string;\n}\n\n/** Builds the URL for the embeddable page with query parameters. */\nexport function buildEmbedUrl(options: EmbedOptions): string {\n const params = new URLSearchParams({ uid: options.uid });\n if (options.lvl !== undefined) params.set('lvl', String(options.lvl));\n if (options.rows !== undefined) params.set('rows', String(options.rows));\n if (options.type) params.set('type', options.type);\n if (options.backgroundColor) params.set('bg', options.backgroundColor);\n if (options.textColor) params.set('text', options.textColor);\n if (options.mainColor) params.set('main', options.mainColor);\n return `${options.embedBase}/embed.html?${params.toString()}`;\n}\n\n/** Returns a ready-to-paste `<iframe>` HTML snippet. */\nexport function buildEmbedSnippet(options: EmbedOptions): string {\n const src = buildEmbedUrl(options);\n const width = options.width ?? '100%';\n const height = options.height ?? '600';\n return `<iframe src=\"${src}\" width=\"${width}\" height=\"${height}\"></iframe>`;\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -55,6 +55,12 @@ export interface HalSearchOptions {
|
|
|
55
55
|
injectStyles?: boolean;
|
|
56
56
|
/** Render the article list as HTML cards or as an inline SVG. Default: 'html' */
|
|
57
57
|
output?: 'html' | 'svg';
|
|
58
|
+
/** Background color for article cards (sets --hal-bg). Example: '#1a1a2e' */
|
|
59
|
+
backgroundColor?: string;
|
|
60
|
+
/** Text color for article content (sets --hal-text). Example: '#e0e0e0' */
|
|
61
|
+
textColor?: string;
|
|
62
|
+
/** Main accent color for links, buttons, and highlights (sets --hal-accent). Example: '#e63946' */
|
|
63
|
+
mainColor?: string;
|
|
58
64
|
/** Called after each successful fetch */
|
|
59
65
|
onResults?: (response: HalApiResponse) => void;
|
|
60
66
|
/** Called on fetch error */
|
package/package.json
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "hal-search",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A library to display article descriptions fetched from the HAL Open Archive API",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "hal-search",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A library to display article descriptions fetched from the HAL Open Archive API",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/JPugetGil/hal-search"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"main": "./dist/hal-search.umd.js",
|
|
10
|
+
"module": "./dist/hal-search.es.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/hal-search.es.js",
|
|
16
|
+
"require": "./dist/hal-search.umd.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"dev": "vite",
|
|
24
|
+
"build": "vite build",
|
|
25
|
+
"preview": "vite preview",
|
|
26
|
+
"example": "npm run build && npx serve -l 8080 example",
|
|
27
|
+
"publish": "npm run build && npm publish --access public"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "^5.4.0",
|
|
31
|
+
"vite": "^8.0.0",
|
|
32
|
+
"vite-plugin-dts": "^4.5.4"
|
|
33
|
+
}
|
|
34
|
+
}
|