Spaces:
Running
Running
Update templates/index.html
Browse files- templates/index.html +151 -40
templates/index.html
CHANGED
|
@@ -20,7 +20,6 @@
|
|
| 20 |
--shadow: 0 10px 30px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.25);
|
| 21 |
}
|
| 22 |
|
| 23 |
-
/* Background */
|
| 24 |
html,body{height:100%;}
|
| 25 |
body{
|
| 26 |
margin:0;
|
|
@@ -33,7 +32,6 @@
|
|
| 33 |
line-height:1.45;
|
| 34 |
}
|
| 35 |
|
| 36 |
-
/* Layout */
|
| 37 |
.wrap{
|
| 38 |
max-width: 980px;
|
| 39 |
margin: 48px auto 120px;
|
|
@@ -49,7 +47,6 @@
|
|
| 49 |
box-shadow: inset 0 0 0 1px #ffffff08;
|
| 50 |
}
|
| 51 |
|
| 52 |
-
/* Cards */
|
| 53 |
.card{
|
| 54 |
background: linear-gradient(180deg, #ffffff08, #00000022);
|
| 55 |
border: 1px solid #ffffff1c;
|
|
@@ -66,12 +63,8 @@
|
|
| 66 |
.card .hdr h2{margin:0; font-size:18px; font-weight:700; color:#fff;}
|
| 67 |
.card .body{ padding:18px; }
|
| 68 |
|
| 69 |
-
|
| 70 |
-
.uploader{
|
| 71 |
-
display:grid; gap:14px;
|
| 72 |
-
}
|
| 73 |
|
| 74 |
-
/* Hide original input but keep for logic */
|
| 75 |
#fileUpload{
|
| 76 |
position: absolute;
|
| 77 |
width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
|
|
@@ -105,7 +98,6 @@
|
|
| 105 |
|
| 106 |
.row{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
| 107 |
|
| 108 |
-
/* Buttons */
|
| 109 |
.btn{
|
| 110 |
--btn-bg: #2331a6;
|
| 111 |
--btn-fg: #eaf0ff;
|
|
@@ -121,7 +113,6 @@
|
|
| 121 |
.btn.secondary{ --btn-bg:#1a213f; --btn-fg:#dbe2ff; }
|
| 122 |
.btn.danger { --btn-bg:#4a1020; --btn-fg:#ffd7df; border-color:#ff6b6b44; }
|
| 123 |
|
| 124 |
-
/* Progress + status */
|
| 125 |
progress{
|
| 126 |
width: 320px; height: 14px; border-radius: 999px; overflow:hidden; vertical-align: middle;
|
| 127 |
background: #293079;
|
|
@@ -133,14 +124,12 @@
|
|
| 133 |
}
|
| 134 |
#uploadStatus, #searchStatus{ margin-left:10px; font-weight:700; color:#d5dcff; }
|
| 135 |
|
| 136 |
-
/* Client list chips */
|
| 137 |
#uploadedListClient ul{ list-style:none; padding:0; margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;}
|
| 138 |
#uploadedListClient li{
|
| 139 |
padding:6px 10px; border-radius:999px; font-size:12px; color:#dfe5ff;
|
| 140 |
background: linear-gradient(180deg,#ffffff0a,#00000025); border:1px solid #ffffff1a;
|
| 141 |
}
|
| 142 |
|
| 143 |
-
/* Forms */
|
| 144 |
label{ color:#cdd4ff; font-weight:600; letter-spacing:.2px; }
|
| 145 |
textarea, input[type="number"]{
|
| 146 |
width:100%; box-sizing:border-box; color:#fff; background:#0d1333; border:1px solid #ffffff22;
|
|
@@ -151,18 +140,52 @@
|
|
| 151 |
|
| 152 |
hr{ border:none; height:1px; background:#ffffff1a; margin:26px 0; }
|
| 153 |
|
| 154 |
-
/* Results */
|
| 155 |
ol{ padding-left: 22px; }
|
| 156 |
-
ol li{ margin:
|
| 157 |
a{ color: var(--accent2); }
|
| 158 |
|
| 159 |
-
/* Small helpers */
|
| 160 |
.muted{ color:var(--muted); font-size:13px; }
|
| 161 |
.cluster{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
}
|
| 167 |
</style>
|
| 168 |
</head>
|
|
@@ -187,10 +210,7 @@
|
|
| 187 |
</div>
|
| 188 |
<div class="body">
|
| 189 |
<div class="uploader">
|
| 190 |
-
<!-- Original input kept for logic -->
|
| 191 |
<input type="file" id="fileUpload" multiple>
|
| 192 |
-
|
| 193 |
-
<!-- Drop zone (click also opens the hidden input) -->
|
| 194 |
<div id="dropZone" class="drop" tabindex="0" role="button" aria-label="Drop files here or click to browse">
|
| 195 |
<div class="big">Drag & Drop files here</div>
|
| 196 |
<div class="small">.txt, .pdf supported</div>
|
|
@@ -202,7 +222,6 @@
|
|
| 202 |
<span id="uploadStatus"></span>
|
| 203 |
</div>
|
| 204 |
|
| 205 |
-
<!-- This shows uploaded files immediately after form submission (server-rendered) -->
|
| 206 |
{% if uploaded_filenames %}
|
| 207 |
<h4 style="margin:14px 0 6px;">Uploaded Files</h4>
|
| 208 |
<ul>
|
|
@@ -212,7 +231,6 @@
|
|
| 212 |
</ul>
|
| 213 |
{% endif %}
|
| 214 |
|
| 215 |
-
<!-- Client-side list -->
|
| 216 |
<div id="uploadedListClient" style="display:none; margin-top:10px;"></div>
|
| 217 |
</div>
|
| 218 |
</div>
|
|
@@ -226,7 +244,6 @@
|
|
| 226 |
<h2 id="ret">Search your uploads</h2>
|
| 227 |
</div>
|
| 228 |
<div class="body">
|
| 229 |
-
<!-- β¬οΈ the hidden sid travels with every form submit -->
|
| 230 |
<form method="post">
|
| 231 |
<input type="hidden" name="sid" value="{{ sid }}">
|
| 232 |
|
|
@@ -262,8 +279,11 @@
|
|
| 262 |
|
| 263 |
<h3 style="margin-top:18px;">Matching Paragraphs</h3>
|
| 264 |
<ol>
|
| 265 |
-
{% for
|
| 266 |
-
<li>
|
|
|
|
|
|
|
|
|
|
| 267 |
{% endfor %}
|
| 268 |
</ol>
|
| 269 |
{% endif %}
|
|
@@ -271,11 +291,26 @@
|
|
| 271 |
</section>
|
| 272 |
</div>
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
<script>
|
| 275 |
const SID = "{{ sid }}";
|
| 276 |
let uploadedNames = [];
|
|
|
|
| 277 |
|
| 278 |
-
//
|
| 279 |
document.getElementById("fileUpload").addEventListener("change", function () {
|
| 280 |
const files = this.files;
|
| 281 |
if (files.length === 0) return;
|
|
@@ -317,7 +352,7 @@
|
|
| 317 |
xhr.send(formData);
|
| 318 |
});
|
| 319 |
|
| 320 |
-
// Show client-side list
|
| 321 |
document.getElementById("showFilesBtn").addEventListener("click", function () {
|
| 322 |
const listDiv = document.getElementById("uploadedListClient");
|
| 323 |
listDiv.innerHTML = "";
|
|
@@ -333,28 +368,22 @@
|
|
| 333 |
listDiv.style.display = "block";
|
| 334 |
});
|
| 335 |
|
| 336 |
-
//
|
| 337 |
document.querySelector('form[method="post"]').addEventListener("submit", function () {
|
| 338 |
const progressBar = document.getElementById("searchProgress");
|
| 339 |
const statusText = document.getElementById("searchStatus");
|
| 340 |
-
|
| 341 |
-
// Show spinner + message (indeterminate)
|
| 342 |
progressBar.removeAttribute("value");
|
| 343 |
progressBar.style.display = "inline-block";
|
| 344 |
statusText.textContent = "π Searching...";
|
| 345 |
});
|
| 346 |
|
| 347 |
-
//
|
| 348 |
const dropZone = document.getElementById('dropZone');
|
| 349 |
const hiddenInput = document.getElementById('fileUpload');
|
| 350 |
-
|
| 351 |
-
// Clicking the zone opens the hidden input (so your original listener runs)
|
| 352 |
dropZone.addEventListener('click', () => hiddenInput.click());
|
| 353 |
dropZone.addEventListener('keypress', (e) => {
|
| 354 |
if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); hiddenInput.click(); }
|
| 355 |
});
|
| 356 |
-
|
| 357 |
-
// Visual state
|
| 358 |
['dragenter','dragover'].forEach(evt => {
|
| 359 |
dropZone.addEventListener(evt, e => {
|
| 360 |
e.preventDefault(); e.stopPropagation();
|
|
@@ -364,14 +393,11 @@
|
|
| 364 |
['dragleave','dragend','drop'].forEach(evt => {
|
| 365 |
dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
|
| 366 |
});
|
| 367 |
-
|
| 368 |
-
// Handle drop by sending the exact same request shape as the original change-handler
|
| 369 |
dropZone.addEventListener('drop', (e) => {
|
| 370 |
e.preventDefault(); e.stopPropagation();
|
| 371 |
const files = e.dataTransfer.files;
|
| 372 |
if(!files || files.length === 0) return;
|
| 373 |
|
| 374 |
-
// Note: we duplicate the same logic here to avoid modifying your original handler.
|
| 375 |
const formData = new FormData();
|
| 376 |
for (const file of files) {
|
| 377 |
formData.append("file", file);
|
|
@@ -408,6 +434,91 @@
|
|
| 408 |
|
| 409 |
xhr.send(formData);
|
| 410 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
</script>
|
| 412 |
</body>
|
| 413 |
-
</html>
|
|
|
|
| 20 |
--shadow: 0 10px 30px rgba(0,0,0,.35), 0 2px 8px rgba(0,0,0,.25);
|
| 21 |
}
|
| 22 |
|
|
|
|
| 23 |
html,body{height:100%;}
|
| 24 |
body{
|
| 25 |
margin:0;
|
|
|
|
| 32 |
line-height:1.45;
|
| 33 |
}
|
| 34 |
|
|
|
|
| 35 |
.wrap{
|
| 36 |
max-width: 980px;
|
| 37 |
margin: 48px auto 120px;
|
|
|
|
| 47 |
box-shadow: inset 0 0 0 1px #ffffff08;
|
| 48 |
}
|
| 49 |
|
|
|
|
| 50 |
.card{
|
| 51 |
background: linear-gradient(180deg, #ffffff08, #00000022);
|
| 52 |
border: 1px solid #ffffff1c;
|
|
|
|
| 63 |
.card .hdr h2{margin:0; font-size:18px; font-weight:700; color:#fff;}
|
| 64 |
.card .body{ padding:18px; }
|
| 65 |
|
| 66 |
+
.uploader{ display:grid; gap:14px; }
|
|
|
|
|
|
|
|
|
|
| 67 |
|
|
|
|
| 68 |
#fileUpload{
|
| 69 |
position: absolute;
|
| 70 |
width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
|
|
|
|
| 98 |
|
| 99 |
.row{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
| 100 |
|
|
|
|
| 101 |
.btn{
|
| 102 |
--btn-bg: #2331a6;
|
| 103 |
--btn-fg: #eaf0ff;
|
|
|
|
| 113 |
.btn.secondary{ --btn-bg:#1a213f; --btn-fg:#dbe2ff; }
|
| 114 |
.btn.danger { --btn-bg:#4a1020; --btn-fg:#ffd7df; border-color:#ff6b6b44; }
|
| 115 |
|
|
|
|
| 116 |
progress{
|
| 117 |
width: 320px; height: 14px; border-radius: 999px; overflow:hidden; vertical-align: middle;
|
| 118 |
background: #293079;
|
|
|
|
| 124 |
}
|
| 125 |
#uploadStatus, #searchStatus{ margin-left:10px; font-weight:700; color:#d5dcff; }
|
| 126 |
|
|
|
|
| 127 |
#uploadedListClient ul{ list-style:none; padding:0; margin:8px 0 0; display:flex; flex-wrap:wrap; gap:8px;}
|
| 128 |
#uploadedListClient li{
|
| 129 |
padding:6px 10px; border-radius:999px; font-size:12px; color:#dfe5ff;
|
| 130 |
background: linear-gradient(180deg,#ffffff0a,#00000025); border:1px solid #ffffff1a;
|
| 131 |
}
|
| 132 |
|
|
|
|
| 133 |
label{ color:#cdd4ff; font-weight:600; letter-spacing:.2px; }
|
| 134 |
textarea, input[type="number"]{
|
| 135 |
width:100%; box-sizing:border-box; color:#fff; background:#0d1333; border:1px solid #ffffff22;
|
|
|
|
| 140 |
|
| 141 |
hr{ border:none; height:1px; background:#ffffff1a; margin:26px 0; }
|
| 142 |
|
|
|
|
| 143 |
ol{ padding-left: 22px; }
|
| 144 |
+
ol li{ margin: 12px 0; }
|
| 145 |
a{ color: var(--accent2); }
|
| 146 |
|
|
|
|
| 147 |
.muted{ color:var(--muted); font-size:13px; }
|
| 148 |
.cluster{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; }
|
| 149 |
|
| 150 |
+
/* Sidebar for context */
|
| 151 |
+
.sidebar-backdrop{
|
| 152 |
+
position: fixed;
|
| 153 |
+
inset: 0;
|
| 154 |
+
background: rgba(0,0,0,.35);
|
| 155 |
+
opacity: 0;
|
| 156 |
+
pointer-events: none;
|
| 157 |
+
transition: .18s ease;
|
| 158 |
+
z-index: 60;
|
| 159 |
+
}
|
| 160 |
+
.sidebar{
|
| 161 |
+
position: fixed;
|
| 162 |
+
top: 0; right: 0; bottom: 0;
|
| 163 |
+
width: min(520px, 90vw);
|
| 164 |
+
background: linear-gradient(180deg, #0e1330, #0a0f28);
|
| 165 |
+
border-left: 1px solid #ffffff22;
|
| 166 |
+
box-shadow: -16px 0 40px rgba(0,0,0,.35);
|
| 167 |
+
transform: translateX(100%);
|
| 168 |
+
transition: transform .22s ease;
|
| 169 |
+
z-index: 70;
|
| 170 |
+
display: grid;
|
| 171 |
+
grid-template-rows: auto 1fr;
|
| 172 |
+
}
|
| 173 |
+
.sidebar.open{ transform: translateX(0); }
|
| 174 |
+
.sidebar-backdrop.open{ opacity: 1; pointer-events: auto; }
|
| 175 |
+
.sidebar .sbar-hdr{
|
| 176 |
+
display:flex; align-items:center; justify-content:space-between; gap:10px;
|
| 177 |
+
padding:14px 16px; border-bottom:1px solid #ffffff1a;
|
| 178 |
+
}
|
| 179 |
+
.sidebar .sbar-hdr h3{ margin:0; font-size:16px; }
|
| 180 |
+
.sidebar .sbar-body{
|
| 181 |
+
overflow: auto; padding: 16px;
|
| 182 |
+
}
|
| 183 |
+
.ctx-p{ margin: 12px 0; padding: 10px 12px; border-radius: 12px; background: #ffffff06; border:1px solid #ffffff14; }
|
| 184 |
+
.ctx-p.hl{ outline: 2px solid #61e7ff; box-shadow: 0 0 0 3px #61e7ff22; }
|
| 185 |
+
|
| 186 |
+
.smallbtn{
|
| 187 |
+
font-size:12px; padding:6px 8px; border-radius:10px;
|
| 188 |
+
background: #1a213f; color:#dbe2ff; border:1px solid #ffffff18; cursor:pointer;
|
| 189 |
}
|
| 190 |
</style>
|
| 191 |
</head>
|
|
|
|
| 210 |
</div>
|
| 211 |
<div class="body">
|
| 212 |
<div class="uploader">
|
|
|
|
| 213 |
<input type="file" id="fileUpload" multiple>
|
|
|
|
|
|
|
| 214 |
<div id="dropZone" class="drop" tabindex="0" role="button" aria-label="Drop files here or click to browse">
|
| 215 |
<div class="big">Drag & Drop files here</div>
|
| 216 |
<div class="small">.txt, .pdf supported</div>
|
|
|
|
| 222 |
<span id="uploadStatus"></span>
|
| 223 |
</div>
|
| 224 |
|
|
|
|
| 225 |
{% if uploaded_filenames %}
|
| 226 |
<h4 style="margin:14px 0 6px;">Uploaded Files</h4>
|
| 227 |
<ul>
|
|
|
|
| 231 |
</ul>
|
| 232 |
{% endif %}
|
| 233 |
|
|
|
|
| 234 |
<div id="uploadedListClient" style="display:none; margin-top:10px;"></div>
|
| 235 |
</div>
|
| 236 |
</div>
|
|
|
|
| 244 |
<h2 id="ret">Search your uploads</h2>
|
| 245 |
</div>
|
| 246 |
<div class="body">
|
|
|
|
| 247 |
<form method="post">
|
| 248 |
<input type="hidden" name="sid" value="{{ sid }}">
|
| 249 |
|
|
|
|
| 279 |
|
| 280 |
<h3 style="margin-top:18px;">Matching Paragraphs</h3>
|
| 281 |
<ol>
|
| 282 |
+
{% for r in results %}
|
| 283 |
+
<li>
|
| 284 |
+
<p>{{ r.text }}</p>
|
| 285 |
+
<button type="button" class="smallbtn" onclick="openContext({{ r.idx }})">π View context</button>
|
| 286 |
+
</li>
|
| 287 |
{% endfor %}
|
| 288 |
</ol>
|
| 289 |
{% endif %}
|
|
|
|
| 291 |
</section>
|
| 292 |
</div>
|
| 293 |
|
| 294 |
+
<!-- Sidebar + backdrop -->
|
| 295 |
+
<div id="sidebarBackdrop" class="sidebar-backdrop"></div>
|
| 296 |
+
<aside id="sidebar" class="sidebar" aria-hidden="true">
|
| 297 |
+
<div class="sbar-hdr">
|
| 298 |
+
<h3>Paragraph context</h3>
|
| 299 |
+
<div style="display:flex; gap:8px; align-items:center;">
|
| 300 |
+
<button class="smallbtn" id="expandLess" title="Show less context">β</button>
|
| 301 |
+
<button class="smallbtn" id="expandMore" title="Show more context">+</button>
|
| 302 |
+
<button class="smallbtn" id="closeSidebar">β</button>
|
| 303 |
+
</div>
|
| 304 |
+
</div>
|
| 305 |
+
<div id="sidebarBody" class="sbar-body"></div>
|
| 306 |
+
</aside>
|
| 307 |
+
|
| 308 |
<script>
|
| 309 |
const SID = "{{ sid }}";
|
| 310 |
let uploadedNames = [];
|
| 311 |
+
let currentWindow = 3;
|
| 312 |
|
| 313 |
+
// Upload (unchanged behavior)
|
| 314 |
document.getElementById("fileUpload").addEventListener("change", function () {
|
| 315 |
const files = this.files;
|
| 316 |
if (files.length === 0) return;
|
|
|
|
| 352 |
xhr.send(formData);
|
| 353 |
});
|
| 354 |
|
| 355 |
+
// Show client-side list
|
| 356 |
document.getElementById("showFilesBtn").addEventListener("click", function () {
|
| 357 |
const listDiv = document.getElementById("uploadedListClient");
|
| 358 |
listDiv.innerHTML = "";
|
|
|
|
| 368 |
listDiv.style.display = "block";
|
| 369 |
});
|
| 370 |
|
| 371 |
+
// Search progress (unchanged)
|
| 372 |
document.querySelector('form[method="post"]').addEventListener("submit", function () {
|
| 373 |
const progressBar = document.getElementById("searchProgress");
|
| 374 |
const statusText = document.getElementById("searchStatus");
|
|
|
|
|
|
|
| 375 |
progressBar.removeAttribute("value");
|
| 376 |
progressBar.style.display = "inline-block";
|
| 377 |
statusText.textContent = "π Searching...";
|
| 378 |
});
|
| 379 |
|
| 380 |
+
// Drag & drop (unchanged)
|
| 381 |
const dropZone = document.getElementById('dropZone');
|
| 382 |
const hiddenInput = document.getElementById('fileUpload');
|
|
|
|
|
|
|
| 383 |
dropZone.addEventListener('click', () => hiddenInput.click());
|
| 384 |
dropZone.addEventListener('keypress', (e) => {
|
| 385 |
if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); hiddenInput.click(); }
|
| 386 |
});
|
|
|
|
|
|
|
| 387 |
['dragenter','dragover'].forEach(evt => {
|
| 388 |
dropZone.addEventListener(evt, e => {
|
| 389 |
e.preventDefault(); e.stopPropagation();
|
|
|
|
| 393 |
['dragleave','dragend','drop'].forEach(evt => {
|
| 394 |
dropZone.addEventListener(evt, () => dropZone.classList.remove('dragover'));
|
| 395 |
});
|
|
|
|
|
|
|
| 396 |
dropZone.addEventListener('drop', (e) => {
|
| 397 |
e.preventDefault(); e.stopPropagation();
|
| 398 |
const files = e.dataTransfer.files;
|
| 399 |
if(!files || files.length === 0) return;
|
| 400 |
|
|
|
|
| 401 |
const formData = new FormData();
|
| 402 |
for (const file of files) {
|
| 403 |
formData.append("file", file);
|
|
|
|
| 434 |
|
| 435 |
xhr.send(formData);
|
| 436 |
});
|
| 437 |
+
|
| 438 |
+
// ββ Context sidebar logic ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 439 |
+
const sidebar = document.getElementById('sidebar');
|
| 440 |
+
const sidebarBody = document.getElementById('sidebarBody');
|
| 441 |
+
const backdrop = document.getElementById('sidebarBackdrop');
|
| 442 |
+
const btnClose = document.getElementById('closeSidebar');
|
| 443 |
+
const btnMore = document.getElementById('expandMore');
|
| 444 |
+
const btnLess = document.getElementById('expandLess');
|
| 445 |
+
|
| 446 |
+
function openSidebar(){
|
| 447 |
+
sidebar.classList.add('open');
|
| 448 |
+
backdrop.classList.add('open');
|
| 449 |
+
sidebar.setAttribute('aria-hidden', 'false');
|
| 450 |
+
}
|
| 451 |
+
function closeSidebar(){
|
| 452 |
+
sidebar.classList.remove('open');
|
| 453 |
+
backdrop.classList.remove('open');
|
| 454 |
+
sidebar.setAttribute('aria-hidden', 'true');
|
| 455 |
+
sidebarBody.innerHTML = '';
|
| 456 |
+
}
|
| 457 |
+
btnClose.addEventListener('click', closeSidebar);
|
| 458 |
+
backdrop.addEventListener('click', closeSidebar);
|
| 459 |
+
|
| 460 |
+
async function fetchContext(idx, windowSize){
|
| 461 |
+
const res = await fetch(`/api/context?sid=${encodeURIComponent(SID)}&idx=${idx}&window=${windowSize}`);
|
| 462 |
+
if(!res.ok){
|
| 463 |
+
const t = await res.text();
|
| 464 |
+
throw new Error(t || 'Failed to fetch context');
|
| 465 |
+
}
|
| 466 |
+
return res.json();
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
function renderContext(ctxJson){
|
| 470 |
+
sidebarBody.innerHTML = '';
|
| 471 |
+
ctxJson.paras.forEach((p, i) => {
|
| 472 |
+
const div = document.createElement('div');
|
| 473 |
+
div.className = 'ctx-p' + (i === ctxJson.center ? ' hl' : '');
|
| 474 |
+
div.innerText = p;
|
| 475 |
+
if(i === ctxJson.center){
|
| 476 |
+
div.id = 'centerPara';
|
| 477 |
+
}
|
| 478 |
+
sidebarBody.appendChild(div);
|
| 479 |
+
});
|
| 480 |
+
// Scroll the highlighted one into view
|
| 481 |
+
setTimeout(() => {
|
| 482 |
+
const el = document.getElementById('centerPara');
|
| 483 |
+
if(el){ el.scrollIntoView({behavior:'smooth', block:'center'}); }
|
| 484 |
+
}, 0);
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
let lastIdx = null;
|
| 488 |
+
|
| 489 |
+
window.openContext = async function(idx){
|
| 490 |
+
try{
|
| 491 |
+
lastIdx = idx;
|
| 492 |
+
const data = await fetchContext(idx, currentWindow);
|
| 493 |
+
renderContext(data);
|
| 494 |
+
openSidebar();
|
| 495 |
+
}catch(err){
|
| 496 |
+
alert('Error: ' + err.message);
|
| 497 |
+
}
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
btnMore.addEventListener('click', async () => {
|
| 501 |
+
if(lastIdx === null) return;
|
| 502 |
+
currentWindow = Math.min(currentWindow + 2, 20);
|
| 503 |
+
const data = await fetchContext(lastIdx, currentWindow);
|
| 504 |
+
renderContext(data);
|
| 505 |
+
});
|
| 506 |
+
|
| 507 |
+
btnLess.addEventListener('click', async () => {
|
| 508 |
+
if(lastIdx === null) return;
|
| 509 |
+
currentWindow = Math.max(1, currentWindow - 2);
|
| 510 |
+
const data = await fetchContext(lastIdx, currentWindow);
|
| 511 |
+
renderContext(data);
|
| 512 |
+
});
|
| 513 |
+
|
| 514 |
+
// Maintain existing search progress behavior
|
| 515 |
+
document.querySelector('form[method="post"]').addEventListener("submit", function () {
|
| 516 |
+
const progressBar = document.getElementById("searchProgress");
|
| 517 |
+
const statusText = document.getElementById("searchStatus");
|
| 518 |
+
progressBar.removeAttribute("value");
|
| 519 |
+
progressBar.style.display = "inline-block";
|
| 520 |
+
statusText.textContent = "π Searching...";
|
| 521 |
+
});
|
| 522 |
</script>
|
| 523 |
</body>
|
| 524 |
+
</html>
|