128 lines
4.0 KiB
TypeScript
128 lines
4.0 KiB
TypeScript
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<WikiPageItem[]>('wiki-items', () => [])
|
|
const isLoading = useState<boolean>('wiki-loading', () => false)
|
|
const isSidebarOpen = useState<boolean>('wiki-sidebar-open', () => true)
|
|
|
|
// NEU: Suchbegriff State
|
|
const searchQuery = useState<string>('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<string, WikiPageItem> = {}
|
|
|
|
// 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<WikiPageItem[]>('/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
|
|
}
|
|
} |