fix(card): a search for blue no longer finds blunt

The card browser looked for a query also with every ae, oe and ue made
a, o and u, so that maedchen found Mädchen: in any language, so blue
found blunt, Spanish que found aquí, and ue nearly everything. A card's
umlauts are written out on its side instead, beside its folded text,
and a query is matched as typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-14 17:36:55 +02:00
commit 31eed85aff
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 29 additions and 8 deletions

View file

@ -109,7 +109,7 @@ typedef BrowsedCard = ({
/// `たべる` finds た.べる). /// `たべる` finds た.べる).
String searchTextOf(Card card, List<Tag> tags) { String searchTextOf(Card card, List<Tag> tags) {
final content = card.content; final content = card.content;
return searchFolded( return searchableText(
[ [
content.front.plainText, content.front.plainText,
content.back.plainText, content.back.plainText,
@ -126,6 +126,26 @@ String searchTextOf(Card card, List<Tag> tags) {
); );
} }
/// [text] as a card's search text holds it: folded ([searchFolded]), and
/// where it has German's umlauts, folded once more with them written out,
/// so `maedchen` finds Mädchen as `madchen` does. Written out on the card's
/// side, not the query's: a query's own `ue` stays as typed, and `blue`
/// doesn't find blunt, nor Spanish `que` aquí.
String searchableText(String text) {
final folded = searchFolded(text);
final lower = text.toLowerCase();
if (!lower.contains(_umlaut)) {
return folded;
}
final written = lower
.replaceAll('ä', 'ae')
.replaceAll('ö', 'oe')
.replaceAll('ü', 'ue');
return '$folded\n${searchFolded(written)}';
}
final _umlaut = RegExp('[äöü]');
/// [text] as searches compare it and A–Z orders it: in lower case, /// [text] as searches compare it and A–Z orders it: in lower case,
/// katakana as hiragana, without accents and tone marks (Latin and Greek /// katakana as hiragana, without accents and tone marks (Latin and Greek
/// letters, pinyin's vowels), ß as ss and Greek's final sigma as σ; so /// letters, pinyin's vowels), ß as ss and Greek's final sigma as σ; so
@ -177,18 +197,14 @@ final _bare = {
}; };
/// Whether a card whose [searchText] is [searchTextOf] holds [query]: as /// Whether a card whose [searchText] is [searchTextOf] holds [query]: as
/// typed, with German's umlauts written out as typed without them /// typed (German's umlauts written out or not, see [searchableText]), or
/// (`maedchen` finds Mädchen), or romaji typed for its kana (`taberu` /// romaji typed for its kana (`taberu` finds たべる).
/// finds たべる).
bool matchesQuery(String searchText, String query) { bool matchesQuery(String searchText, String query) {
final typed = searchFolded(query.trim()); final typed = searchFolded(query.trim());
if (typed.isEmpty) { if (typed.isEmpty) {
return true; return true;
} }
return searchText.contains(typed) || return searchText.contains(typed) ||
searchText.contains(
typed.replaceAll('ae', 'a').replaceAll('oe', 'o').replaceAll('ue', 'u'),
) ||
searchText.contains(romajiToHiragana(typed, finished: true)); searchText.contains(romajiToHiragana(typed, finished: true));
} }

View file

@ -5,7 +5,7 @@ void main() {
test('a search finds words of any language as typed on any keyboard: ' test('a search finds words of any language as typed on any keyboard: '
'without accents, tone marks or umlauts, ß as ss, kana by romaji', () { 'without accents, tone marks or umlauts, ß as ss, kana by romaji', () {
bool finds(String text, String query) => bool finds(String text, String query) =>
matchesQuery(searchFolded(text), query); matchesQuery(searchableText(text), query);
expect(finds('Straße', 'strasse'), isTrue); expect(finds('Straße', 'strasse'), isTrue);
expect(finds('Mädchen', 'madchen'), isTrue); expect(finds('Mädchen', 'madchen'), isTrue);
@ -18,6 +18,11 @@ void main() {
expect(finds('たべる', 'taberu'), isTrue); expect(finds('たべる', 'taberu'), isTrue);
expect(finds('テレビ', 'てれび'), isTrue); expect(finds('テレビ', 'てれび'), isTrue);
expect(finds('Haus', 'maus'), isFalse); expect(finds('Haus', 'maus'), isFalse);
// Only a card's umlauts are written out, never a query's ue, oe or ae.
expect(finds('Müller', 'mueller'), isTrue);
expect(finds('blunt', 'blue'), isFalse);
expect(finds('aquí', 'que'), isFalse);
expect(finds('god', 'goed'), isFalse);
}); });
test('A–Z compares letters as their bare ones: Ärger among the a, xué ' test('A–Z compares letters as their bare ones: Ärger among the a, xué '