Blacklist removes from grid in-place, video copy as file URI

- Blacklisting a tag or post removes matching thumbnails from
  the grid without re-searching (preserves infinite scroll state)
- Video files copy as file:// URI so file managers can paste them
- Images still copy as raw data with correct MIME type
This commit is contained in:
pax 2026-04-05 15:24:27 -05:00
parent cd3946c494
commit 0a9b57621d

View File

@ -1254,27 +1254,16 @@ class BooruApp(QMainWindow):
def _blacklist_tag_from_slideshow(self, tag: str) -> None: def _blacklist_tag_from_slideshow(self, tag: str) -> None:
self._db.add_blacklisted_tag(tag) self._db.add_blacklisted_tag(tag)
self._db.set_setting("blacklist_enabled", "1") self._db.set_setting("blacklist_enabled", "1")
# Clear slideshow if previewed post has this tag
idx = self._grid.selected_index
if 0 <= idx < len(self._posts) and tag in self._posts[idx].tag_list:
self._preview.clear()
if self._fullscreen_window:
self._fullscreen_window._viewer.clear()
self._fullscreen_window._video.stop()
self._status.showMessage(f"Blacklisted: {tag}") self._status.showMessage(f"Blacklisted: {tag}")
self._do_search() self._remove_blacklisted_from_grid(tag=tag)
def _blacklist_post_from_slideshow(self) -> None: def _blacklist_post_from_slideshow(self) -> None:
idx = self._grid.selected_index idx = self._grid.selected_index
if 0 <= idx < len(self._posts): if 0 <= idx < len(self._posts):
post = self._posts[idx] post = self._posts[idx]
self._db.add_blacklisted_post(post.file_url) self._db.add_blacklisted_post(post.file_url)
self._preview.clear()
if self._fullscreen_window:
self._fullscreen_window._viewer.clear()
self._fullscreen_window._video.stop()
self._status.showMessage(f"Post #{post.id} blacklisted") self._status.showMessage(f"Post #{post.id} blacklisted")
self._do_search() self._remove_blacklisted_from_grid(post_url=post.file_url)
def _open_fullscreen_preview(self) -> None: def _open_fullscreen_preview(self) -> None:
path = self._preview._current_path path = self._preview._current_path
@ -1496,19 +1485,59 @@ class BooruApp(QMainWindow):
self._fullscreen_window._viewer.clear() self._fullscreen_window._viewer.clear()
self._fullscreen_window._video.stop() self._fullscreen_window._video.stop()
self._status.showMessage(f"Blacklisted: {tag}") self._status.showMessage(f"Blacklisted: {tag}")
self._do_search() self._remove_blacklisted_from_grid(tag=tag)
elif action == bl_post_action: elif action == bl_post_action:
self._db.add_blacklisted_post(post.file_url) self._db.add_blacklisted_post(post.file_url)
# Clear preview/slideshow if this is the previewed post self._remove_blacklisted_from_grid(post_url=post.file_url)
self._status.showMessage(f"Post #{post.id} blacklisted")
self._do_search()
def _remove_blacklisted_from_grid(self, tag: str = None, post_url: str = None) -> None:
"""Remove matching posts from the grid in-place without re-searching."""
to_remove = []
for i, post in enumerate(self._posts):
if tag and tag in post.tag_list:
to_remove.append(i)
elif post_url and post.file_url == post_url:
to_remove.append(i)
if not to_remove:
return
# Check if previewed post is being removed
from ..core.cache import cached_path_for from ..core.cache import cached_path_for
cp = str(cached_path_for(post.file_url)) for i in to_remove:
cp = str(cached_path_for(self._posts[i].file_url))
if cp == self._preview._current_path: if cp == self._preview._current_path:
self._preview.clear() self._preview.clear()
if self._fullscreen_window and self._fullscreen_window.isVisible(): if self._fullscreen_window and self._fullscreen_window.isVisible():
self._fullscreen_window._viewer.clear() self._fullscreen_window._viewer.clear()
self._fullscreen_window._video.stop() self._fullscreen_window._video.stop()
self._status.showMessage(f"Post #{post.id} blacklisted") break
self._do_search()
# Remove from posts list (reverse order to keep indices valid)
for i in reversed(to_remove):
self._posts.pop(i)
# Rebuild grid with remaining posts
thumbs = self._grid.set_posts(len(self._posts))
from ..core.config import saved_dir
site_id = self._site_combo.currentData()
_sd = saved_dir()
_saved_ids = {int(f.stem) for f in _sd.iterdir() if f.is_file() and f.stem.isdigit()} if _sd.exists() else set()
for i, (post, thumb) in enumerate(zip(self._posts, thumbs)):
if site_id and self._db.is_bookmarked(site_id, post.id):
thumb.set_bookmarked(True)
thumb.set_saved_locally(post.id in _saved_ids)
from ..core.cache import cached_path_for as cpf
cached = cpf(post.file_url)
if cached.exists():
thumb._cached_path = str(cached)
if post.preview_url:
self._fetch_thumbnail(i, post.preview_url)
self._status.showMessage(f"{len(self._posts)} results — {len(to_remove)} removed")
@staticmethod @staticmethod
def _is_child_of_menu(action, menu) -> bool: def _is_child_of_menu(action, menu) -> bool:
@ -1925,19 +1954,21 @@ class BooruApp(QMainWindow):
return return
import shutil import shutil
_IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
ext = Path(path).suffix.lower()
if shutil.which("wl-copy"): if shutil.which("wl-copy"):
import subprocess import subprocess
_MIMES = {
".jpg": "image/jpeg", ".jpeg": "image/jpeg",
".png": "image/png", ".gif": "image/gif",
".webp": "image/webp", ".mp4": "video/mp4",
".webm": "video/webm", ".mkv": "video/x-matroska",
".avi": "video/x-msvideo", ".mov": "video/quicktime",
}
mime = _MIMES.get(Path(path).suffix.lower(), "application/octet-stream")
try: try:
if ext in _IMAGE_EXTS:
_MIMES = {".jpg": "image/jpeg", ".jpeg": "image/jpeg",
".png": "image/png", ".gif": "image/gif", ".webp": "image/webp"}
with open(path, "rb") as f: with open(path, "rb") as f:
subprocess.run(["wl-copy", "--type", mime], stdin=f, timeout=10) subprocess.run(["wl-copy", "--type", _MIMES[ext]], stdin=f, timeout=10)
else:
# Videos/other: copy as file URI
uri = f"file://{Path(path).resolve()}"
subprocess.run(["wl-copy", "--type", "text/uri-list", uri], input=uri.encode(), timeout=5)
self._status.showMessage(f"Copied to clipboard: {Path(path).name}") self._status.showMessage(f"Copied to clipboard: {Path(path).name}")
return return
except Exception as e: except Exception as e: