(markdown: string)
| 300 | * Returns the moved raw lines (in document order) and the remaining body. |
| 301 | */ |
| 302 | export function extractUncheckedTaskBlocks(markdown: string): { |
| 303 | moved: string[] |
| 304 | rest: string |
| 305 | } { |
| 306 | const lines = markdown.split('\n') |
| 307 | const consumed = new Array<boolean>(lines.length).fill(false) |
| 308 | const moved: string[] = [] |
| 309 | let inFence = false |
| 310 | let fenceMarker: string | null = null |
| 311 | |
| 312 | for (let i = 0; i < lines.length; i++) { |
| 313 | const line = lines[i] |
| 314 | const fenceMatch = line.match(FENCE_RE) |
| 315 | if (fenceMatch) { |
| 316 | const marker = fenceMatch[2] |
| 317 | if (!inFence) { |
| 318 | inFence = true |
| 319 | fenceMarker = marker |
| 320 | } else if (marker === fenceMarker) { |
| 321 | inFence = false |
| 322 | fenceMarker = null |
| 323 | } |
| 324 | continue |
| 325 | } |
| 326 | if (inFence) continue |
| 327 | |
| 328 | const taskMatch = line.match(TASK_LINE_RE) |
| 329 | if (!taskMatch) continue |
| 330 | if (taskMatch[2] !== ' ') continue // only unchecked tasks roll over |
| 331 | |
| 332 | const baseIndent = leadingIndentWidth(line) |
| 333 | moved.push(line) |
| 334 | consumed[i] = true |
| 335 | |
| 336 | // Carry indented continuation/child lines along with the task. |
| 337 | let j = i + 1 |
| 338 | while (j < lines.length) { |
| 339 | const next = lines[j] |
| 340 | if (next.trim() === '') break |
| 341 | if (FENCE_RE.test(next)) break |
| 342 | if (leadingIndentWidth(next) <= baseIndent) break |
| 343 | moved.push(next) |
| 344 | consumed[j] = true |
| 345 | j++ |
| 346 | } |
| 347 | i = j - 1 // skip the consumed block (its children are not new tasks) |
| 348 | } |
| 349 | |
| 350 | const rest = lines.filter((_, idx) => !consumed[idx]).join('\n') |
| 351 | return { moved, rest } |
| 352 | } |
no test coverage detected