export const SOMETHING_ELSE_VALUE = "__something_else__"; export const SOMETHING_ELSE_LABEL = "Something else…"; export function normalizeQuestions(inputQuestions) { return inputQuestions.map((question, index) => ({ ...question, label: question.label?.trim() ? question.label : `Q${index + 1}`, options: [ ...question.options, { value: SOMETHING_ELSE_VALUE, label: SOMETHING_ELSE_LABEL, }, ], })); } export function isSomethingElseOption(option) { return option?.value === SOMETHING_ELSE_VALUE; } export function createPredefinedAnswer(questionId, option, index) { return { id: questionId, value: option.value, label: option.label, wasCustom: false, index, }; } export function createCustomAnswer(questionId, text) { return { id: questionId, value: text, label: text, wasCustom: true, }; } export function summarizeAnswers(questions, answers) { const answerById = new Map(answers.map((answer) => [answer.id, answer])); return questions.flatMap((question) => { const answer = answerById.get(question.id); if (!answer) return []; if (answer.wasCustom) { return [`${question.label}: user wrote: ${answer.label}`]; } return [`${question.label}: user selected: ${answer.index}. ${answer.label}`]; }); } export function createCancelledResult(questions = []) { return { questions, answers: [], cancelled: true, }; } export function createAnsweredResult(questions, answers) { const order = new Map(questions.map((question, index) => [question.id, index])); return { questions, answers: [...answers].sort( (left, right) => (order.get(left.id) ?? Number.POSITIVE_INFINITY) - (order.get(right.id) ?? Number.POSITIVE_INFINITY), ), cancelled: false, }; } export function allQuestionsAnswered(questions, answers) { return questions.every((question) => answers.has(question.id)); } export function nextTabAfterAnswer(currentTab, questionCount) { return currentTab < questionCount - 1 ? currentTab + 1 : questionCount; } function takeWrappedSegment(text, maxWidth) { if (text.length <= maxWidth) { return { line: text, rest: "" }; } let breakpoint = -1; for (let index = 0; index < maxWidth; index += 1) { if (/\s/.test(text[index])) { breakpoint = index; } } if (breakpoint > 0) { return { line: text.slice(0, breakpoint).trimEnd(), rest: text.slice(breakpoint + 1).trimStart(), }; } return { line: text.slice(0, maxWidth), rest: text.slice(maxWidth), }; } export function wrapPrefixedText(text, width, firstPrefix = "", continuationPrefix = firstPrefix) { const source = String(text ?? ""); if (source.length === 0) { return [firstPrefix]; } const lines = []; const blocks = source.split(/\r?\n/); let isFirstLine = true; for (const block of blocks) { let remaining = block.trim(); if (remaining.length === 0) { lines.push(isFirstLine ? firstPrefix : continuationPrefix); isFirstLine = false; continue; } while (remaining.length > 0) { const prefix = isFirstLine ? firstPrefix : continuationPrefix; const maxTextWidth = Math.max(1, width - prefix.length); const { line, rest } = takeWrappedSegment(remaining, maxTextWidth); lines.push(prefix + line); remaining = rest; isFirstLine = false; } } return lines; }