Release v0.4.0: dev -> main #251
@@ -288,6 +288,8 @@ html[data-wrap] .float { overflow-x: hidden; }
|
|||||||
/* 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; }
|
||||||
.codebody { padding: 0 18px; white-space: pre; }
|
.codebody { padding: 0 18px; white-space: pre; }
|
||||||
|
/* #167: each logical line is its own block so offsetTop identifies its first visual row */
|
||||||
|
.codeline { display: block; }
|
||||||
/* syntax highlight tokens (#1) */
|
/* syntax highlight tokens (#1) */
|
||||||
.tok-kw { color: #c792ea; }
|
.tok-kw { color: #c792ea; }
|
||||||
.tok-str { color: #a5e075; }
|
.tok-str { color: #a5e075; }
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// #167: with line wrapping on, a logical line can occupy several visual
|
||||||
|
// lines; the gutter must show one number per VISUAL line. Per-line spans
|
||||||
|
// give each logical line its own box so offsetTop order stays correct even
|
||||||
|
// when highlighting spans cross no line boundaries.
|
||||||
|
(function () {
|
||||||
|
var body = document.getElementById('codebody');
|
||||||
|
var gutter = document.getElementById('gutter');
|
||||||
|
var code = document.getElementById('code');
|
||||||
|
if (!body || !gutter || !code) return;
|
||||||
|
|
||||||
|
var wrapOn = function () {
|
||||||
|
return document.documentElement.hasAttribute('data-wrap');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wrap each logical line (split on newline; spans never contain newlines
|
||||||
|
// because HighlightCode highlights per line) in a .codeline block.
|
||||||
|
function splitLines() {
|
||||||
|
var html = body.innerHTML;
|
||||||
|
var parts = html.split('\n');
|
||||||
|
var out = [];
|
||||||
|
for (var i = 0; i < parts.length; i++) {
|
||||||
|
out.push('<span class="codeline">' + parts[i] + '</span>');
|
||||||
|
}
|
||||||
|
body.innerHTML = out.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renumber() {
|
||||||
|
var lines = body.querySelectorAll('.codeline');
|
||||||
|
var s = '';
|
||||||
|
if (wrapOn() && lines.length) {
|
||||||
|
// Each logical line block occupies rows = height / line-height when
|
||||||
|
// wrapped; its number sits on the first row and the remaining rows get
|
||||||
|
// blank gutter lines so numbers stay aligned with line starts.
|
||||||
|
// The code column width must not change while measuring (the gutter is
|
||||||
|
// flex-shrink:0 so its own row count never affects it), and renumber
|
||||||
|
// must be idempotent to avoid a ResizeObserver feedback loop.
|
||||||
|
var lh = parseFloat(getComputedStyle(body).lineHeight) || 1;
|
||||||
|
gutter.textContent = '';
|
||||||
|
var frag = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var num = document.createElement('span');
|
||||||
|
num.className = 'gutline';
|
||||||
|
num.textContent = String(i + 1);
|
||||||
|
frag.appendChild(num);
|
||||||
|
var rows = Math.max(1, Math.round(lines[i].getBoundingClientRect().height / lh));
|
||||||
|
for (var r = 1; r < rows; r++) {
|
||||||
|
var blank = document.createElement('span');
|
||||||
|
blank.className = 'gutline';
|
||||||
|
blank.textContent = '\u00a0';
|
||||||
|
frag.appendChild(blank);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gutter.appendChild(frag);
|
||||||
|
} else {
|
||||||
|
for (var k = 1; k <= lines.length; k++) s += k + '\n';
|
||||||
|
gutter.textContent = lines.length ? s.slice(0, -1) : '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
splitLines();
|
||||||
|
renumber();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 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);
|
||||||
|
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-wrap'] });
|
||||||
|
var lastW = body.getBoundingClientRect().width;
|
||||||
|
if (window.ResizeObserver) {
|
||||||
|
var ro = new ResizeObserver(function () {
|
||||||
|
var w = body.getBoundingClientRect().width;
|
||||||
|
if (Math.abs(w - lastW) < 0.5) return;
|
||||||
|
lastW = w;
|
||||||
|
renumber();
|
||||||
|
});
|
||||||
|
ro.observe(body);
|
||||||
|
} else {
|
||||||
|
window.addEventListener('resize', renumber);
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -55,9 +55,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="float">
|
<div class="float">
|
||||||
<div class="code"><div class="gutter">{{.Gutter}}</div><div class="codebody" id="codebody">{{.ContentHTML}}</div></div>
|
<div class="code" id="code"><div class="gutter" id="gutter">{{.Gutter}}</div><div class="codebody" id="codebody">{{.ContentHTML}}</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="hidden" id="raw-content" value="{{.ContentAttr}}">
|
<input type="hidden" id="raw-content" value="{{.ContentAttr}}">
|
||||||
<script src="/static/paste.js" defer data-paste-id="{{.ID}}"></script>
|
<script src="/static/paste.js" defer data-paste-id="{{.ID}}"></script>
|
||||||
|
<script src="/static/paste-lines.js" defer></script>
|
||||||
{{template "foot" .}}
|
{{template "foot" .}}
|
||||||
|
|||||||
Reference in New Issue
Block a user