t commited on
Commit
2b07d6b
·
1 Parent(s): fb7e5e9

fix: consolidate pending fixes for PDF manager UI, dashboard filtering, and SQL syntax

Browse files

- Removed corrupted 'arial.ttf'.
- Updated 'dashboard.py' to filter out 'final_pdf' sessions.
- Applied migrations in 'database.py' for session metadata.
- Fixed SQL syntax error in 'routes/library.py'.
- Set session type to 'final_pdf' in 'routes/upload.py'.
- Refined 'templates/pdf_manager.html' with search, dropdowns, and individual delete buttons.

dashboard.py CHANGED
@@ -140,7 +140,7 @@ def dashboard():
140
  COUNT(CASE WHEN i.image_type = 'cropped' THEN 1 END) as question_count
141
  FROM sessions s
142
  LEFT JOIN images i ON s.id = i.session_id
143
- WHERE s.user_id = ?
144
  GROUP BY s.id, s.created_at, s.original_filename, s.persist, s.name, s.session_type
145
  ORDER BY s.created_at DESC
146
  """, (current_user.id,)).fetchall()
 
140
  COUNT(CASE WHEN i.image_type = 'cropped' THEN 1 END) as question_count
141
  FROM sessions s
142
  LEFT JOIN images i ON s.id = i.session_id
143
+ WHERE s.user_id = ? AND (s.session_type IS NULL OR s.session_type != 'final_pdf')
144
  GROUP BY s.id, s.created_at, s.original_filename, s.persist, s.name, s.session_type
145
  ORDER BY s.created_at DESC
146
  """, (current_user.id,)).fetchall()
database.py CHANGED
@@ -263,6 +263,21 @@ def setup_database():
263
  except sqlite3.OperationalError:
264
  cursor.execute("ALTER TABLE questions ADD COLUMN chapter TEXT")
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  # --- Multi-user Migrations ---
267
  try:
268
  cursor.execute("SELECT user_id FROM sessions LIMIT 1")
 
263
  except sqlite3.OperationalError:
264
  cursor.execute("ALTER TABLE questions ADD COLUMN chapter TEXT")
265
 
266
+ try:
267
+ cursor.execute("SELECT subject FROM sessions LIMIT 1")
268
+ except sqlite3.OperationalError:
269
+ cursor.execute("ALTER TABLE sessions ADD COLUMN subject TEXT")
270
+
271
+ try:
272
+ cursor.execute("SELECT tags FROM sessions LIMIT 1")
273
+ except sqlite3.OperationalError:
274
+ cursor.execute("ALTER TABLE sessions ADD COLUMN tags TEXT")
275
+
276
+ try:
277
+ cursor.execute("SELECT notes FROM sessions LIMIT 1")
278
+ except sqlite3.OperationalError:
279
+ cursor.execute("ALTER TABLE sessions ADD COLUMN notes TEXT")
280
+
281
  # --- Multi-user Migrations ---
282
  try:
283
  cursor.execute("SELECT user_id FROM sessions LIMIT 1")
routes/library.py CHANGED
@@ -1,6 +1,8 @@
1
  import os
 
 
2
  from datetime import datetime
3
- from flask import request, jsonify, render_template, redirect, url_for, send_file
4
  from .common import main_bp, get_db_connection, login_required, current_user
5
  from database import get_folder_tree, get_all_descendant_folder_ids
6
  from strings import METHOD_POST, METHOD_DELETE
@@ -222,7 +224,7 @@ def bulk_move_pdfs():
222
  def get_all_subjects_and_tags():
223
  conn = get_db_connection()
224
  subjects = [row['subject'] for row in conn.execute('SELECT DISTINCT subject FROM generated_pdfs WHERE subject IS NOT NULL AND user_id = ?', (current_user.id,)).fetchall()]
225
- tags_query = conn.execute('SELECT DISTINCT tags FROM generated_pdfs WHERE tags IS NOT NULL AND tags != '' AND user_id = ?', (current_user.id,)).fetchall()
226
  all_tags = set()
227
  for row in tags_query:
228
  for t in row['tags'].split(','): all_tags.add(t.strip())
@@ -233,7 +235,7 @@ def get_all_subjects_and_tags():
233
  def get_metadata_suggestions():
234
  conn = get_db_connection()
