fix subdirectory search and tag highlighting edge cases

- filter notes by relative path instead of bare filename so searching
  "/subdir" or "subdir" matches notes inside that subdirectory
- strip leading "/" from search text before matching so both forms work
- tighten tag regex in _extractTags and _formatTags: require a letter
  after the symbol and a non-word boundary before it ((?<!\S)) to avoid
  matching phone numbers (+44…) and email-local-part suffixes
- fix subdir/notename creation: use contains('/') instead of
  startsWith('/') so names without a leading slash are split correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
randogoth 2026-06-19 17:32:48 +03:00
parent bf214afe72
commit e2c3ae5472

View file

@ -294,7 +294,7 @@ Happy note taking! ✨
/// Extracts unique tags sorted by +, @, # /// Extracts unique tags sorted by +, @, #
Set<String> _extractTags(String content) { Set<String> _extractTags(String content) {
final tagPattern = RegExp(r'([#@+][a-zA-Z][a-zA-Z0-9_]*)'); final tagPattern = RegExp(r'(?<!\S)[#@+][a-zA-Z][a-zA-Z0-9_]*');
final matches = tagPattern.allMatches(content).map((m) => m.group(0)!).toSet(); final matches = tagPattern.allMatches(content).map((m) => m.group(0)!).toSet();
// Sort the tags in order: +tags first, @contexts second, #hashtags last // Sort the tags in order: +tags first, @contexts second, #hashtags last
@ -325,8 +325,11 @@ Happy note taking! ✨
final searchTags = terms.where((t) => t.startsWith(RegExp(r'[#@+]'))).toSet(); final searchTags = terms.where((t) => t.startsWith(RegExp(r'[#@+]'))).toSet();
final searchText = terms.where((t) => !t.startsWith(RegExp(r'[#@+]'))).join(' '); final searchText = terms.where((t) => !t.startsWith(RegExp(r'[#@+]'))).join(' ');
// Strip leading '/' so "/subdir" and "subdir" match the same notes
final normalizedSearch = searchText.replaceFirst(RegExp(r'^/'), '');
filteredNotes = notes.where((note) { filteredNotes = notes.where((note) {
final filename = note.uri.pathSegments.last.toLowerCase(); final relativePath = path.relative(note.path, from: notesDirectoryPath!).toLowerCase();
final noteTags = _extractTags(note.readAsStringSync()); final noteTags = _extractTags(note.readAsStringSync());
// Read file content ONLY if `Include File Content` is enabled // Read file content ONLY if `Include File Content` is enabled
@ -337,9 +340,9 @@ Happy note taking! ✨
noteTags.any((noteTag) => noteTag.startsWith(tag))); noteTags.any((noteTag) => noteTag.startsWith(tag)));
if (!_includeFileContent) { if (!_includeFileContent) {
return filename.contains(searchText) && matchesTags; return relativePath.contains(normalizedSearch) && matchesTags;
} else { } else {
return (filename.contains(searchText) || content.contains(searchText)) && matchesTags; return (relativePath.contains(normalizedSearch) || content.contains(normalizedSearch)) && matchesTags;
} }
}).toList(); }).toList();
}); });
@ -360,7 +363,7 @@ Happy note taking! ✨
} }
List<InlineSpan> _formatTags(String content) { List<InlineSpan> _formatTags(String content) {
final tagPattern = RegExp(r'([#@+][a-zA-Z0-9_]+)'); final tagPattern = RegExp(r'(?<!\S)[#@+][a-zA-Z][a-zA-Z0-9_]*');
final Set<String> tagSet = {}; // Avoid duplicate tags final Set<String> tagSet = {}; // Avoid duplicate tags
for (final match in tagPattern.allMatches(content)) { for (final match in tagPattern.allMatches(content)) {
@ -405,7 +408,7 @@ Happy note taking! ✨
} }
} }
if (notePath.startsWith('/')) { if (notePath.contains('/')) {
final parts = notePath.split('/'); final parts = notePath.split('/');
parts.removeWhere((element) => element.isEmpty); parts.removeWhere((element) => element.isEmpty);
if (parts.length > 1) { if (parts.length > 1) {