diff --git a/apps/readest-app/public/locales/ar/translation.json b/apps/readest-app/public/locales/ar/translation.json index af63869a..6cda2658 100644 --- a/apps/readest-app/public/locales/ar/translation.json +++ b/apps/readest-app/public/locales/ar/translation.json @@ -1932,5 +1932,8 @@ "Date modified": "تاريخ التعديل", "Date created": "تاريخ الإنشاء", "Sort ascending": "ترتيب تصاعدي", - "Sort descending": "ترتيب تنازلي" + "Sort descending": "ترتيب تنازلي", + "Filter Annotations": "تصفية التعليقات التوضيحية", + "Colors": "الألوان", + "Styles": "الأنماط" } diff --git a/apps/readest-app/public/locales/bn/translation.json b/apps/readest-app/public/locales/bn/translation.json index f26a38da..3ab70a28 100644 --- a/apps/readest-app/public/locales/bn/translation.json +++ b/apps/readest-app/public/locales/bn/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "পরিবর্তনের তারিখ", "Date created": "তৈরির তারিখ", "Sort ascending": "ঊর্ধ্বক্রমে সাজান", - "Sort descending": "অধঃক্রমে সাজান" + "Sort descending": "অধঃক্রমে সাজান", + "Filter Annotations": "টীকা ফিল্টার করুন", + "Colors": "রঙ", + "Styles": "শৈলী" } diff --git a/apps/readest-app/public/locales/bo/translation.json b/apps/readest-app/public/locales/bo/translation.json index 3e6fa4c9..5c96a8ac 100644 --- a/apps/readest-app/public/locales/bo/translation.json +++ b/apps/readest-app/public/locales/bo/translation.json @@ -1761,5 +1761,8 @@ "Background Image (Reader)": "གནས་སྟངས་ཀྱི་རི་མོ། (ཀློག་ངོས།)", "updated metadata for {{n}} book(s)": "དཔེ་ཆ་{{n}}་ཡི་གནད་སྨིན་གོ་དོན་གསར་བསྒྱུར་བྱས།", "{{n}} metadata": "{{n}} གནད་སྨིན་གོ་དོན།", - "Metadata updated": "གནད་སྨིན་གོ་དོན་གསར་བསྒྱུར་བྱས།" + "Metadata updated": "གནད་སྨིན་གོ་དོན་གསར་བསྒྱུར་བྱས།", + "Filter Annotations": "མཆན་འདེམས་སྒྲིག", + "Colors": "ཁ་དོག", + "Styles": "རྣམ་པ" } diff --git a/apps/readest-app/public/locales/de/translation.json b/apps/readest-app/public/locales/de/translation.json index dc180c80..f83c2d34 100644 --- a/apps/readest-app/public/locales/de/translation.json +++ b/apps/readest-app/public/locales/de/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Änderungsdatum", "Date created": "Erstellungsdatum", "Sort ascending": "Aufsteigend sortieren", - "Sort descending": "Absteigend sortieren" + "Sort descending": "Absteigend sortieren", + "Filter Annotations": "Anmerkungen filtern", + "Colors": "Farben", + "Styles": "Stile" } diff --git a/apps/readest-app/public/locales/el/translation.json b/apps/readest-app/public/locales/el/translation.json index 548a4ed5..b086e451 100644 --- a/apps/readest-app/public/locales/el/translation.json +++ b/apps/readest-app/public/locales/el/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Ημερομηνία τροποποίησης", "Date created": "Ημερομηνία δημιουργίας", "Sort ascending": "Αύξουσα ταξινόμηση", - "Sort descending": "Φθίνουσα ταξινόμηση" + "Sort descending": "Φθίνουσα ταξινόμηση", + "Filter Annotations": "Φιλτράρισμα σχολιασμών", + "Colors": "Χρώματα", + "Styles": "Στυλ" } diff --git a/apps/readest-app/public/locales/es/translation.json b/apps/readest-app/public/locales/es/translation.json index ad84348f..81f0c6e5 100644 --- a/apps/readest-app/public/locales/es/translation.json +++ b/apps/readest-app/public/locales/es/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Fecha de modificación", "Date created": "Fecha de creación", "Sort ascending": "Orden ascendente", - "Sort descending": "Orden descendente" + "Sort descending": "Orden descendente", + "Filter Annotations": "Filtrar anotaciones", + "Colors": "Colores", + "Styles": "Estilos" } diff --git a/apps/readest-app/public/locales/fa/translation.json b/apps/readest-app/public/locales/fa/translation.json index 192826b9..c5387cc2 100644 --- a/apps/readest-app/public/locales/fa/translation.json +++ b/apps/readest-app/public/locales/fa/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "تاریخ تغییر", "Date created": "تاریخ ایجاد", "Sort ascending": "مرتب‌سازی صعودی", - "Sort descending": "مرتب‌سازی نزولی" + "Sort descending": "مرتب‌سازی نزولی", + "Filter Annotations": "فیلتر یادداشت‌ها", + "Colors": "رنگ‌ها", + "Styles": "سبک‌ها" } diff --git a/apps/readest-app/public/locales/fr/translation.json b/apps/readest-app/public/locales/fr/translation.json index b21233da..6c035eb6 100644 --- a/apps/readest-app/public/locales/fr/translation.json +++ b/apps/readest-app/public/locales/fr/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Date de modification", "Date created": "Date de création", "Sort ascending": "Tri croissant", - "Sort descending": "Tri décroissant" + "Sort descending": "Tri décroissant", + "Filter Annotations": "Filtrer les annotations", + "Colors": "Couleurs", + "Styles": "Styles" } diff --git a/apps/readest-app/public/locales/he/translation.json b/apps/readest-app/public/locales/he/translation.json index bcd3732b..0acdcfc8 100644 --- a/apps/readest-app/public/locales/he/translation.json +++ b/apps/readest-app/public/locales/he/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "תאריך שינוי", "Date created": "תאריך יצירה", "Sort ascending": "מיון עולה", - "Sort descending": "מיון יורד" + "Sort descending": "מיון יורד", + "Filter Annotations": "סינון הערות", + "Colors": "צבעים", + "Styles": "סגנונות" } diff --git a/apps/readest-app/public/locales/hi/translation.json b/apps/readest-app/public/locales/hi/translation.json index ab866d83..77545c39 100644 --- a/apps/readest-app/public/locales/hi/translation.json +++ b/apps/readest-app/public/locales/hi/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "संशोधन तिथि", "Date created": "निर्माण तिथि", "Sort ascending": "आरोही क्रम", - "Sort descending": "अवरोही क्रम" + "Sort descending": "अवरोही क्रम", + "Filter Annotations": "टिप्पणियाँ फ़िल्टर करें", + "Colors": "रंग", + "Styles": "शैलियाँ" } diff --git a/apps/readest-app/public/locales/hu/translation.json b/apps/readest-app/public/locales/hu/translation.json index 5c435f17..770ac1a9 100644 --- a/apps/readest-app/public/locales/hu/translation.json +++ b/apps/readest-app/public/locales/hu/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Módosítás dátuma", "Date created": "Létrehozás dátuma", "Sort ascending": "Növekvő sorrend", - "Sort descending": "Csökkenő sorrend" + "Sort descending": "Csökkenő sorrend", + "Filter Annotations": "Jegyzetek szűrése", + "Colors": "Színek", + "Styles": "Stílusok" } diff --git a/apps/readest-app/public/locales/id/translation.json b/apps/readest-app/public/locales/id/translation.json index dd87e931..c66db0f3 100644 --- a/apps/readest-app/public/locales/id/translation.json +++ b/apps/readest-app/public/locales/id/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "Tanggal diubah", "Date created": "Tanggal dibuat", "Sort ascending": "Urutkan menaik", - "Sort descending": "Urutkan menurun" + "Sort descending": "Urutkan menurun", + "Filter Annotations": "Filter Anotasi", + "Colors": "Warna", + "Styles": "Gaya" } diff --git a/apps/readest-app/public/locales/it/translation.json b/apps/readest-app/public/locales/it/translation.json index 65bdc6de..cdcd2ffe 100644 --- a/apps/readest-app/public/locales/it/translation.json +++ b/apps/readest-app/public/locales/it/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Data di modifica", "Date created": "Data di creazione", "Sort ascending": "Ordine crescente", - "Sort descending": "Ordine decrescente" + "Sort descending": "Ordine decrescente", + "Filter Annotations": "Filtra annotazioni", + "Colors": "Colori", + "Styles": "Stili" } diff --git a/apps/readest-app/public/locales/ja/translation.json b/apps/readest-app/public/locales/ja/translation.json index 9e140f62..07c99168 100644 --- a/apps/readest-app/public/locales/ja/translation.json +++ b/apps/readest-app/public/locales/ja/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "更新日", "Date created": "作成日", "Sort ascending": "昇順で並べ替え", - "Sort descending": "降順で並べ替え" + "Sort descending": "降順で並べ替え", + "Filter Annotations": "注釈をフィルター", + "Colors": "色", + "Styles": "スタイル" } diff --git a/apps/readest-app/public/locales/ko/translation.json b/apps/readest-app/public/locales/ko/translation.json index a0ebe4db..edbbc7bd 100644 --- a/apps/readest-app/public/locales/ko/translation.json +++ b/apps/readest-app/public/locales/ko/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "수정한 날짜", "Date created": "만든 날짜", "Sort ascending": "오름차순 정렬", - "Sort descending": "내림차순 정렬" + "Sort descending": "내림차순 정렬", + "Filter Annotations": "주석 필터링", + "Colors": "색상", + "Styles": "스타일" } diff --git a/apps/readest-app/public/locales/ms/translation.json b/apps/readest-app/public/locales/ms/translation.json index 1334e5cd..28753d19 100644 --- a/apps/readest-app/public/locales/ms/translation.json +++ b/apps/readest-app/public/locales/ms/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "Tarikh diubah suai", "Date created": "Tarikh dicipta", "Sort ascending": "Isih menaik", - "Sort descending": "Isih menurun" + "Sort descending": "Isih menurun", + "Filter Annotations": "Tapis Anotasi", + "Colors": "Warna", + "Styles": "Gaya" } diff --git a/apps/readest-app/public/locales/nl/translation.json b/apps/readest-app/public/locales/nl/translation.json index db2ef335..d5965afb 100644 --- a/apps/readest-app/public/locales/nl/translation.json +++ b/apps/readest-app/public/locales/nl/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Wijzigingsdatum", "Date created": "Aanmaakdatum", "Sort ascending": "Oplopend sorteren", - "Sort descending": "Aflopend sorteren" + "Sort descending": "Aflopend sorteren", + "Filter Annotations": "Aantekeningen filteren", + "Colors": "Kleuren", + "Styles": "Stijlen" } diff --git a/apps/readest-app/public/locales/pl/translation.json b/apps/readest-app/public/locales/pl/translation.json index 29949420..fec2c7db 100644 --- a/apps/readest-app/public/locales/pl/translation.json +++ b/apps/readest-app/public/locales/pl/translation.json @@ -1866,5 +1866,8 @@ "Date modified": "Data modyfikacji", "Date created": "Data utworzenia", "Sort ascending": "Sortuj rosnąco", - "Sort descending": "Sortuj malejąco" + "Sort descending": "Sortuj malejąco", + "Filter Annotations": "Filtruj adnotacje", + "Colors": "Kolory", + "Styles": "Style" } diff --git a/apps/readest-app/public/locales/pt-BR/translation.json b/apps/readest-app/public/locales/pt-BR/translation.json index e9782ca6..764b56df 100644 --- a/apps/readest-app/public/locales/pt-BR/translation.json +++ b/apps/readest-app/public/locales/pt-BR/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Data de modificação", "Date created": "Data de criação", "Sort ascending": "Ordem crescente", - "Sort descending": "Ordem decrescente" + "Sort descending": "Ordem decrescente", + "Filter Annotations": "Filtrar anotações", + "Colors": "Cores", + "Styles": "Estilos" } diff --git a/apps/readest-app/public/locales/pt/translation.json b/apps/readest-app/public/locales/pt/translation.json index 8135800e..4eb4eecd 100644 --- a/apps/readest-app/public/locales/pt/translation.json +++ b/apps/readest-app/public/locales/pt/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Data de modificação", "Date created": "Data de criação", "Sort ascending": "Ordem crescente", - "Sort descending": "Ordem decrescente" + "Sort descending": "Ordem decrescente", + "Filter Annotations": "Filtrar anotações", + "Colors": "Cores", + "Styles": "Estilos" } diff --git a/apps/readest-app/public/locales/ro/translation.json b/apps/readest-app/public/locales/ro/translation.json index 7eac417e..8e9787d2 100644 --- a/apps/readest-app/public/locales/ro/translation.json +++ b/apps/readest-app/public/locales/ro/translation.json @@ -1833,5 +1833,8 @@ "Date modified": "Data modificării", "Date created": "Data creării", "Sort ascending": "Sortare crescătoare", - "Sort descending": "Sortare descrescătoare" + "Sort descending": "Sortare descrescătoare", + "Filter Annotations": "Filtrează adnotările", + "Colors": "Culori", + "Styles": "Stiluri" } diff --git a/apps/readest-app/public/locales/ru/translation.json b/apps/readest-app/public/locales/ru/translation.json index 1720d6e9..34a6df17 100644 --- a/apps/readest-app/public/locales/ru/translation.json +++ b/apps/readest-app/public/locales/ru/translation.json @@ -1866,5 +1866,8 @@ "Date modified": "Дата изменения", "Date created": "Дата создания", "Sort ascending": "По возрастанию", - "Sort descending": "По убыванию" + "Sort descending": "По убыванию", + "Filter Annotations": "Фильтровать аннотации", + "Colors": "Цвета", + "Styles": "Стили" } diff --git a/apps/readest-app/public/locales/si/translation.json b/apps/readest-app/public/locales/si/translation.json index 9b69fe26..ff19bef1 100644 --- a/apps/readest-app/public/locales/si/translation.json +++ b/apps/readest-app/public/locales/si/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "වෙනස් කළ දිනය", "Date created": "සෑදූ දිනය", "Sort ascending": "ආරෝහණව සකසන්න", - "Sort descending": "අවරෝහණව සකසන්න" + "Sort descending": "අවරෝහණව සකසන්න", + "Filter Annotations": "සටහන් පෙරහන් කරන්න", + "Colors": "වර්ණ", + "Styles": "මෝස්තර" } diff --git a/apps/readest-app/public/locales/sl/translation.json b/apps/readest-app/public/locales/sl/translation.json index 42ad8c0a..1322239e 100644 --- a/apps/readest-app/public/locales/sl/translation.json +++ b/apps/readest-app/public/locales/sl/translation.json @@ -1866,5 +1866,8 @@ "Date modified": "Datum spremembe", "Date created": "Datum ustvarjanja", "Sort ascending": "Naraščajoče razvrščanje", - "Sort descending": "Padajoče razvrščanje" + "Sort descending": "Padajoče razvrščanje", + "Filter Annotations": "Filtriraj opombe", + "Colors": "Barve", + "Styles": "Slogi" } diff --git a/apps/readest-app/public/locales/sv/translation.json b/apps/readest-app/public/locales/sv/translation.json index a1fdf948..5ac61e7e 100644 --- a/apps/readest-app/public/locales/sv/translation.json +++ b/apps/readest-app/public/locales/sv/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Ändringsdatum", "Date created": "Skapandedatum", "Sort ascending": "Stigande sortering", - "Sort descending": "Fallande sortering" + "Sort descending": "Fallande sortering", + "Filter Annotations": "Filtrera anteckningar", + "Colors": "Färger", + "Styles": "Stilar" } diff --git a/apps/readest-app/public/locales/ta/translation.json b/apps/readest-app/public/locales/ta/translation.json index 7d2bad1b..c3c7d2fd 100644 --- a/apps/readest-app/public/locales/ta/translation.json +++ b/apps/readest-app/public/locales/ta/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "மாற்றிய தேதி", "Date created": "உருவாக்கிய தேதி", "Sort ascending": "ஏறுவரிசை", - "Sort descending": "இறங்குவரிசை" + "Sort descending": "இறங்குவரிசை", + "Filter Annotations": "சிறுகுறிப்புகளை வடிகட்டு", + "Colors": "நிறங்கள்", + "Styles": "பாணிகள்" } diff --git a/apps/readest-app/public/locales/th/translation.json b/apps/readest-app/public/locales/th/translation.json index 406d708a..ec41949f 100644 --- a/apps/readest-app/public/locales/th/translation.json +++ b/apps/readest-app/public/locales/th/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "วันที่แก้ไข", "Date created": "วันที่สร้าง", "Sort ascending": "เรียงจากน้อยไปมาก", - "Sort descending": "เรียงจากมากไปน้อย" + "Sort descending": "เรียงจากมากไปน้อย", + "Filter Annotations": "กรองคำอธิบายประกอบ", + "Colors": "สี", + "Styles": "สไตล์" } diff --git a/apps/readest-app/public/locales/tr/translation.json b/apps/readest-app/public/locales/tr/translation.json index 2319e954..ea701f6f 100644 --- a/apps/readest-app/public/locales/tr/translation.json +++ b/apps/readest-app/public/locales/tr/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Değiştirilme tarihi", "Date created": "Oluşturulma tarihi", "Sort ascending": "Artan sıralama", - "Sort descending": "Azalan sıralama" + "Sort descending": "Azalan sıralama", + "Filter Annotations": "Açıklamaları Filtrele", + "Colors": "Renkler", + "Styles": "Stiller" } diff --git a/apps/readest-app/public/locales/uk/translation.json b/apps/readest-app/public/locales/uk/translation.json index 9483214c..9f2f7eb8 100644 --- a/apps/readest-app/public/locales/uk/translation.json +++ b/apps/readest-app/public/locales/uk/translation.json @@ -1866,5 +1866,8 @@ "Date modified": "Дата зміни", "Date created": "Дата створення", "Sort ascending": "За зростанням", - "Sort descending": "За спаданням" + "Sort descending": "За спаданням", + "Filter Annotations": "Фільтрувати анотації", + "Colors": "Кольори", + "Styles": "Стилі" } diff --git a/apps/readest-app/public/locales/uz/translation.json b/apps/readest-app/public/locales/uz/translation.json index fb7b5d83..cd9787ba 100644 --- a/apps/readest-app/public/locales/uz/translation.json +++ b/apps/readest-app/public/locales/uz/translation.json @@ -1800,5 +1800,8 @@ "Date modified": "Oʻzgartirilgan sana", "Date created": "Yaratilgan sana", "Sort ascending": "Oʻsish boʻyicha", - "Sort descending": "Kamayish boʻyicha" + "Sort descending": "Kamayish boʻyicha", + "Filter Annotations": "Izohlarni filtrlash", + "Colors": "Ranglar", + "Styles": "Uslublar" } diff --git a/apps/readest-app/public/locales/vi/translation.json b/apps/readest-app/public/locales/vi/translation.json index 7a092e34..33d4cab8 100644 --- a/apps/readest-app/public/locales/vi/translation.json +++ b/apps/readest-app/public/locales/vi/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "Ngày sửa đổi", "Date created": "Ngày tạo", "Sort ascending": "Sắp xếp tăng dần", - "Sort descending": "Sắp xếp giảm dần" + "Sort descending": "Sắp xếp giảm dần", + "Filter Annotations": "Lọc chú thích", + "Colors": "Màu sắc", + "Styles": "Kiểu" } diff --git a/apps/readest-app/public/locales/zh-CN/translation.json b/apps/readest-app/public/locales/zh-CN/translation.json index 1bc45108..856a958c 100644 --- a/apps/readest-app/public/locales/zh-CN/translation.json +++ b/apps/readest-app/public/locales/zh-CN/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "修改日期", "Date created": "创建日期", "Sort ascending": "升序排列", - "Sort descending": "降序排列" + "Sort descending": "降序排列", + "Filter Annotations": "筛选笔记", + "Colors": "颜色", + "Styles": "样式" } diff --git a/apps/readest-app/public/locales/zh-TW/translation.json b/apps/readest-app/public/locales/zh-TW/translation.json index 7716636f..c04ad959 100644 --- a/apps/readest-app/public/locales/zh-TW/translation.json +++ b/apps/readest-app/public/locales/zh-TW/translation.json @@ -1767,5 +1767,8 @@ "Date modified": "修改日期", "Date created": "建立日期", "Sort ascending": "升序排列", - "Sort descending": "降序排列" + "Sort descending": "降序排列", + "Filter Annotations": "篩選筆記", + "Colors": "顏色", + "Styles": "樣式" } diff --git a/apps/readest-app/src/__tests__/app/reader/utils/annotatorUtil.test.ts b/apps/readest-app/src/__tests__/app/reader/utils/annotatorUtil.test.ts index 16ea1197..5c5e831f 100644 --- a/apps/readest-app/src/__tests__/app/reader/utils/annotatorUtil.test.ts +++ b/apps/readest-app/src/__tests__/app/reader/utils/annotatorUtil.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it } from 'vitest'; import { decideAnnotationDraw, + filterExportGroups, findAnnotationAtCfi, mergeRestyledAnnotation, } from '@/app/reader/utils/annotatorUtil'; -import { BookNote } from '@/types/book'; +import { BookNote, BooknoteGroup } from '@/types/book'; import { NOTE_PREFIX } from '@/types/view'; const makeNote = (over: Partial): BookNote => ({ @@ -80,3 +81,105 @@ describe('mergeRestyledAnnotation', () => { expect(merged.updatedAt).toBe(200); }); }); + +describe('filterExportGroups', () => { + const group = (booknotes: BookNote[], over: Partial = {}): BooknoteGroup => ({ + id: 0, + href: 'h', + label: 'Chapter', + booknotes, + ...over, + }); + + it('keeps everything when nothing is excluded', () => { + const groups = [group([makeNote({ color: 'yellow' }), makeNote({ color: 'red' })])]; + const result = filterExportGroups(groups, { excludedColors: [], excludedStyles: [] }); + expect(result.groups[0]!.booknotes).toHaveLength(2); + expect(result.applyColorFilter).toBe(true); + expect(result.distinctColors).toEqual(['red', 'yellow']); + }); + + it('excludes a color and keeps the others', () => { + const groups = [ + group([makeNote({ id: 'a', color: 'yellow' }), makeNote({ id: 'b', color: 'red' })]), + ]; + const result = filterExportGroups(groups, { excludedColors: ['red'], excludedStyles: [] }); + expect(result.groups[0]!.booknotes.map((n) => n.id)).toEqual(['a']); + }); + + it('excludes a style and keeps the others', () => { + const groups = [ + group([ + makeNote({ id: 'a', color: 'yellow', style: 'highlight' }), + makeNote({ id: 'b', color: 'yellow', style: 'underline' }), + ]), + ]; + const result = filterExportGroups(groups, { + excludedColors: [], + excludedStyles: ['underline'], + }); + expect(result.groups[0]!.booknotes.map((n) => n.id)).toEqual(['a']); + }); + + it('combines color and style filters with AND', () => { + const groups = [ + group([ + makeNote({ id: 'a', color: 'yellow', style: 'highlight' }), + makeNote({ id: 'b', color: 'red', style: 'highlight' }), + makeNote({ id: 'c', color: 'yellow', style: 'underline' }), + ]), + ]; + const result = filterExportGroups(groups, { + excludedColors: ['red'], + excludedStyles: ['underline'], + }); + expect(result.groups[0]!.booknotes.map((n) => n.id)).toEqual(['a']); + }); + + it('drops groups that become empty', () => { + const groups = [ + group([makeNote({ id: 'a', color: 'red' })], { href: 'h1' }), + group([makeNote({ id: 'b', color: 'yellow' })], { href: 'h2' }), + ]; + const result = filterExportGroups(groups, { excludedColors: ['red'], excludedStyles: [] }); + expect(result.groups).toHaveLength(1); + expect(result.groups[0]!.href).toBe('h2'); + }); + + it('always keeps notes without a color or style (e.g. bookmarks)', () => { + const groups = [ + group([ + makeNote({ id: 'a', color: 'yellow' }), + makeNote({ id: 'b', color: 'red' }), + makeNote({ id: 'bm', type: 'bookmark', color: undefined, style: undefined }), + ]), + ]; + const result = filterExportGroups(groups, { + excludedColors: ['red', 'yellow'], + excludedStyles: [], + }); + expect(result.groups[0]!.booknotes.map((n) => n.id)).toEqual(['bm']); + }); + + it('does not apply the color filter when fewer than two colors are present', () => { + const groups = [ + group([makeNote({ id: 'a', color: 'yellow' }), makeNote({ id: 'b', color: 'yellow' })]), + ]; + const result = filterExportGroups(groups, { excludedColors: ['yellow'], excludedStyles: [] }); + expect(result.applyColorFilter).toBe(false); + expect(result.groups[0]!.booknotes).toHaveLength(2); + }); + + it('orders distinct colors by default palette then custom, and styles canonically', () => { + const groups = [ + group([ + makeNote({ color: '#abcdef', style: 'squiggly' }), + makeNote({ color: 'blue', style: 'highlight' }), + makeNote({ color: 'red', style: 'underline' }), + ]), + ]; + const result = filterExportGroups(groups, { excludedColors: [], excludedStyles: [] }); + expect(result.distinctColors).toEqual(['red', 'blue', '#abcdef']); + expect(result.distinctStyles).toEqual(['highlight', 'underline', 'squiggly']); + }); +}); diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index b95cb807..42443681 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -164,7 +164,6 @@ const Annotator: React.FC<{ bookKey: string; contentInsets: Insets }> = ({ // would otherwise tear down the dialog state immediately. const [clearAnnotationsCount, setClearAnnotationsCount] = useState(0); const [exportData, setExportData] = useState<{ - booknotes: BookNote[]; booknoteGroups: { [href: string]: BooknoteGroup }; } | null>(null); @@ -1519,7 +1518,7 @@ const Annotator: React.FC<{ bookKey: string; contentInsets: Insets }> = ({ }); }); - setExportData({ booknotes, booknoteGroups }); + setExportData({ booknoteGroups }); setShowExportDialog(true); }; @@ -1795,7 +1794,6 @@ const Annotator: React.FC<{ bookKey: string; contentInsets: Insets }> = ({ bookHash={bookData.book.hash} bookTitle={bookData.book.title} bookAuthor={bookData.book.author || ''} - booknotes={exportData.booknotes} booknoteGroups={exportData.booknoteGroups} onCancel={handleCancelExport} onExport={handleConfirmExport} diff --git a/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx b/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx index fb5869ab..eac2aef0 100644 --- a/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/ExportMarkdownDialog.tsx @@ -4,9 +4,15 @@ import { marked } from 'marked'; import { useEnv } from '@/context/EnvContext'; import { useTranslation } from '@/hooks/useTranslation'; import { useReaderStore } from '@/store/readerStore'; -import { BookNote, BooknoteGroup, NoteExportConfig } from '@/types/book'; +import { useSettingsStore } from '@/store/settingsStore'; +import { BooknoteGroup, HighlightColor, HighlightStyle, NoteExportConfig } from '@/types/book'; import { DEFAULT_NOTE_EXPORT_CONFIG } from '@/services/constants'; import { saveViewSettings } from '@/helpers/settings'; +import { + filterExportGroups, + getHighlightColorHex, + getHighlightColorLabel, +} from '@/app/reader/utils/annotatorUtil'; import { renderNoteTemplate, formatBlockQuote } from '@/utils/note'; import { AnnotationLinkType, @@ -22,7 +28,6 @@ interface ExportMarkdownDialogProps { bookHash: string; bookTitle: string; bookAuthor: string; - booknotes: BookNote[]; booknoteGroups: { [href: string]: BooknoteGroup }; onCancel: () => void; onExport: ( @@ -38,13 +43,13 @@ const ExportMarkdownDialog: React.FC = ({ bookHash, bookTitle, bookAuthor, - booknotes, booknoteGroups, onCancel, onExport, }) => { const _ = useTranslation(); const { envConfig } = useEnv(); + const { settings } = useSettingsStore(); const { getViewSettings } = useReaderStore(); const viewSettings = getViewSettings(bookKey); @@ -90,6 +95,10 @@ const ExportMarkdownDialog: React.FC = ({ // platform-aware default (app in the native app, web on the web). linkType: noteExportConfig.linkType ?? DEFAULT_NOTE_EXPORT_CONFIG.linkType, customTemplate: noteExportConfig.customTemplate || defaultTemplate, + // Configs persisted before color/style filtering existed have no + // exclusion arrays; default to exporting everything. + excludedColors: noteExportConfig.excludedColors ?? [], + excludedStyles: noteExportConfig.excludedStyles ?? [], }; }); @@ -119,13 +128,64 @@ const ExportMarkdownDialog: React.FC = ({ .trim(); }; + // Apply the color/style filter once; both the default formatter and the custom + // template render the filtered groups, and the same metadata drives the filter UI. + const { + groups: filteredGroups, + distinctColors, + distinctStyles, + applyColorFilter, + applyStyleFilter, + } = useMemo( + () => + filterExportGroups( + Object.values(booknoteGroups).sort((a, b) => a.id - b.id), + { + excludedColors: exportConfig.excludedColors, + excludedStyles: exportConfig.excludedStyles, + }, + ), + [booknoteGroups, exportConfig.excludedColors, exportConfig.excludedStyles], + ); + + const filteredNotesCount = useMemo( + () => filteredGroups.reduce((count, group) => count + group.booknotes.length, 0), + [filteredGroups], + ); + + const toggleExcludedColor = (color: HighlightColor) => { + setExportConfig((prev) => ({ + ...prev, + excludedColors: prev.excludedColors.includes(color) + ? prev.excludedColors.filter((c) => c !== color) + : [...prev.excludedColors, color], + })); + }; + + const toggleExcludedStyle = (style: HighlightStyle) => { + setExportConfig((prev) => ({ + ...prev, + excludedStyles: prev.excludedStyles.includes(style) + ? prev.excludedStyles.filter((s) => s !== style) + : [...prev.excludedStyles, style], + })); + }; + + // Mirror HighlightOptions: user label, else translated default name, else the raw hex. + const resolveColorLabel = (color: HighlightColor): string => { + const userLabel = getHighlightColorLabel(settings, color); + if (userLabel) return userLabel; + if (!color.startsWith('#')) return _(color); + return color; + }; + // Generate markdown preview based on current format settings const markdownPreview = useMemo(() => { let output = ''; if (exportConfig.useCustomTemplate) { // Prepare data for template rendering - const sortedGroups = Object.values(booknoteGroups).sort((a, b) => a.id - b.id); + const sortedGroups = filteredGroups; const templateData = { title: bookTitle, @@ -157,7 +217,7 @@ const ExportMarkdownDialog: React.FC = ({ output = renderNoteTemplate(exportConfig.customTemplate, templateData); } else { // Default formatting (non-template mode) - const sortedGroups = Object.values(booknoteGroups).sort((a, b) => a.id - b.id); + const sortedGroups = filteredGroups; const lines: string[] = []; @@ -247,7 +307,7 @@ const ExportMarkdownDialog: React.FC = ({ } return output; - }, [exportConfig, booknoteGroups, bookTitle, bookAuthor, bookHash, _]); + }, [exportConfig, filteredGroups, bookTitle, bookAuthor, bookHash, _]); // Convert markdown to HTML for preview const htmlPreview = useMemo(() => { @@ -415,6 +475,63 @@ const ExportMarkdownDialog: React.FC = ({ + {/* Filter by color / style */} + {(applyColorFilter || applyStyleFilter) && ( +
+

