deepresearch-flow 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl

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.
Files changed (26) hide show
  1. deepresearch_flow/paper/db.py +184 -0
  2. deepresearch_flow/paper/db_ops.py +1939 -0
  3. deepresearch_flow/paper/web/app.py +38 -3705
  4. deepresearch_flow/paper/web/constants.py +23 -0
  5. deepresearch_flow/paper/web/filters.py +255 -0
  6. deepresearch_flow/paper/web/handlers/__init__.py +14 -0
  7. deepresearch_flow/paper/web/handlers/api.py +217 -0
  8. deepresearch_flow/paper/web/handlers/pages.py +334 -0
  9. deepresearch_flow/paper/web/markdown.py +549 -0
  10. deepresearch_flow/paper/web/static/css/main.css +857 -0
  11. deepresearch_flow/paper/web/static/js/detail.js +406 -0
  12. deepresearch_flow/paper/web/static/js/index.js +266 -0
  13. deepresearch_flow/paper/web/static/js/outline.js +58 -0
  14. deepresearch_flow/paper/web/static/js/stats.js +39 -0
  15. deepresearch_flow/paper/web/templates/base.html +43 -0
  16. deepresearch_flow/paper/web/templates/detail.html +332 -0
  17. deepresearch_flow/paper/web/templates/index.html +114 -0
  18. deepresearch_flow/paper/web/templates/stats.html +29 -0
  19. deepresearch_flow/paper/web/templates.py +85 -0
  20. deepresearch_flow/paper/web/text.py +68 -0
  21. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/METADATA +23 -2
  22. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/RECORD +26 -8
  23. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/WHEEL +0 -0
  24. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/entry_points.txt +0 -0
  25. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/licenses/LICENSE +0 -0
  26. {deepresearch_flow-0.3.0.dist-info → deepresearch_flow-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,406 @@
1
+ /* Detail page JavaScript - extracted from embedded inline scripts in paper_detail handler */
2
+ (function() {
3
+ 'use strict';
4
+
5
+ // ========================================
6
+ // View-specific initialization based on body classes/data attributes
7
+ // ========================================
8
+
9
+ function init() {
10
+ initFullscreen();
11
+ initMarkdownRendering();
12
+ initFootnotes();
13
+ initBackToTop();
14
+
15
+ // View-specific initializers
16
+ if (document.getElementById('translationLang')) {
17
+ initTranslationSelect();
18
+ }
19
+ if (document.getElementById('splitLeft') || document.getElementById('splitRight')) {
20
+ initSplitView();
21
+ }
22
+ if (document.getElementById('the-canvas')) {
23
+ initPdfView();
24
+ }
25
+ }
26
+
27
+ // ========================================
28
+ // Back-to-top button
29
+ // ========================================
30
+
31
+ function initBackToTop() {
32
+ var button = document.getElementById('backToTop');
33
+ if (!button) return;
34
+
35
+ function updateVisibility() {
36
+ if (window.scrollY > 120) {
37
+ button.classList.add('visible');
38
+ } else {
39
+ button.classList.remove('visible');
40
+ }
41
+ }
42
+
43
+ button.addEventListener('click', function() {
44
+ window.scrollTo({ top: 0, behavior: 'smooth' });
45
+ });
46
+
47
+ document.addEventListener('scroll', updateVisibility, { passive: true });
48
+ updateVisibility();
49
+ }
50
+
51
+ // ========================================
52
+ // Fullscreen functionality
53
+ // ========================================
54
+
55
+ function initFullscreen() {
56
+ var fullscreenEnter = document.getElementById('fullscreenEnter');
57
+ var fullscreenExit = document.getElementById('fullscreenExit');
58
+
59
+ function setFullscreen(enable) {
60
+ document.body.classList.toggle('detail-fullscreen', enable);
61
+ if (document.body.classList.contains('split-view')) {
62
+ document.body.classList.toggle('split-controls-collapsed', enable);
63
+ var toggle = document.getElementById('splitControlsToggle');
64
+ if (toggle) {
65
+ toggle.setAttribute('aria-expanded', enable ? 'false' : 'true');
66
+ }
67
+ }
68
+ }
69
+
70
+ if (fullscreenEnter) {
71
+ fullscreenEnter.addEventListener('click', function() { setFullscreen(true); });
72
+ }
73
+ if (fullscreenExit) {
74
+ fullscreenExit.addEventListener('click', function() { setFullscreen(false); });
75
+ }
76
+ document.addEventListener('keydown', function(event) {
77
+ if (event.key === 'Escape' && document.body.classList.contains('detail-fullscreen')) {
78
+ setFullscreen(false);
79
+ }
80
+ });
81
+ }
82
+
83
+ // ========================================
84
+ // Markdown rendering (Mermaid + KaTeX)
85
+ // ========================================
86
+
87
+ function initMarkdownRendering() {
88
+ // Only run if we have content with mermaid/katex
89
+ if (!document.getElementById('content')) return;
90
+
91
+ function initRendering() {
92
+ // Markmap: convert fenced markmap blocks to svg mindmaps
93
+ if (window.markmap && window.markmap.Transformer && window.markmap.Markmap) {
94
+ var transformer = new window.markmap.Transformer();
95
+ document.querySelectorAll('code.language-markmap').forEach(function(code) {
96
+ var pre = code.parentElement;
97
+ if (!pre) return;
98
+ var svg = document.createElement('svg');
99
+ svg.className = 'markmap';
100
+ pre.replaceWith(svg);
101
+ try {
102
+ var result = transformer.transform(code.textContent || '');
103
+ window.markmap.Markmap.create(svg, null, result.root);
104
+ } catch (err) {
105
+ // Ignore markmap parse errors
106
+ }
107
+ });
108
+ }
109
+
110
+ // Mermaid: convert fenced code blocks to mermaid divs
111
+ document.querySelectorAll('code.language-mermaid').forEach(function(code) {
112
+ var pre = code.parentElement;
113
+ var div = document.createElement('div');
114
+ div.className = 'mermaid';
115
+ div.textContent = code.textContent;
116
+ pre.replaceWith(div);
117
+ });
118
+
119
+ if (window.mermaid) {
120
+ mermaid.initialize({ startOnLoad: false });
121
+ mermaid.run();
122
+ }
123
+
124
+ if (window.renderMathInElement) {
125
+ renderMathInElement(document.getElementById('content'), {
126
+ delimiters: [
127
+ {left: '$$', right: '$$', display: true},
128
+ {left: '$', right: '$', display: false},
129
+ {left: '\\(', right: '\\)', display: false},
130
+ {left: '\\[', right: '\\]', display: true}
131
+ ],
132
+ throwOnError: false
133
+ });
134
+ }
135
+ }
136
+
137
+ if (document.readyState === 'loading') {
138
+ document.addEventListener('DOMContentLoaded', initRendering);
139
+ } else {
140
+ initRendering();
141
+ }
142
+ }
143
+
144
+ // ========================================
145
+ // Footnote tooltips
146
+ // ========================================
147
+
148
+ function initFootnotes() {
149
+ if (!document.querySelector('.footnotes')) return;
150
+
151
+ var notes = {};
152
+ document.querySelectorAll('.footnotes li[id]').forEach(function(li) {
153
+ var id = li.getAttribute('id');
154
+ if (!id) return;
155
+ var clone = li.cloneNode(true);
156
+ clone.querySelectorAll('a.footnote-backref').forEach(function(el) { el.remove(); });
157
+ var text = (clone.textContent || '').replace(/\s+/g, ' ').trim();
158
+ if (text) notes['#' + id] = text.length > 400 ? text.slice(0, 397) + '…' : text;
159
+ });
160
+ document.querySelectorAll('.footnote-ref a[href^="#fn"]').forEach(function(link) {
161
+ var ref = link.getAttribute('href');
162
+ var text = notes[ref];
163
+ if (!text) return;
164
+ link.dataset.footnote = text;
165
+ link.classList.add('footnote-tip');
166
+ });
167
+ }
168
+
169
+ // ========================================
170
+ // Translation language select
171
+ // ========================================
172
+
173
+ function initTranslationSelect() {
174
+ var translationSelect = document.getElementById('translationLang');
175
+ if (!translationSelect) return;
176
+
177
+ translationSelect.addEventListener('change', function() {
178
+ var params = new URLSearchParams(window.location.search);
179
+ params.set('view', 'translated');
180
+ params.set('lang', translationSelect.value);
181
+ window.location.search = params.toString();
182
+ });
183
+ }
184
+
185
+ // ========================================
186
+ // Split view controls
187
+ // ========================================
188
+
189
+ function initSplitView() {
190
+ var leftSelect = document.getElementById('splitLeft');
191
+ var rightSelect = document.getElementById('splitRight');
192
+ var swapButton = document.getElementById('splitSwap');
193
+ var tightenButton = document.getElementById('splitTighten');
194
+ var widenButton = document.getElementById('splitWiden');
195
+ var controlsToggle = document.getElementById('splitControlsToggle');
196
+ var searchInput = document.getElementById('splitSearch');
197
+
198
+ if (!leftSelect || !rightSelect) return;
199
+
200
+ function updateSplit() {
201
+ var params = new URLSearchParams(window.location.search);
202
+ params.set('view', 'split');
203
+ params.set('left', leftSelect.value);
204
+ params.set('right', rightSelect.value);
205
+ window.location.search = params.toString();
206
+ }
207
+
208
+ leftSelect.addEventListener('change', updateSplit);
209
+ rightSelect.addEventListener('change', updateSplit);
210
+
211
+ function setControlsCollapsed(collapsed) {
212
+ document.body.classList.toggle('split-controls-collapsed', collapsed);
213
+ if (controlsToggle) {
214
+ controlsToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
215
+ }
216
+ }
217
+
218
+ if (controlsToggle) {
219
+ controlsToggle.addEventListener('click', function() {
220
+ var collapsed = document.body.classList.contains('split-controls-collapsed');
221
+ setControlsCollapsed(!collapsed);
222
+ });
223
+ }
224
+
225
+ if (document.body.classList.contains('detail-fullscreen')) {
226
+ setControlsCollapsed(true);
227
+ }
228
+
229
+ function filterOptions(select, term) {
230
+ var value = select.value;
231
+ for (var i = 0; i < select.options.length; i++) {
232
+ var option = select.options[i];
233
+ var label = (option.textContent || "").toLowerCase();
234
+ var match = !term || label.indexOf(term) !== -1 || option.value === value;
235
+ option.hidden = !match;
236
+ option.style.display = match ? "" : "none";
237
+ }
238
+ }
239
+
240
+ if (searchInput) {
241
+ searchInput.addEventListener('input', function() {
242
+ var term = searchInput.value.trim().toLowerCase();
243
+ filterOptions(leftSelect, term);
244
+ filterOptions(rightSelect, term);
245
+ });
246
+ }
247
+
248
+ if (swapButton) {
249
+ swapButton.addEventListener('click', function() {
250
+ var leftValue = leftSelect.value;
251
+ leftSelect.value = rightSelect.value;
252
+ rightSelect.value = leftValue;
253
+ updateSplit();
254
+ });
255
+ }
256
+
257
+ // Width adjustment with localStorage persistence
258
+ var widthSteps = ["1200px", "1400px", "1600px", "1800px", "2000px", "100%"];
259
+ var widthIndex = widthSteps.length - 1;
260
+
261
+ try {
262
+ var stored = localStorage.getItem('splitWidthIndex');
263
+ if (stored !== null) {
264
+ var parsed = Number.parseInt(stored, 10);
265
+ if (!Number.isNaN(parsed)) {
266
+ widthIndex = Math.max(0, Math.min(widthSteps.length - 1, parsed));
267
+ }
268
+ }
269
+ } catch (err) {
270
+ // Ignore storage errors (e.g. private mode)
271
+ }
272
+
273
+ function applySplitWidth() {
274
+ var value = widthSteps[widthIndex];
275
+ document.documentElement.style.setProperty('--split-max-width', value);
276
+ try {
277
+ localStorage.setItem('splitWidthIndex', String(widthIndex));
278
+ } catch (err) {
279
+ // Ignore storage errors
280
+ }
281
+ }
282
+
283
+ if (tightenButton) {
284
+ tightenButton.addEventListener('click', function() {
285
+ widthIndex = Math.max(0, widthIndex - 1);
286
+ applySplitWidth();
287
+ });
288
+ }
289
+
290
+ if (widenButton) {
291
+ widenButton.addEventListener('click', function() {
292
+ widthIndex = Math.min(widthSteps.length - 1, widthIndex + 1);
293
+ applySplitWidth();
294
+ });
295
+ }
296
+
297
+ applySplitWidth();
298
+ }
299
+
300
+ // ========================================
301
+ // PDF view (canvas-based)
302
+ // ========================================
303
+
304
+ function initPdfView() {
305
+ var pdfUrl = document.body.dataset.pdfUrl;
306
+ if (!pdfUrl) return;
307
+
308
+ var pdfJsLib = window.pdfjsLib;
309
+ if (!pdfJsLib) return;
310
+
311
+ pdfJsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.js';
312
+
313
+ var pdfDoc = null;
314
+ var pageNum = 1;
315
+ var pageRendering = false;
316
+ var pageNumPending = null;
317
+ var zoomLevel = 1.0;
318
+ var canvas = document.getElementById('the-canvas');
319
+ var ctx = canvas.getContext('2d');
320
+
321
+ function renderPage(num) {
322
+ pageRendering = true;
323
+ pdfDoc.getPage(num).then(function(page) {
324
+ var baseViewport = page.getViewport({scale: 1});
325
+ var containerWidth = canvas.clientWidth || baseViewport.width;
326
+ var fitScale = containerWidth / baseViewport.width;
327
+ var scale = fitScale * zoomLevel;
328
+
329
+ var viewport = page.getViewport({scale: scale});
330
+ var outputScale = window.devicePixelRatio || 1;
331
+
332
+ canvas.width = Math.floor(viewport.width * outputScale);
333
+ canvas.height = Math.floor(viewport.height * outputScale);
334
+ canvas.style.width = Math.floor(viewport.width) + 'px';
335
+ canvas.style.height = Math.floor(viewport.height) + 'px';
336
+
337
+ var transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;
338
+ var renderContext = { canvasContext: ctx, viewport: viewport, transform: transform };
339
+ var renderTask = page.render(renderContext);
340
+ renderTask.promise.then(function() {
341
+ pageRendering = false;
342
+ document.getElementById('page_num').textContent = String(pageNum);
343
+ if (pageNumPending !== null) {
344
+ var next = pageNumPending;
345
+ pageNumPending = null;
346
+ renderPage(next);
347
+ }
348
+ });
349
+ });
350
+ }
351
+
352
+ function queueRenderPage(num) {
353
+ if (pageRendering) {
354
+ pageNumPending = num;
355
+ } else {
356
+ renderPage(num);
357
+ }
358
+ }
359
+
360
+ function onPrevPage() {
361
+ if (pageNum <= 1) return;
362
+ pageNum--;
363
+ queueRenderPage(pageNum);
364
+ }
365
+
366
+ function onNextPage() {
367
+ if (pageNum >= pdfDoc.numPages) return;
368
+ pageNum++;
369
+ queueRenderPage(pageNum);
370
+ }
371
+
372
+ function adjustZoom(delta) {
373
+ zoomLevel = Math.max(0.5, Math.min(3.0, zoomLevel + delta));
374
+ queueRenderPage(pageNum);
375
+ }
376
+
377
+ document.getElementById('prev').addEventListener('click', onPrevPage);
378
+ document.getElementById('next').addEventListener('click', onNextPage);
379
+ document.getElementById('zoomOut').addEventListener('click', function() { adjustZoom(-0.1); });
380
+ document.getElementById('zoomIn').addEventListener('click', function() { adjustZoom(0.1); });
381
+
382
+ pdfJsLib.getDocument(pdfUrl).promise.then(function(pdfDoc_) {
383
+ pdfDoc = pdfDoc_;
384
+ document.getElementById('page_count').textContent = String(pdfDoc.numPages);
385
+ renderPage(pageNum);
386
+ });
387
+
388
+ var resizeTimer = null;
389
+ window.addEventListener('resize', function() {
390
+ if (!pdfDoc) return;
391
+ if (resizeTimer) clearTimeout(resizeTimer);
392
+ resizeTimer = setTimeout(function() { queueRenderPage(pageNum); }, 150);
393
+ });
394
+ }
395
+
396
+ // ========================================
397
+ // Initialize on DOM ready
398
+ // ========================================
399
+
400
+ if (document.readyState === 'loading') {
401
+ document.addEventListener('DOMContentLoaded', init);
402
+ } else {
403
+ init();
404
+ }
405
+
406
+ })();
@@ -0,0 +1,266 @@
1
+ /* Index page functionality */
2
+ (function() {
3
+ 'use strict';
4
+
5
+ var page = 1;
6
+ var loading = false;
7
+ var done = false;
8
+
9
+ function currentParams(nextPage) {
10
+ var params = new URLSearchParams();
11
+ params.set("page", String(nextPage));
12
+ params.set("page_size", "30");
13
+ var q = document.getElementById("query").value.trim();
14
+ if (q) params.set("q", q);
15
+ var fq = document.getElementById("filterQuery").value.trim();
16
+ if (fq) params.set("fq", fq);
17
+ var sortBy = document.getElementById("sortBy").value;
18
+ if (sortBy) params.set("sort_by", sortBy);
19
+ var sortDir = document.getElementById("sortDir").value;
20
+ if (sortDir) params.set("sort_dir", sortDir);
21
+ function addMulti(id, key) {
22
+ var el = document.getElementById(id);
23
+ var values = Array.from(el.selectedOptions).map(function(opt) { return opt.value; }).filter(Boolean);
24
+ for (var i = 0; i < values.length; i++) {
25
+ params.append(key, values[i]);
26
+ }
27
+ }
28
+ addMulti("filterPdf", "pdf");
29
+ addMulti("filterSource", "source");
30
+ addMulti("filterTranslated", "translated");
31
+ addMulti("filterSummary", "summary");
32
+ addMulti("filterTemplate", "template");
33
+ return params;
34
+ }
35
+
36
+ function escapeHtml(text) {
37
+ var div = document.createElement("div");
38
+ div.textContent = text;
39
+ return div.innerHTML;
40
+ }
41
+
42
+ function normalizeText(text) {
43
+ return String(text || "").replace(/\s+/g, " ").trim();
44
+ }
45
+
46
+ function cleanVenue(text) {
47
+ return normalizeText(text).replace(/\{\{|\}\}/g, "");
48
+ }
49
+
50
+ function viewSuffixForItem(item) {
51
+ var viewSelect = document.getElementById("openView");
52
+ var view = viewSelect ? viewSelect.value : "summary";
53
+ var isPdfOnly = item.is_pdf_only;
54
+ var pdfFallback = item.has_pdf ? "pdfjs" : "pdf";
55
+ if (isPdfOnly && (view === "summary" || view === "source" || view === "translated")) {
56
+ view = pdfFallback;
57
+ }
58
+ if (!view || view === "summary") return "";
59
+ var params = new URLSearchParams();
60
+ params.set("view", view);
61
+ if (view === "split") {
62
+ if (isPdfOnly) {
63
+ params.set("left", pdfFallback);
64
+ params.set("right", pdfFallback);
65
+ } else {
66
+ params.set("left", "summary");
67
+ if (item.has_pdf) {
68
+ params.set("right", "pdfjs");
69
+ } else if (item.has_source) {
70
+ params.set("right", "source");
71
+ } else {
72
+ params.set("right", "summary");
73
+ }
74
+ }
75
+ }
76
+ return "?" + params.toString();
77
+ }
78
+
79
+ function renderItem(item, ordinal) {
80
+ var tags = (item.tags || []).map(function(t) { return '<span class="pill">' + escapeHtml(t) + '</span>'; }).join("");
81
+ var templateTags = (item.template_tags || []).map(function(t) { return '<span class="pill template">tmpl:' + escapeHtml(t) + '</span>'; }).join("");
82
+ var authors = (item.authors || []).slice(0, 6).map(function(a) { return escapeHtml(a); }).join(", ");
83
+ var venue = cleanVenue(item.venue || "");
84
+ var dateLabel = escapeHtml(item.year || "") + "-" + escapeHtml(item.month || "");
85
+ var meta = venue ? (dateLabel + " · <strong>" + escapeHtml(venue) + "</strong>") : dateLabel;
86
+ var excerpt = "";
87
+ var fullSummary = normalizeText(item.summary_full || "");
88
+ var shortSummary = normalizeText(item.summary_excerpt || fullSummary);
89
+ if (shortSummary) {
90
+ if (fullSummary && fullSummary !== shortSummary) {
91
+ excerpt = '<div class="summary-snippet" data-summary="1">' +
92
+ '<button class="summary-toggle" type="button" aria-expanded="false" title="Expand summary">▾</button>' +
93
+ '<div class="summary-text summary-short">' + escapeHtml(shortSummary) + '</div>' +
94
+ '<div class="summary-text summary-full">' + escapeHtml(fullSummary) + '</div>' +
95
+ '</div>';
96
+ } else {
97
+ excerpt = '<div class="summary-snippet"><div class="summary-text summary-short">' + escapeHtml(shortSummary) + '</div></div>';
98
+ }
99
+ }
100
+ var viewSuffix = viewSuffixForItem(item);
101
+ var badges = [];
102
+ if (item.has_source) badges.push('<span class="pill">source</span>');
103
+ if (item.has_translation) badges.push('<span class="pill">translated</span>');
104
+ if (item.has_pdf) badges.push('<span class="pill">pdf</span>');
105
+ if (item.is_pdf_only) badges.push('<span class="pill pdf-only">pdf-only</span>');
106
+ var indexBadge = typeof ordinal === "number" ? '<span class="card-index">#' + ordinal + '</span>' : "";
107
+ return '<div class="card paper-card">' +
108
+ indexBadge +
109
+ '<div><a href="/paper/' + encodeURIComponent(item.source_hash) + viewSuffix + '">' + escapeHtml(item.title || "") + '</a></div>' +
110
+ '<div class="muted">' + authors + '</div>' +
111
+ '<div class="muted">' + meta + '</div>' +
112
+ excerpt +
113
+ '<div style="margin-top:6px">' + badges.join("") + " " + templateTags + " " + tags + '</div>' +
114
+ '</div>';
115
+ }
116
+
117
+ function renderStatsRow(targetId, label, counts) {
118
+ var row = document.getElementById(targetId);
119
+ if (!row || !counts) return;
120
+ var pills = [];
121
+ pills.push('<span class="stats-label">' + escapeHtml(label) + '</span>');
122
+ pills.push('<span class="pill stat">Count ' + counts.total + '</span>');
123
+ pills.push('<span class="pill stat">PDF ' + counts.pdf + '</span>');
124
+ pills.push('<span class="pill stat">Source ' + counts.source + '</span>');
125
+ pills.push('<span class="pill stat">Translated ' + (counts.translated || 0) + '</span>');
126
+ pills.push('<span class="pill stat">Summary ' + counts.summary + '</span>');
127
+ var order = counts.template_order || Object.keys(counts.templates || {});
128
+ for (var i = 0; i < order.length; i++) {
129
+ var tag = order[i];
130
+ var count = (counts.templates && counts.templates[tag]) || 0;
131
+ pills.push('<span class="pill stat">tmpl:' + escapeHtml(tag) + ' ' + count + '</span>');
132
+ }
133
+ row.innerHTML = pills.join("");
134
+ }
135
+
136
+ function updateStats(stats) {
137
+ if (!stats) return;
138
+ renderStatsRow("statsTotal", "Total", stats.all);
139
+ renderStatsRow("statsFiltered", "Filtered", stats.filtered);
140
+ }
141
+
142
+ function loadMore() {
143
+ if (loading || done) return;
144
+ loading = true;
145
+ var loadingEl = document.getElementById("loading");
146
+ if (loadingEl) loadingEl.textContent = "Loading...";
147
+ var url = "/api/papers?" + currentParams(page).toString();
148
+ fetch(url).then(function(res) { return res.json(); }).then(function(data) {
149
+ if (data.stats) updateStats(data.stats);
150
+ var results = document.getElementById("results");
151
+ if (results) {
152
+ var startIndex = (data.page - 1) * data.page_size;
153
+ for (var i = 0; i < data.items.length; i++) {
154
+ results.insertAdjacentHTML("beforeend", renderItem(data.items[i], startIndex + i + 1));
155
+ }
156
+ if (window.renderMathInElement) {
157
+ renderMathInElement(results, {
158
+ delimiters: [
159
+ {left: '$$', right: '$$', display: true},
160
+ {left: '$', right: '$', display: false},
161
+ {left: '\\\\(', right: '\\\\)', display: false},
162
+ {left: '\\\\[', right: '\\\\]', display: true}
163
+ ],
164
+ throwOnError: false
165
+ });
166
+ }
167
+ }
168
+ if (!data.has_more) {
169
+ done = true;
170
+ if (loadingEl) loadingEl.textContent = "End.";
171
+ } else {
172
+ page++;
173
+ if (loadingEl) loadingEl.textContent = "Scroll to load more...";
174
+ }
175
+ loading = false;
176
+ }).catch(function() {
177
+ loading = false;
178
+ if (loadingEl) loadingEl.textContent = "Error loading papers.";
179
+ });
180
+ }
181
+
182
+ function resetAndLoad() {
183
+ page = 1;
184
+ done = false;
185
+ var results = document.getElementById("results");
186
+ if (results) results.innerHTML = "";
187
+ loadMore();
188
+ }
189
+
190
+ function initEventListeners() {
191
+ var eventElements = ["query", "openView", "filterQuery", "filterPdf", "filterSource", "filterTranslated", "filterSummary", "filterTemplate", "sortBy", "sortDir"];
192
+ for (var i = 0; i < eventElements.length; i++) {
193
+ var el = document.getElementById(eventElements[i]);
194
+ if (el) el.addEventListener("change", resetAndLoad);
195
+ }
196
+ var buildBtn = document.getElementById("buildQuery");
197
+ if (buildBtn) {
198
+ buildBtn.addEventListener("click", function() {
199
+ function add(field, value) {
200
+ value = value.trim();
201
+ if (!value) return "";
202
+ if (value.includes(" ")) return field + ':"' + value + '"';
203
+ return field + ":" + value;
204
+ }
205
+ var parts = [];
206
+ var t = document.getElementById("advTitle").value.trim();
207
+ var a = document.getElementById("advAuthor").value.trim();
208
+ var tag = document.getElementById("advTag").value.trim();
209
+ var y = document.getElementById("advYear").value.trim();
210
+ var m = document.getElementById("advMonth").value.trim();
211
+ var v = document.getElementById("advVenue").value.trim();
212
+ if (t) parts.push(add("title", t));
213
+ if (a) parts.push(add("author", a));
214
+ if (tag) {
215
+ var tagParts = tag.split(",");
216
+ for (var j = 0; j < tagParts.length; j++) {
217
+ var val = tagParts[j].trim();
218
+ if (val) parts.push(add("tag", val));
219
+ }
220
+ }
221
+ if (y) parts.push(add("year", y));
222
+ if (m) parts.push(add("month", m));
223
+ if (v) parts.push(add("venue", v));
224
+ var q = parts.join(" ");
225
+ var generatedEl = document.getElementById("generated");
226
+ if (generatedEl) generatedEl.textContent = q;
227
+ var queryEl = document.getElementById("query");
228
+ if (queryEl) queryEl.value = q;
229
+ resetAndLoad();
230
+ });
231
+ }
232
+ }
233
+
234
+ function initScrollHandler() {
235
+ window.addEventListener("scroll", function() {
236
+ if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 600)) {
237
+ loadMore();
238
+ }
239
+ });
240
+ }
241
+
242
+ function initSummaryToggle() {
243
+ document.addEventListener("click", function(event) {
244
+ var target = event.target;
245
+ if (!target || !target.classList.contains("summary-toggle")) return;
246
+ var container = target.closest(".summary-snippet");
247
+ if (!container) return;
248
+ var isOpen = container.classList.toggle("is-open");
249
+ target.setAttribute("aria-expanded", isOpen ? "true" : "false");
250
+ target.textContent = isOpen ? "▴" : "▾";
251
+ });
252
+ }
253
+
254
+ function init() {
255
+ initEventListeners();
256
+ initScrollHandler();
257
+ initSummaryToggle();
258
+ loadMore();
259
+ }
260
+
261
+ if (document.readyState === "loading") {
262
+ document.addEventListener("DOMContentLoaded", init);
263
+ } else {
264
+ init();
265
+ }
266
+ })();