/*
* Copyright © 2024-2025 The CTAN Team and individual authors
*
* This file is distributed under the 3-clause BSD license.
* See file LICENSE for details.
*/
import { defineStore } from 'pinia'
const OPTS = [
{
icon: 'mdi-package-variant-closed',
id: 'pkg',
letter: 'P',
value: true
},
{
icon: 'mdi-tag-outline',
id: 'topic',
letter: 'T',
value: true
},
{
icon: 'mdi-account-badge-outline',
id: 'author',
letter: 'A',
value: true
},
{
icon: 'mdi-gavel',
id: 'license',
letter: 'L',
value: true
},
{
icon: 'mdi-folder-outline',
id: 'file',
letter: 'F',
value: false
},
{
icon: 'mdi-help-circle-outline',
id: 'help',
letter: 'S',
value: true
},
{
icon: 'mdi-mirror',
id: 'mirror',
letter: 'M',
value: false
}
]
/**
* This is the search store.
*
* @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
*/
export const useSearchStore = defineStore('search', {
state: () => ({
/**
* The search term.
*/
filter: '',
/**
* The search history contains previous searches.
* The elements have the form
* [id, terms, options]
*/
history: [],
/**
*
*/
loaded: false,
/**
* The list of options.
*/
opts: OPTS,
/**
* Flags for the sections to be searched.
*/
options: OPTS.reduce(function(map, obj) {
map[obj.id] = obj.value
return map
}, {}),
/**
* Icons for the sections to be searched.
*/
optIcons: OPTS.reduce(function(map, obj) {
map[obj.letter] = obj.icon
map[obj.id] = obj.icon
return map
}, {}),
/**
* The last search term.
*/
recentFilter: '',
/**
* Indicator for an update request.
*/
update: 0,
/**
* The indicator whether the search bar is opened.
*/
visible: false
}),
getters: {
/**
* Translate the options flags into a compressed single-string form.
*/
compressed: (state) =>
(state.options.pkg ? 'P' : '') +
(state.options.topic ? 'T' : '') +
(state.options.author ? 'A' : '') +
(state.options.file ? 'F' : '') +
(state.options.license ? 'L' : '') +
(state.options.mirror ? 'M' : '') +
(state.options.help ? 'S' : ''),
/**
* Translate the options flags into a list.
*/
list: (state) =>
['pkg', 'author', 'topic', 'license', 'file', 'mirror', 'help']
.filter(it => state.options[it])
},
actions: {
/**
* Insert the current search to the beginnng of the history.
* If the history contains this search already then the double is deleted.
* The history is clipped to 32 elements.
*/
addToHistory () {
this.recentFilter = this.filter
let filter = this.filter
if (filter === null) {
filter = ''
}
const id = this.searchId(this.filter, this.compressed)
const i = this.history.findIndex(it => it[0] === id)
if (i >= 0) {
this.history.splice(i, 1)
} else if (this.history.length > 32) {
this.history.splice(31)
}
this.history.unshift([id, this.filter, this.compressed])
},
/**
* Compute a unique id for the history entry.
*/
searchId (filter, sections) {
if (filter === null) {
filter = ''
}
return `${sections}:${filter.trim().toLowerCase()}`
},
/**
* Add the term to the history.
*/
add(term) {
this.history.push(term)
},
/**
* Translate the compressed form of search sections into the options flags.
* @param {*} s the compressed form
*/
setOptions(s) {
if (s) {
for ( const x of this.opts) {
this.options[x.id] = s.includes(x.letter)
}
} else {
for ( const x of this.opts) {
this.options[x.id] = x.value
}
}
}
}
})