From 922d2f860b4cfaab139485f60f1d911a9081f440 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 4 Jul 2026 15:09:12 +0300 Subject: [PATCH 1/2] feat(bookmarks): support excluding tags with - prefix in search Users can now prefix a tag with - in the bookmark search bar to exclude bookmarks containing that tag. For example, searching cat_ears -white_background shows bookmarks tagged cat_ears that are not also tagged white_background. Previously, the search only supported positive tag matching. --- .../src/providers/local_providers.dart | 17 +++++--- .../src/providers/suggestion_provider.dart | 12 ++++-- .../src/widgets/bookmark_scroll_view.dart | 39 +++++++++++++------ .../src/widgets/bookmark_search_bar.dart | 8 +++- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/lib/core/bookmarks/src/providers/local_providers.dart b/lib/core/bookmarks/src/providers/local_providers.dart index fd74a6a074..1d232adbbb 100644 --- a/lib/core/bookmarks/src/providers/local_providers.dart +++ b/lib/core/bookmarks/src/providers/local_providers.dart @@ -28,22 +28,29 @@ enum BookmarkSortType { List filterBookmarks({ required List bookmarks, required List selectedTags, + required List excludedTags, required BookmarkSortType sortType, String? selectedBooruUrl, BookmarkShuffleState? shuffleState, }) { final tagsList = selectedTags; + final excludeList = excludedTags; + + final hasFilters = + selectedBooruUrl != null || tagsList.isNotEmpty || excludeList.isNotEmpty; // Filter bookmarks based on URL and tags. - final filtered = selectedBooruUrl == null && tagsList.isEmpty - ? bookmarks - : bookmarks.where( + final filtered = hasFilters + ? bookmarks.where( (bookmark) => (selectedBooruUrl == null || bookmark.sourceUrl.contains(selectedBooruUrl)) && (tagsList.isEmpty || - tagsList.every((tag) => bookmark.tags.contains(tag))), - ); + tagsList.every((tag) => bookmark.tags.contains(tag))) && + (excludeList.isEmpty || + excludeList.every((tag) => !bookmark.tags.contains(tag))), + ) + : bookmarks; final sorted = filtered .sorted( diff --git a/lib/core/bookmarks/src/providers/suggestion_provider.dart b/lib/core/bookmarks/src/providers/suggestion_provider.dart index 10cd9ce0d2..48acab637d 100644 --- a/lib/core/bookmarks/src/providers/suggestion_provider.dart +++ b/lib/core/bookmarks/src/providers/suggestion_provider.dart @@ -43,10 +43,16 @@ class TagSuggestionsNotifier ); try { - final tags = tagString.trim().split(' '); - final tag = tags.lastOrNull?.trim(); + final segments = tagString.trim().split(' '); + final rawTag = segments.lastOrNull?.trim() ?? ''; - if (tag == null || tag.isEmpty) { + // Strip leading - for exclusion tag suggestions + final tag = switch (rawTag) { + String t when t.startsWith('-') => t.substring(1), + _ => rawTag, + }; + + if (tag.isEmpty) { state = AsyncValue.data( currentState.copyWith( suggestions: const [], diff --git a/lib/core/bookmarks/src/widgets/bookmark_scroll_view.dart b/lib/core/bookmarks/src/widgets/bookmark_scroll_view.dart index 1f6c2b304d..7b66a4d27c 100644 --- a/lib/core/bookmarks/src/widgets/bookmark_scroll_view.dart +++ b/lib/core/bookmarks/src/widgets/bookmark_scroll_view.dart @@ -55,15 +55,29 @@ class BookmarkScrollView extends ConsumerStatefulWidget { class _BookmarkScrollViewState extends ConsumerState { final _selectionModeController = SelectionModeController(); - List _parseTagsFromText(String text) { - return text.isEmpty - ? [] - : text - .trim() - .replaceAll(RegExp(r'\s+'), ' ') - .split(' ') - .where((e) => e.isNotEmpty) - .toList(); + ({ + List include, + List exclude, + }) + _parseTagsFromText(String text) { + if (text.isEmpty) { + return (include: [], exclude: []); + } + + final segments = text + .trim() + .replaceAll(RegExp(r'\s+'), ' ') + .split(' ') + .where((e) => e.isNotEmpty) + .toList(); + + return ( + include: segments.where((e) => !e.startsWith('-')).toList(), + exclude: segments + .where((e) => e.startsWith('-')) + .map((e) => e.substring(1)) + .toList(), + ); } @override @@ -81,12 +95,15 @@ class _BookmarkScrollViewState extends ConsumerState { }, fetcher: (page) => TaskEither.Do( ($) async { - final searchTags = _parseTagsFromText(widget.searchController.text); + final (:include, :exclude) = _parseTagsFromText( + widget.searchController.text, + ); final sortType = ref.read(selectedBookmarkSortTypeProvider); final selectedBooruUrl = ref.read(selectedBooruUrlProvider); final shuffleState = ref.read(bookmarkShuffleProvider); final bookmarks = filterBookmarks( - selectedTags: searchTags, + selectedTags: include, + excludedTags: exclude, bookmarks: await (await ref.read(bookmarkRepoProvider.future)) .getAllBookmarksOrEmpty( imageUrlResolver: (booruId) => diff --git a/lib/core/bookmarks/src/widgets/bookmark_search_bar.dart b/lib/core/bookmarks/src/widgets/bookmark_search_bar.dart index c2879538fa..4daf8cf1be 100644 --- a/lib/core/bookmarks/src/widgets/bookmark_search_bar.dart +++ b/lib/core/bookmarks/src/widgets/bookmark_search_bar.dart @@ -84,9 +84,13 @@ class _BookmarkSearchBarState extends ConsumerState { child: _Overlay( controller: widget.controller, onTap: (tag) { + final text = widget.controller.text; + final lastSegment = text.split(' ').last; + final isExcluding = lastSegment.startsWith('-'); + final adjustedTag = isExcluding ? '-${tag.tag}' : tag.tag; widget.controller.text = replaceOrAppendTag( - widget.controller.text, - tag.tag, + text, + adjustedTag, ); _search(); }, From 351c7a3f56d4257c07ddfb010d1000f2a3a5a582 Mon Sep 17 00:00:00 2001 From: esefxdz Date: Sat, 4 Jul 2026 18:49:55 +0300 Subject: [PATCH 2/2] feat(bookmarks): extend tag exclusion to also match source host Excluded search terms now also check the bookmark source URL host, not just tags. Typing -danbooru excludes bookmarks from danbooru.donmai.us in addition to those tagged danbooru. --- lib/core/bookmarks/src/providers/local_providers.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core/bookmarks/src/providers/local_providers.dart b/lib/core/bookmarks/src/providers/local_providers.dart index 1d232adbbb..31dea4be08 100644 --- a/lib/core/bookmarks/src/providers/local_providers.dart +++ b/lib/core/bookmarks/src/providers/local_providers.dart @@ -48,7 +48,11 @@ List filterBookmarks({ (tagsList.isEmpty || tagsList.every((tag) => bookmark.tags.contains(tag))) && (excludeList.isEmpty || - excludeList.every((tag) => !bookmark.tags.contains(tag))), + excludeList.every( + (tag) => + !bookmark.tags.contains(tag) && + !bookmark.sourceUrl.contains(tag), + )), ) : bookmarks;