diff --git a/frontend/utils/documentTitleSections.js b/frontend/utils/documentTitleSections.js new file mode 100644 index 00000000..bcc41bcd --- /dev/null +++ b/frontend/utils/documentTitleSections.js @@ -0,0 +1,41 @@ +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 +}