synthesisui 0.16.431 → 0.16.432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/report-html.js +158 -39
- package/package.json +1 -1
package/dist/report-html.js
CHANGED
|
@@ -47,12 +47,17 @@ function escapar(s) {
|
|
|
47
47
|
export function relatorioHtml(input) {
|
|
48
48
|
const { cabecalho, destaques, secoes } = input;
|
|
49
49
|
const quando = legivel(cabecalho.medidoEm);
|
|
50
|
+
/**
|
|
51
|
+
* O ÍNDICE DIZ O TAMANHO DE CADA CAPÍTULO, e é isso que o torna uma escolha em vez de uma lista.
|
|
52
|
+
* Dezesseis links iguais obrigam a entrar para descobrir o custo; com a contagem ao lado, quem
|
|
53
|
+
* está com pressa sabe onde não entrar.
|
|
54
|
+
*/
|
|
50
55
|
const indice = secoes
|
|
51
|
-
.map((s, i) => `<li><a href="#s${i}">${escapar(s.titulo)}</a></li>`)
|
|
56
|
+
.map((s, i) => `<li><a href="#s${i}">${escapar(s.titulo)}</a><span>${s.linhas.filter((l) => l.trim()).length}</span></li>`)
|
|
52
57
|
.join("\n ");
|
|
53
58
|
const corpo = secoes
|
|
54
59
|
.map((s, i) => ` <section id="s${i}" class="chapter" style="--ordem:${Math.min(i, 12)}">
|
|
55
|
-
<h2>${escapar(s.titulo)}</h2>
|
|
60
|
+
<h2><span>${escapar(s.titulo)}</span></h2>
|
|
56
61
|
${desenharCorpo(s)}
|
|
57
62
|
</section>`)
|
|
58
63
|
.join("\n");
|
|
@@ -130,26 +135,57 @@ function cartaoDeNumero(d) {
|
|
|
130
135
|
/**
|
|
131
136
|
* COMO CADA CAPÍTULO É DESENHADO - e a decisão é sobre REPETIÇÃO, não sobre o assunto.
|
|
132
137
|
*
|
|
133
|
-
* Medido
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
138
|
+
* Medido na página dele em 18/09: as 94 linhas com a mesma forma saíram em SEIS pedaços, porque o
|
|
139
|
+
* agrupamento só juntava linhas VIZINHAS e as frases de `mx-auto` caem no meio delas. Seis blocos
|
|
140
|
+
* de dez são a mesma parede com mais bordas.
|
|
141
|
+
*
|
|
142
|
+
* Agora a família é da SEÇÃO INTEIRA: irmãs distantes se encontram, e a ordem que sobrevive é a da
|
|
143
|
+
* primeira aparição - o texto continua sendo lido na ordem em que a esteira o escreveu.
|
|
137
144
|
*
|
|
138
|
-
* O
|
|
139
|
-
*
|
|
145
|
+
* E O PRIMEIRO FATO DO CAPÍTULO SOBE: a linha de abertura, que é onde mora o número, deixa de ter
|
|
146
|
+
* o peso do detalhe monoespaçado e vira o CALLOUT que a pessoa lê antes de decidir se desce.
|
|
140
147
|
*/
|
|
141
148
|
function desenharCorpo(s) {
|
|
142
|
-
const
|
|
143
|
-
|
|
149
|
+
const { destaque, resto } = abertura(s.linhas);
|
|
150
|
+
const familias = agrupar(resto);
|
|
151
|
+
const corpo = familias
|
|
144
152
|
.map((f) => f.tipo === "tabela"
|
|
145
153
|
? ` <details class="familia" open>
|
|
146
|
-
<summary>${escapar(f.rotulo)} <b>${f.itens.length}</b></summary>
|
|
154
|
+
<summary><span>${escapar(f.rotulo)}</span> <b>${f.itens.length}</b></summary>
|
|
147
155
|
<ul class="grade">
|
|
148
156
|
${f.itens.map((i) => ` <li>${escapar(i)}</li>`).join("\n")}
|
|
149
157
|
</ul>
|
|
150
158
|
</details>`
|
|
151
159
|
: ` <pre>${escapar(f.itens.join("\n"))}</pre>`)
|
|
152
160
|
.join("\n");
|
|
161
|
+
return destaque
|
|
162
|
+
? ` <p class="callout">${escapar(destaque)}</p>\n${corpo}`
|
|
163
|
+
: corpo;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* A ABERTURA DO CAPÍTULO - a primeira linha com conteúdo, e só quando ela CARREGA UM NÚMERO.
|
|
167
|
+
*
|
|
168
|
+
* É o número que faz a frase valer o destaque: "78 of 78 components came out with a blueprint" é
|
|
169
|
+
* um fato que decide se a pessoa lê o resto. Uma abertura sem número seria só a primeira linha
|
|
170
|
+
* promovida por ser a primeira, e aí o destaque deixaria de significar alguma coisa.
|
|
171
|
+
*/
|
|
172
|
+
function abertura(linhas) {
|
|
173
|
+
const i = linhas.findIndex((l) => l.trim());
|
|
174
|
+
if (i < 0)
|
|
175
|
+
return { destaque: null, resto: linhas };
|
|
176
|
+
const primeira = linhas[i].trim();
|
|
177
|
+
if (!/\d/.test(primeira) || primeira.length > 240)
|
|
178
|
+
return { destaque: null, resto: linhas };
|
|
179
|
+
/**
|
|
180
|
+
* E ELA NÃO PODE SER MEMBRO DE UMA FAMÍLIA. Um capítulo que abre com a primeira de noventa
|
|
181
|
+
* linhas iguais promoveria uma irmã qualquer a conclusão, e ainda tiraria uma da contagem -
|
|
182
|
+
* duas mentiras pelo preço de uma. O destaque é para o fato ÚNICO.
|
|
183
|
+
*/
|
|
184
|
+
const f = forma(primeira);
|
|
185
|
+
const irmas = linhas.filter((l) => l.trim() && forma(l) === f).length;
|
|
186
|
+
if (irmas >= MINIMO_PARA_TABELA)
|
|
187
|
+
return { destaque: null, resto: linhas };
|
|
188
|
+
return { destaque: primeira, resto: linhas.slice(i + 1) };
|
|
153
189
|
}
|
|
154
190
|
const MINIMO_PARA_TABELA = 6;
|
|
155
191
|
/**
|
|
@@ -168,30 +204,41 @@ function forma(linha) {
|
|
|
168
204
|
.slice(0, 6)
|
|
169
205
|
.join(" ");
|
|
170
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* AGRUPA A SEÇÃO INTEIRA, e a ordem é a da PRIMEIRA APARIÇÃO de cada forma.
|
|
209
|
+
*
|
|
210
|
+
* Uma varredura só: cada linha entra no balde da forma dela, e os baldes saem na ordem em que
|
|
211
|
+
* nasceram. Quem tem irmãs suficientes vira família; quem não tem volta para a prosa, no lugar
|
|
212
|
+
* onde estava. O que a pessoa lê continua sendo o texto da esteira, na ordem da esteira.
|
|
213
|
+
*/
|
|
171
214
|
function agrupar(linhas) {
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const atual = linhas[i];
|
|
176
|
-
if (!atual.trim()) {
|
|
177
|
-
empilharProsa(saida, atual);
|
|
178
|
-
i += 1;
|
|
215
|
+
const contagem = new Map();
|
|
216
|
+
for (const l of linhas) {
|
|
217
|
+
if (!l.trim())
|
|
179
218
|
continue;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
219
|
+
const f = forma(l);
|
|
220
|
+
contagem.set(f, (contagem.get(f) ?? 0) + 1);
|
|
221
|
+
}
|
|
222
|
+
const saida = [];
|
|
223
|
+
const familiaDe = new Map();
|
|
224
|
+
for (const l of linhas) {
|
|
225
|
+
const f = l.trim() ? forma(l) : "";
|
|
226
|
+
if (l.trim() && (contagem.get(f) ?? 0) >= MINIMO_PARA_TABELA) {
|
|
227
|
+
const existente = familiaDe.get(f);
|
|
228
|
+
if (existente) {
|
|
229
|
+
existente.itens.push(l.trim());
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const nova = {
|
|
233
|
+
tipo: "tabela",
|
|
234
|
+
rotulo: rotuloDe(f),
|
|
235
|
+
itens: [l.trim()],
|
|
236
|
+
};
|
|
237
|
+
familiaDe.set(f, nova);
|
|
238
|
+
saida.push(nova);
|
|
191
239
|
continue;
|
|
192
240
|
}
|
|
193
|
-
empilharProsa(saida,
|
|
194
|
-
i += 1;
|
|
241
|
+
empilharProsa(saida, l);
|
|
195
242
|
}
|
|
196
243
|
return saida.filter((f) => f.itens.some((l) => l.trim()));
|
|
197
244
|
}
|
|
@@ -202,9 +249,18 @@ function empilharProsa(saida, linha) {
|
|
|
202
249
|
else
|
|
203
250
|
saida.push({ tipo: "prosa", rotulo: "", itens: [linha] });
|
|
204
251
|
}
|
|
205
|
-
/**
|
|
252
|
+
/**
|
|
253
|
+
* O RÓTULO DA FAMÍLIA - uma frase que se lê, e não a forma crua.
|
|
254
|
+
*
|
|
255
|
+
* `two parts named · - the second is ·` saía como `two parts named … - the 10`: reticência solta,
|
|
256
|
+
* minúscula, e sem dizer o que aquilo é. O rótulo agora é a parte ESTÁVEL da frase - o que todas as
|
|
257
|
+
* irmãs têm em comum - cortada no primeiro buraco e começando com maiúscula.
|
|
258
|
+
*/
|
|
206
259
|
function rotuloDe(f) {
|
|
207
|
-
|
|
260
|
+
const estavel = f.split("·")[0].replace(/#/g, "n").trim();
|
|
261
|
+
const limpo = estavel.replace(/[\s\-–—:,]+$/, "");
|
|
262
|
+
const frase = limpo || "Repeated lines";
|
|
263
|
+
return frase.charAt(0).toUpperCase() + frase.slice(1);
|
|
208
264
|
}
|
|
209
265
|
/** A data em palavras. Data ilegível some da frase em vez de imprimir `Invalid Date`. */
|
|
210
266
|
function legivel(iso) {
|
|
@@ -259,7 +315,7 @@ function layout() {
|
|
|
259
315
|
.bar-in {
|
|
260
316
|
max-width: 920px;
|
|
261
317
|
margin-inline: auto;
|
|
262
|
-
padding:
|
|
318
|
+
padding: 11px 24px;
|
|
263
319
|
display: flex;
|
|
264
320
|
flex-wrap: wrap;
|
|
265
321
|
gap: 8px 24px;
|
|
@@ -321,10 +377,45 @@ h1 {
|
|
|
321
377
|
}
|
|
322
378
|
|
|
323
379
|
nav { margin: 44px 0 8px; }
|
|
324
|
-
nav ol {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
380
|
+
nav ol {
|
|
381
|
+
margin: 12px 0 0;
|
|
382
|
+
padding: 0;
|
|
383
|
+
list-style: none;
|
|
384
|
+
counter-reset: capitulo;
|
|
385
|
+
color: var(--ds-color-semantic-muted);
|
|
386
|
+
display: grid;
|
|
387
|
+
gap: 1px;
|
|
388
|
+
}
|
|
389
|
+
nav li {
|
|
390
|
+
counter-increment: capitulo;
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: baseline;
|
|
393
|
+
gap: 12px;
|
|
394
|
+
padding: 7px 10px;
|
|
395
|
+
border-radius: var(--ds-radius-sm, 8px);
|
|
396
|
+
}
|
|
397
|
+
nav li:hover { background: var(--ds-color-semantic-surface); }
|
|
398
|
+
nav li::before {
|
|
399
|
+
content: counter(capitulo);
|
|
400
|
+
min-width: 18px;
|
|
401
|
+
font-variant-numeric: tabular-nums;
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
color: var(--ds-color-semantic-faint);
|
|
404
|
+
}
|
|
405
|
+
nav a { color: inherit; text-decoration: none; }
|
|
406
|
+
nav li:hover a { color: var(--ds-color-semantic-accent); }
|
|
407
|
+
/* A regua que sobra entre o nome e a contagem - sem ela os numeros flutuam soltos. */
|
|
408
|
+
nav a::after {
|
|
409
|
+
content: "";
|
|
410
|
+
display: inline-block;
|
|
411
|
+
width: 100%;
|
|
412
|
+
}
|
|
413
|
+
nav li > span {
|
|
414
|
+
margin-left: auto;
|
|
415
|
+
font-variant-numeric: tabular-nums;
|
|
416
|
+
font-size: 12px;
|
|
417
|
+
color: var(--ds-color-semantic-faint);
|
|
418
|
+
}
|
|
328
419
|
|
|
329
420
|
section.chapter {
|
|
330
421
|
background: var(--ds-color-semantic-surface);
|
|
@@ -335,7 +426,35 @@ section.chapter {
|
|
|
335
426
|
scroll-margin-top: 84px;
|
|
336
427
|
}
|
|
337
428
|
.hero, nav { background: none; border: 0; padding-inline: 0; }
|
|
338
|
-
|
|
429
|
+
/* O TITULO DO CAPITULO SEGURA O TOPO ENQUANTO O CAPITULO PASSA - e e assim que a pessoa sempre
|
|
430
|
+
sabe o que esta lendo, sem uma linha de script. Um capitulo de cem linhas rolava ate o fim sem
|
|
431
|
+
nada na tela dizendo de qual dos dezesseis ele era. O deslocamento e a altura da barra: eles se
|
|
432
|
+
encostam em vez de se sobrepor, e o segundo empurra o primeiro so quando chega a vez dele. */
|
|
433
|
+
h2 {
|
|
434
|
+
position: sticky;
|
|
435
|
+
top: 60px;
|
|
436
|
+
z-index: 5;
|
|
437
|
+
margin: -24px -26px 14px;
|
|
438
|
+
padding: 14px 26px 12px;
|
|
439
|
+
font-size: 18px;
|
|
440
|
+
letter-spacing: -0.01em;
|
|
441
|
+
background: var(--ds-color-semantic-surface);
|
|
442
|
+
border-bottom: 1px solid var(--ds-color-semantic-border);
|
|
443
|
+
border-radius: var(--ds-radius-lg, 14px) var(--ds-radius-lg, 14px) 0 0;
|
|
444
|
+
}
|
|
445
|
+
h2 span { display: block; }
|
|
446
|
+
|
|
447
|
+
/* O PRIMEIRO FATO DO CAPITULO, no peso de quem decide se vale descer. Ele e prosa, nao codigo -
|
|
448
|
+
entao sai do monoespacado e ganha a regua da esquerda, que e a unica marca do documento que diz
|
|
449
|
+
isto aqui e a conclusao, o resto e a evidencia. */
|
|
450
|
+
.callout {
|
|
451
|
+
margin: 0 0 16px;
|
|
452
|
+
padding: 2px 0 2px 16px;
|
|
453
|
+
border-left: 2px solid var(--ds-color-semantic-accent);
|
|
454
|
+
font-size: 15.5px;
|
|
455
|
+
line-height: 1.55;
|
|
456
|
+
color: var(--ds-color-semantic-foreground);
|
|
457
|
+
}
|
|
339
458
|
|
|
340
459
|
pre {
|
|
341
460
|
margin: 0 0 12px;
|
package/package.json
CHANGED