@cplieger/web-terminal-engine 2.2.0 → 2.2.1
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/jsr.json +1 -1
- package/package.json +1 -1
- package/src/render.ts +48 -5
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/render.ts
CHANGED
|
@@ -346,7 +346,14 @@ function linkifySpans(
|
|
|
346
346
|
a.href = match[0];
|
|
347
347
|
a.target = "_blank";
|
|
348
348
|
a.rel = "noopener noreferrer";
|
|
349
|
-
|
|
349
|
+
// Auto-detected bare URLs are tightly scoped to the matched URL text
|
|
350
|
+
// (never a padded/bordered region), so `.term-autolink` keeps a persistent
|
|
351
|
+
// underline for discoverability. OSC 8 hyperlinks (buildRowSpans below) get
|
|
352
|
+
// only `.term-link`, which the UI underlines on hover — an app can attach a
|
|
353
|
+
// single OSC 8 link to a whole region (e.g. a URL wrapping inside a table
|
|
354
|
+
// cell, where the link stays open across the cell padding/borders), and a
|
|
355
|
+
// persistent underline would then bleed across the cell/row.
|
|
356
|
+
a.className = "term-link term-autolink";
|
|
350
357
|
a.textContent = match[0];
|
|
351
358
|
// Copy inline styles from the source span
|
|
352
359
|
a.style.cssText = span.style.cssText;
|
|
@@ -364,6 +371,29 @@ function linkifySpans(
|
|
|
364
371
|
return out;
|
|
365
372
|
}
|
|
366
373
|
|
|
374
|
+
// A hyperlink run is "link text" only if it has at least one glyph that is not
|
|
375
|
+
// whitespace and not a box-drawing (U+2500–U+257F) or block-element
|
|
376
|
+
// (U+2580–U+259F) character. Terminal table structure (borders `│`, padding,
|
|
377
|
+
// empty columns, margins) is made of exactly those, and an app may keep an OSC 8
|
|
378
|
+
// hyperlink open across it while a URL wraps; such decorative runs are not
|
|
379
|
+
// anchored, so the link decoration hugs the actual text instead of bleeding
|
|
380
|
+
// across the cell/row.
|
|
381
|
+
function runHasLinkText(spans: (HTMLSpanElement | HTMLAnchorElement)[]): boolean {
|
|
382
|
+
for (const s of spans) {
|
|
383
|
+
for (const ch of s.textContent) {
|
|
384
|
+
if (/\s/.test(ch)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
388
|
+
if (cp >= 0x2500 && cp <= 0x259f) {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
367
397
|
// --- Build row DOM ---
|
|
368
398
|
function buildRowSpans(runs: WireRun[], cursorAt: number): (HTMLSpanElement | HTMLAnchorElement)[] {
|
|
369
399
|
const out: (HTMLSpanElement | HTMLAnchorElement)[] = [];
|
|
@@ -509,20 +539,33 @@ function buildRowSpans(runs: WireRun[], cursorAt: number): (HTMLSpanElement | HT
|
|
|
509
539
|
col++;
|
|
510
540
|
}
|
|
511
541
|
flush();
|
|
512
|
-
// Wrap
|
|
542
|
+
// Wrap this run's spans in an <a> when it has a hyperlink URL — but only if
|
|
543
|
+
// the run actually contains link *text*. An OSC 8 hyperlink can stay open
|
|
544
|
+
// across a whole region the app never meant as the clickable "link": a URL
|
|
545
|
+
// that wraps inside a table cell keeps the link open across the cell padding,
|
|
546
|
+
// the borders `│` and the empty adjacent column. Anchoring those decorative
|
|
547
|
+
// cells makes the link's decoration (the underline) bleed across the cell and,
|
|
548
|
+
// on wrap, the full row. Skip them so the anchor — and its underline, at rest
|
|
549
|
+
// or on hover — hugs the visible link text (they stay as plain, non-clickable
|
|
550
|
+
// spans; the wrapped URL's text runs each still carry the full href).
|
|
513
551
|
const href = run.u && /^https?:\/\//i.test(run.u) ? run.u : null;
|
|
514
|
-
|
|
552
|
+
const runSpans = out.splice(runStartIdx);
|
|
553
|
+
if (href && runHasLinkText(runSpans)) {
|
|
515
554
|
const a = document.createElement("a");
|
|
516
555
|
a.href = href;
|
|
517
556
|
a.target = "_blank";
|
|
518
557
|
a.rel = "noopener";
|
|
558
|
+
// OSC 8 app hyperlink: base `.term-link`. Auto-detected URLs additionally
|
|
559
|
+
// get `.term-autolink` (see linkifySpans).
|
|
519
560
|
a.className = "term-link";
|
|
520
|
-
// Move spans from this run into the anchor.
|
|
521
|
-
const runSpans = out.splice(runStartIdx);
|
|
522
561
|
for (const s of runSpans) {
|
|
523
562
|
a.appendChild(s);
|
|
524
563
|
}
|
|
525
564
|
out.push(a);
|
|
565
|
+
} else {
|
|
566
|
+
for (const s of runSpans) {
|
|
567
|
+
out.push(s);
|
|
568
|
+
}
|
|
526
569
|
}
|
|
527
570
|
}
|
|
528
571
|
if (cursorAt >= 0 && col <= cursorAt) {
|