1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
/**
* Quiz Parser - Converte sintaxe customizada em JSON estruturado
* Sintaxe suportada:
* ? Enunciado da questão
* - Opção A / - Opção B
* = Opção correta (incluída nas opções automaticamente se ausente)
* > Comentário (pode repetir; linha vazia separa parágrafos)
*
* ? Questão aberta
* [ Resposta de referência (pode repetir)
* > Comentário
*
* --- ou linha em branco separam questões
* # linha ignorada
*/
function parseQuizBody(raw) {
if (!raw || !raw.trim()) return [];
const lines = raw.replace(/\r\n/g, '\n').split('\n');
const result = [];
let enunciado = '', tipo = '', opcoes = [], gabarito = '', referencia = '', comentario = '';
function flush() {
if (!enunciado.trim()) return;
const q = { type: tipo || 'mc', text: enunciado.trim() };
if (tipo === 'mc') {
q.options = opcoes;
q.answer = gabarito;
} else {
q.reference = referencia.trim();
}
if (comentario.trim()) q.comment = comentario.trim();
result.push(q);
enunciado = '';
tipo = '';
opcoes = [];
gabarito = '';
referencia = '';
comentario = '';
}
for (const rawLine of lines) {
const line = rawLine.replace(/\r$/, '').trimEnd();
if (/^---+\s*$/.test(line)) {
flush();
continue;
}
if (line.trim() === '') continue;
const mark = line[0];
const content = line.length > 1 ? line.slice(2) : '';
switch (mark) {
case '#':
break;
case '?':
flush();
enunciado = content;
break;
case '-':
opcoes.push(content);
tipo = 'mc';
break;
case '=':
gabarito = content;
tipo = 'mc';
if (content && !opcoes.includes(content)) opcoes.push(content);
break;
case '[':
referencia += (referencia ? '\n' : '') + content;
tipo = 'open';
break;
case '>':
comentario += (comentario ? '\n' : '') + content;
break;
default:
if (enunciado && !tipo) enunciado += '\n' + line;
else if (tipo === 'open' && referencia) referencia += '\n' + line;
}
}
flush();
return result;
}
/* ── UTILITÁRIOS BASE ── */
const LETRAS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
function prepareQuizQuestions(questions) {
const letterCounts = [];
let previousCorrectIndex = -1;
return questions.map((q) => {
if (!q || q.type !== 'mc' || !Array.isArray(q.options) || q.options.length < 2) {
return q;
}
const answerIndex = q.options.findIndex((o) => o === q.answer);
if (answerIndex < 0) {
return { ...q, options: shuffle(q.options) };
}
const optionCount = q.options.length;
const availableIndexes = Array.from({ length: optionCount }, (_, i) => i);
const lowestCount = Math.min(...availableIndexes.map((i) => letterCounts[i] || 0));
let candidates = availableIndexes.filter((i) => (letterCounts[i] || 0) === lowestCount);
if (optionCount > 1 && candidates.length > 1) {
candidates = candidates.filter((i) => i !== previousCorrectIndex);
}
const correctIndex = shuffle(candidates)[0] ?? 0;
const otherOptions = shuffle(q.options.filter((_, i) => i !== answerIndex));
const arrangedOptions = [...otherOptions];
arrangedOptions.splice(correctIndex, 0, q.options[answerIndex]);
letterCounts[correctIndex] = (letterCounts[correctIndex] || 0) + 1;
previousCorrectIndex = correctIndex;
return { ...q, options: arrangedOptions };
});
}
/* Para uso em atributos HTML (onclick="...") */
function esc(s) {
return String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/* Para innerHTML: escapa HTML e renderiza Markdown inline.
Suporta: **negrito**, *itálico*, `código`, \n→<br> */
function md(s) {
return String(s ?? '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
}
|