235
  subjects = [row['subject'] for row in conn.execute('SELECT DISTINCT subject FROM generated_pdfs WHERE subject IS NOT NULL AND user_id = ?', (current_user.id,)).fetchall()]
236
- tags_query = conn.execute('SELECT DISTINCT tags FROM generated_pdfs WHERE tags IS NOT NULL AND tags != '' AND user_id = ?', (current_user.id,)).fetchall()
237
  all_tags = set()
238
  for row in tags_query:
239
  for t in row['tags'].split(','): all_tags.add(t.strip())
 
1
  import os
2
+ import io
3
+ import zipfile
4
  from datetime import datetime
5
+ from flask import request, jsonify, render_template, redirect, url_for, send_file, current_app
6
  from .common import main_bp, get_db_connection, login_required, current_user
7
  from database import get_folder_tree, get_all_descendant_folder_ids
8
  from strings import METHOD_POST, METHOD_DELETE
 
224
  def get_all_subjects_and_tags():
225
  conn = get_db_connection()
226
  subjects = [row['subject'] for row in conn.execute('SELECT DISTINCT subject FROM generated_pdfs WHERE subject IS NOT NULL AND user_id = ?', (current_user.id,)).fetchall()]
227
+ tags_query = conn.execute("SELECT DISTINCT tags FROM generated_pdfs WHERE tags IS NOT NULL AND tags != '' AND user_id = ?", (current_user.id,)).fetchall()
228
  all_tags = set()
229
  for row in tags_query:
230
  for t in row['tags'].split(','): all_tags.add(t.strip())
 
235
  def get_metadata_suggestions():
236
  conn = get_db_connection()
237
  subjects = [row['subject'] for row in conn.execute('SELECT DISTINCT subject FROM generated_pdfs WHERE subject IS NOT NULL AND user_id = ?', (current_user.id,)).fetchall()]
238
+ tags_query = conn.execute("SELECT DISTINCT tags FROM generated_pdfs WHERE tags IS NOT NULL AND tags != '' AND user_id = ?", (current_user.id,)).fetchall()
239
  all_tags = set()
240
  for row in tags_query:
241
  for t in row['tags'].split(','): all_tags.add(t.strip())
routes/upload.py CHANGED
@@ -193,7 +193,9 @@ def handle_final_pdf_upload():
193
  conn = get_db_connection()
194
  def process_and_save_pdf(file_content, original_filename):
195
  sid = str(uuid.uuid4())
196
- conn.execute('INSERT INTO sessions (id, original_filename, user_id) VALUES (?, ?, ?)', (sid, original_filename, current_user.id))
 
 
197
  out_name = f"{sid}_{secure_filename(original_filename)}"
198
  with open(os.path.join(current_app.config['OUTPUT_FOLDER'], out_name), 'wb') as f: f.write(file_content)
199
  conn.execute('INSERT INTO generated_pdfs (session_id, filename, subject, tags, notes, source_filename, user_id) VALUES (?, ?, ?, ?, ?, ?, ?)', (sid, out_name, subject, tags, notes, original_filename, current_user.id))
 
193
  conn = get_db_connection()
194
  def process_and_save_pdf(file_content, original_filename):
195
  sid = str(uuid.uuid4())
196
+ # Associate session with user and mark as final_pdf
197
+ conn.execute('INSERT INTO sessions (id, original_filename, user_id, session_type) VALUES (?, ?, ?, ?)',
198
+ (sid, original_filename, current_user.id, 'final_pdf'))
199
  out_name = f"{sid}_{secure_filename(original_filename)}"
200
  with open(os.path.join(current_app.config['OUTPUT_FOLDER'], out_name), 'wb') as f: f.write(file_content)
201
  conn.execute('INSERT INTO generated_pdfs (session_id, filename, subject, tags, notes, source_filename, user_id) VALUES (?, ?, ?, ?, ?, ?, ?)', (sid, out_name, subject, tags, notes, original_filename, current_user.id))
templates/pdf_manager.html CHANGED
@@ -43,6 +43,9 @@
43
  .table-hover .highlighted-row {
44
  background-color: #0d6efd !important;
45
  }
 
 
 
46
 
47
  /* Tom Select Dark Mode Fix */