{_('Filter Annotations')}

+ + {applyColorFilter && ( +
+ {_('Colors')} +
+ {distinctColors.map((color) => { + const included = !exportConfig.excludedColors.includes(color); + const hex = getHighlightColorHex(settings, color) ?? color; + const label = resolveColorLabel(color); + return ( + + ); + })} +
+
+ )} + + {applyStyleFilter && ( +
+ {_('Styles')} +
+ {distinctStyles.map((style) => { + const included = !exportConfig.excludedStyles.includes(style); + return ( + + ); + })} +
+
+ )} +
+ )} + {/* Advanced Options */}
@@ -714,7 +831,7 @@ const ExportMarkdownDialog: React.FC = ({ diff --git a/apps/readest-app/src/app/reader/utils/annotatorUtil.ts b/apps/readest-app/src/app/reader/utils/annotatorUtil.ts index 6c6b73de..8b95bc5b 100644 --- a/apps/readest-app/src/app/reader/utils/annotatorUtil.ts +++ b/apps/readest-app/src/app/reader/utils/annotatorUtil.ts @@ -1,5 +1,11 @@ import { HIGHLIGHT_COLOR_HEX } from '@/services/constants'; -import { BookNote, DEFAULT_HIGHLIGHT_COLORS, HighlightColor, HighlightStyle } from '@/types/book'; +import { + BookNote, + BooknoteGroup, + DEFAULT_HIGHLIGHT_COLORS, + HighlightColor, + HighlightStyle, +} from '@/types/book'; import { uniqueId } from '@/utils/misc'; import { SystemSettings } from '@/types/settings'; import { FoliateView, NOTE_PREFIX } from '@/types/view'; @@ -42,6 +48,67 @@ export const getHighlightColorLabel = ( return undefined; }; +const ALL_HIGHLIGHT_STYLES: readonly HighlightStyle[] = ['highlight', 'underline', 'squiggly']; + +export interface ExportFilter { + excludedColors: HighlightColor[]; + excludedStyles: HighlightStyle[]; +} + +export interface FilteredExportGroups { + groups: BooknoteGroup[]; + distinctColors: HighlightColor[]; + distinctStyles: HighlightStyle[]; + applyColorFilter: boolean; + applyStyleFilter: boolean; +} + +/** + * Filter chapter groups for annotation export by highlight color and style (#4801). + * + * Exclusions (not inclusions) are stored so an empty filter exports everything and + * colors/styles introduced later are included by default. A dimension is only + * filtered when at least two distinct values are present, so a filter row the user + * cannot see can never silently drop notes. Notes without a color/style (e.g. + * bookmarks) always pass. Groups left empty by the filter are dropped. + * + * `distinctColors` is ordered by the default palette first, then custom colors in + * first-seen order; `distinctStyles` follows the canonical highlight/underline/ + * squiggly order — both drive the filter UI. + */ +export function filterExportGroups( + groups: BooknoteGroup[], + { excludedColors, excludedStyles }: ExportFilter, +): FilteredExportGroups { + const colorsSeen = new Set(); + const stylesSeen = new Set(); + for (const group of groups) { + for (const note of group.booknotes) { + if (note.color) colorsSeen.add(note.color); + if (note.style) stylesSeen.add(note.style); + } + } + + const distinctColors = [ + ...DEFAULT_HIGHLIGHT_COLORS.filter((color) => colorsSeen.has(color)), + ...[...colorsSeen].filter((color) => !isDefaultHighlightColor(color)), + ]; + const distinctStyles = ALL_HIGHLIGHT_STYLES.filter((style) => stylesSeen.has(style)); + + const applyColorFilter = distinctColors.length >= 2; + const applyStyleFilter = distinctStyles.length >= 2; + + const keep = (note: BookNote) => + (!applyColorFilter || !note.color || !excludedColors.includes(note.color)) && + (!applyStyleFilter || !note.style || !excludedStyles.includes(note.style)); + + const filtered = groups + .map((group) => ({ ...group, booknotes: group.booknotes.filter(keep) })) + .filter((group) => group.booknotes.length > 0); + + return { groups: filtered, distinctColors, distinctStyles, applyColorFilter, applyStyleFilter }; +} + export function getExternalDragHandle( currentStart: Point, currentEnd: Point, diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index c850b310..45cb8072 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -401,6 +401,8 @@ export const DEFAULT_NOTE_EXPORT_CONFIG: NoteExportConfig = { useCustomTemplate: false, customTemplate: '', exportAsPlainText: false, + excludedColors: [], + excludedStyles: [], }; export const DEFAULT_ANNOTATOR_CONFIG: AnnotatorConfig = { diff --git a/apps/readest-app/src/types/book.ts b/apps/readest-app/src/types/book.ts index 99fd5d07..a41b3f72 100644 --- a/apps/readest-app/src/types/book.ts +++ b/apps/readest-app/src/types/book.ts @@ -340,6 +340,11 @@ export interface NoteExportConfig { useCustomTemplate: boolean; customTemplate: string; exportAsPlainText: boolean; + // Highlight colors/styles to omit from the export. Empty arrays export + // everything; storing exclusions keeps colors/styles added later included + // by default (#4801). + excludedColors: HighlightColor[]; + excludedStyles: HighlightStyle[]; } export interface AnnotatorConfig {