export interface WikiPageItem { id: string parentId: string | null title: string isFolder: boolean sortOrder: number entityType?: string | null children?: WikiPageItem[] } export const useWikiTree = () => { const { $api } = useNuxtApp() // STATE const items = useState('wiki-items', () => []) const isLoading = useState('wiki-loading', () => false) const isSidebarOpen = useState('wiki-sidebar-open', () => true) // NEU: Suchbegriff State const searchQuery = useState('wiki-search-query', () => '') // 1. Basis-Baum bauen (Hierarchie & Sortierung) const baseTree = computed(() => { const rawItems = items.value || [] if (!rawItems.length) return [] const roots: WikiPageItem[] = [] const lookup: Record = {} // Init Lookup (Shallow Copy um Originaldaten nicht zu mutieren) rawItems.forEach(item => { lookup[item.id] = { ...item, children: [] } }) // Build Hierarchy rawItems.forEach(item => { const node = lookup[item.id] if (item.parentId && lookup[item.parentId]) { lookup[item.parentId].children?.push(node) } else { roots.push(node) } }) // Sort Helper const sortNodes = (nodes: WikiPageItem[]) => { nodes.sort((a, b) => { if (a.isFolder !== b.isFolder) return a.isFolder ? -1 : 1 return a.title.localeCompare(b.title) }) nodes.forEach(n => { if (n.children?.length) sortNodes(n.children) }) } sortNodes(roots) return roots }) // 2. NEU: Gefilterter Baum (basiert auf baseTree + searchQuery) const filteredTree = computed(() => { const query = searchQuery.value.toLowerCase().trim() // Wenn keine Suche: Gib originalen Baum zurück if (!query) return baseTree.value // Rekursive Filterfunktion const filterNodes = (nodes: WikiPageItem[]): WikiPageItem[] => { return nodes.reduce((acc: WikiPageItem[], node) => { // Matcht der Knoten selbst? const matchesSelf = node.title.toLowerCase().includes(query) // Matchen Kinder? (Rekursion) const filteredChildren = node.children ? filterNodes(node.children) : [] // Wenn selbst matcht ODER Kinder matchen -> behalten if (matchesSelf || filteredChildren.length > 0) { // Wir erstellen eine Kopie des Knotens mit den gefilterten Kindern acc.push({ ...node, children: filteredChildren }) } return acc }, []) } return filterNodes(baseTree.value) }) // ACTIONS const loadTree = async () => { isLoading.value = true try { const data = await $api('/api/wiki/tree', { method: 'GET' }) items.value = data } catch (e) { console.error(e) } finally { isLoading.value = false } } const createItem = async (title: string, parentId: string | null, isFolder: boolean) => { try { const newItem = await $api('/api/wiki', { method: 'POST', body: { title, parentId, isFolder } }) await loadTree() return newItem } catch (e) { throw e } } const deleteItem = async (id: string) => { try { await $api(`/api/wiki/${id}`, { method: 'DELETE' }) await loadTree() return true } catch (e) { throw e } } return { tree: filteredTree, // Wir geben jetzt immer den (evtl. gefilterten) Baum zurück searchQuery, // Damit die UI das Input-Feld binden kann items, isLoading, isSidebarOpen, loadTree, createItem, deleteItem } }