mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-13 02:39:42 +02:00
fix(preview): bound rich-text run walks, fold format notices, match Excel number display
- richTextPrefix visits at most maxCellTextChars + 1 runs. An empty run adds no text, so a length check alone walked every run of a shared string for every cell referencing it, on every tile. - Two or more unsupported number format warnings in a tile fold into one "N unsupported number formats" entry, and the notice bar has a max-height and scrolls. - General-format and unsupported-format numbers render at 15 significant digits, as Excel does (0.1+0.2 shows 0.3). - TIME_FORMAT accepts a trailing AM/PM, so h:mm AM/PM renders as 2:30 PM instead of falling back to a date. - SPREADSHEET_ASSET_VERSION refreshed.
This commit is contained in:
@@ -265,7 +265,8 @@ function sendTile(message) {
|
||||
sheetId: String(message.sheetId),
|
||||
cells,
|
||||
merges,
|
||||
warnings: Array.from(warnings),
|
||||
// One counted entry for many unsupported number formats keeps the notice bar short.
|
||||
warnings: core.foldWarnings(Array.from(warnings)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
(function initSpreadsheetPreview(global) {
|
||||
'use strict';
|
||||
|
||||
const SPREADSHEET_ASSET_VERSION = 'd83f632d5c69';
|
||||
const SPREADSHEET_ASSET_VERSION = '4d543b11c25c';
|
||||
const MAX_PREVIEW_BYTES = 10 * 1024 * 1024;
|
||||
const DEFAULT_TIMEOUT_MS = 20000;
|
||||
const MAX_SCROLL_PX = 8000000;
|
||||
|
||||
@@ -569,7 +569,8 @@
|
||||
}
|
||||
|
||||
const DATE_FORMAT = /^[ymd\-/ ]+$/i;
|
||||
const TIME_FORMAT = /^[hms: ]+$/i;
|
||||
// An optional trailing AM/PM (built-in format 18 is `h:mm AM/PM`).
|
||||
const TIME_FORMAT = /^[hms: ]+(?:AM\/PM)?$/i;
|
||||
const DATE_TIME_FORMAT = /^[ymdhis\-/: ]+$/i;
|
||||
|
||||
function isFormulaValue(value) {
|
||||
@@ -588,16 +589,50 @@
|
||||
}
|
||||
|
||||
// Joins rich-text runs only until the cap is passed, so a long run is never
|
||||
// copied whole once per cell.
|
||||
// copied whole once per cell. It also visits at most maxCellTextChars + 1
|
||||
// runs: an empty run (`<r/>`, 4 bytes) adds no text, so a length check alone
|
||||
// walked every run, for every cell sharing the string, on every tile. Any
|
||||
// maxCellTextChars + 1 non-empty runs already pass the cap.
|
||||
function richTextPrefix(runs) {
|
||||
let text = '';
|
||||
for (const run of runs) {
|
||||
const visit = Math.min(runs.length, LIMITS.maxCellTextChars + 1);
|
||||
for (let index = 0; index < visit; index += 1) {
|
||||
if (text.length > LIMITS.maxCellTextChars) break;
|
||||
const run = runs[index];
|
||||
if (typeof run?.text === 'string') text += run.text.slice(0, LIMITS.maxCellTextChars + 1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Excel displays at most 15 significant digits, so `=0.1+0.2` shows 0.3,
|
||||
// never the binary float's 0.30000000000000004.
|
||||
function generalNumber(value) {
|
||||
return Number.isFinite(value) ? String(Number(value.toPrecision(15))) : String(value);
|
||||
}
|
||||
|
||||
const UNSUPPORTED_FORMAT_PREFIX = 'Unsupported number format: ';
|
||||
|
||||
/**
|
||||
* Folds every unsupported number format warning into one counted entry when
|
||||
* there is more than one, so a sheet with a code per cell cannot grow the
|
||||
* notice bar without bound. A lone warning is kept as is; its code is at
|
||||
* most 255 characters (admission refuses longer ones). Order is preserved.
|
||||
*/
|
||||
function foldWarnings(warnings) {
|
||||
const formats = warnings.filter((warning) => String(warning).startsWith(UNSUPPORTED_FORMAT_PREFIX));
|
||||
if (formats.length < 2) return warnings.slice();
|
||||
const folded = [];
|
||||
let placed = false;
|
||||
for (const warning of warnings) {
|
||||
if (!String(warning).startsWith(UNSUPPORTED_FORMAT_PREFIX)) folded.push(warning);
|
||||
else if (!placed) {
|
||||
folded.push(`${formats.length} unsupported number formats`);
|
||||
placed = true;
|
||||
}
|
||||
}
|
||||
return folded;
|
||||
}
|
||||
|
||||
/**
|
||||
* A cell's display text and any warning. The text is ALWAYS at most
|
||||
* `LIMITS.maxCellTextChars` characters, whatever shape the value has.
|
||||
@@ -622,7 +657,7 @@
|
||||
const formatted = formatCellValueUncapped(serial, fallback, date1904);
|
||||
return /^General$/i.test(code)
|
||||
? formatted
|
||||
: { text: formatted.text, warning: `Unsupported number format: ${code}` };
|
||||
: { text: formatted.text, warning: `${UNSUPPORTED_FORMAT_PREFIX}${code}` };
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
if (isFormulaValue(value)) {
|
||||
@@ -642,7 +677,7 @@
|
||||
}
|
||||
const code = String(format || 'General');
|
||||
if (typeof value !== 'number') return { text: String(value) };
|
||||
if (/^General$/i.test(code)) return { text: String(value) };
|
||||
if (/^General$/i.test(code)) return { text: generalNumber(value) };
|
||||
if (DATE_FORMAT.test(code)) {
|
||||
const date = excelDate(value, Boolean(date1904));
|
||||
const yyyy = date.getUTCFullYear();
|
||||
@@ -652,9 +687,16 @@
|
||||
}
|
||||
if (TIME_FORMAT.test(code)) {
|
||||
const seconds = Math.round((value - Math.floor(value)) * 86400) % 86400;
|
||||
const hh = String(Math.floor(seconds / 3600)).padStart(2, '0');
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const mm = String(Math.floor((seconds % 3600) / 60)).padStart(2, '0');
|
||||
const ss = String(seconds % 60).padStart(2, '0');
|
||||
if (/AM\/PM$/i.test(code)) {
|
||||
const hour12 = String(hours % 12 || 12);
|
||||
const hh = /hh/i.test(code) ? hour12.padStart(2, '0') : hour12;
|
||||
const clock = /s/i.test(code) ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
|
||||
return { text: `${clock} ${hours < 12 ? 'AM' : 'PM'}` };
|
||||
}
|
||||
const hh = String(hours).padStart(2, '0');
|
||||
return { text: `${hh}:${mm}:${ss}` };
|
||||
}
|
||||
if (DATE_TIME_FORMAT.test(code)) {
|
||||
@@ -685,7 +727,7 @@
|
||||
(percent ? '%' : ''),
|
||||
};
|
||||
}
|
||||
return { text: String(value), warning: `Unsupported number format: ${code}` };
|
||||
return { text: generalNumber(value), warning: `${UNSUPPORTED_FORMAT_PREFIX}${code}` };
|
||||
}
|
||||
|
||||
// Colour resolution --------------------------------------------------------
|
||||
@@ -923,6 +965,7 @@
|
||||
computeViewport,
|
||||
intersectingMerges,
|
||||
formatCellValue,
|
||||
foldWarnings,
|
||||
DEFAULT_THEME_PALETTE,
|
||||
INDEXED_PALETTE,
|
||||
MIN_CONTRAST_RATIO,
|
||||
|
||||
@@ -19318,6 +19318,10 @@ html[data-session-list="sidebar"][data-sidebar="collapsed"] .btn-sidebar-toggle
|
||||
|
||||
.spreadsheet-preview-notice {
|
||||
flex: 0 0 auto;
|
||||
/* A long warning list scrolls inside the bar instead of pushing the grid out of view. */
|
||||
max-height: 4.5em;
|
||||
overflow-y: auto;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 5px 10px;
|
||||
color: var(--warning, #f59e0b);
|
||||
font-size: 12px;
|
||||
|
||||
Reference in New Issue
Block a user