diff --git a/src/Util.js b/src/Util.js index f073b4306..188c61ef1 100644 --- a/src/Util.js +++ b/src/Util.js @@ -29,12 +29,22 @@ export function categoryLabel(category) { return category === '' ? t('notes', 'Uncategorized') : category.replace(/\//g, ' / ') } -export function rootCategory(category) { - if (!category) { - return '' +/** + * Whether a note's category falls within the selected one. + * + * A null selection matches every note. Otherwise a note matches the selected + * category itself and any category nested below it. + * + * @param {string} noteCategory the note's category + * @param {string|null} selectedCategory the selected category, or null for all notes + * @return {boolean} whether the note belongs to the selection + */ +export function isInCategory(noteCategory, selectedCategory) { + if (selectedCategory === null) { + return true } - const separator = category.indexOf('/') - return separator === -1 ? category : category.substring(0, separator) + return noteCategory === selectedCategory + || noteCategory.startsWith(selectedCategory + '/') } export function routeIsNewNote($route) { diff --git a/src/components/NotesView.vue b/src/components/NotesView.vue index aeae89526..16324a8a4 100644 --- a/src/components/NotesView.vue +++ b/src/components/NotesView.vue @@ -93,7 +93,7 @@ import logger from '../Logger.js' import { createNote } from '../NotesService.js' import store from '../store.js' import { fetchNoteTemplates, fetchTemplateContent } from '../TemplateService.js' -import { categoryLabel, rootCategory } from '../Util.js' +import { categoryLabel, isInCategory } from '../Util.js' export default { name: 'NotesView', @@ -252,7 +252,7 @@ export default { } const selectedCategory = store.notes.getSelectedCategory() - if (selectedCategory !== null && selectedCategory !== rootCategory(this.note.category)) { + if (!isInCategory(this.note.category, selectedCategory)) { this.showNote = false } }, diff --git a/src/tests/Util.spec.js b/src/tests/Util.spec.js index 8f2dbf502..1fc0fb034 100644 --- a/src/tests/Util.spec.js +++ b/src/tests/Util.spec.js @@ -11,9 +11,9 @@ import { getDefaultSampleNote, getDefaultSampleNoteTitle, getDraggedNoteId, + isInCategory, isNoteDrag, noteAttributes, - rootCategory, routeIsNewNote, } from '../Util.js' @@ -81,17 +81,31 @@ describe('categoryLabel', () => { }) }) -describe('rootCategory', () => { - it('returns the first category segment', () => { - expect(rootCategory('a/b/c')).toBe('a') +describe('isInCategory', () => { + it('matches a note in the selected category itself', () => { + expect(isInCategory('Work', 'Work')).toBe(true) }) - it('leaves a plain category alone', () => { - expect(rootCategory('Recipes')).toBe('Recipes') + it('matches a note in a descendant of the selected category', () => { + expect(isInCategory('Work/Projects/2026', 'Work/Projects')).toBe(true) + }) + + it('matches a note whose nested category is selected exactly', () => { + expect(isInCategory('Personal/Work', 'Personal/Work')).toBe(true) + }) + + it('does not match a sibling category sharing a name prefix', () => { + expect(isInCategory('Workshop', 'Work')).toBe(false) + }) + + it('matches every note when no category is selected', () => { + expect(isInCategory('Work/Projects', null)).toBe(true) + expect(isInCategory('', null)).toBe(true) }) - it('leaves the empty category empty', () => { - expect(rootCategory('')).toBe('') + it('matches only uncategorized notes for the uncategorized selection', () => { + expect(isInCategory('', '')).toBe(true) + expect(isInCategory('Work', '')).toBe(false) }) })