48
  .ts-control {
@@ -78,7 +81,14 @@
78
  <!-- Header & Actions -->
79
  <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-4">
80
  <h1 class="mb-0">PDF Manager</h1>
81
- <div class="d-flex flex-wrap gap-2">
 
 
 
 
 
 
 
82
  {% if all_view %}
83
  <a href="/pdf_manager" class="btn btn-info">Show Folders</a>
84
  {% else %}
@@ -130,8 +140,18 @@
130
  <h5 class="card-title mt-2">{{ folder.name }}</h5>
131
  <p class="card-text text-muted">{{ folder.created_at.strftime('%Y-%m-%d %I:%M %p') }}</p>
132
  </div>
133
- <div class="card-footer text-center">
134
  <input type="checkbox" class="form-check-input item-checkbox" data-item-type="folder" value="{{ folder.id }}">
 
 
 
 
 
 
 
 
 
 
135
  </div>
136
  </div>
137
  </div>
@@ -181,9 +201,20 @@
181
  </div>
182
  <div class="card-footer d-flex justify-content-between align-items-center">
183
  <small class="text-muted">{{ pdf.created_at.strftime('%Y-%m-%d %I:%M %p') }}</small>
184
- <span>
185
  {% if pdf.persist %}<i class="bi bi-pin-angle-fill text-primary" title="Persisted"></i>{% endif %}
186
- </span>
 
 
 
 
 
 
 
 
 
 
 
187
  </div>
188
  </div>
189
  </div>
@@ -367,6 +398,77 @@
367
  cb.addEventListener('change', updateBulkButtons);
368
  });
