const titleLevel = row => Math.min(Math.max(Number(row.titleLevel) || 1, 1), 3) // A section includes every row up to the next title at the same or a higher level. const sectionEnd = (rows, start) => { const level = titleLevel(rows[start]) let end = start + 1 while (end < rows.length && (rows[end].mode !== 'title' || titleLevel(rows[end]) > level)) end += 1 return end } export const getTitleSectionMove = (rows, rowId, direction) => { if (!['up', 'down'].includes(direction)) return null const start = rows.findIndex(row => row.id === rowId) if (start < 0 || rows[start].mode !== 'title') return null const level = titleLevel(rows[start]) const end = sectionEnd(rows, start) if (direction === 'down') { // A title at a higher level ends the parent section: never cross it. if (end === rows.length || titleLevel(rows[end]) !== level) return null return { start, end, target: sectionEnd(rows, end) } } for (let previous = start - 1; previous >= 0; previous -= 1) { if (rows[previous].mode !== 'title' || titleLevel(rows[previous]) > level) continue if (titleLevel(rows[previous]) < level) return null return { start, end, target: previous } } return null } export const moveTitleSection = (rows, rowId, direction) => { const move = getTitleSectionMove(rows, rowId, direction) if (!move) return rows const { start, end, target } = move const result = rows.slice() const section = result.splice(start, end - start) result.splice(target > start ? target - section.length : target, 0, ...section) return result }