@teammates/consolonia 0.2.0 → 0.2.7
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/widgets/markdown.js +108 -17
- package/package.json +1 -1
package/dist/widgets/markdown.js
CHANGED
|
@@ -297,9 +297,10 @@ function renderList(token, lines, theme, synTheme, width, indent, ctx) {
|
|
|
297
297
|
lines.push([{ text: indent, style: theme.text }]);
|
|
298
298
|
}
|
|
299
299
|
// ── Tables ───────────────────────────────────────────────────────
|
|
300
|
-
function renderTable(token, lines, theme,
|
|
300
|
+
function renderTable(token, lines, theme, width, indent) {
|
|
301
301
|
const numCols = token.header.length;
|
|
302
|
-
|
|
302
|
+
const avail = width - indent.length;
|
|
303
|
+
// Compute natural column widths from content
|
|
303
304
|
const colWidths = token.header.map((h) => plainText(h.tokens).length);
|
|
304
305
|
for (const row of token.rows) {
|
|
305
306
|
for (let c = 0; c < numCols; c++) {
|
|
@@ -312,42 +313,132 @@ function renderTable(token, lines, theme, _width, indent) {
|
|
|
312
313
|
for (let c = 0; c < numCols; c++) {
|
|
313
314
|
colWidths[c] = Math.max(3, colWidths[c]) + 2;
|
|
314
315
|
}
|
|
316
|
+
// Shrink columns if total table width exceeds available width
|
|
317
|
+
// Total = sum(colWidths) + numCols + 1 (for │ borders)
|
|
318
|
+
const MIN_COL = 6; // minimum inner width to be readable
|
|
319
|
+
const totalBorders = numCols + 1;
|
|
320
|
+
const totalNatural = colWidths.reduce((a, b) => a + b, 0) + totalBorders;
|
|
321
|
+
if (totalNatural > avail && avail > totalBorders + numCols * MIN_COL) {
|
|
322
|
+
const budgetForCols = avail - totalBorders;
|
|
323
|
+
// Proportional shrink, respecting minimum
|
|
324
|
+
let remaining = budgetForCols;
|
|
325
|
+
const fixed = new Array(numCols).fill(false);
|
|
326
|
+
// First pass: lock columns that are already small (at or below fair share)
|
|
327
|
+
// so they keep their natural width instead of being shrunk further
|
|
328
|
+
const fairShare = Math.floor(budgetForCols / numCols);
|
|
329
|
+
for (let c = 0; c < numCols; c++) {
|
|
330
|
+
if (colWidths[c] <= fairShare) {
|
|
331
|
+
fixed[c] = true;
|
|
332
|
+
remaining -= colWidths[c];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// Second pass: distribute remaining budget proportionally among shrinkable columns
|
|
336
|
+
const shrinkSum = colWidths
|
|
337
|
+
.filter((_w, i) => !fixed[i])
|
|
338
|
+
.reduce((a, b) => a + b, 0);
|
|
339
|
+
if (shrinkSum > 0) {
|
|
340
|
+
let distributed = 0;
|
|
341
|
+
const shrinkable = colWidths.map((_w, i) => !fixed[i]);
|
|
342
|
+
for (let c = 0; c < numCols; c++) {
|
|
343
|
+
if (shrinkable[c]) {
|
|
344
|
+
const share = Math.max(MIN_COL, Math.floor((colWidths[c] / shrinkSum) * remaining));
|
|
345
|
+
colWidths[c] = share;
|
|
346
|
+
distributed += share;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Give any leftover pixels to the last shrinkable column
|
|
350
|
+
const leftover = remaining - distributed;
|
|
351
|
+
if (leftover !== 0) {
|
|
352
|
+
for (let c = numCols - 1; c >= 0; c--) {
|
|
353
|
+
if (shrinkable[c]) {
|
|
354
|
+
colWidths[c] += leftover;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
315
361
|
const border = theme.tableBorder;
|
|
316
362
|
// Helper to build a horizontal rule
|
|
317
363
|
const hRule = (left, mid, right) => {
|
|
318
364
|
const parts = colWidths.map((w) => "─".repeat(w));
|
|
319
365
|
return left + parts.join(mid) + right;
|
|
320
366
|
};
|
|
321
|
-
//
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
367
|
+
// Word-wrap text to fit within a column width (breaks on spaces)
|
|
368
|
+
const wrapCellText = (text, maxW) => {
|
|
369
|
+
if (maxW <= 0)
|
|
370
|
+
return [text];
|
|
371
|
+
if (text.length <= maxW)
|
|
372
|
+
return [text];
|
|
373
|
+
const wrapped = [];
|
|
374
|
+
let rest = text;
|
|
375
|
+
while (rest.length > maxW) {
|
|
376
|
+
// Find last space within the limit
|
|
377
|
+
let breakAt = rest.lastIndexOf(" ", maxW);
|
|
378
|
+
if (breakAt <= 0) {
|
|
379
|
+
// No space — hard break
|
|
380
|
+
breakAt = maxW;
|
|
381
|
+
wrapped.push(rest.slice(0, breakAt));
|
|
382
|
+
rest = rest.slice(breakAt);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
wrapped.push(rest.slice(0, breakAt));
|
|
386
|
+
rest = rest.slice(breakAt + 1); // skip the space
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (rest.length > 0)
|
|
390
|
+
wrapped.push(rest);
|
|
391
|
+
return wrapped;
|
|
392
|
+
};
|
|
393
|
+
// Helper to build a (possibly multi-line) data row
|
|
394
|
+
const dataRows = (cells, style) => {
|
|
395
|
+
// Get wrapped lines for each cell
|
|
396
|
+
const cellLines = [];
|
|
397
|
+
let maxLines = 1;
|
|
327
398
|
for (let c = 0; c < numCols; c++) {
|
|
328
399
|
const cellText = cells[c] ? plainText(cells[c].tokens) : "";
|
|
329
|
-
const
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
400
|
+
const innerW = colWidths[c] - 2; // 1 char padding each side
|
|
401
|
+
const wrapped = wrapCellText(cellText, innerW);
|
|
402
|
+
cellLines.push(wrapped);
|
|
403
|
+
if (wrapped.length > maxLines)
|
|
404
|
+
maxLines = wrapped.length;
|
|
333
405
|
}
|
|
334
|
-
|
|
406
|
+
const result = [];
|
|
407
|
+
for (let row = 0; row < maxLines; row++) {
|
|
408
|
+
const segs = [
|
|
409
|
+
{ text: indent, style: theme.text },
|
|
410
|
+
{ text: "│", style: border },
|
|
411
|
+
];
|
|
412
|
+
for (let c = 0; c < numCols; c++) {
|
|
413
|
+
const lineText = row < cellLines[c].length ? cellLines[c][row] : "";
|
|
414
|
+
const align = token.header[c]?.align;
|
|
415
|
+
const padded = padCell(lineText, colWidths[c], align);
|
|
416
|
+
segs.push({ text: padded, style });
|
|
417
|
+
segs.push({ text: "│", style: border });
|
|
418
|
+
}
|
|
419
|
+
result.push(segs);
|
|
420
|
+
}
|
|
421
|
+
return result;
|
|
335
422
|
};
|
|
336
423
|
// Top border
|
|
337
424
|
lines.push([
|
|
338
425
|
{ text: indent, style: theme.text },
|
|
339
426
|
{ text: hRule("┌", "┬", "┐"), style: border },
|
|
340
427
|
]);
|
|
341
|
-
// Header row
|
|
342
|
-
|
|
428
|
+
// Header row (may be multi-line)
|
|
429
|
+
for (const line of dataRows(token.header, theme.tableHeader)) {
|
|
430
|
+
lines.push(line);
|
|
431
|
+
}
|
|
343
432
|
// Header separator
|
|
344
433
|
lines.push([
|
|
345
434
|
{ text: indent, style: theme.text },
|
|
346
435
|
{ text: hRule("├", "┼", "┤"), style: border },
|
|
347
436
|
]);
|
|
348
|
-
// Data rows
|
|
437
|
+
// Data rows (may be multi-line each)
|
|
349
438
|
for (const row of token.rows) {
|
|
350
|
-
|
|
439
|
+
for (const line of dataRows(row, theme.text)) {
|
|
440
|
+
lines.push(line);
|
|
441
|
+
}
|
|
351
442
|
}
|
|
352
443
|
// Bottom border
|
|
353
444
|
lines.push([
|
package/package.json
CHANGED