Fix cyrillic
This commit is contained in:
128
lib/db.dart
128
lib/db.dart
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user