369
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
  document.querySelectorAll('.folder-card').forEach(card => {
371
  card.addEventListener('click', e => {
372
  // If the click target is the folder name link, let the link handle it
 
43
  .table-hover .highlighted-row {
44
  background-color: #0d6efd !important;
45
  }
46
+ .no-caret::after {
47
+ display: none !important;
48
+ }
49
 
50
  /* Tom Select Dark Mode Fix */
51
  .ts-control {
 
81
  <!-- Header & Actions -->
82
  <div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-4">
83
  <h1 class="mb-0">PDF Manager</h1>
84
+ <div class="d-flex flex-wrap gap-2 align-items-center">
85
+ <form action="/pdf_manager" method="get" class="d-flex gap-2">
86
+ <input type="text" name="search" class="form-control bg-dark text-white border-secondary" placeholder="Search..." value="{{ request.args.get('search', '') }}">
87
+ <button type="submit" class="btn btn-outline-secondary"><i class="bi bi-search"></i></button>
88
+ {% if request.args.get('search') %}
89
+ <a href="/pdf_manager" class="btn btn-outline-danger"><i class="bi bi-x-lg"></i></a>
90
+ {% endif %}
91
+ </form>
92
  {% if all_view %}
93
  <a href="/pdf_manager" class="btn btn-info">Show Folders</a>
94
  {% else %}
 
140
  <h5 class="card-title mt-2">{{ folder.name }}</h5>
141
  <p class="card-text text-muted">{{ folder.created_at.strftime('%Y-%m-%d %I:%M %p') }}</p>
142
  </div>
143
+ <div class="card-footer d-flex justify-content-between align-items-center">
144
  <input type="checkbox" class="form-check-input item-checkbox" data-item-type="folder" value="{{ folder.id }}">
145
+ <div class="dropdown">
146
+ <button class="btn btn-sm btn-outline-secondary dropdown-toggle no-caret" type="button" data-bs-toggle="dropdown">
147
+ <i class="bi bi-three-dots-vertical"></i>
148
+ </button>
149
+ <ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end">
150
+ <li><a class="dropdown-item rename-single-btn" href="#" data-id="{{ folder.id }}" data-type="folder"><i class="bi bi-pencil-square me-2"></i> Rename</a></li>
151
+ <li><hr class="dropdown-divider"></li>
152
+ <li><a class="dropdown-item text-danger delete-item-btn" href="#" data-item-type="folder" data-id="{{ folder.id }}"><i class="bi bi-trash me-2"></i> Delete</a></li>
153
+ </ul>
154
+ </div>
155
  </div>
156
  </div>
157
  </div>
 
201
  </div>
202
  <div class="card-footer d-flex justify-content-between align-items-center">
203
  <small class="text-muted">{{ pdf.created_at.strftime('%Y-%m-%d %I:%M %p') }}</small>
204
+ <div class="d-flex align-items-center gap-2">
205
  {% if pdf.persist %}<i class="bi bi-pin-angle-fill text-primary" title="Persisted"></i>{% endif %}
206
+ <div class="dropdown">
207
+ <button class="btn btn-sm btn-outline-secondary dropdown-toggle no-caret" type="button" data-bs-toggle="dropdown">
208
+ <i class="bi bi-three-dots-vertical"></i>
209
+ </button>
210
+ <ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end">
211
+ <li><a class="dropdown-item edit-single-btn" href="#" data-id="{{ pdf.id }}"><i class="bi bi-info-circle me-2"></i> Details</a></li>
212
+ <li><a class="dropdown-item" href="{{ url_for('main.download_file', filename=pdf.filename) }}"><i class="bi bi-download me-2"></i> Download</a></li>
213
+ <li><hr class="dropdown-divider"></li>
214
+ <li><a class="dropdown-item text-danger delete-item-btn" href="#" data-item-type="pdf" data-id="{{ pdf.id }}"><i class="bi bi-trash me-2"></i> Delete</a></li>
215
+ </ul>
216
+ </div>
217
+ </div>
218
  </div>
219
  </div>
220
  </div>
 
398
  cb.addEventListener('change', updateBulkButtons);
399
  });
400
 
401
+ // Single Deletion
402
+ document.querySelectorAll('.delete-item-btn').forEach(btn => {
403
+ btn.addEventListener('click', async (e) => {
404
+ e.preventDefault();
405
+ e.stopPropagation();
406
+ const itemType = btn.dataset.itemType;
407
+ const itemId = btn.dataset.id;
408
+ if (!confirm(`Are you sure you want to delete this ${itemType}?`)) return;
409
+
410
+ const url = itemType === 'folder' ? `/delete_folder/${itemId}` : `/delete_generated_pdf/${itemId}`;
411
+ const response = await fetch(url, { method: 'DELETE' });
412
+ if (response.ok) {
413
+ location.reload();
414
+ } else {
415
+ alert('Failed to delete item.');
416
+ }
417
+ });
418
+ });
419
+
420
+ // Single Rename
421
+ document.querySelectorAll('.rename-single-btn').forEach(btn => {
422
+ btn.addEventListener('click', (e) => {
423
+ e.preventDefault();
424
+ e.stopPropagation();
425
+
426
+ const card = btn.closest('.card');
427
+ const titleElement = card.querySelector('.card-title') || card.querySelector('.fw-bold');
428
+ const currentName = titleElement.textContent.trim();
429
+ const itemType = btn.dataset.type || 'pdf';
430
+ const itemId = btn.dataset.id;
431
+
432
+ titleElement.innerHTML = `<input type="text" class="editable-input" value="${currentName}" />`;
433
+ const input = titleElement.querySelector('input');
434
+ input.focus();
435
+ input.select();
436
+
437
+ const saveChanges = async () => {
438
+ const newName = input.value;
439
+ if (newName && newName !== currentName) {
440
+ const response = await fetch('/rename_item', {
441
+ method: 'POST',
442
+ headers: { 'Content-Type': 'application/json' },
443
+ body: JSON.stringify({
444
+ item_type: itemType,
445
+ item_id: itemId,
446
+ new_name: newName
447
+ })
448
+ });
449
+ if (response.ok) location.reload();
450
+ else {
451
+ alert('Failed to rename.');
452
+ titleElement.textContent = currentName;
453
+ }
454
+ } else {
455
+ titleElement.textContent = currentName;
456
+ }
457
+ };
458
+
459
+ input.addEventListener('blur', saveChanges);
460
+ input.addEventListener('keydown', ev => {
461
+ if (ev.key === 'Enter') {
462
+ ev.preventDefault();
463
+ saveChanges();
464
+ }
465
+ if (ev.key === 'Escape') titleElement.textContent = currentName;
466
+ });
467
+ });
468
+ });
469
+
470
+ // Single Edit
471
+
472
  document.querySelectorAll('.folder-card').forEach(card => {
473
  card.addEventListener('click', e => {
474
  // If the click target is the folder name link, let the link handle it