GameSentenceMiner 2.18.15__py3-none-any.whl → 2.18.16__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.
Potentially problematic release.
This version of GameSentenceMiner might be problematic. Click here for more details.
- GameSentenceMiner/anki.py +8 -53
- GameSentenceMiner/ui/anki_confirmation.py +16 -2
- GameSentenceMiner/util/db.py +11 -7
- GameSentenceMiner/util/games_table.py +320 -0
- GameSentenceMiner/web/anki_api_endpoints.py +506 -0
- GameSentenceMiner/web/database_api.py +239 -117
- GameSentenceMiner/web/static/css/loading-skeleton.css +41 -0
- GameSentenceMiner/web/static/css/search.css +54 -0
- GameSentenceMiner/web/static/css/stats.css +76 -0
- GameSentenceMiner/web/static/js/anki_stats.js +304 -50
- GameSentenceMiner/web/static/js/database.js +44 -7
- GameSentenceMiner/web/static/js/heatmap.js +326 -0
- GameSentenceMiner/web/static/js/overview.js +20 -224
- GameSentenceMiner/web/static/js/search.js +190 -23
- GameSentenceMiner/web/static/js/stats.js +371 -1
- GameSentenceMiner/web/stats.py +188 -0
- GameSentenceMiner/web/templates/anki_stats.html +145 -58
- GameSentenceMiner/web/templates/components/date-range.html +19 -0
- GameSentenceMiner/web/templates/components/html-head.html +45 -0
- GameSentenceMiner/web/templates/components/js-config.html +37 -0
- GameSentenceMiner/web/templates/components/popups.html +15 -0
- GameSentenceMiner/web/templates/components/settings-modal.html +233 -0
- GameSentenceMiner/web/templates/database.html +13 -3
- GameSentenceMiner/web/templates/goals.html +9 -31
- GameSentenceMiner/web/templates/overview.html +16 -223
- GameSentenceMiner/web/templates/search.html +46 -0
- GameSentenceMiner/web/templates/stats.html +49 -311
- GameSentenceMiner/web/texthooking_page.py +4 -66
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/RECORD +34 -25
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.18.15.dist-info → gamesentenceminer-2.18.16.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// Search Page JavaScript
|
|
2
|
-
// Dependencies: shared.js (provides utility functions like escapeHtml, escapeRegex)
|
|
3
|
-
|
|
4
1
|
class SentenceSearchApp {
|
|
5
2
|
constructor() {
|
|
6
3
|
this.searchInput = document.getElementById('searchInput');
|
|
@@ -14,6 +11,9 @@ class SentenceSearchApp {
|
|
|
14
11
|
this.searchStats = document.getElementById('searchStats');
|
|
15
12
|
this.searchTime = document.getElementById('searchTime');
|
|
16
13
|
this.regexCheckbox = document.getElementById('regexCheckbox');
|
|
14
|
+
this.deleteLinesBtn = document.getElementById('deleteLinesBtn');
|
|
15
|
+
this.selectAllBtn = document.getElementById('selectAllBtn');
|
|
16
|
+
this.pageSizeFilter = document.getElementById('pageSizeFilter');
|
|
17
17
|
|
|
18
18
|
this.currentPage = 1;
|
|
19
19
|
this.pageSize = 20;
|
|
@@ -21,30 +21,31 @@ class SentenceSearchApp {
|
|
|
21
21
|
this.currentQuery = '';
|
|
22
22
|
this.totalResults = 0;
|
|
23
23
|
this.currentUseRegex = false;
|
|
24
|
-
|
|
25
|
-
// Move initialization logic to async method
|
|
26
24
|
this.initialize();
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
async initialize() {
|
|
30
|
-
// Check for ?q= parameter and pre-fill input
|
|
31
28
|
const urlParams = new URLSearchParams(window.location.search);
|
|
32
29
|
const qParam = urlParams.get('q');
|
|
33
30
|
if (qParam) {
|
|
34
31
|
this.searchInput.value = qParam;
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
if (this.pageSizeFilter) {
|
|
35
|
+
this.pageSizeFilter.value = this.pageSize.toString();
|
|
36
|
+
} else {
|
|
37
|
+
console.error('Page size filter element not found!');
|
|
38
|
+
}
|
|
39
|
+
|
|
37
40
|
this.initializeEventListeners();
|
|
38
41
|
await this.loadGamesList();
|
|
39
42
|
|
|
40
|
-
// Trigger search after games list loads if q param is present
|
|
41
43
|
if (qParam) {
|
|
42
44
|
this.performSearch();
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
initializeEventListeners() {
|
|
47
|
-
// Debounced search input
|
|
48
49
|
this.searchInput.addEventListener('input', (e) => {
|
|
49
50
|
clearTimeout(this.searchTimeout);
|
|
50
51
|
this.searchTimeout = setTimeout(() => {
|
|
@@ -52,11 +53,17 @@ class SentenceSearchApp {
|
|
|
52
53
|
}, 300);
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
// Filter changes
|
|
56
56
|
this.gameFilter.addEventListener('change', () => this.performSearch());
|
|
57
57
|
this.sortFilter.addEventListener('change', () => this.performSearch());
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
if (this.pageSizeFilter) {
|
|
60
|
+
this.pageSizeFilter.addEventListener('change', () => {
|
|
61
|
+
this.pageSize = parseInt(this.pageSizeFilter.value);
|
|
62
|
+
this.currentPage = 1;
|
|
63
|
+
this.performSearch();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
document.getElementById('prevPage').addEventListener('click', () => {
|
|
61
68
|
if (this.currentPage > 1) {
|
|
62
69
|
this.currentPage--;
|
|
@@ -69,12 +76,23 @@ class SentenceSearchApp {
|
|
|
69
76
|
this.performSearch();
|
|
70
77
|
});
|
|
71
78
|
|
|
72
|
-
// Regex checkbox toggle triggers search
|
|
73
79
|
if (this.regexCheckbox) {
|
|
74
80
|
this.regexCheckbox.addEventListener('change', () => {
|
|
75
81
|
this.performSearch();
|
|
76
82
|
});
|
|
77
83
|
}
|
|
84
|
+
|
|
85
|
+
if (this.deleteLinesBtn) {
|
|
86
|
+
this.deleteLinesBtn.addEventListener('click', () => {
|
|
87
|
+
this.showDeleteConfirmation();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (this.selectAllBtn) {
|
|
92
|
+
this.selectAllBtn.addEventListener('click', () => {
|
|
93
|
+
this.toggleSelectAll();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
78
96
|
}
|
|
79
97
|
|
|
80
98
|
async loadGamesList() {
|
|
@@ -84,7 +102,6 @@ class SentenceSearchApp {
|
|
|
84
102
|
|
|
85
103
|
if (response.ok && data.games) {
|
|
86
104
|
const gameSelect = this.gameFilter;
|
|
87
|
-
// Clear existing options except "All Games"
|
|
88
105
|
gameSelect.innerHTML = '<option value="">All Games</option>';
|
|
89
106
|
|
|
90
107
|
data.games.forEach(game => {
|
|
@@ -105,14 +122,12 @@ class SentenceSearchApp {
|
|
|
105
122
|
const sortBy = this.sortFilter.value;
|
|
106
123
|
const useRegex = this.regexCheckbox && this.regexCheckbox.checked;
|
|
107
124
|
|
|
108
|
-
// Reset to first page for new searches or regex toggle
|
|
109
125
|
if (query !== this.currentQuery || useRegex !== this.currentUseRegex) {
|
|
110
126
|
this.currentPage = 1;
|
|
111
127
|
}
|
|
112
128
|
this.currentQuery = query;
|
|
113
129
|
this.currentUseRegex = useRegex;
|
|
114
130
|
|
|
115
|
-
// Show appropriate state
|
|
116
131
|
if (!query) {
|
|
117
132
|
this.showEmptyState();
|
|
118
133
|
return;
|
|
@@ -156,7 +171,6 @@ class SentenceSearchApp {
|
|
|
156
171
|
this.hideAllStates();
|
|
157
172
|
this.totalResults = data.total;
|
|
158
173
|
|
|
159
|
-
// Update stats
|
|
160
174
|
const resultText = data.total === 1 ? 'result' : 'results';
|
|
161
175
|
this.searchStats.textContent = `${data.total.toLocaleString()} ${resultText} found`;
|
|
162
176
|
this.searchTime.textContent = `Search completed in ${searchTime}ms`;
|
|
@@ -165,8 +179,7 @@ class SentenceSearchApp {
|
|
|
165
179
|
this.showNoResultsState();
|
|
166
180
|
return;
|
|
167
181
|
}
|
|
168
|
-
|
|
169
|
-
// Render results
|
|
182
|
+
|
|
170
183
|
this.searchResults.innerHTML = '';
|
|
171
184
|
data.results.forEach(result => {
|
|
172
185
|
const resultElement = this.createResultElement(result);
|
|
@@ -175,20 +188,39 @@ class SentenceSearchApp {
|
|
|
175
188
|
|
|
176
189
|
this.updatePagination(data);
|
|
177
190
|
this.searchResults.style.display = 'block';
|
|
191
|
+
this.updateDeleteButtonState();
|
|
178
192
|
}
|
|
179
193
|
|
|
180
194
|
createResultElement(result) {
|
|
181
195
|
const div = document.createElement('div');
|
|
182
196
|
div.className = 'search-result';
|
|
197
|
+
div.style.display = 'flex';
|
|
198
|
+
div.style.alignItems = 'flex-start';
|
|
199
|
+
div.style.gap = '12px';
|
|
200
|
+
|
|
201
|
+
const checkbox = document.createElement('input');
|
|
202
|
+
checkbox.type = 'checkbox';
|
|
203
|
+
checkbox.className = 'line-checkbox';
|
|
204
|
+
checkbox.dataset.lineId = result.id;
|
|
205
|
+
checkbox.checked = false;
|
|
206
|
+
|
|
207
|
+
checkbox.addEventListener('change', () => {
|
|
208
|
+
this.updateDeleteButtonState();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (typeof result.sentence !== 'string') {
|
|
212
|
+
console.warn('Unexpected sentence format:', result.sentence);
|
|
213
|
+
result.sentence = JSON.stringify(result.sentence);
|
|
214
|
+
}
|
|
183
215
|
|
|
184
|
-
// Highlight search terms
|
|
185
216
|
const highlightedText = this.highlightSearchTerms(result.sentence, this.currentQuery);
|
|
186
217
|
|
|
187
|
-
// Format timestamp to ISO format
|
|
188
218
|
const date = new Date(result.timestamp * 1000);
|
|
189
219
|
const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${date.toTimeString().split(' ')[0]}`;
|
|
190
220
|
|
|
191
|
-
|
|
221
|
+
const contentDiv = document.createElement('div');
|
|
222
|
+
contentDiv.style.flex = '1';
|
|
223
|
+
contentDiv.innerHTML = `
|
|
192
224
|
<div class="result-sentence">${highlightedText}</div>
|
|
193
225
|
<div class="result-metadata">
|
|
194
226
|
<div class="metadata-item">
|
|
@@ -207,6 +239,9 @@ class SentenceSearchApp {
|
|
|
207
239
|
</div>
|
|
208
240
|
`;
|
|
209
241
|
|
|
242
|
+
div.appendChild(checkbox);
|
|
243
|
+
div.appendChild(contentDiv);
|
|
244
|
+
|
|
210
245
|
return div;
|
|
211
246
|
}
|
|
212
247
|
|
|
@@ -221,7 +256,6 @@ class SentenceSearchApp {
|
|
|
221
256
|
const pattern = new RegExp(query, 'gi');
|
|
222
257
|
return escapedText.replace(pattern, '<span class="search-highlight">$&</span>');
|
|
223
258
|
} catch (e) {
|
|
224
|
-
// If invalid regex, just return escaped text
|
|
225
259
|
return escapedText;
|
|
226
260
|
}
|
|
227
261
|
} else {
|
|
@@ -288,10 +322,143 @@ class SentenceSearchApp {
|
|
|
288
322
|
this.errorMessage.style.display = 'none';
|
|
289
323
|
this.searchResults.style.display = 'none';
|
|
290
324
|
document.getElementById('pagination').style.display = 'none';
|
|
325
|
+
|
|
326
|
+
if (this.selectAllBtn) {
|
|
327
|
+
this.selectAllBtn.disabled = true;
|
|
328
|
+
this.selectAllBtn.textContent = 'Select All';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
updateDeleteButtonState() {
|
|
333
|
+
const selectedCount = this.getSelectedCount();
|
|
334
|
+
|
|
335
|
+
if (this.deleteLinesBtn) {
|
|
336
|
+
this.deleteLinesBtn.disabled = selectedCount === 0;
|
|
337
|
+
this.deleteLinesBtn.textContent = selectedCount > 0
|
|
338
|
+
? `Delete Selected (${selectedCount})`
|
|
339
|
+
: 'Delete Selected';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (this.selectAllBtn) {
|
|
343
|
+
const totalVisible = document.querySelectorAll('.line-checkbox').length;
|
|
344
|
+
|
|
345
|
+
if (totalVisible === 0) {
|
|
346
|
+
this.selectAllBtn.disabled = true;
|
|
347
|
+
this.selectAllBtn.textContent = 'Select All';
|
|
348
|
+
} else {
|
|
349
|
+
this.selectAllBtn.disabled = false;
|
|
350
|
+
if (this.areAllVisibleSelected()) {
|
|
351
|
+
this.selectAllBtn.textContent = 'Deselect All';
|
|
352
|
+
} else {
|
|
353
|
+
this.selectAllBtn.textContent = 'Select All';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
getSelectedLineIds() {
|
|
360
|
+
const selectedIds = [];
|
|
361
|
+
const checkboxes = document.querySelectorAll('.line-checkbox:checked');
|
|
362
|
+
|
|
363
|
+
checkboxes.forEach(checkbox => {
|
|
364
|
+
const lineId = checkbox.dataset.lineId;
|
|
365
|
+
selectedIds.push(lineId);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return selectedIds;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
getSelectedCount() {
|
|
372
|
+
return document.querySelectorAll('.line-checkbox:checked').length;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
areAllVisibleSelected() {
|
|
376
|
+
const allCheckboxes = document.querySelectorAll('.line-checkbox');
|
|
377
|
+
const selectedCheckboxes = document.querySelectorAll('.line-checkbox:checked');
|
|
378
|
+
return allCheckboxes.length > 0 && allCheckboxes.length === selectedCheckboxes.length;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
toggleSelectAll() {
|
|
382
|
+
const visibleCheckboxes = document.querySelectorAll('.line-checkbox');
|
|
383
|
+
const shouldSelect = !this.areAllVisibleSelected();
|
|
384
|
+
|
|
385
|
+
visibleCheckboxes.forEach(checkbox => {
|
|
386
|
+
checkbox.checked = shouldSelect;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
this.updateDeleteButtonState();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
showDeleteConfirmation() {
|
|
393
|
+
const count = this.getSelectedCount();
|
|
394
|
+
if (count === 0) return;
|
|
395
|
+
|
|
396
|
+
const message = `Are you sure you want to delete ${count} selected sentence${count > 1 ? 's' : ''}? This action cannot be undone.`;
|
|
397
|
+
|
|
398
|
+
document.getElementById('deleteConfirmationMessage').textContent = message;
|
|
399
|
+
openModal('deleteConfirmationModal');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async deleteSelectedLines() {
|
|
403
|
+
const lineIds = this.getSelectedLineIds();
|
|
404
|
+
|
|
405
|
+
if (lineIds.length === 0) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
this.showLoadingState();
|
|
411
|
+
|
|
412
|
+
const response = await fetch('/api/delete-sentence-lines', {
|
|
413
|
+
method: 'POST',
|
|
414
|
+
headers: {
|
|
415
|
+
'Content-Type': 'application/json'
|
|
416
|
+
},
|
|
417
|
+
body: JSON.stringify({ line_ids: lineIds })
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const data = await response.json();
|
|
421
|
+
|
|
422
|
+
if (!response.ok) {
|
|
423
|
+
throw new Error(data.error || 'Failed to delete sentences');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
document.querySelectorAll('.line-checkbox:checked').forEach(cb => cb.checked = false);
|
|
427
|
+
this.updateDeleteButtonState();
|
|
428
|
+
|
|
429
|
+
await this.performSearch();
|
|
430
|
+
|
|
431
|
+
this.showMessage('Success', `Successfully deleted ${data.deleted_count} sentence${data.deleted_count > 1 ? 's' : ''}`);
|
|
432
|
+
|
|
433
|
+
} catch (error) {
|
|
434
|
+
this.showErrorState(`Failed to delete sentences: ${error.message}`);
|
|
435
|
+
console.error('Delete error:', error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
showMessage(title, message) {
|
|
440
|
+
document.getElementById('messageModalTitle').textContent = title;
|
|
441
|
+
document.getElementById('messageModalText').textContent = message;
|
|
442
|
+
openModal('messageModal');
|
|
291
443
|
}
|
|
292
444
|
}
|
|
293
445
|
|
|
294
|
-
// Initialize the app when DOM is loaded
|
|
295
446
|
document.addEventListener('DOMContentLoaded', () => {
|
|
296
|
-
new SentenceSearchApp();
|
|
447
|
+
const app = new SentenceSearchApp();
|
|
448
|
+
|
|
449
|
+
const closeButtons = document.querySelectorAll('[data-action="closeModal"]');
|
|
450
|
+
closeButtons.forEach(btn => {
|
|
451
|
+
const modalId = btn.getAttribute('data-modal');
|
|
452
|
+
if (modalId) {
|
|
453
|
+
btn.addEventListener('click', () => closeModal(modalId));
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
|
|
458
|
+
if (confirmDeleteBtn) {
|
|
459
|
+
confirmDeleteBtn.addEventListener('click', () => {
|
|
460
|
+
closeModal('deleteConfirmationModal');
|
|
461
|
+
app.deleteSelectedLines();
|
|
462
|
+
});
|
|
463
|
+
}
|
|
297
464
|
});
|