Fix cyrillic

This commit is contained in:
2025-05-16 15:32:58 +02:00
parent fcb8e41cde
commit a84973def6

View File

@@ -441,10 +441,8 @@ END;
return []; return [];
} }
// Process the query for partial word matching
// Split into individual terms, filter empty ones // Split into individual terms, filter empty ones
List<String> terms = List<String> terms = query
query
.trim() .trim()
.split(RegExp(r'\s+')) .split(RegExp(r'\s+'))
.where((term) => term.isNotEmpty) .where((term) => term.isNotEmpty)
@@ -454,50 +452,122 @@ END;
return []; return [];
} }
// Add wildcards to each term for prefix matching // Create the FTS match expression for each term
// Join terms with AND for all-term matching (results must contain ALL terms) List<String> matchExpressions = terms.map((term) {
String ftsQuery = terms // Remove dangerous characters but preserve Unicode
.map((term) {
// Only remove dangerous characters but preserve Unicode
String sanitizedTerm = term.replaceAll(RegExp(r'''['\\]'''), ''); String sanitizedTerm = term.replaceAll(RegExp(r'''['\\]'''), '');
if (sanitizedTerm.isEmpty) return ''; if (sanitizedTerm.isEmpty) return '';
if (_hasIcuSupport) { // Escape special characters in the term for the LIKE pattern
// With ICU support, we can use wildcards with Unicode String escapedTerm = sanitizedTerm
return '*$sanitizedTerm*'; .replaceAll('%', '\\%')
} else { .replaceAll('_', '\\_');
// Without ICU, just use the term as is for basic matching
return sanitizedTerm;
}
})
.where((term) => term.isNotEmpty)
.join(' AND ');
if (ftsQuery.isEmpty) { // Use LIKE for prefix/suffix matching
return '''(
content LIKE '%' || ? || '%' COLLATE NOCASE OR
content LIKE ? || '%' COLLATE NOCASE OR
content LIKE '%' || ? COLLATE NOCASE
)''';
}).where((expr) => expr.isNotEmpty).toList();
if (matchExpressions.isEmpty) {
debugPrint('Query was sanitized to empty string'); debugPrint('Query was sanitized to empty string');
return []; return [];
} }
debugPrint('FTS query: "$ftsQuery" (ICU: $_hasIcuSupport)'); // Combine all terms with AND logic
String whereClause = matchExpressions.join(' AND ');
// Execute the FTS query with AND logic // Create the parameter list (each term needs to be repeated 3 times for the different LIKE patterns)
List<String> parameters = [];
for (String term in terms) {
String sanitizedTerm = term.replaceAll(RegExp(r'''['\\]'''), '');
if (sanitizedTerm.isNotEmpty) {
parameters.addAll([sanitizedTerm, sanitizedTerm, sanitizedTerm]);
}
}
debugPrint('Search query: "$query" with ${terms.length} terms');
debugPrint('Where clause: $whereClause');
debugPrint('Parameters: $parameters');
// Execute the search query
final List<Map<String, dynamic>> results = await db.rawQuery( final List<Map<String, dynamic>> results = await db.rawQuery(
''' '''
SELECT n.id, n.date, n.content, snippet(notes_fts, 0, '<b>', '</b>', '...', 20) as snippet SELECT n.id, n.date, n.content
FROM notes_fts FROM notes n
JOIN notes n ON notes_fts.rowid = n.id WHERE $whereClause
WHERE notes_fts MATCH ?
ORDER BY n.date DESC ORDER BY n.date DESC
LIMIT 100 LIMIT 100
''', ''',
[ftsQuery], parameters,
); );
debugPrint('Search query "$ftsQuery" returned ${results.length} results'); // Add snippets with highlighting in Dart code
return results; final processedResults = results.map((row) {
} catch (e) { final content = row['content'] as String;
final snippet = _createSnippet(content, terms);
return {
...row,
'snippet': snippet,
};
}).toList();
debugPrint('Search returned ${results.length} results');
return processedResults;
} catch (e, stackTrace) {
debugPrint('Search failed: $e'); debugPrint('Search failed: $e');
debugPrint('Stack trace: $stackTrace');
rethrow; rethrow;
} }
} }
// Helper function to create a snippet with highlighted terms
static String _createSnippet(String content, List<String> terms) {
const int snippetLength = 150; // Maximum length of the snippet
const String ellipsis = '...';
// Find the first match position
int firstMatchPos = content.length;
String? firstMatchTerm;
for (final term in terms) {
final pos = content.toLowerCase().indexOf(term.toLowerCase());
if (pos != -1 && pos < firstMatchPos) {
firstMatchPos = pos;
firstMatchTerm = term;
}
}
if (firstMatchTerm == null) {
// No matches found, return start of content
return content.length <= snippetLength
? content
: content.substring(0, snippetLength) + ellipsis;
}
// Calculate snippet range around the first match
int start = (firstMatchPos - snippetLength ~/ 3).clamp(0, content.length);
int end = (start + snippetLength).clamp(0, content.length);
// Adjust start to not cut words
if (start > 0) {
start = content.lastIndexOf(RegExp(r'\s'), start) + 1;
}
// Create the snippet
String snippet = content.substring(start, end);
if (start > 0) snippet = ellipsis + snippet;
if (end < content.length) snippet = snippet + ellipsis;
// Highlight all term matches in the snippet
for (final term in terms) {
final pattern = RegExp(RegExp.escape(term), caseSensitive: false);
snippet = snippet.replaceAllMapped(pattern,
(match) => '<b>${match.group(0)}</b>');
}
return snippet;
}
} }