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/CHANGELOG.md +34 -0
- package/README.md +10 -11
- package/dist/cli.js +829 -248
- package/package.json +7 -2
- package/release-notes/9.8.0.md +55 -0
- package/src/help.ts +2 -1
- package/src/image-loader.ts +248 -0
- package/src/image-render.ts +209 -0
- package/src/markdown-images.ts +123 -0
- package/src/markdown-view.ts +300 -0
- package/src/png.ts +260 -0
- package/src/tui.ts +77 -41
- package/src/markdown-render.ts +0 -211
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.
|
|
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(
|
|
2386
|
-
writeJSON(RELEASES_FILE,
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
|
2476
|
+
const cache2 = loadCache();
|
|
2477
2477
|
const bookmarks = getBookmarks();
|
|
2478
|
-
return Object.entries(
|
|
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
|
|
2957
|
-
TextRenderable as
|
|
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-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
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
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
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
|
-
|
|
3302
|
-
if (
|
|
3303
|
-
|
|
3304
|
-
|
|
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
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
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
|
-
|
|
3367
|
-
flushTable();
|
|
3368
|
-
inTable = false;
|
|
3547
|
+
runs.push({ text: HALF_BLOCK, top: cell.top, bottom: cell.bottom });
|
|
3369
3548
|
}
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
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
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
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
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
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
|
-
|
|
3394
|
-
|
|
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
|
-
|
|
3405
|
-
|
|
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 (
|
|
3416
|
-
out.
|
|
3750
|
+
if (token.tokens?.length) {
|
|
3751
|
+
out += inlinePlainText(token.tokens);
|
|
3417
3752
|
continue;
|
|
3418
3753
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
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
|
-
|
|
3425
|
-
if (
|
|
3426
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4076
|
-
const titleLine = new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
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(
|
|
5868
|
+
readmeScroll.add(readmeView.renderable);
|
|
5310
5869
|
readmeBox.add(readmeScroll);
|
|
5311
|
-
const
|
|
5312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
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
|
|
5339
|
-
|
|
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
|
-
|
|
5910
|
+
readmeView.setContent(`_(no README found for ${repo.fullName})_`);
|
|
5348
5911
|
} else {
|
|
5349
|
-
|
|
5350
|
-
readmeText.content = renderMarkdown(text, { width: termW });
|
|
5912
|
+
readmeView.setContent(text, sourceUrl);
|
|
5351
5913
|
}
|
|
5352
5914
|
} catch {
|
|
5353
|
-
|
|
5915
|
+
readmeView.setContent("_Failed to load README._");
|
|
5354
5916
|
}
|
|
5355
5917
|
renderer.requestRender();
|
|
5356
5918
|
}
|
|
5357
|
-
const statusBar = new
|
|
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") {
|