Skip to content
Merged

Tags #73

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/renderer/components/sidebar/FilterInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,14 @@ export const FilterInput = props => {
}

const handleTagClick = tag => {
const tagFilter = `#${tag}`
const newFilter = search.filter
? `${search.filter} ${tagFilter}`
: tagFilter
const tagFilter = `#${tag.toUpperCase()}`
const tokens = search.filter.split(' ').filter(Boolean)
const tagIndex = tokens.findIndex(t => t.toUpperCase() === tagFilter)

const newFilter = tagIndex >= 0
? tokens.filter((_, i) => i !== tagIndex).join(' ')
: search.filter ? `${search.filter} ${tagFilter}` : tagFilter

setCursor(null)
setSearch({ ...search, filter: newFilter, force: true })
}
Expand Down
6 changes: 3 additions & 3 deletions src/renderer/store/MiniSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export const parseQuery = (terms, ids = []) => {
// Remove hyphens to match the extractField transformation for scope
const scopeValue = token.substring(1).replace(/-/g, '')
scopeValue.length > 1 && acc.scope.push(scopeValue)
} else if (token.startsWith('-#')) token.length > 3 && acc.excludeTags.push(token.substring(2).toLowerCase())
else if (token.startsWith('#')) token.length > 2 && acc.tags.push(token.substring(1))
} else if (token.startsWith('-#')) token.length > 2 && acc.excludeTags.push(token.substring(2))
else if (token.startsWith('#')) token.length > 1 && acc.tags.push(token.substring(1))
else if (token.startsWith('!')) token.length > 2 && acc.ids.push(token.substring(1))
else if (token.startsWith('&')) { /* ignore */ } else if (token) acc.text.push(token)
return acc
Expand All @@ -57,7 +57,7 @@ export const parseQuery = (terms, ids = []) => {

add('scope', 'OR')
add('text', 'AND', true)
add('tags', 'AND', true)
add('tags', 'AND')

const filter = parts.ids.length
? result => parts.ids.some(id => result.id.startsWith(id))
Expand Down
4 changes: 2 additions & 2 deletions src/renderer/store/SearchIndex.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ SearchIndex.prototype.search = async function (terms, options) {
? matches.filter(match => {
const doc = this.cachedDocuments[match.id]
if (!doc || !doc.tags) return true
const docTags = doc.tags.filter(Boolean).map(t => t.toLowerCase())
return !excludeTags.some(tag => docTags.includes(tag))
const docTags = doc.tags.filter(Boolean).map(t => t.toUpperCase())
return !excludeTags.some(tag => docTags.includes(tag.toUpperCase()))
})
: matches

Expand Down
28 changes: 28 additions & 0 deletions test/renderer/store/MiniSearch-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,33 @@ describe('MiniSearch', function () {
const actual = index.search(query)
assert.strictEqual(actual.length, _3OSC.docs.length - 1) // minus one installation
})

it('tag search uses exact matching, not prefix matching', function () {
const index = createIndex()
index.addAll([
{ id: 'feature:1', text: 'First feature', tags: ['A'] },
{ id: 'feature:2', text: 'Second feature', tags: ['AA'] },
{ id: 'feature:3', text: 'Third feature', tags: ['AAA'] }
])

const searchTag = (tag) => {
const [query] = parseQuery(tag)
return index.search(query).map(({ id }) => id).sort()
}

// Each tag search should only return exact matches
assert.deepStrictEqual(searchTag('#A'), ['feature:1'])
assert.deepStrictEqual(searchTag('#AA'), ['feature:2'])
assert.deepStrictEqual(searchTag('#AAA'), ['feature:3'])
})

it('single-letter exclude tags are parsed correctly', function () {
// Verify parseQuery handles single-letter exclude tags
const [, options] = parseQuery('-#A')
assert.deepStrictEqual(options.excludeTags, ['A'])

const [, options2] = parseQuery('-#B -#C')
assert.deepStrictEqual(options2.excludeTags, ['B', 'C'])
})
})
})