Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e430eb57b7 |
@@ -279,6 +279,10 @@ body {
|
|||||||
.btn-icon.wrap-toggle[aria-pressed="true"] { background: var(--accent); color: var(--bg); border-color: var(--accent); }
|
.btn-icon.wrap-toggle[aria-pressed="true"] { background: var(--accent); color: var(--bg); border-color: var(--accent); }
|
||||||
/* #152: wrap ON must break long unbroken tokens mid-word and allow no horizontal scrolling */
|
/* #152: wrap ON must break long unbroken tokens mid-word and allow no horizontal scrolling */
|
||||||
html[data-wrap] .codebody { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; overflow-x: hidden; }
|
html[data-wrap] .codebody { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; overflow-x: hidden; }
|
||||||
|
/* #167: overflow:hidden zeroes the flex auto-minimum, so the codebody must
|
||||||
|
be told to fill the space left of the gutter or it collapses and the
|
||||||
|
gutter (flex-shrink:0) consumes the whole row */
|
||||||
|
html[data-wrap] .code .codebody { flex: 1 1 auto; min-width: 0; }
|
||||||
html[data-wrap] .editor { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; overflow-x: hidden; }
|
html[data-wrap] .editor { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; overflow-x: hidden; }
|
||||||
/* #152: with wrap on nothing may scroll horizontally, including the code container and mobile floats */
|
/* #152: with wrap on nothing may scroll horizontally, including the code container and mobile floats */
|
||||||
html[data-wrap] .code { overflow-x: hidden; }
|
html[data-wrap] .code { overflow-x: hidden; }
|
||||||
@@ -293,6 +297,12 @@ html[data-wrap] .float { overflow-x: hidden; }
|
|||||||
font-family: var(--font-mono); font-size: var(--code-fs); line-height: var(--code-lh);
|
font-family: var(--font-mono); font-size: var(--code-fs); line-height: var(--code-lh);
|
||||||
padding: 14px 0; display: flex; overflow-x: auto;
|
padding: 14px 0; display: flex; overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
/* #167: the gutter must not drive the flex layout — its content width
|
||||||
|
(row count × number width) shrinks the code column, which re-wraps lines,
|
||||||
|
which grows the gutter: a feedback loop. Pin the gutter with a fixed
|
||||||
|
basis, take its width out of the negotiation, and let the codebody take
|
||||||
|
the rest. */
|
||||||
|
.code .gutter { flex: 0 0 auto; width: 3ch; min-width: 3ch; overflow: visible; }
|
||||||
.code .gutter { flex-shrink: 0; }
|
.code .gutter { flex-shrink: 0; }
|
||||||
/* gutter/code share line metrics; the editor gutter keeps its own padding (#50) */
|
/* gutter/code share line metrics; the editor gutter keeps its own padding (#50) */
|
||||||
.code .gutter { padding-top: 0; padding-bottom: 0; }
|
.code .gutter { padding-top: 0; padding-bottom: 0; }
|
||||||
@@ -631,10 +641,6 @@ a.admin-link:hover { color: var(--fg); text-decoration: underline; }
|
|||||||
.stats-head { font-size: 13px; }
|
.stats-head { font-size: 13px; }
|
||||||
.stats-grid { font-size: 13px; padding: 10px 12px; }
|
.stats-grid { font-size: 13px; padding: 10px 12px; }
|
||||||
.code { font-size: 13px; }
|
.code { font-size: 13px; }
|
||||||
/* #167: gutter and code must share one line box at mobile width too — a
|
|
||||||
taller gutter font stacks its rows taller than the code rows and every
|
|
||||||
number drifts off its line start */
|
|
||||||
.code .gutter { font-size: 13px; }
|
|
||||||
.codebody { padding: 0 12px; }
|
.codebody { padding: 0 12px; }
|
||||||
.footnote { font-size: 12px; padding: 8px 12px; gap: 10px; }
|
.footnote { font-size: 12px; padding: 8px 12px; gap: 10px; }
|
||||||
.iconbtn { font-size: 13px; padding: 6px 10px; }
|
.iconbtn { font-size: 13px; padding: 6px 10px; }
|
||||||
@@ -815,3 +821,6 @@ button[type="submit"]:focus-visible,
|
|||||||
.mt18 { margin-top: 18px; }
|
.mt18 { margin-top: 18px; }
|
||||||
.toggle-inline { display: inline-flex; }
|
.toggle-inline { display: inline-flex; }
|
||||||
.wrap-normal { word-break: normal; overflow-wrap: break-word; }
|
.wrap-normal { word-break: normal; overflow-wrap: break-word; }
|
||||||
|
.created-banner { display: block; }
|
||||||
|
/* #167: gutter rows for wrapped paste view — one row per visual code line */
|
||||||
|
.gutline { display: block; }
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// #167: with line wrapping on, a logical line can occupy several visual
|
// #167: with line wrapping on, a logical line can occupy several visual
|
||||||
// lines; the gutter must show one number per VISUAL line, and each number
|
// lines; the gutter must show one number per VISUAL line. Per-line spans
|
||||||
// must sit on the visual row where its logical line STARTS. Per-line spans
|
// give each logical line its own box so its height identifies the wrapped
|
||||||
// give each logical line its own box so offsetTop order stays correct even
|
// row count; blank gutter rows keep numbers aligned with line starts.
|
||||||
// when highlighting spans cross no line boundaries.
|
|
||||||
(function () {
|
(function () {
|
||||||
var body = document.getElementById('codebody');
|
var body = document.getElementById('codebody');
|
||||||
var gutter = document.getElementById('gutter');
|
var gutter = document.getElementById('gutter');
|
||||||
@@ -14,10 +13,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Wrap each logical line (split on newline; spans never contain newlines
|
// Wrap each logical line (split on newline; spans never contain newlines
|
||||||
// because HighlightCode highlights per line) in a .codeline block. The
|
// because HighlightCode highlights per line) in a .codeline block.
|
||||||
// spans are display:block, so they are joined with '' — a '\n' join leaves
|
|
||||||
// newline text nodes between blocks that pre-wrap renders as an extra line
|
|
||||||
// box per line, which would shift every following number down one row (#167).
|
|
||||||
function splitLines() {
|
function splitLines() {
|
||||||
var html = body.innerHTML;
|
var html = body.innerHTML;
|
||||||
var parts = html.split('\n');
|
var parts = html.split('\n');
|
||||||
@@ -25,88 +21,51 @@
|
|||||||
for (var i = 0; i < parts.length; i++) {
|
for (var i = 0; i < parts.length; i++) {
|
||||||
out.push('<span class="codeline">' + parts[i] + '</span>');
|
out.push('<span class="codeline">' + parts[i] + '</span>');
|
||||||
}
|
}
|
||||||
body.innerHTML = out.join('');
|
body.innerHTML = out.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// One .gutline block per visual row. Numbers are placed at the gutter row
|
|
||||||
// whose top matches their .codeline's top; filler rows pad the gaps so
|
|
||||||
// alignment is driven by measured geometry, not by uniform row counts.
|
|
||||||
function renumber() {
|
function renumber() {
|
||||||
var lines = body.querySelectorAll('.codeline');
|
var lines = body.querySelectorAll('.codeline');
|
||||||
if (!wrapOn() || !lines.length) {
|
|
||||||
// wrap OFF: one number per logical line (pre-existing behavior,
|
|
||||||
// including the gutter scrolling with horizontal scroll).
|
|
||||||
var s = '';
|
var s = '';
|
||||||
for (var k = 1; k <= lines.length; k++) s += k + '\n';
|
if (wrapOn() && lines.length) {
|
||||||
gutter.textContent = lines.length ? s.slice(0, -1) : '1';
|
// Each logical line block occupies rows = height / line-height when
|
||||||
return;
|
// wrapped; its number sits on the first row and the remaining rows get
|
||||||
}
|
// blank gutter lines so numbers stay aligned with line starts.
|
||||||
|
// The gutter has a fixed CSS width now (.code .gutter), so its row
|
||||||
// Measure each logical line's offsetTop (viewport-relative for
|
// count never changes the code column width: measurement is stable
|
||||||
// comparison with gutter rows rendered in the same scroll flow).
|
// with no measurement/layout feedback.
|
||||||
// Reading all rects first avoids interleaved layout reads/writes.
|
|
||||||
var tops = [];
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
tops.push(lines[i].getBoundingClientRect().top);
|
|
||||||
}
|
|
||||||
// Build one gutline per visual row of the tallest column (the code
|
|
||||||
// body itself); each number is assigned to the visual row whose top
|
|
||||||
// is closest to its line's top.
|
|
||||||
var bodyTop = body.getBoundingClientRect().top;
|
|
||||||
var lh = parseFloat(getComputedStyle(body).lineHeight) || 1;
|
var lh = parseFloat(getComputedStyle(body).lineHeight) || 1;
|
||||||
var totalRows = Math.max(lines.length, Math.ceil(body.getBoundingClientRect().height / lh));
|
|
||||||
gutter.textContent = '';
|
gutter.textContent = '';
|
||||||
var frag = document.createDocumentFragment();
|
var frag = document.createDocumentFragment();
|
||||||
var spans = [];
|
for (var i = 0; i < lines.length; i++) {
|
||||||
for (var r = 0; r < totalRows; r++) {
|
var num = document.createElement('span');
|
||||||
var cell = document.createElement('span');
|
num.className = 'gutline';
|
||||||
cell.className = 'gutline';
|
num.textContent = String(i + 1);
|
||||||
cell.textContent = '\u00a0';
|
frag.appendChild(num);
|
||||||
spans.push(cell);
|
var rows = Math.max(1, Math.round(lines[i].getBoundingClientRect().height / lh));
|
||||||
frag.appendChild(cell);
|
for (var r = 1; r < rows; r++) {
|
||||||
|
var blank = document.createElement('span');
|
||||||
|
blank.className = 'gutline';
|
||||||
|
blank.textContent = '\u00a0';
|
||||||
|
frag.appendChild(blank);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
gutter.appendChild(frag);
|
gutter.appendChild(frag);
|
||||||
for (var j = 0; j < tops.length; j++) {
|
} else {
|
||||||
var row = Math.round((tops[j] - bodyTop) / lh);
|
for (var k = 1; k <= lines.length; k++) s += k + '\n';
|
||||||
if (row < 0) row = 0;
|
gutter.textContent = lines.length ? s.slice(0, -1) : '1';
|
||||||
if (row > totalRows - 1) row = totalRows - 1;
|
|
||||||
spans[row].textContent = String(j + 1);
|
|
||||||
}
|
}
|
||||||
// A wrapped line whose last visual row is only partially filled can
|
|
||||||
// settle a hair under N * line-height after the gutter is rebuilt; a
|
|
||||||
// gutter pass that changes the code column width reflows it. Verify the
|
|
||||||
// placement one frame later and re-run if any line's row moved (#167).
|
|
||||||
var placed = {};
|
|
||||||
for (var q = 0; q < tops.length; q++) placed[q] = Math.round((tops[q] - bodyTop) / lh);
|
|
||||||
requestAnimationFrame(function () {
|
|
||||||
var moved = false;
|
|
||||||
var tops2 = [];
|
|
||||||
for (var q2 = 0; q2 < lines.length; q2++) tops2.push(lines[q2].getBoundingClientRect().top);
|
|
||||||
var bodyTop2 = body.getBoundingClientRect().top;
|
|
||||||
for (var q3 = 0; q3 < tops2.length; q3++) {
|
|
||||||
if (Math.round((tops2[q3] - bodyTop2) / lh) !== placed[q3]) { moved = true; break; }
|
|
||||||
}
|
|
||||||
if (moved) renumber();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
splitLines();
|
splitLines();
|
||||||
renumber();
|
renumber();
|
||||||
|
|
||||||
// Re-renumber on toggle (theme.js toggles data-wrap on <html>) and on any
|
// Re-renumber on toggle (theme.js toggles data-wrap on <html>) and on any
|
||||||
// size change (paste of lots of text, window resize, zoom). Only width
|
// size change (paste of lots of text, window resize, zoom).
|
||||||
// changes of the code column affect wrapping, so observe width only —
|
|
||||||
// height changes caused by our own renumbering must not re-trigger.
|
|
||||||
var mo = new MutationObserver(renumber);
|
var mo = new MutationObserver(renumber);
|
||||||
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-wrap'] });
|
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-wrap'] });
|
||||||
var lastW = body.getBoundingClientRect().width;
|
|
||||||
if (window.ResizeObserver) {
|
if (window.ResizeObserver) {
|
||||||
var ro = new ResizeObserver(function () {
|
var ro = new ResizeObserver(function () { renumber(); });
|
||||||
var w = body.getBoundingClientRect().width;
|
|
||||||
if (Math.abs(w - lastW) < 0.5) return;
|
|
||||||
lastW = w;
|
|
||||||
renumber();
|
|
||||||
});
|
|
||||||
ro.observe(body);
|
ro.observe(body);
|
||||||
} else {
|
} else {
|
||||||
window.addEventListener('resize', renumber);
|
window.addEventListener('resize', renumber);
|
||||||
|
|||||||
Reference in New Issue
Block a user