feat: troncature annoncee et lecture par fenetres des tableurs #153
- BUG-090 (#153 A8) : render_sheets() expose total_rows/total_cols, max_rows/max_cols et truncated ; la visionneuse affiche un bandeau « Feuille tronquée » (i18n FR/EN) au lieu de couper en silence, et la ligne d'en-têtes devient sticky (top:auto sur les numéros de ligne). - #153 A9 : GET /api/file/{vault}/xlsx/sheet?sheet&offset&limit sert une fenêtre de 1 à 1000 lignes avec les vraies coordonnées A1, has_more de pagination et valeurs calculées A12 ; 404 feuille inconnue, 415 non-xlsx. - Tests : TestXlsxTruncationNotice (4) + TestXlsxSheetWindow (11) avec contre-preuves, xlsx-viewer.test.mjs 14/14, E2E 7/7 (fixture sample-xlsx-large.xlsx 520 lignes), suite 1417 passed / 6 skipped, ruff/mypy 0, i18n parity. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <[email protected]>
This commit is contained in:
+30
-1
@@ -1005,6 +1005,35 @@ export function renderVideoViewer(area, data) {
|
||||
// confirmation before retrying with `force: true`. A value starting with "=" or
|
||||
// "@" is stored as text unless the user turns the formula toggle on, so a typed
|
||||
// `=cmd|…` cannot execute when the file is later opened in Excel.
|
||||
// #153 A8 — a sheet bigger than the render caps used to be silently cut: the
|
||||
// user saw a short table and no way to tell the rest of the workbook still
|
||||
// existed. The backend now reports the real dimensions of every sheet, so the
|
||||
// note states exactly what is hidden (and that those cells are not editable
|
||||
// here — the workbook itself is untouched). A payload without those fields
|
||||
// (older cache) simply shows no note.
|
||||
function truncationNote(sheet) {
|
||||
// The cap is the real "shown" figure, not `rows`/`cols`: those are post-trim
|
||||
// (a sparse sheet renders 1x1) while the note must say how far the view
|
||||
// reaches.
|
||||
const cap = { rows: Number(sheet.max_rows) || 0, cols: Number(sheet.max_cols) || 0 };
|
||||
const reasons = [];
|
||||
if (Number(sheet.total_rows) > cap.rows) {
|
||||
reasons.push(t("xlsx.truncated_rows", { shown: cap.rows, total: Number(sheet.total_rows) }));
|
||||
}
|
||||
if (Number(sheet.total_cols) > cap.cols) {
|
||||
reasons.push(t("xlsx.truncated_cols", { shown: cap.cols, total: Number(sheet.total_cols) }));
|
||||
}
|
||||
if (!reasons.length) return "";
|
||||
return `<div class="xlsx-truncated" role="note">
|
||||
<i data-lucide="scissors" class="xlsx-truncated-icon"></i>
|
||||
<div class="xlsx-warning-body">
|
||||
<strong>${escapeHtml(t("xlsx.truncated_title"))}</strong>
|
||||
<span>${escapeHtml(reasons.join(" "))}</span>
|
||||
<span class="xlsx-warning-hint">${escapeHtml(t("xlsx.truncated_hint"))}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export function renderXlsxViewer(area, data) {
|
||||
const sheets = data.xlsx_sheets || [];
|
||||
const lossy = data.xlsx_lossy_features || [];
|
||||
@@ -1019,7 +1048,7 @@ export function renderXlsxViewer(area, data) {
|
||||
).join("")}</div>`
|
||||
: "";
|
||||
const panels = sheets.map((s, i) =>
|
||||
`<div class="xlsx-panel" data-sheet="${i}"${i === 0 ? "" : ' style="display:none"'}>${s.html}</div>`
|
||||
`<div class="xlsx-panel" data-sheet="${i}"${i === 0 ? "" : ' style="display:none"'}>${truncationNote(s)}${s.html}</div>`
|
||||
).join("");
|
||||
const lossWarning = lossy.length
|
||||
? `<div class="xlsx-warning" role="note">
|
||||
|
||||
Reference in New Issue
Block a user