diff --git a/handlers.py b/handlers.py index 6a03142..c3240ce 100644 --- a/handlers.py +++ b/handlers.py @@ -174,6 +174,11 @@ def _set_page_tags(page_id, tag_string, db=None): return_db(db) +def _cleanup_orphaned_tags(db): + """Delete tags that have no page associations.""" + db.execute("DELETE FROM tags WHERE id NOT IN (SELECT DISTINCT tag_id FROM page_tags)") + + # --- Route handlers --- @@ -499,7 +504,8 @@ def handle_pages(query=None): tag_links = " ".join(f'[{esc(t)}]' for t in tags) tags_html = f' {tag_links}' items += ( - f'
  • {esc(r["title"])}{note_html}{tags_html} ' + f'
  • {note_html}{tags_html} ' f'({esc(r["url"])}) ' f'edit ' f'remove
  • ' @@ -509,13 +515,71 @@ def handle_pages(query=None): return _respond( f"

    indexed pages ({total})

    " f"{msg_html}" + f'
    ' + f'{_csrf_field()}' + f'

    ' f"" f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}' + f'
    bulk actions' + f'

    ' + f'

    ' + f' ' + f'

    ' + f'
    ' + f'
    ' + f'' f'

    export | import

    ' f'back' ) +def handle_bulk_action(body): + ids = body.get("ids", []) + action = body.get("action", [""])[0] + if not ids: + return _redirect("/pages") + # Validate all ids are integers + try: + page_ids = [int(i) for i in ids] + except ValueError: + return _error(400) + db = get_db() + try: + if action == "delete": + for pid in page_ids: + db.execute("DELETE FROM page_tags WHERE page_id = ?", (pid,)) + db.execute("DELETE FROM links WHERE page_id = ?", (pid,)) + db.execute("DELETE FROM pages WHERE id = ?", (pid,)) + _cleanup_orphaned_tags(db) + db.commit() + elif action == "retag": + bulk_tags = body.get("bulk_tags", [""])[0].strip() + tag_mode = body.get("tag_mode", ["add"])[0] + if bulk_tags: + for pid in page_ids: + if tag_mode == "add": + existing = _get_page_tags(pid, db) + new_tags = [t.strip().lower() for t in bulk_tags.split(",") if t.strip()] + merged = ", ".join(sorted(set(existing + new_tags))) + _set_page_tags(pid, merged, db) + else: + _set_page_tags(pid, bulk_tags, db) + _cleanup_orphaned_tags(db) + db.commit() + finally: + return_db(db) + return _redirect("/pages") + + def handle_edit_form(page_id, msg=""): db = get_db() try: @@ -561,6 +625,7 @@ def handle_edit_submit(page_id, body): ) _set_page_tags(page_id, tags, db) + _cleanup_orphaned_tags(db) db.commit() @@ -596,6 +661,7 @@ def handle_delete(page_id): db.execute("DELETE FROM page_tags WHERE page_id = ?", (page_id,)) db.execute("DELETE FROM links WHERE page_id = ?", (page_id,)) db.execute("DELETE FROM pages WHERE id = ?", (page_id,)) + _cleanup_orphaned_tags(db) db.commit() finally: return_db(db) @@ -1344,6 +1410,8 @@ def _dispatch_inner(data): return _respond("

    403 Forbidden

    Invalid or missing CSRF token.

    ", status=403) if path == "/add": return handle_add_submit(body) + elif path == "/pages/bulk": + return handle_bulk_action(body) elif path == "/add/manual": return handle_add_manual_submit(body) elif path.startswith("/edit/"):