github-search-cli 9.7.1 → 9.8.0

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/dist/cli.js CHANGED
@@ -1538,7 +1538,7 @@ var cached = null;
1538
1538
  function getVersion() {
1539
1539
  if (cached) return cached;
1540
1540
  if (true) {
1541
- cached = "9.7.1";
1541
+ cached = "9.8.0";
1542
1542
  return cached;
1543
1543
  }
1544
1544
  try {
@@ -2382,8 +2382,8 @@ var PER_REPO = 5;
2382
2382
  function loadCache() {
2383
2383
  return readJSON(RELEASES_FILE, {});
2384
2384
  }
2385
- function saveCache(cache) {
2386
- writeJSON(RELEASES_FILE, cache);
2385
+ function saveCache(cache2) {
2386
+ writeJSON(RELEASES_FILE, cache2);
2387
2387
  }
2388
2388
  function toTracked(raw) {
2389
2389
  return raw.filter((r) => r.draft !== true).map((r) => ({
@@ -2414,13 +2414,13 @@ async function fetchReleases(fullName, etag, token) {
2414
2414
  }
2415
2415
  async function checkReleases(opts = {}) {
2416
2416
  const bookmarks = getBookmarks();
2417
- const cache = loadCache();
2417
+ const cache2 = loadCache();
2418
2418
  const config = loadConfig();
2419
2419
  const token = opts.token ?? config.githubToken ?? process.env.GITHUB_TOKEN;
2420
2420
  const results2 = [];
2421
2421
  for (const bm of bookmarks) {
2422
2422
  try {
2423
- const prev = cache[bm.repo.fullName];
2423
+ const prev = cache2[bm.repo.fullName];
2424
2424
  const lastSeen = getLastSeen(bm.repo.fullName);
2425
2425
  const isNew = (r) => !r.publishedAt || new Date(r.publishedAt).getTime() > lastSeen;
2426
2426
  const { status, etag, releases } = await fetchReleases(
@@ -2443,7 +2443,7 @@ async function checkReleases(opts = {}) {
2443
2443
  });
2444
2444
  continue;
2445
2445
  }
2446
- cache[bm.repo.fullName] = { checkedAt: Date.now(), etag, releases };
2446
+ cache2[bm.repo.fullName] = { checkedAt: Date.now(), etag, releases };
2447
2447
  results2.push({
2448
2448
  fullName: bm.repo.fullName,
2449
2449
  newReleases: releases.filter(isNew)
@@ -2456,7 +2456,7 @@ async function checkReleases(opts = {}) {
2456
2456
  });
2457
2457
  }
2458
2458
  }
2459
- saveCache(cache);
2459
+ saveCache(cache2);
2460
2460
  return results2;
2461
2461
  }
2462
2462
  async function checkAndNotify(opts = {}) {
@@ -2473,9 +2473,9 @@ async function checkAndNotify(opts = {}) {
2473
2473
  return results2;
2474
2474
  }
2475
2475
  function allCachedReleases() {
2476
- const cache = loadCache();
2476
+ const cache2 = loadCache();
2477
2477
  const bookmarks = getBookmarks();
2478
- return Object.entries(cache).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
2478
+ return Object.entries(cache2).filter(([fullName]) => bookmarks.some((b) => b.repo.fullName === fullName)).flatMap(([fullName, s]) => s.releases.map((r) => ({ ...r, fullName }))).sort(
2479
2479
  (a, b) => new Date(b.publishedAt).getTime() - new Date(a.publishedAt).getTime()
2480
2480
  );
2481
2481
  }
@@ -2953,8 +2953,8 @@ import { fileURLToPath as fileURLToPath4 } from "url";
2953
2953
  // src/tui.ts
2954
2954
  import {
2955
2955
  createCliRenderer as createCliRenderer3,
2956
- BoxRenderable as BoxRenderable4,
2957
- TextRenderable as TextRenderable4,
2956
+ BoxRenderable as BoxRenderable5,
2957
+ TextRenderable as TextRenderable5,
2958
2958
  InputRenderable,
2959
2959
  SelectRenderable,
2960
2960
  ScrollBoxRenderable as ScrollBoxRenderable3
@@ -3059,7 +3059,7 @@ function buildHelpSections() {
3059
3059
  { keys: "Limit", action: "Cycle limit: 10 \u2192 25 \u2192 50 \u2192 100" },
3060
3060
  { keys: "Refresh", action: "Re-run current search" },
3061
3061
  { keys: "Graph", action: "Toggle commit activity chart" },
3062
- { keys: "Readme", action: "Full README viewer" },
3062
+ { keys: "Readme", action: "Full markdown README viewer" },
3063
3063
  { keys: "Deep-dive", action: "Languages + contributors + excerpt" },
3064
3064
  { keys: "Bookmark", action: "Save / unsave selected repo" },
3065
3065
  { keys: "Compare", action: "Add to / remove from comparison" },
@@ -3099,6 +3099,7 @@ function buildHelpSections() {
3099
3099
  { keys: "d", action: "Delete entry (history, saved)" },
3100
3100
  { keys: "\u2191 \u2193 / j k", action: "Navigate overlay list" },
3101
3101
  { keys: "\u2190 \u2192 / h l", action: "Navigate tabs / panels" },
3102
+ { keys: "i", action: "Toggle inline images (README viewer)" },
3102
3103
  {
3103
3104
  keys: "PgUp/PgDn",
3104
3105
  action: "Scroll update notes; Home/End jump to ends"
@@ -3260,170 +3261,731 @@ function restoreSession() {
3260
3261
  return readJSON(SESSION_FILE, null);
3261
3262
  }
3262
3263
 
3263
- // src/markdown-render.ts
3264
- function renderMarkdown(md, opts = {}) {
3265
- const width = opts.width ?? 80;
3266
- const lines = md.split("\n");
3267
- const out = [];
3268
- let inCodeBlock = false;
3269
- let codeLang = "";
3270
- let codeLines = [];
3271
- let inTable = false;
3272
- let tableRows = [];
3273
- function flushCode() {
3274
- if (codeLines.length === 0) return;
3275
- const maxLen = Math.max(...codeLines.map((l) => l.length), codeLang.length);
3276
- const barW = Math.min(maxLen + 4, width - 2);
3277
- out.push(`\u250C${"\u2500".repeat(barW)}\u2510`);
3278
- if (codeLang) out.push(`\u2502 ${codeLang.padEnd(barW - 1)}\u2502`);
3279
- for (const cl of codeLines) {
3280
- out.push(`\u2502 ${cl.padEnd(barW - 1)}\u2502`);
3281
- }
3282
- out.push(`\u2514${"\u2500".repeat(barW)}\u2518`);
3283
- out.push("");
3284
- codeLines = [];
3285
- codeLang = "";
3286
- }
3287
- function flushTable() {
3288
- if (tableRows.length === 0) return;
3289
- const dataRows = tableRows.filter(
3290
- (r) => !r.every((c) => /^[\s\-:|]+$/.test(c))
3264
+ // src/markdown-view.ts
3265
+ import {
3266
+ BoxRenderable as BoxRenderable3,
3267
+ MarkdownRenderable,
3268
+ RGBA,
3269
+ StyledText as StyledText3,
3270
+ SyntaxStyle,
3271
+ TextRenderable as TextRenderable3,
3272
+ createTextAttributes
3273
+ } from "@opentui/core";
3274
+
3275
+ // src/png.ts
3276
+ import { inflateSync } from "zlib";
3277
+ var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
3278
+ function channelsForColorType(colorType) {
3279
+ switch (colorType) {
3280
+ case 0:
3281
+ return 1;
3282
+ // greyscale
3283
+ case 2:
3284
+ return 3;
3285
+ // truecolour
3286
+ case 3:
3287
+ return 1;
3288
+ // palette index
3289
+ case 4:
3290
+ return 2;
3291
+ // greyscale + alpha
3292
+ case 6:
3293
+ return 4;
3294
+ // truecolour + alpha
3295
+ default:
3296
+ throw new Error(`Unsupported PNG colour type ${colorType}`);
3297
+ }
3298
+ }
3299
+ function paeth(a, b, c) {
3300
+ const p = a + b - c;
3301
+ const pa = Math.abs(p - a);
3302
+ const pb = Math.abs(p - b);
3303
+ const pc = Math.abs(p - c);
3304
+ if (pa <= pb && pa <= pc) return a;
3305
+ if (pb <= pc) return b;
3306
+ return c;
3307
+ }
3308
+ function unfilter(raw, height, bytesPerRow, bytesPerPixel) {
3309
+ const out = new Uint8Array(height * bytesPerRow);
3310
+ let src = 0;
3311
+ for (let y = 0; y < height; y++) {
3312
+ const filter = raw[src++];
3313
+ const rowStart = y * bytesPerRow;
3314
+ const prevStart = rowStart - bytesPerRow;
3315
+ for (let x = 0; x < bytesPerRow; x++) {
3316
+ const value = raw[src + x];
3317
+ const left = x >= bytesPerPixel ? out[rowStart + x - bytesPerPixel] : 0;
3318
+ const up = y > 0 ? out[prevStart + x] : 0;
3319
+ const upLeft = y > 0 && x >= bytesPerPixel ? out[prevStart + x - bytesPerPixel] : 0;
3320
+ let result;
3321
+ switch (filter) {
3322
+ case 0:
3323
+ result = value;
3324
+ break;
3325
+ case 1:
3326
+ result = value + left;
3327
+ break;
3328
+ case 2:
3329
+ result = value + up;
3330
+ break;
3331
+ case 3:
3332
+ result = value + (left + up >> 1);
3333
+ break;
3334
+ case 4:
3335
+ result = value + paeth(left, up, upLeft);
3336
+ break;
3337
+ default:
3338
+ throw new Error(`Unsupported PNG filter type ${filter}`);
3339
+ }
3340
+ out[rowStart + x] = result & 255;
3341
+ }
3342
+ src += bytesPerRow;
3343
+ }
3344
+ return out;
3345
+ }
3346
+ function sampleAt(row, offsetInRow, index, bitDepth) {
3347
+ if (bitDepth === 8) return row[offsetInRow + index];
3348
+ if (bitDepth === 16) return row[offsetInRow + index * 2];
3349
+ const bitOffset = index * bitDepth;
3350
+ const byte = row[offsetInRow + (bitOffset >> 3)];
3351
+ const shift = 8 - bitDepth - (bitOffset & 7);
3352
+ return byte >> shift & (1 << bitDepth) - 1;
3353
+ }
3354
+ function decodePng(bytes) {
3355
+ if (bytes.length < 8) throw new Error("PNG too short");
3356
+ for (let i = 0; i < PNG_SIGNATURE.length; i++) {
3357
+ if (bytes[i] !== PNG_SIGNATURE[i]) throw new Error("Not a PNG file");
3358
+ }
3359
+ let width = 0;
3360
+ let height = 0;
3361
+ let bitDepth = 8;
3362
+ let colorType = 6;
3363
+ let interlace = 0;
3364
+ let palette = null;
3365
+ let transparency = null;
3366
+ const idat = [];
3367
+ let offset = 8;
3368
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3369
+ while (offset + 8 <= bytes.length) {
3370
+ const length = view.getUint32(offset);
3371
+ const type = String.fromCharCode(
3372
+ bytes[offset + 4],
3373
+ bytes[offset + 5],
3374
+ bytes[offset + 6],
3375
+ bytes[offset + 7]
3291
3376
  );
3292
- if (dataRows.length === 0) {
3293
- tableRows = [];
3294
- return;
3295
- }
3296
- const colCount = Math.max(...dataRows.map((r) => r.length));
3297
- const colW = [];
3298
- for (let ci = 0; ci < colCount; ci++) {
3299
- colW.push(Math.max(3, ...dataRows.map((r) => (r[ci] ?? "").length)));
3377
+ const dataStart = offset + 8;
3378
+ const dataEnd = dataStart + length;
3379
+ if (dataEnd > bytes.length) throw new Error("Truncated PNG chunk");
3380
+ switch (type) {
3381
+ case "IHDR": {
3382
+ width = view.getUint32(dataStart);
3383
+ height = view.getUint32(dataStart + 4);
3384
+ bitDepth = bytes[dataStart + 8];
3385
+ colorType = bytes[dataStart + 9];
3386
+ interlace = bytes[dataStart + 12];
3387
+ break;
3388
+ }
3389
+ case "PLTE":
3390
+ palette = bytes.subarray(dataStart, dataEnd);
3391
+ break;
3392
+ case "tRNS":
3393
+ transparency = bytes.subarray(dataStart, dataEnd);
3394
+ break;
3395
+ case "IDAT":
3396
+ idat.push(bytes.subarray(dataStart, dataEnd));
3397
+ break;
3398
+ default:
3399
+ break;
3300
3400
  }
3301
- const totalW = colW.reduce((a, b) => a + b, 0) + colCount * 3 - 1;
3302
- if (totalW > width) {
3303
- for (const row of dataRows) {
3304
- out.push(` ${row.map((c) => c.trim()).join(" \u2502 ")}`);
3401
+ offset = dataEnd + 4;
3402
+ if (type === "IEND") break;
3403
+ }
3404
+ if (width === 0 || height === 0) throw new Error("PNG has no IHDR");
3405
+ if (interlace !== 0) throw new Error("Interlaced PNG is not supported");
3406
+ const channels = channelsForColorType(colorType);
3407
+ const bitsPerPixel = channels * bitDepth;
3408
+ const bytesPerRow = Math.ceil(width * bitsPerPixel / 8);
3409
+ const bytesPerPixel = Math.max(1, Math.ceil(bitsPerPixel / 8));
3410
+ const compressed = idat.length === 1 ? idat[0] : concat(idat);
3411
+ const raw = new Uint8Array(inflateSync(compressed));
3412
+ if (raw.length < height * (bytesPerRow + 1)) {
3413
+ throw new Error("Truncated PNG pixel data");
3414
+ }
3415
+ const rows = unfilter(raw, height, bytesPerRow, bytesPerPixel);
3416
+ const out = new Uint8Array(width * height * 4);
3417
+ const maxSample = (1 << Math.min(bitDepth, 8)) - 1;
3418
+ const scale = bitDepth === 16 ? 1 / 257 : 255 / maxSample;
3419
+ for (let y = 0; y < height; y++) {
3420
+ const row = rows.subarray(y * bytesPerRow, (y + 1) * bytesPerRow);
3421
+ for (let x = 0; x < width; x++) {
3422
+ const to8 = (sample) => bitDepth === 8 || bitDepth === 16 ? sample : Math.round(sample * scale);
3423
+ let r;
3424
+ let g;
3425
+ let b;
3426
+ let a = 255;
3427
+ switch (colorType) {
3428
+ case 0: {
3429
+ const grey = to8(sampleAt(row, 0, x, bitDepth));
3430
+ r = g = b = grey;
3431
+ break;
3432
+ }
3433
+ case 2:
3434
+ r = to8(sampleAt(row, 0, x * 3, bitDepth));
3435
+ g = to8(sampleAt(row, 0, x * 3 + 1, bitDepth));
3436
+ b = to8(sampleAt(row, 0, x * 3 + 2, bitDepth));
3437
+ break;
3438
+ case 3: {
3439
+ const index = sampleAt(row, 0, x, bitDepth);
3440
+ const p = index * 3;
3441
+ if (!palette || p + 2 >= palette.length)
3442
+ throw new Error("PNG palette entry missing");
3443
+ r = palette[p];
3444
+ g = palette[p + 1];
3445
+ b = palette[p + 2];
3446
+ if (transparency && index < transparency.length)
3447
+ a = transparency[index];
3448
+ break;
3449
+ }
3450
+ case 4: {
3451
+ const grey = to8(sampleAt(row, 0, x * 2, bitDepth));
3452
+ r = g = b = grey;
3453
+ a = to8(sampleAt(row, 0, x * 2 + 1, bitDepth));
3454
+ break;
3455
+ }
3456
+ default: {
3457
+ r = to8(sampleAt(row, 0, x * 4, bitDepth));
3458
+ g = to8(sampleAt(row, 0, x * 4 + 1, bitDepth));
3459
+ b = to8(sampleAt(row, 0, x * 4 + 2, bitDepth));
3460
+ a = to8(sampleAt(row, 0, x * 4 + 3, bitDepth));
3461
+ break;
3462
+ }
3305
3463
  }
3306
- } else {
3307
- const sep = `\u250C${colW.map((w) => "\u2500".repeat(w + 2)).join("\u252C")}\u2510`;
3308
- const mid = `\u251C${colW.map((w) => "\u2500".repeat(w + 2)).join("\u253C")}\u2524`;
3309
- const end = `\u2514${colW.map((w) => "\u2500".repeat(w + 2)).join("\u2534")}\u2518`;
3310
- out.push(sep);
3311
- dataRows.forEach((row, i) => {
3312
- const cells = colW.map((w, ci) => (row[ci] ?? "").trim().padEnd(w));
3313
- out.push(`\u2502 ${cells.map((c) => ` ${c} `).join("\u2502")}\u2502`);
3314
- if (i === 0) out.push(mid);
3315
- });
3316
- out.push(end);
3317
- }
3318
- out.push("");
3319
- tableRows = [];
3320
- }
3321
- function renderInline(line) {
3322
- line = line.replace(/\*\*(.+?)\*\*/g, "$1");
3323
- line = line.replace(/\*([^*]+?)\*/g, "$1");
3324
- line = line.replace(/`([^`]+)`/g, "\u27E8$1\u27E9");
3325
- line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
3326
- line = line.replace(/!\[([^\]]*)\]\([^)]+\)/g, "\u{1F5BC} $1");
3327
- return line;
3328
- }
3329
- function wrapLine(line, maxW) {
3330
- if (line.length <= maxW) return [line];
3331
- const parts = [];
3332
- let remaining = line;
3333
- while (remaining.length > maxW) {
3334
- let breakAt = maxW;
3335
- const spaceIdx = remaining.lastIndexOf(" ", maxW);
3336
- if (spaceIdx > maxW / 2) breakAt = spaceIdx;
3337
- parts.push(remaining.slice(0, breakAt));
3338
- remaining = remaining.slice(breakAt).replace(/^ /, "");
3339
- }
3340
- if (remaining) parts.push(remaining);
3341
- return parts;
3342
- }
3343
- for (let i = 0; i < lines.length; i++) {
3344
- const line = lines[i];
3345
- if (/^```/.test(line)) {
3346
- if (inCodeBlock) {
3347
- flushCode();
3348
- inCodeBlock = false;
3349
- } else {
3350
- inCodeBlock = true;
3351
- codeLang = line.slice(3).trim();
3464
+ if (colorType === 0 && transparency && transparency.length >= 2) {
3465
+ const key = transparency[0] << 8 | transparency[1];
3466
+ const sample = sampleAt(row, 0, x, bitDepth);
3467
+ if (sample === key) a = 0;
3352
3468
  }
3353
- continue;
3469
+ const o = (y * width + x) * 4;
3470
+ out[o] = r;
3471
+ out[o + 1] = g;
3472
+ out[o + 2] = b;
3473
+ out[o + 3] = a;
3354
3474
  }
3355
- if (inCodeBlock) {
3356
- codeLines.push(line);
3357
- continue;
3358
- }
3359
- if (line.includes("|")) {
3360
- const cells = line.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
3361
- if (cells.length >= 2) {
3362
- inTable = true;
3363
- tableRows.push(cells);
3475
+ }
3476
+ return { width, height, data: out };
3477
+ }
3478
+ function concat(chunks) {
3479
+ let total = 0;
3480
+ for (const c of chunks) total += c.length;
3481
+ const merged = new Uint8Array(total);
3482
+ let offset = 0;
3483
+ for (const c of chunks) {
3484
+ merged.set(c, offset);
3485
+ offset += c.length;
3486
+ }
3487
+ return merged;
3488
+ }
3489
+
3490
+ // src/image-render.ts
3491
+ var DEFAULT_CELL_ASPECT = 0.5;
3492
+ var HALF_BLOCK = "\u2580";
3493
+ function fitImageToCells(imgWidth, imgHeight, opts) {
3494
+ const cellAspect = opts.cellAspect ?? DEFAULT_CELL_ASPECT;
3495
+ const minCols = Math.max(1, Math.min(opts.maxCols, 8));
3496
+ if (imgWidth <= 0 || imgHeight <= 0) {
3497
+ return { cols: minCols, rows: 1 };
3498
+ }
3499
+ const rowsPerCol = cellAspect * (imgHeight / imgWidth);
3500
+ let cols = Math.max(minCols, Math.floor(opts.maxCols));
3501
+ let rows = Math.max(1, Math.round(cols * rowsPerCol));
3502
+ if (rows > opts.maxRows) {
3503
+ rows = Math.max(1, opts.maxRows);
3504
+ cols = Math.max(
3505
+ minCols,
3506
+ Math.min(cols, Math.floor(rows / Math.max(rowsPerCol, 1e-6)))
3507
+ );
3508
+ }
3509
+ return { cols, rows };
3510
+ }
3511
+ function pixelAt(img, x, y, background) {
3512
+ const o = (y * img.width + x) * 4;
3513
+ const a = img.data[o + 3] / 255;
3514
+ if (a >= 1) return [img.data[o], img.data[o + 1], img.data[o + 2]];
3515
+ const mix = (channel, bg) => Math.round(channel * a + bg * (1 - a));
3516
+ return [
3517
+ mix(img.data[o], background[0]),
3518
+ mix(img.data[o + 1], background[1]),
3519
+ mix(img.data[o + 2], background[2])
3520
+ ];
3521
+ }
3522
+ function toHalfBlocks(img, opts = {}) {
3523
+ const background = opts.background ?? [0, 0, 0];
3524
+ const cells = [];
3525
+ for (let row = 0; row * 2 < img.height; row++) {
3526
+ const cellsRow = [];
3527
+ const topY = row * 2;
3528
+ const bottomY = topY + 1;
3529
+ for (let x = 0; x < img.width; x++) {
3530
+ const top = pixelAt(img, x, topY, background);
3531
+ const bottom = bottomY < img.height ? pixelAt(img, x, bottomY, background) : top;
3532
+ cellsRow.push({ top, bottom });
3533
+ }
3534
+ cells.push(cellsRow);
3535
+ }
3536
+ return { cols: img.width, rows: cells.length, cells };
3537
+ }
3538
+ function toImageRuns(image) {
3539
+ return image.cells.map((cells) => {
3540
+ const runs = [];
3541
+ for (const cell of cells) {
3542
+ const last = runs[runs.length - 1];
3543
+ if (last && last.top[0] === cell.top[0] && last.top[1] === cell.top[1] && last.top[2] === cell.top[2] && last.bottom[0] === cell.bottom[0] && last.bottom[1] === cell.bottom[1] && last.bottom[2] === cell.bottom[2]) {
3544
+ last.text += HALF_BLOCK;
3364
3545
  continue;
3365
3546
  }
3366
- } else if (inTable) {
3367
- flushTable();
3368
- inTable = false;
3547
+ runs.push({ text: HALF_BLOCK, top: cell.top, bottom: cell.bottom });
3369
3548
  }
3370
- if (/^(\s*[-*_]){3,}\s*$/.test(line)) {
3371
- out.push("\u2500".repeat(Math.min(width, 40)));
3372
- out.push("");
3373
- continue;
3549
+ return runs;
3550
+ });
3551
+ }
3552
+ var MAX_SOURCE_PIXELS = 2e7;
3553
+ async function rasterizeImage(bytes, opts) {
3554
+ const ImageCtor = globalThis.Bun?.Image;
3555
+ if (!ImageCtor) return null;
3556
+ const source = new ImageCtor(bytes, { maxPixels: MAX_SOURCE_PIXELS });
3557
+ try {
3558
+ const meta = await source.metadata();
3559
+ const { cols, rows } = fitImageToCells(meta.width, meta.height, opts);
3560
+ const png = await source.resize(cols, rows * 2, { fit: "fill" }).png({ compressionLevel: 1 }).bytes();
3561
+ return toHalfBlocks(decodePng(png), { background: opts.background });
3562
+ } catch {
3563
+ return null;
3564
+ }
3565
+ }
3566
+
3567
+ // src/image-loader.ts
3568
+ var MAX_IMAGE_BYTES = 6 * 1024 * 1024;
3569
+ var BADGE_HOST = /(^|\.)(shields\.io|badgen\.net|badge\.fury\.io|codecov\.io|coveralls\.io|travis-ci\.(org|com)|snyk\.io|sonarcloud\.io|david-dm\.(org|io))$/i;
3570
+ var RAW_HOSTS = /* @__PURE__ */ new Set([
3571
+ "raw.githubusercontent.com",
3572
+ "camo.githubusercontent.com",
3573
+ "user-images.githubusercontent.com",
3574
+ "private-user-images.githubusercontent.com",
3575
+ "avatars.githubusercontent.com",
3576
+ "objects.githubusercontent.com"
3577
+ ]);
3578
+ var RASTER_EXT = /\.(png|jpe?g|webp|gif|bmp|avif|heic|tiff?)(?:[?#]|$)/i;
3579
+ var CACHE_LIMIT = 24;
3580
+ var cache = /* @__PURE__ */ new Map();
3581
+ function decodeDataUri(uri) {
3582
+ const match = /^data:([^;,]+)?(;base64)?,(.*)$/is.exec(uri);
3583
+ if (!match) return null;
3584
+ const mime = (match[1] ?? "").toLowerCase();
3585
+ if (mime.includes("svg")) return null;
3586
+ const payload = match[3];
3587
+ try {
3588
+ if (match[2]) return new Uint8Array(Buffer.from(payload, "base64"));
3589
+ return new Uint8Array(Buffer.from(decodeURIComponent(payload), "binary"));
3590
+ } catch {
3591
+ return null;
3592
+ }
3593
+ }
3594
+ function toRawGitHubUrl(url) {
3595
+ if (url.hostname.toLowerCase() !== "github.com") return url;
3596
+ const match = /^\/([^/]+)\/([^/]+)\/(?:blob|raw)\/(.+)$/.exec(url.pathname);
3597
+ if (!match) return url;
3598
+ const [, owner, repo, rest] = match;
3599
+ return new URL(
3600
+ `https://raw.githubusercontent.com/${owner}/${repo}/${rest}${url.search}`
3601
+ );
3602
+ }
3603
+ function resolveRootRelative(path, base) {
3604
+ let parsed;
3605
+ try {
3606
+ parsed = new URL(base);
3607
+ } catch {
3608
+ return null;
3609
+ }
3610
+ if (parsed.hostname.toLowerCase() !== "raw.githubusercontent.com") {
3611
+ return null;
3612
+ }
3613
+ const segments = parsed.pathname.split("/").filter(Boolean);
3614
+ if (segments.length < 3) return null;
3615
+ return new URL(`${parsed.origin}/${segments.slice(0, 3).join("/")}${path}`);
3616
+ }
3617
+ function resolveReadmeImageUrl(raw, base) {
3618
+ const cleaned = raw.trim();
3619
+ if (!cleaned) return null;
3620
+ if (/^data:/i.test(cleaned)) {
3621
+ return isRenderableImageUrl(cleaned) ? cleaned : null;
3622
+ }
3623
+ let url = null;
3624
+ try {
3625
+ url = cleaned.startsWith("/") ? resolveRootRelative(cleaned, base) : new URL(cleaned, base);
3626
+ } catch {
3627
+ return null;
3628
+ }
3629
+ if (!url) return null;
3630
+ url = toRawGitHubUrl(url);
3631
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
3632
+ url.hash = "";
3633
+ const resolved = url.toString();
3634
+ return isRenderableImageUrl(resolved) ? resolved : null;
3635
+ }
3636
+ function isRenderableImageUrl(url) {
3637
+ if (/^data:/i.test(url)) return !/svg/i.test(url.slice(0, 64));
3638
+ let parsed;
3639
+ try {
3640
+ parsed = new URL(url);
3641
+ } catch {
3642
+ return false;
3643
+ }
3644
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
3645
+ if (BADGE_HOST.test(parsed.hostname)) return false;
3646
+ const path = parsed.pathname.toLowerCase();
3647
+ if (path.endsWith(".svg") || path.endsWith(".svgz")) return false;
3648
+ if (parsed.hostname.toLowerCase().endsWith("github.com")) {
3649
+ if (/^\/user-attachments\//.test(path)) return true;
3650
+ if (!RAW_HOSTS.has(parsed.hostname)) {
3651
+ return /\.(png|jpe?g|webp|gif|bmp|avif|heic|tiff?)$/i.test(path);
3374
3652
  }
3375
- const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
3376
- if (headingMatch) {
3377
- const level = headingMatch[1].length;
3378
- const text = renderInline(headingMatch[2]);
3379
- const prefix = "\u2588 ".repeat(level);
3380
- out.push("");
3381
- out.push(prefix + text);
3382
- out.push("");
3383
- continue;
3653
+ }
3654
+ if (/\.[a-z0-9]{1,5}$/.test(path) && !RASTER_EXT.test(path)) return false;
3655
+ return true;
3656
+ }
3657
+ function isGitHubHost(hostname) {
3658
+ return /(^|\.)githubusercontent\.com$|(^|\.)github\.com$/i.test(hostname);
3659
+ }
3660
+ async function fetchImageBytes(url, opts = {}) {
3661
+ const cached2 = cache.get(url);
3662
+ if (cached2) return cached2;
3663
+ const pending = loadImageBytes(url, opts);
3664
+ cache.set(url, pending);
3665
+ while (cache.size > CACHE_LIMIT) {
3666
+ const oldest = cache.keys().next().value;
3667
+ if (oldest === void 0) break;
3668
+ cache.delete(oldest);
3669
+ }
3670
+ return pending;
3671
+ }
3672
+ async function loadImageBytes(url, opts) {
3673
+ if (/^data:/i.test(url)) return decodeDataUri(url);
3674
+ const fetchImpl = opts.fetchImpl ?? fetch;
3675
+ const timeoutMs = opts.timeoutMs ?? 1e4;
3676
+ const controller = new AbortController();
3677
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
3678
+ try {
3679
+ const headers = {
3680
+ "User-Agent": "ghfind/1.0",
3681
+ Accept: "image/*,*/*;q=0.5"
3682
+ };
3683
+ let host = "";
3684
+ try {
3685
+ host = new URL(url).hostname;
3686
+ } catch {
3687
+ return null;
3384
3688
  }
3385
- const bqMatch = line.match(/^>\s?(.*)/);
3386
- if (bqMatch) {
3387
- const inner = renderInline(bqMatch[1]);
3388
- for (const wl of wrapLine(inner, width - 4)) {
3389
- out.push(`\u2502 ${wl}`);
3390
- }
3689
+ if (opts.token && isGitHubHost(host)) {
3690
+ headers.Authorization = `Bearer ${opts.token}`;
3691
+ }
3692
+ const res = await fetchImpl(url, { headers, signal: controller.signal });
3693
+ if (!res.ok) return null;
3694
+ const contentType = res.headers.get("content-type") ?? "";
3695
+ if (contentType && !/^image\//i.test(contentType)) {
3696
+ const githubHost = isGitHubHost(host);
3697
+ if (!githubHost || !/octet-stream/i.test(contentType)) return null;
3698
+ }
3699
+ if (/svg/i.test(contentType)) return null;
3700
+ const declared = Number(res.headers.get("content-length") ?? "0");
3701
+ if (declared > MAX_IMAGE_BYTES) return null;
3702
+ const buffer = new Uint8Array(await res.arrayBuffer());
3703
+ if (buffer.byteLength === 0 || buffer.byteLength > MAX_IMAGE_BYTES) {
3704
+ return null;
3705
+ }
3706
+ return buffer;
3707
+ } catch {
3708
+ return null;
3709
+ } finally {
3710
+ clearTimeout(timer);
3711
+ }
3712
+ }
3713
+ function readmeBaseUrl(owner, repo, branch, path = "README.md") {
3714
+ return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
3715
+ }
3716
+
3717
+ // src/markdown-images.ts
3718
+ var IMG_TAG = /<img\b[^>]*?src\s*=\s*["']([^"']+)["'][^>]*>/gi;
3719
+ var ALT_ATTR = /\balt\s*=\s*["']([^"']*)["']/i;
3720
+ var HTML_TAG = /<\/?[a-z][^>]*>/gi;
3721
+ function imagesFromHtml(raw) {
3722
+ if (!raw || !/<img\b/i.test(raw)) return [];
3723
+ const withoutTags = raw.replace(IMG_TAG, "").replace(/<\/?(?:p|div|center|br|a|picture|source)\b[^>]*>/gi, "").trim();
3724
+ if (withoutTags) return [];
3725
+ const refs = [];
3726
+ for (const match of raw.matchAll(IMG_TAG)) {
3727
+ const tag = match[0];
3728
+ const alt = ALT_ATTR.exec(tag)?.[1] ?? "";
3729
+ refs.push({ href: match[1], alt });
3730
+ }
3731
+ return refs;
3732
+ }
3733
+ function inlinePlainText(tokens) {
3734
+ if (!tokens) return "";
3735
+ let out = "";
3736
+ for (const token of tokens) {
3737
+ if (token.type === "image") continue;
3738
+ if (token.type === "html") {
3739
+ out += (token.raw ?? "").replace(HTML_TAG, "");
3391
3740
  continue;
3392
3741
  }
3393
- const ulMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
3394
- if (ulMatch) {
3395
- const indent = ulMatch[1].length;
3396
- const depth = Math.floor(indent / 2);
3397
- const prefix = `${" ".repeat(depth)}\u2022 `;
3398
- const inner = renderInline(ulMatch[2]);
3399
- for (const wl of wrapLine(inner, width - prefix.length)) {
3400
- out.push(prefix + wl);
3401
- }
3742
+ if (token.type === "br") {
3743
+ out += "\n";
3402
3744
  continue;
3403
3745
  }
3404
- const olMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
3405
- if (olMatch) {
3406
- const indent = olMatch[1].length;
3407
- const depth = Math.floor(indent / 2);
3408
- const prefix = `${" ".repeat(depth)}\u25E6 `;
3409
- const inner = renderInline(olMatch[2]);
3410
- for (const wl of wrapLine(inner, width - prefix.length)) {
3411
- out.push(prefix + wl);
3412
- }
3746
+ if (token.text && (!token.tokens || token.tokens.length === 0)) {
3747
+ out += token.text;
3413
3748
  continue;
3414
3749
  }
3415
- if (line.trim() === "") {
3416
- out.push("");
3750
+ if (token.tokens?.length) {
3751
+ out += inlinePlainText(token.tokens);
3417
3752
  continue;
3418
3753
  }
3419
- const rendered = renderInline(line);
3420
- for (const wl of wrapLine(rendered, width)) {
3421
- out.push(wl);
3754
+ if (token.raw) out += token.raw.replace(HTML_TAG, "");
3755
+ }
3756
+ return out;
3757
+ }
3758
+ function extractImageRefs(token) {
3759
+ const images = [];
3760
+ const collect = (inline2) => {
3761
+ if (inline2.type === "image") {
3762
+ if (inline2.href)
3763
+ images.push({ href: inline2.href, alt: inline2.text ?? "" });
3764
+ return true;
3422
3765
  }
3766
+ if (inline2.type === "html") {
3767
+ const refs = imagesFromHtml(inline2.raw);
3768
+ if (refs.length > 0) {
3769
+ images.push(...refs);
3770
+ return true;
3771
+ }
3772
+ return !(inline2.raw ?? "").replace(HTML_TAG, "").trim();
3773
+ }
3774
+ if (inline2.type === "text" || inline2.type === "escape") {
3775
+ return !(inline2.text ?? "").trim();
3776
+ }
3777
+ return false;
3778
+ };
3779
+ if (token.type === "image" && token.href) {
3780
+ return [{ href: token.href, alt: token.text ?? "" }];
3781
+ }
3782
+ if (token.type === "html") {
3783
+ const refs = imagesFromHtml(token.raw);
3784
+ return refs.length > 0 ? refs : null;
3423
3785
  }
3424
- if (inCodeBlock) flushCode();
3425
- if (inTable) flushTable();
3426
- return out.join("\n");
3786
+ const inline = token.tokens ?? [];
3787
+ if (inline.length === 0) return null;
3788
+ for (const child of inline) {
3789
+ if (!collect(child)) return null;
3790
+ }
3791
+ return images.length > 0 ? images : null;
3792
+ }
3793
+
3794
+ // src/markdown-view.ts
3795
+ var MAX_IMAGE_ROWS = 24;
3796
+ function createMarkdownStyle(colors2) {
3797
+ const surface = colors2.surface ?? colors2.bg;
3798
+ return SyntaxStyle.fromStyles({
3799
+ default: { fg: colors2.text ?? "#ffffff" },
3800
+ conceal: { fg: colors2.muted ?? "#888888" },
3801
+ "markup.heading": { fg: colors2.accent ?? colors2.blue, bold: true },
3802
+ "markup.strong": { fg: colors2.text ?? "#ffffff", bold: true },
3803
+ "markup.italic": { fg: colors2.text ?? "#ffffff", italic: true },
3804
+ "markup.strikethrough": { fg: colors2.muted ?? "#888888", dim: true },
3805
+ "markup.raw": { fg: colors2.green ?? "#a6e3a1", bg: surface },
3806
+ "markup.raw.block": { fg: colors2.green ?? "#a6e3a1" },
3807
+ "markup.link": { fg: colors2.accent ?? colors2.blue },
3808
+ "markup.link.label": {
3809
+ fg: colors2.accent ?? colors2.blue,
3810
+ underline: true
3811
+ },
3812
+ "markup.link.url": { fg: colors2.muted ?? "#888888", underline: true },
3813
+ "markup.quote": { fg: colors2.muted ?? "#888888", italic: true },
3814
+ "markup.list": { fg: colors2.blue ?? colors2.accent }
3815
+ });
3816
+ }
3817
+ function hexToRgb(hex) {
3818
+ const fallback = [0, 0, 0];
3819
+ if (!hex) return fallback;
3820
+ const match = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
3821
+ if (!match) return fallback;
3822
+ const value = Number.parseInt(match[1], 16);
3823
+ return [value >> 16 & 255, value >> 8 & 255, value & 255];
3824
+ }
3825
+ function rgba(color) {
3826
+ return RGBA.fromValues(color[0], color[1], color[2], 255);
3827
+ }
3828
+ function imageStyledText(image) {
3829
+ const chunks = [];
3830
+ toImageRuns(image).forEach((runs, rowIndex) => {
3831
+ if (rowIndex > 0) chunks.push({ __isChunk: true, text: "\n" });
3832
+ for (const run2 of runs) {
3833
+ chunks.push({
3834
+ __isChunk: true,
3835
+ text: run2.text,
3836
+ fg: rgba(run2.top),
3837
+ bg: rgba(run2.bottom)
3838
+ });
3839
+ }
3840
+ });
3841
+ return new StyledText3(chunks);
3842
+ }
3843
+ function createMarkdownView(opts) {
3844
+ const { renderer, colors: colors2 } = opts;
3845
+ const maxImageRows = opts.maxImageRows ?? MAX_IMAGE_ROWS;
3846
+ const rasterCache = /* @__PURE__ */ new Map();
3847
+ let imagesEnabled = opts.images ?? true;
3848
+ let imageBase = opts.imageBase;
3849
+ const text = () => colors2.text ?? "#ffffff";
3850
+ const bg = () => opts.background ?? colors2.bg ?? "#000000";
3851
+ const muted = () => colors2.muted ?? "#888888";
3852
+ const renderable = new MarkdownRenderable(renderer, {
3853
+ content: "",
3854
+ syntaxStyle: createMarkdownStyle(colors2),
3855
+ fg: text(),
3856
+ bg: bg(),
3857
+ conceal: true,
3858
+ streaming: true,
3859
+ internalBlockMode: "top-level",
3860
+ // Borderless columns are the top-level default; boxed tables read better
3861
+ // for the multi-column tables READMEs use.
3862
+ tableOptions: { style: "grid" },
3863
+ width: "100%"
3864
+ });
3865
+ const caption = (label, color = muted()) => new TextRenderable3(renderer, {
3866
+ content: ` ${label}`,
3867
+ fg: color,
3868
+ bg: bg(),
3869
+ wrapMode: "word",
3870
+ flexShrink: 0,
3871
+ width: "100%"
3872
+ });
3873
+ const rasterize = (url) => {
3874
+ const maxCols = Math.max(8, Math.floor(opts.getImageWidth?.() ?? 100));
3875
+ const key = `${maxCols}|${url}`;
3876
+ const cached2 = rasterCache.get(key);
3877
+ if (cached2) return cached2;
3878
+ const pending = (async () => {
3879
+ const loader = opts.loadImage;
3880
+ if (!loader) return null;
3881
+ const bytes = await loader(url);
3882
+ if (!bytes) return null;
3883
+ return rasterizeImage(bytes, {
3884
+ maxCols,
3885
+ maxRows: maxImageRows,
3886
+ background: hexToRgb(bg())
3887
+ });
3888
+ })();
3889
+ rasterCache.set(key, pending);
3890
+ return pending;
3891
+ };
3892
+ const imageNode = (image) => {
3893
+ const label = `\u{1F5BC} ${image.alt.trim() || "image"}`;
3894
+ const url = imageBase ? resolveReadmeImageUrl(image.href, imageBase) : null;
3895
+ if (!imagesEnabled || !url || !opts.loadImage) return caption(label);
3896
+ const container = new BoxRenderable3(renderer, {
3897
+ flexDirection: "column",
3898
+ flexShrink: 0,
3899
+ width: "100%"
3900
+ });
3901
+ const placeholder = caption(`${label} (loading\u2026)`);
3902
+ container.add(placeholder);
3903
+ void rasterize(url).then(
3904
+ (cells) => {
3905
+ console.error(
3906
+ `[dbg] image resolved: cells=${cells ? `${cells.cols}x${cells.rows}` : "null"} destroyed=${container.isDestroyed} parent=${!!container.parent} enabled=${imagesEnabled}`
3907
+ );
3908
+ if (container.isDestroyed || !container.parent) return;
3909
+ container.remove(placeholder);
3910
+ placeholder.destroy();
3911
+ container.add(caption(label));
3912
+ if (cells) {
3913
+ container.add(
3914
+ new TextRenderable3(renderer, {
3915
+ content: imageStyledText(cells),
3916
+ bg: bg(),
3917
+ wrapMode: "none",
3918
+ selectable: false,
3919
+ flexShrink: 0
3920
+ })
3921
+ );
3922
+ }
3923
+ renderable.requestRender();
3924
+ },
3925
+ (err) => {
3926
+ console.error("[dbg] image promise rejected:", err);
3927
+ }
3928
+ );
3929
+ return container;
3930
+ };
3931
+ const headingNode = (token) => {
3932
+ const heading = inlinePlainText(token.tokens).trim();
3933
+ if (!heading) return void 0;
3934
+ const depth = token.depth ?? 1;
3935
+ const color = depth <= 2 ? colors2.accent : colors2.teal ?? colors2.accent;
3936
+ return new TextRenderable3(renderer, {
3937
+ content: new StyledText3([
3938
+ {
3939
+ __isChunk: true,
3940
+ text: heading,
3941
+ fg: RGBA.fromHex(color ?? "#ffffff"),
3942
+ attributes: createTextAttributes({
3943
+ bold: true,
3944
+ underline: depth === 1
3945
+ })
3946
+ }
3947
+ ]),
3948
+ bg: bg(),
3949
+ wrapMode: "word",
3950
+ flexShrink: 0,
3951
+ width: "100%"
3952
+ });
3953
+ };
3954
+ const buildRenderNode = () => (token, _context) => {
3955
+ if (token.type === "heading") return headingNode(token);
3956
+ const images = extractImageRefs(token);
3957
+ if (images && images.length > 0) {
3958
+ const box = new BoxRenderable3(renderer, {
3959
+ flexDirection: "column",
3960
+ flexShrink: 0,
3961
+ width: "100%",
3962
+ marginTop: 1
3963
+ });
3964
+ for (const image of images) box.add(imageNode(image));
3965
+ return box;
3966
+ }
3967
+ return void 0;
3968
+ };
3969
+ renderable.renderNode = buildRenderNode();
3970
+ return {
3971
+ renderable,
3972
+ get imagesEnabled() {
3973
+ return imagesEnabled;
3974
+ },
3975
+ setContent(markdown, base) {
3976
+ if (base !== void 0) imageBase = base;
3977
+ renderable.syntaxStyle = createMarkdownStyle(colors2);
3978
+ renderable.fg = text();
3979
+ renderable.bg = bg();
3980
+ renderable.content = markdown;
3981
+ },
3982
+ setImagesEnabled(enabled) {
3983
+ if (imagesEnabled === enabled) return;
3984
+ imagesEnabled = enabled;
3985
+ renderable.renderNode = buildRenderNode();
3986
+ renderable.requestRender();
3987
+ }
3988
+ };
3427
3989
  }
3428
3990
 
3429
3991
  // src/explore.ts
@@ -3746,7 +4308,7 @@ function fetchReleaseNotes(version) {
3746
4308
  }
3747
4309
 
3748
4310
  // src/landing.ts
3749
- import { BoxRenderable as BoxRenderable3, TextRenderable as TextRenderable3 } from "@opentui/core";
4311
+ import { BoxRenderable as BoxRenderable4, TextRenderable as TextRenderable4 } from "@opentui/core";
3750
4312
  function landingKeysActive(mode, overlay) {
3751
4313
  return mode === "landing" && overlay === "none";
3752
4314
  }
@@ -3878,13 +4440,13 @@ async function launchBrowser(theme_override) {
3878
4440
  let deepDiveActive = false;
3879
4441
  const compareList = [];
3880
4442
  const _quitArmed = false;
3881
- const header = new TextRenderable4(renderer, {
4443
+ const header = new TextRenderable5(renderer, {
3882
4444
  content: " ghfind \u2014 GitHub repo browser [/]search [Space]menu [\u2192]open [?]help [q]uit",
3883
4445
  bg: colors.bg,
3884
4446
  fg: colors.muted,
3885
4447
  height: 1
3886
4448
  });
3887
- const trendingTabBox = new BoxRenderable4(renderer, {
4449
+ const trendingTabBox = new BoxRenderable5(renderer, {
3888
4450
  flexDirection: "row",
3889
4451
  height: 1,
3890
4452
  backgroundColor: colors.surface,
@@ -3899,7 +4461,7 @@ async function launchBrowser(theme_override) {
3899
4461
  TAB_NAMES.forEach((name, i) => {
3900
4462
  const isActive = name === trendingTab;
3901
4463
  const label = ` ${name} `;
3902
- const tt = new TextRenderable4(renderer, {
4464
+ const tt = new TextRenderable5(renderer, {
3903
4465
  content: label,
3904
4466
  fg: isActive ? colors.bg : colors.muted,
3905
4467
  bg: isActive ? colors.blue : colors.bg,
@@ -3908,7 +4470,7 @@ async function launchBrowser(theme_override) {
3908
4470
  trendingTabTexts.push(tt);
3909
4471
  trendingTabBox.add(tt);
3910
4472
  if (i < TAB_NAMES.length - 1) {
3911
- const sp = new TextRenderable4(renderer, {
4473
+ const sp = new TextRenderable5(renderer, {
3912
4474
  content: " ",
3913
4475
  fg: colors.muted,
3914
4476
  bg: colors.bg,
@@ -3921,7 +4483,7 @@ async function launchBrowser(theme_override) {
3921
4483
  }
3922
4484
  root.add(header);
3923
4485
  root.add(trendingTabBox);
3924
- const searchBox = new BoxRenderable4(renderer, {
4486
+ const searchBox = new BoxRenderable5(renderer, {
3925
4487
  visible: true,
3926
4488
  width: "100%",
3927
4489
  flexDirection: "row",
@@ -3940,7 +4502,7 @@ async function launchBrowser(theme_override) {
3940
4502
  });
3941
4503
  searchBox.add(searchInput);
3942
4504
  root.add(searchBox);
3943
- const toolbarText = new TextRenderable4(renderer, {
4505
+ const toolbarText = new TextRenderable5(renderer, {
3944
4506
  content: formatToolbar(currentSort, currentLimit, totalCount),
3945
4507
  visible: true,
3946
4508
  // hidden in trending mode
@@ -3949,14 +4511,14 @@ async function launchBrowser(theme_override) {
3949
4511
  paddingX: 1
3950
4512
  });
3951
4513
  root.add(toolbarText);
3952
- const body = new BoxRenderable4(renderer, {
4514
+ const body = new BoxRenderable5(renderer, {
3953
4515
  flexDirection: "row",
3954
4516
  flexGrow: 1,
3955
4517
  gap: 1,
3956
4518
  paddingY: 1,
3957
4519
  paddingX: 1
3958
4520
  });
3959
- const resultsBox = new BoxRenderable4(renderer, {
4521
+ const resultsBox = new BoxRenderable5(renderer, {
3960
4522
  border: true,
3961
4523
  borderColor: colors.border,
3962
4524
  title: " Results ",
@@ -3985,7 +4547,7 @@ async function launchBrowser(theme_override) {
3985
4547
  itemSpacing: 0
3986
4548
  });
3987
4549
  resultsBox.add(resultsSelect);
3988
- const detailBox = new BoxRenderable4(renderer, {
4550
+ const detailBox = new BoxRenderable5(renderer, {
3989
4551
  width: "50%",
3990
4552
  flexDirection: "column",
3991
4553
  paddingX: 1,
@@ -3993,7 +4555,7 @@ async function launchBrowser(theme_override) {
3993
4555
  focusable: false,
3994
4556
  backgroundColor: colors.surface
3995
4557
  });
3996
- const detailText = new TextRenderable4(renderer, {
4558
+ const detailText = new TextRenderable5(renderer, {
3997
4559
  content: "",
3998
4560
  fg: colors.text,
3999
4561
  wrapMode: "none"
@@ -4002,7 +4564,7 @@ async function launchBrowser(theme_override) {
4002
4564
  body.add(resultsBox);
4003
4565
  body.add(detailBox);
4004
4566
  root.add(body);
4005
- const landingBox = new BoxRenderable4(renderer, {
4567
+ const landingBox = new BoxRenderable5(renderer, {
4006
4568
  visible: false,
4007
4569
  flexGrow: 1,
4008
4570
  flexDirection: "column",
@@ -4010,7 +4572,7 @@ async function launchBrowser(theme_override) {
4010
4572
  justifyContent: "center"
4011
4573
  });
4012
4574
  root.add(landingBox);
4013
- const landingBanner = new TextRenderable4(renderer, {
4575
+ const landingBanner = new TextRenderable5(renderer, {
4014
4576
  content: [
4015
4577
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
4016
4578
  "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
@@ -4060,7 +4622,7 @@ async function launchBrowser(theme_override) {
4060
4622
  const landingCards = [];
4061
4623
  for (let i = 0; i < landingOptions.length; i++) {
4062
4624
  const opt = landingOptions[i];
4063
- const card = new BoxRenderable4(renderer, {
4625
+ const card = new BoxRenderable5(renderer, {
4064
4626
  flexDirection: "column",
4065
4627
  height: 5,
4066
4628
  width: "60%",
@@ -4072,18 +4634,18 @@ async function launchBrowser(theme_override) {
4072
4634
  });
4073
4635
  landingCards.push(card);
4074
4636
  landingBox.add(card);
4075
- const spacerTop = new TextRenderable4(renderer, { content: "", height: 1 });
4076
- const titleLine = new TextRenderable4(renderer, {
4637
+ const spacerTop = new TextRenderable5(renderer, { content: "", height: 1 });
4638
+ const titleLine = new TextRenderable5(renderer, {
4077
4639
  content: opt.title,
4078
4640
  fg: i === 0 ? colors.accent : colors.text,
4079
4641
  height: 1
4080
4642
  });
4081
- const spacerBot = new TextRenderable4(renderer, { content: "", height: 1 });
4643
+ const spacerBot = new TextRenderable5(renderer, { content: "", height: 1 });
4082
4644
  card.add(spacerTop);
4083
4645
  card.add(titleLine);
4084
4646
  card.add(spacerBot);
4085
4647
  }
4086
- const landingHint = new TextRenderable4(renderer, {
4648
+ const landingHint = new TextRenderable5(renderer, {
4087
4649
  content: " \u2191\u2193 Navigate \u21E9 Select [b]ookmarks [?]help [q]uit",
4088
4650
  fg: colors.muted,
4089
4651
  bg: colors.bg,
@@ -4226,7 +4788,7 @@ async function launchBrowser(theme_override) {
4226
4788
  renderer.requestRender();
4227
4789
  }
4228
4790
  const helpSections = buildHelpSections();
4229
- const helpBox = new BoxRenderable4(renderer, {
4791
+ const helpBox = new BoxRenderable5(renderer, {
4230
4792
  visible: false,
4231
4793
  flexGrow: 1,
4232
4794
  backgroundColor: colors.bg
@@ -4248,7 +4810,7 @@ async function launchBrowser(theme_override) {
4248
4810
  }
4249
4811
  });
4250
4812
  for (const section of helpSections) {
4251
- const sectionBox = new BoxRenderable4(renderer, {
4813
+ const sectionBox = new BoxRenderable5(renderer, {
4252
4814
  title: section.title,
4253
4815
  titleColor: colors[section.titleColor] ?? section.titleColor,
4254
4816
  flexDirection: "column",
@@ -4260,7 +4822,7 @@ async function launchBrowser(theme_override) {
4260
4822
  for (const row of section.rows) {
4261
4823
  const keys = row.keys.padEnd(HELP_KEYS_COLUMN);
4262
4824
  sectionBox.add(
4263
- new TextRenderable4(renderer, {
4825
+ new TextRenderable5(renderer, {
4264
4826
  content: ` ${keys} ${row.action}`,
4265
4827
  fg: colors.text
4266
4828
  })
@@ -4268,7 +4830,7 @@ async function launchBrowser(theme_override) {
4268
4830
  }
4269
4831
  if (section.note) {
4270
4832
  sectionBox.add(
4271
- new TextRenderable4(renderer, {
4833
+ new TextRenderable5(renderer, {
4272
4834
  content: ` ${section.note}`,
4273
4835
  fg: colors.muted
4274
4836
  })
@@ -4278,7 +4840,7 @@ async function launchBrowser(theme_override) {
4278
4840
  }
4279
4841
  helpBox.add(helpScroll);
4280
4842
  root.add(helpBox);
4281
- const historyBox = new BoxRenderable4(renderer, {
4843
+ const historyBox = new BoxRenderable5(renderer, {
4282
4844
  visible: false,
4283
4845
  flexGrow: 1,
4284
4846
  backgroundColor: colors.bg,
@@ -4313,7 +4875,7 @@ async function launchBrowser(theme_override) {
4313
4875
  }
4314
4876
  historySelect.setSelectedIndex(0);
4315
4877
  }
4316
- const bookmarksBox = new BoxRenderable4(renderer, {
4878
+ const bookmarksBox = new BoxRenderable5(renderer, {
4317
4879
  visible: false,
4318
4880
  flexGrow: 1,
4319
4881
  backgroundColor: colors.bg,
@@ -4348,7 +4910,7 @@ async function launchBrowser(theme_override) {
4348
4910
  }
4349
4911
  bookmarksSelect.setSelectedIndex(0);
4350
4912
  }
4351
- const savedBox = new BoxRenderable4(renderer, {
4913
+ const savedBox = new BoxRenderable5(renderer, {
4352
4914
  visible: false,
4353
4915
  flexGrow: 1,
4354
4916
  backgroundColor: colors.bg,
@@ -4368,7 +4930,7 @@ async function launchBrowser(theme_override) {
4368
4930
  });
4369
4931
  savedBox.add(savedSelect);
4370
4932
  root.add(savedBox);
4371
- const topicsBox = new BoxRenderable4(renderer, {
4933
+ const topicsBox = new BoxRenderable5(renderer, {
4372
4934
  visible: false,
4373
4935
  flexGrow: 1,
4374
4936
  backgroundColor: colors.bg,
@@ -4403,7 +4965,7 @@ async function launchBrowser(theme_override) {
4403
4965
  }
4404
4966
  topicsSelect.setSelectedIndex(0);
4405
4967
  }
4406
- const exportBox = new BoxRenderable4(renderer, {
4968
+ const exportBox = new BoxRenderable5(renderer, {
4407
4969
  visible: false,
4408
4970
  flexGrow: 1,
4409
4971
  backgroundColor: colors.bg,
@@ -4440,28 +5002,28 @@ async function launchBrowser(theme_override) {
4440
5002
  });
4441
5003
  exportBox.add(exportSelect);
4442
5004
  root.add(exportBox);
4443
- const compareBox = new BoxRenderable4(renderer, {
5005
+ const compareBox = new BoxRenderable5(renderer, {
4444
5006
  visible: false,
4445
5007
  flexGrow: 1,
4446
5008
  backgroundColor: colors.bg,
4447
5009
  title: " Comparison ",
4448
5010
  titleColor: colors.yellow
4449
5011
  });
4450
- const compareText = new TextRenderable4(renderer, {
5012
+ const compareText = new TextRenderable5(renderer, {
4451
5013
  content: "",
4452
5014
  fg: colors.text,
4453
5015
  bg: colors.bg
4454
5016
  });
4455
5017
  compareBox.add(compareText);
4456
5018
  root.add(compareBox);
4457
- const notifsBox = new BoxRenderable4(renderer, {
5019
+ const notifsBox = new BoxRenderable5(renderer, {
4458
5020
  visible: false,
4459
5021
  flexGrow: 1,
4460
5022
  backgroundColor: colors.bg,
4461
5023
  title: " Notifications ",
4462
5024
  titleColor: colors.yellow
4463
5025
  });
4464
- const notifsText = new TextRenderable4(renderer, {
5026
+ const notifsText = new TextRenderable5(renderer, {
4465
5027
  content: "(no notifications)",
4466
5028
  fg: colors.text,
4467
5029
  bg: colors.bg
@@ -4476,7 +5038,7 @@ async function launchBrowser(theme_override) {
4476
5038
  notifsText.content = notifs2.map((n) => ` ${n.icon} ${n.message}`).join("\n");
4477
5039
  }
4478
5040
  }
4479
- const shareBox = new BoxRenderable4(renderer, {
5041
+ const shareBox = new BoxRenderable5(renderer, {
4480
5042
  visible: false,
4481
5043
  flexGrow: 1,
4482
5044
  backgroundColor: colors.bg,
@@ -4535,7 +5097,7 @@ async function launchBrowser(theme_override) {
4535
5097
  }
4536
5098
  savedSelect.setSelectedIndex(0);
4537
5099
  }
4538
- const leaderDim = new BoxRenderable4(renderer, {
5100
+ const leaderDim = new BoxRenderable5(renderer, {
4539
5101
  visible: false,
4540
5102
  position: "absolute",
4541
5103
  top: 0,
@@ -4546,7 +5108,7 @@ async function launchBrowser(theme_override) {
4546
5108
  // 60% opacity black (hex alpha: 0x99 ≈ 153/255)
4547
5109
  });
4548
5110
  root.add(leaderDim);
4549
- const leaderBox = new BoxRenderable4(renderer, {
5111
+ const leaderBox = new BoxRenderable5(renderer, {
4550
5112
  visible: false,
4551
5113
  position: "absolute",
4552
5114
  width: "80%",
@@ -4578,7 +5140,7 @@ async function launchBrowser(theme_override) {
4578
5140
  selectedDescriptionColor: colors.selectionText,
4579
5141
  itemSpacing: 0
4580
5142
  });
4581
- const leaderFooter = new TextRenderable4(renderer, {
5143
+ const leaderFooter = new TextRenderable5(renderer, {
4582
5144
  content: " \u2191\u2193 select Enter run Esc/q close ",
4583
5145
  fg: colors.muted,
4584
5146
  bg: colors.surfaceDim,
@@ -4589,7 +5151,7 @@ async function launchBrowser(theme_override) {
4589
5151
  leaderBox.add(leaderFooter);
4590
5152
  root.add(leaderDim);
4591
5153
  root.add(leaderBox);
4592
- const updateDim = new BoxRenderable4(renderer, {
5154
+ const updateDim = new BoxRenderable5(renderer, {
4593
5155
  visible: false,
4594
5156
  position: "absolute",
4595
5157
  top: 0,
@@ -4599,7 +5161,7 @@ async function launchBrowser(theme_override) {
4599
5161
  backgroundColor: "#00000088"
4600
5162
  });
4601
5163
  root.add(updateDim);
4602
- const updateBox = new BoxRenderable4(renderer, {
5164
+ const updateBox = new BoxRenderable5(renderer, {
4603
5165
  visible: false,
4604
5166
  position: "absolute",
4605
5167
  width: "55%",
@@ -4618,11 +5180,19 @@ async function launchBrowser(theme_override) {
4618
5180
  paddingLeft: 2,
4619
5181
  paddingRight: 2
4620
5182
  });
4621
- const updateText = new TextRenderable4(renderer, {
5183
+ const updateHeader = new TextRenderable5(renderer, {
4622
5184
  content: "",
4623
5185
  fg: colors.accent,
4624
5186
  bg: colors.surfaceDim
4625
5187
  });
5188
+ const updateNotes = createMarkdownView({
5189
+ renderer,
5190
+ colors,
5191
+ // Notes ship with the release and are mostly prose/lists; skip the
5192
+ // image pipeline in this narrow panel.
5193
+ images: false,
5194
+ background: colors.surfaceDim
5195
+ });
4626
5196
  const updateScroll = new ScrollBoxRenderable3(renderer, {
4627
5197
  flexGrow: 1,
4628
5198
  backgroundColor: colors.surfaceDim,
@@ -4642,14 +5212,14 @@ async function launchBrowser(theme_override) {
4642
5212
  }
4643
5213
  });
4644
5214
  let updateSelectedOption = 0;
4645
- const updateOptionsRow = new BoxRenderable4(renderer, {
5215
+ const updateOptionsRow = new BoxRenderable5(renderer, {
4646
5216
  flexDirection: "row",
4647
5217
  gap: 1,
4648
5218
  backgroundColor: colors.surfaceDim,
4649
5219
  height: 3,
4650
5220
  width: "100%"
4651
5221
  });
4652
- const updateNowBox = new BoxRenderable4(renderer, {
5222
+ const updateNowBox = new BoxRenderable5(renderer, {
4653
5223
  border: true,
4654
5224
  borderColor: updateSelectedOption === 0 ? colors.yellow : colors.border,
4655
5225
  backgroundColor: colors.surfaceDim,
@@ -4658,13 +5228,13 @@ async function launchBrowser(theme_override) {
4658
5228
  paddingLeft: 1,
4659
5229
  paddingRight: 1
4660
5230
  });
4661
- const updateNowText = new TextRenderable4(renderer, {
5231
+ const updateNowText = new TextRenderable5(renderer, {
4662
5232
  content: " Update Now ",
4663
5233
  fg: updateSelectedOption === 0 ? colors.yellow : colors.text,
4664
5234
  bg: colors.surfaceDim
4665
5235
  });
4666
5236
  updateNowBox.add(updateNowText);
4667
- const laterBox = new BoxRenderable4(renderer, {
5237
+ const laterBox = new BoxRenderable5(renderer, {
4668
5238
  border: true,
4669
5239
  borderColor: updateSelectedOption === 1 ? colors.yellow : colors.border,
4670
5240
  backgroundColor: colors.surfaceDim,
@@ -4673,13 +5243,13 @@ async function launchBrowser(theme_override) {
4673
5243
  paddingLeft: 1,
4674
5244
  paddingRight: 1
4675
5245
  });
4676
- const laterText = new TextRenderable4(renderer, {
5246
+ const laterText = new TextRenderable5(renderer, {
4677
5247
  content: " Later",
4678
5248
  fg: updateSelectedOption === 1 ? colors.yellow : colors.text,
4679
5249
  bg: colors.surfaceDim
4680
5250
  });
4681
5251
  laterBox.add(laterText);
4682
- const neverBox = new BoxRenderable4(renderer, {
5252
+ const neverBox = new BoxRenderable5(renderer, {
4683
5253
  border: true,
4684
5254
  borderColor: updateSelectedOption === 2 ? colors.yellow : colors.border,
4685
5255
  backgroundColor: colors.surfaceDim,
@@ -4688,7 +5258,7 @@ async function launchBrowser(theme_override) {
4688
5258
  paddingLeft: 1,
4689
5259
  paddingRight: 1
4690
5260
  });
4691
- const neverText = new TextRenderable4(renderer, {
5261
+ const neverText = new TextRenderable5(renderer, {
4692
5262
  content: " Don't show again",
4693
5263
  fg: updateSelectedOption === 2 ? colors.yellow : colors.text,
4694
5264
  bg: colors.surfaceDim
@@ -4705,7 +5275,7 @@ async function launchBrowser(theme_override) {
4705
5275
  }
4706
5276
  updateOptionsRow.add(updateNowBox);
4707
5277
  updateOptionsRow.add(laterBox);
4708
- const updateFooter = new TextRenderable4(renderer, {
5278
+ const updateFooter = new TextRenderable5(renderer, {
4709
5279
  content: " \u2191\u2193/jk/PgUp/PgDn scroll \u2190\u2192 select Enter Esc/q close",
4710
5280
  fg: colors.muted,
4711
5281
  bg: colors.surfaceDim,
@@ -4713,7 +5283,8 @@ async function launchBrowser(theme_override) {
4713
5283
  paddingX: 1
4714
5284
  });
4715
5285
  updateBox.add(updateScroll);
4716
- updateScroll.add(updateText);
5286
+ updateScroll.add(updateHeader);
5287
+ updateScroll.add(updateNotes.renderable);
4717
5288
  updateBox.add(updateOptionsRow);
4718
5289
  updateBox.add(updateFooter);
4719
5290
  root.add(updateDim);
@@ -5061,15 +5632,12 @@ async function launchBrowser(theme_override) {
5061
5632
  if (!state.lastInstalledVersion) return;
5062
5633
  if (state.lastInstalledVersion === currentVersion) return;
5063
5634
  const notes = fetchReleaseNotes(currentVersion);
5064
- const notesWidth = Math.max(
5065
- 30,
5066
- Math.floor((process.stdout.columns || 80) * 0.55) - 8
5067
- );
5068
- const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : " No release notes found for this version.";
5069
5635
  updateSelectedOption = 0;
5070
- updateText.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
5071
-
5072
- ${notesBlock}`;
5636
+ updateHeader.content = ` Updated ghfind ${state.lastInstalledVersion} \u2192 ${currentVersion}
5637
+ `;
5638
+ updateNotes.setContent(
5639
+ notes ?? "_No release notes found for this version._"
5640
+ );
5073
5641
  renderUpdateOptions();
5074
5642
  showOverlay("update");
5075
5643
  }
@@ -5084,22 +5652,12 @@ ${notesBlock}`;
5084
5652
  if (latest) {
5085
5653
  updateSelectedOption = 0;
5086
5654
  const notes = fetchReleaseNotes(latest);
5087
- const notesWidth = Math.max(
5088
- 30,
5089
- Math.floor((process.stdout.columns || 80) * 0.55) - 8
5090
- );
5091
- const notesBlock = notes ? renderMarkdown(notes, { width: notesWidth }) : ` A new version is available
5655
+ updateHeader.content = ` A new version is available
5092
5656
 
5093
5657
  Current: ${currentVersion}
5094
5658
  Latest: ${latest}
5095
-
5096
- Run "npm install -g ghfind"`;
5097
- updateText.content = ` A new version is available
5098
-
5099
- Current: ${currentVersion}
5100
- Latest: ${latest}
5101
-
5102
- ${notesBlock}`;
5659
+ `;
5660
+ updateNotes.setContent(notes ?? "Run `npm install -g ghfind` to update.");
5103
5661
  renderUpdateOptions();
5104
5662
  showOverlay("update");
5105
5663
  } else {
@@ -5182,7 +5740,7 @@ ${notesBlock}`;
5182
5740
  renderer.requestRender();
5183
5741
  }
5184
5742
  }
5185
- const orgBox = new BoxRenderable4(renderer, {
5743
+ const orgBox = new BoxRenderable5(renderer, {
5186
5744
  visible: false,
5187
5745
  flexGrow: 1,
5188
5746
  backgroundColor: colors.surface,
@@ -5205,14 +5763,14 @@ ${notesBlock}`;
5205
5763
  }
5206
5764
  }
5207
5765
  });
5208
- const orgText = new TextRenderable4(renderer, {
5766
+ const orgText = new TextRenderable5(renderer, {
5209
5767
  content: "",
5210
5768
  fg: colors.text,
5211
5769
  bg: colors.bg
5212
5770
  });
5213
5771
  orgScroll.add(orgText);
5214
5772
  orgBox.add(orgScroll);
5215
- const orgFooter = new TextRenderable4(renderer, {
5773
+ const orgFooter = new TextRenderable5(renderer, {
5216
5774
  content: " \u2191\u2193/jk scroll Esc/q close",
5217
5775
  fg: colors.muted,
5218
5776
  bg: colors.bg,
@@ -5278,7 +5836,7 @@ ${notesBlock}`;
5278
5836
  }
5279
5837
  renderer.requestRender();
5280
5838
  }
5281
- const readmeBox = new BoxRenderable4(renderer, {
5839
+ const readmeBox = new BoxRenderable5(renderer, {
5282
5840
  visible: false,
5283
5841
  flexGrow: 1,
5284
5842
  backgroundColor: colors.surface,
@@ -5301,15 +5859,17 @@ ${notesBlock}`;
5301
5859
  }
5302
5860
  }
5303
5861
  });
5304
- const readmeText = new TextRenderable4(renderer, {
5305
- content: "",
5306
- fg: colors.text,
5307
- bg: colors.bg
5862
+ const readmeView = createMarkdownView({
5863
+ renderer,
5864
+ colors,
5865
+ loadImage: (url) => fetchImageBytes(url, { token: githubToken }),
5866
+ getImageWidth: () => Math.max(24, (process.stdout.columns || 80) - 12)
5308
5867
  });
5309
- readmeScroll.add(readmeText);
5868
+ readmeScroll.add(readmeView.renderable);
5310
5869
  readmeBox.add(readmeScroll);
5311
- const readmeFooter = new TextRenderable4(renderer, {
5312
- content: " \u2191\u2193/jk scroll Esc/q close",
5870
+ const readmeFooterText = () => ` \u2191\u2193/jk scroll PgUp/PgDn page i images: ${readmeView.imagesEnabled ? "on" : "off"} Esc/q close`;
5871
+ const readmeFooter = new TextRenderable5(renderer, {
5872
+ content: readmeFooterText(),
5313
5873
  fg: colors.muted,
5314
5874
  bg: colors.bg,
5315
5875
  height: 1,
@@ -5324,37 +5884,39 @@ ${notesBlock}`;
5324
5884
  setStatus("No repo selected");
5325
5885
  return;
5326
5886
  }
5327
- readmeText.content = ` Loading README for ${repo.fullName}...`;
5887
+ readmeView.setContent(`Loading README for ${repo.fullName}\u2026`);
5888
+ readmeFooter.content = readmeFooterText();
5889
+ readmeScroll.scrollTop = 0;
5328
5890
  showOverlay("readme");
5329
5891
  try {
5330
5892
  const headers = { "User-Agent": "ghfind/1.0" };
5331
5893
  if (githubToken) headers.Authorization = `Bearer ${githubToken}`;
5332
5894
  let text = "";
5333
- for (const path of [
5334
- `${repo.owner}/${repo.name}/main/README.md`,
5335
- `${repo.owner}/${repo.name}/master/README.md`,
5336
- `${repo.owner}/${repo.name}/main/README.rst`
5895
+ let sourceUrl = "";
5896
+ for (const [branch, file] of [
5897
+ ["main", "README.md"],
5898
+ ["master", "README.md"],
5899
+ ["main", "README.rst"]
5337
5900
  ]) {
5338
- const r = await fetch(`https://raw.githubusercontent.com/${path}`, {
5339
- headers
5340
- });
5901
+ const url = readmeBaseUrl(repo.owner, repo.name, branch, file);
5902
+ const r = await fetch(url, { headers });
5341
5903
  if (r.ok) {
5342
5904
  text = await r.text();
5905
+ sourceUrl = url;
5343
5906
  break;
5344
5907
  }
5345
5908
  }
5346
5909
  if (!text) {
5347
- readmeText.content = ` (no README found for ${repo.fullName})`;
5910
+ readmeView.setContent(`_(no README found for ${repo.fullName})_`);
5348
5911
  } else {
5349
- const termW = Math.max(40, (process.stdout.columns || 80) - 4);
5350
- readmeText.content = renderMarkdown(text, { width: termW });
5912
+ readmeView.setContent(text, sourceUrl);
5351
5913
  }
5352
5914
  } catch {
5353
- readmeText.content = " Failed to load README";
5915
+ readmeView.setContent("_Failed to load README._");
5354
5916
  }
5355
5917
  renderer.requestRender();
5356
5918
  }
5357
- const statusBar = new TextRenderable4(renderer, {
5919
+ const statusBar = new TextRenderable5(renderer, {
5358
5920
  content: " Ready. Press Enter to search.",
5359
5921
  fg: colors.muted,
5360
5922
  height: 1,
@@ -5835,6 +6397,17 @@ ${notesBlock}`;
5835
6397
  doSearch(searchInput.value);
5836
6398
  }
5837
6399
  });
6400
+ searchInput.on("input", () => {
6401
+ const v = searchInput.value;
6402
+ if (v.length > 0) {
6403
+ const t3 = v.trim();
6404
+ if (t3.startsWith("{") && t3.endsWith("}") || t3.startsWith("[") && t3.endsWith("]")) {
6405
+ searchInput.value = "";
6406
+ setStatus("Ignored JSON paste \u2014 search queries accept plain text");
6407
+ renderer.requestRender();
6408
+ }
6409
+ }
6410
+ });
5838
6411
  resultsSelect.on("selectionChanged", () => {
5839
6412
  const opt = resultsSelect.getSelectedOption();
5840
6413
  if (currentMode === "packages") {
@@ -5917,6 +6490,14 @@ ${pack.description ?? ""}`;
5917
6490
  return;
5918
6491
  }
5919
6492
  if (currentOverlay === "readme") {
6493
+ if (key.name === "i") {
6494
+ readmeView.setImagesEnabled(!readmeView.imagesEnabled);
6495
+ readmeFooter.content = readmeFooterText();
6496
+ setStatus(
6497
+ readmeView.imagesEnabled ? "README images on" : "README images off"
6498
+ );
6499
+ renderer.requestRender();
6500
+ }
5920
6501
  return;
5921
6502
  }
5922
6503
  if (currentOverlay === "history") {