feat(fc2a): add tag store, TagAutocomplete, and FandomPicker
TagAutocomplete: kind selector + debounced (200ms) search + keyboard nav (up/down/enter/esc). When the operator types a character that doesn't exist, the create flow first opens FandomPicker so the new character tag lands with a valid fandom_id (otherwise the API rejects it). FandomPicker lists existing fandoms (cached after first load) with an inline create-new affordance, so the operator never has to leave the modal to manage fandom hierarchies. Tag store also exposes a kind→icon→color mapping that the TagPanel reuses in Task 22 — keeping chip colors visually distinct per kind without per-app accent collisions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
|
||||
const KIND_OPTIONS = [
|
||||
{ value: 'general', label: 'General', icon: 'mdi-tag' },
|
||||
{ value: 'artist', label: 'Artist', icon: 'mdi-palette' },
|
||||
{ value: 'character', label: 'Character', icon: 'mdi-account-circle' },
|
||||
{ value: 'fandom', label: 'Fandom', icon: 'mdi-book-open-page-variant' },
|
||||
{ value: 'series', label: 'Series', icon: 'mdi-bookshelf' },
|
||||
{ value: 'meta', label: 'Meta', icon: 'mdi-cog-outline' },
|
||||
{ value: 'rating', label: 'Rating', icon: 'mdi-shield-check-outline' }
|
||||
]
|
||||
|
||||
const KIND_COLOR = {
|
||||
artist: 'accent',
|
||||
character: 'info',
|
||||
fandom: 'secondary',
|
||||
series: 'warning',
|
||||
general: 'on-surface',
|
||||
meta: 'on-surface',
|
||||
rating: 'on-surface',
|
||||
archive: 'on-surface',
|
||||
post: 'on-surface'
|
||||
}
|
||||
|
||||
export const useTagStore = defineStore('tags', () => {
|
||||
const api = useApi()
|
||||
const fandomCache = ref([])
|
||||
|
||||
async function autocomplete(q, kind = null, limit = 20) {
|
||||
if (!q || !q.trim()) return []
|
||||
const params = { q, limit }
|
||||
if (kind) params.kind = kind
|
||||
return await api.get('/api/tags/autocomplete', { params })
|
||||
}
|
||||
|
||||
async function loadFandoms() {
|
||||
fandomCache.value = await api.get('/api/tags/autocomplete', {
|
||||
params: { q: ' ', kind: 'fandom', limit: 200 }
|
||||
})
|
||||
return fandomCache.value
|
||||
}
|
||||
|
||||
async function createFandom(name) {
|
||||
const fandom = await api.post('/api/tags', { body: { name, kind: 'fandom' } })
|
||||
fandomCache.value = [...fandomCache.value, {
|
||||
id: fandom.id, name: fandom.name, kind: 'fandom',
|
||||
fandom_id: null, fandom_name: null, image_count: 0
|
||||
}]
|
||||
return fandom
|
||||
}
|
||||
|
||||
function kindOptions() { return KIND_OPTIONS }
|
||||
function colorFor(kind) { return KIND_COLOR[kind] || 'on-surface' }
|
||||
|
||||
return { fandomCache, autocomplete, loadFandoms, createFandom, kindOptions, colorFor }
|
||||
})
|
||||
Reference in New Issue
Block a user