aac51bf183
Implement schema v2 migration, outline/setting/inspiration CRUD, unified TipTap editing, reference panel with mentions, FTS global search, chapter version history, writing landmarks, word frequency analysis, light theme contrast fixes, and full unit/E2E test coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
37 lines
1.1 KiB
TypeScript
37 lines
1.1 KiB
TypeScript
import type { OutlineItem } from '@shared/types'
|
|
|
|
export function buildOutlineTree(items: OutlineItem[]): OutlineItem[] {
|
|
const byParent = new Map<string | null, OutlineItem[]>()
|
|
for (const item of items) {
|
|
const key = item.parentId
|
|
if (!byParent.has(key)) byParent.set(key, [])
|
|
byParent.get(key)!.push({ ...item, children: [] })
|
|
}
|
|
const attach = (parentId: string | null): OutlineItem[] => {
|
|
const nodes = byParent.get(parentId) ?? []
|
|
nodes.sort((a, b) => a.sortOrder - b.sortOrder)
|
|
for (const node of nodes) {
|
|
node.children = attach(node.id)
|
|
}
|
|
return nodes
|
|
}
|
|
return attach(null)
|
|
}
|
|
|
|
export function flattenOutlineTree(items: OutlineItem[]): OutlineItem[] {
|
|
const result: OutlineItem[] = []
|
|
const walk = (nodes: OutlineItem[]): void => {
|
|
for (const node of nodes) {
|
|
result.push(node)
|
|
if (node.children?.length) walk(node.children)
|
|
}
|
|
}
|
|
walk(items)
|
|
return result
|
|
}
|
|
|
|
export function calcDeviation(expected: number | null, actual: number): number | null {
|
|
if (!expected || expected <= 0) return null
|
|
return Math.round(((actual - expected) / expected) * 100)
|
|
}
|