@kecan0406/ttheme 0.1.1 → 0.1.3
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/README.md +98 -37
- package/bin/ttheme.js +1524 -429
- package/dist/manifest.json +1 -1
- package/dist/shell/palettes.zsh +29 -103
- package/package.json +1 -1
- package/shell/adapters/ghostty.zsh +1 -1
- package/shell/ttheme.zsh +7 -2
package/bin/ttheme.js
CHANGED
|
@@ -6144,7 +6144,7 @@ var program = new Command;
|
|
|
6144
6144
|
// package.json
|
|
6145
6145
|
var package_default = {
|
|
6146
6146
|
name: "@kecan0406/ttheme",
|
|
6147
|
-
version: "0.1.
|
|
6147
|
+
version: "0.1.3",
|
|
6148
6148
|
type: "module",
|
|
6149
6149
|
bin: {
|
|
6150
6150
|
ttheme: "bin/ttheme.js"
|
|
@@ -6804,6 +6804,42 @@ var CONFIG_SETTINGS = {
|
|
|
6804
6804
|
TTHEME_SORT: {
|
|
6805
6805
|
doc: "# series and palettes in ttheme and preview: abc sorts them by name, series keeps the order they were added (default abc)",
|
|
6806
6806
|
default: "abc"
|
|
6807
|
+
},
|
|
6808
|
+
TTHEME_FIND_RATING: {
|
|
6809
|
+
doc: "# the ratings find lists, any of safe, questionable and explicit, each booru read in its own rating vocabulary (default safe)",
|
|
6810
|
+
default: "safe"
|
|
6811
|
+
},
|
|
6812
|
+
TTHEME_FIND_BLOCK: {
|
|
6813
|
+
doc: '# the posts find drops by tag: nudity, underwear, both, or none to keep them all (default "nudity underwear")',
|
|
6814
|
+
default: "nudity underwear"
|
|
6815
|
+
},
|
|
6816
|
+
TTHEME_FIND_POSTS: {
|
|
6817
|
+
doc: "# what find lists first: all is every post of the character, cutouts are the transparent ones (default all)",
|
|
6818
|
+
default: "all"
|
|
6819
|
+
},
|
|
6820
|
+
TTHEME_FIND_CUTOUTS: {
|
|
6821
|
+
doc: '# the tags find calls a transparent cutout, per site as key=tag,tag pairs — e.g. "safebooru=transparent_background yande=transparent_png,vector" (default the built-in tags)',
|
|
6822
|
+
default: ""
|
|
6823
|
+
},
|
|
6824
|
+
TTHEME_FIND_ORDER: {
|
|
6825
|
+
doc: "# the order find lists posts in: newest or score (default newest)",
|
|
6826
|
+
default: "newest"
|
|
6827
|
+
},
|
|
6828
|
+
TTHEME_FIND_SETS: {
|
|
6829
|
+
doc: "# runs of the same picture at the same size from one uploader: fold shows them as one tile, show lists each (default fold)",
|
|
6830
|
+
default: "fold"
|
|
6831
|
+
},
|
|
6832
|
+
TTHEME_FIND_REMOVE_BG: {
|
|
6833
|
+
doc: "# an opaque picture tried on in find: on cuts the character out with macOS Vision, off leaves it as it is (default on)",
|
|
6834
|
+
default: "on"
|
|
6835
|
+
},
|
|
6836
|
+
TTHEME_FIND_UNBLOCK: {
|
|
6837
|
+
doc: "# when a network blocks a booru by name, 1 sends find through a local proxy that splits the TLS handshake (default 0)",
|
|
6838
|
+
default: "0"
|
|
6839
|
+
},
|
|
6840
|
+
TTHEME_FIND_HOSTS: {
|
|
6841
|
+
doc: '# send a find site somewhere else, as key=https://host pairs — e.g. "konachan=https://konachan.com danbooru=https://danbooru.donmai.us" (default none)',
|
|
6842
|
+
default: ""
|
|
6807
6843
|
}
|
|
6808
6844
|
};
|
|
6809
6845
|
function settingLine(name, value) {
|
|
@@ -6812,23 +6848,17 @@ function settingLine(name, value) {
|
|
|
6812
6848
|
function settingPattern(name) {
|
|
6813
6849
|
return new RegExp(String.raw`^#? ?: \$\{${name}[^\n]*$`, "m");
|
|
6814
6850
|
}
|
|
6815
|
-
function
|
|
6851
|
+
function ensureSetting(content, name) {
|
|
6852
|
+
if (settingPattern(name).test(content)) {
|
|
6853
|
+
return content;
|
|
6854
|
+
}
|
|
6855
|
+
const setting = CONFIG_SETTINGS[name];
|
|
6816
6856
|
return `${content.replace(/\n*$/, `
|
|
6817
6857
|
`)}
|
|
6818
|
-
${
|
|
6819
|
-
${settingLine(name,
|
|
6858
|
+
${setting.doc}
|
|
6859
|
+
${settingLine(name, setting.default)}
|
|
6820
6860
|
`;
|
|
6821
6861
|
}
|
|
6822
|
-
function applySetting(content, name, value) {
|
|
6823
|
-
const pattern = settingPattern(name);
|
|
6824
|
-
if (pattern.test(content)) {
|
|
6825
|
-
return content.replace(pattern, () => settingLine(name, value));
|
|
6826
|
-
}
|
|
6827
|
-
return appendSetting(content, name, value);
|
|
6828
|
-
}
|
|
6829
|
-
function ensureSetting(content, name) {
|
|
6830
|
-
return settingPattern(name).test(content) ? content : appendSetting(content, name, CONFIG_SETTINGS[name].default);
|
|
6831
|
-
}
|
|
6832
6862
|
function configTemplate() {
|
|
6833
6863
|
const sections = Object.entries(CONFIG_SETTINGS).map(([name, s]) => `${s.doc}
|
|
6834
6864
|
${settingLine(name, s.default)}`);
|
|
@@ -6837,11 +6867,22 @@ ${settingLine(name, s.default)}`);
|
|
|
6837
6867
|
`)}
|
|
6838
6868
|
`;
|
|
6839
6869
|
}
|
|
6840
|
-
function
|
|
6841
|
-
const
|
|
6842
|
-
const
|
|
6843
|
-
|
|
6844
|
-
|
|
6870
|
+
function withSetting(content, name, value) {
|
|
6871
|
+
const line = settingLine(name, value);
|
|
6872
|
+
const pattern = settingPattern(name);
|
|
6873
|
+
if (pattern.test(content)) {
|
|
6874
|
+
return content.replace(pattern, line);
|
|
6875
|
+
}
|
|
6876
|
+
return `${content.replace(/\n*$/, `
|
|
6877
|
+
`)}
|
|
6878
|
+
${line}
|
|
6879
|
+
`;
|
|
6880
|
+
}
|
|
6881
|
+
function configFile(content) {
|
|
6882
|
+
if (content === "") {
|
|
6883
|
+
return configTemplate();
|
|
6884
|
+
}
|
|
6885
|
+
return Object.keys(CONFIG_SETTINGS).reduce(ensureSetting, content);
|
|
6845
6886
|
}
|
|
6846
6887
|
function kittyBlock(palette) {
|
|
6847
6888
|
return palette ? `include themes/${palette}.conf` : "";
|
|
@@ -6854,7 +6895,7 @@ import = ["${themePath}"]` : "";
|
|
|
6854
6895
|
// src/emit/shell.ts
|
|
6855
6896
|
function palettesZsh(palettes) {
|
|
6856
6897
|
for (const p of palettes) {
|
|
6857
|
-
for (const field of [p.name, p.group, p.native ?? "", p.ansiSource
|
|
6898
|
+
for (const field of [p.name, p.group, p.native ?? "", p.ansiSource]) {
|
|
6858
6899
|
if (/["$`\\]/.test(field)) {
|
|
6859
6900
|
throw new Error(`${p.name}: "${field}" contains a character that breaks zsh quoting`);
|
|
6860
6901
|
}
|
|
@@ -6896,11 +6937,6 @@ function palettesZsh(palettes) {
|
|
|
6896
6937
|
"typeset -gA TTHEME_SRC=(",
|
|
6897
6938
|
...palettes.map((p) => entry(p, p.ansiSource)),
|
|
6898
6939
|
")",
|
|
6899
|
-
"",
|
|
6900
|
-
"# the safebooru tag preview searches when a palette has no background yet",
|
|
6901
|
-
"typeset -gA TTHEME_BOORU=(",
|
|
6902
|
-
...palettes.filter((p) => p.booru).map((p) => entry(p, p.booru ?? "")),
|
|
6903
|
-
")",
|
|
6904
6940
|
""
|
|
6905
6941
|
].join(`
|
|
6906
6942
|
`);
|
|
@@ -7005,7 +7041,8 @@ ${themes.length} themes (${rotation(themes).length} in the new-tab rotation) ->
|
|
|
7005
7041
|
}
|
|
7006
7042
|
|
|
7007
7043
|
// src/find.ts
|
|
7008
|
-
import {
|
|
7044
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
7045
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, mkdtempSync, readFileSync as readFileSync5, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7009
7046
|
import { tmpdir } from "node:os";
|
|
7010
7047
|
import { dirname as dirname3, join as join7 } from "node:path";
|
|
7011
7048
|
|
|
@@ -7029,12 +7066,21 @@ function decodePng(bytes) {
|
|
|
7029
7066
|
const png = import_pngjs.PNG.sync.read(Buffer.from(bytes));
|
|
7030
7067
|
return { width: png.width, height: png.height, data: new Uint8Array(png.data) };
|
|
7031
7068
|
}
|
|
7032
|
-
function decodeImage(bytes) {
|
|
7069
|
+
function decodeImage(bytes, limit) {
|
|
7033
7070
|
if (isPng(bytes)) {
|
|
7071
|
+
const head = pngHead(bytes);
|
|
7072
|
+
if (head && head.width * head.height > limit) {
|
|
7073
|
+
throw new Error(`${head.width}×${head.height} is over ${limit / 1e6} megapixels`);
|
|
7074
|
+
}
|
|
7034
7075
|
return decodePng(bytes);
|
|
7035
7076
|
}
|
|
7036
7077
|
if (bytes[0] === 255 && bytes[1] === 216) {
|
|
7037
|
-
const jpeg = import_jpeg_js.decode(bytes, {
|
|
7078
|
+
const jpeg = import_jpeg_js.decode(bytes, {
|
|
7079
|
+
useTArray: true,
|
|
7080
|
+
formatAsRGBA: true,
|
|
7081
|
+
maxResolutionInMP: limit / 1e6,
|
|
7082
|
+
maxMemoryUsageInMB: 1024
|
|
7083
|
+
});
|
|
7038
7084
|
return { width: jpeg.width, height: jpeg.height, data: jpeg.data };
|
|
7039
7085
|
}
|
|
7040
7086
|
throw new Error("not a PNG or JPEG image");
|
|
@@ -7066,9 +7112,8 @@ function pngHead(bytes) {
|
|
|
7066
7112
|
if (bytes[25] === 4 || bytes[25] === 6) {
|
|
7067
7113
|
return { width, height, alpha: true };
|
|
7068
7114
|
}
|
|
7069
|
-
const trns = find(bytes, "tRNS");
|
|
7070
7115
|
const idat = find(bytes, "IDAT");
|
|
7071
|
-
return { width, height, alpha:
|
|
7116
|
+
return { width, height, alpha: find(idat === -1 ? bytes : bytes.subarray(0, idat), "tRNS") !== -1 };
|
|
7072
7117
|
}
|
|
7073
7118
|
function alphaBox(image, threshold = CLEAR) {
|
|
7074
7119
|
let x0 = image.width;
|
|
@@ -7370,14 +7415,30 @@ function origins(configHome) {
|
|
|
7370
7415
|
}
|
|
7371
7416
|
|
|
7372
7417
|
// src/booru.ts
|
|
7418
|
+
import { setDefaultAutoSelectFamilyAttemptTimeout } from "node:net";
|
|
7373
7419
|
import { homedir } from "node:os";
|
|
7374
7420
|
import { join as join4 } from "node:path";
|
|
7421
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
7375
7422
|
var PAGE = 100;
|
|
7376
|
-
var
|
|
7423
|
+
var MAX_PIXELS = 25000000;
|
|
7424
|
+
var RATINGS = ["safe", "questionable", "explicit"];
|
|
7425
|
+
var BLOCKS = ["nudity", "underwear"];
|
|
7426
|
+
var EXPOSED = {
|
|
7427
|
+
nudity: new Set(["nude", "naked", "topless", "bottomless", "nipples", "naked_towel", "undressing"]),
|
|
7428
|
+
underwear: new Set(["underwear", "panties", "pantsu", "bra", "lingerie", "pantyshot"])
|
|
7429
|
+
};
|
|
7430
|
+
var MOEBOORU = { safe: "s", questionable: "q", explicit: "e" };
|
|
7377
7431
|
var MIRRORS = new Set(["danbooru", "gelbooru", "konachan", "yande.re", "sankaku"]);
|
|
7378
7432
|
var HEAD = 8191;
|
|
7379
7433
|
var TIMEOUT = 20000;
|
|
7434
|
+
var CONNECT = 3000;
|
|
7435
|
+
var MAX_WAIT = 60000;
|
|
7436
|
+
var TRIES = 3;
|
|
7380
7437
|
var AGENT = `ttheme/${package_default.version} (+${package_default.homepage})`;
|
|
7438
|
+
var paused = new Map;
|
|
7439
|
+
var HOSTS = hostMap(process.env.TTHEME_FIND_HOSTS);
|
|
7440
|
+
var CUTOUTS = cutoutMap(process.env.TTHEME_FIND_CUTOUTS);
|
|
7441
|
+
setDefaultAutoSelectFamilyAttemptTimeout(CONNECT);
|
|
7381
7442
|
function absolute(url) {
|
|
7382
7443
|
return url.startsWith("//") ? `https:${url}` : url;
|
|
7383
7444
|
}
|
|
@@ -7390,26 +7451,45 @@ function records(text) {
|
|
|
7390
7451
|
return [];
|
|
7391
7452
|
}
|
|
7392
7453
|
const raw = JSON.parse(body);
|
|
7393
|
-
|
|
7454
|
+
if (Array.isArray(raw)) {
|
|
7455
|
+
return raw;
|
|
7456
|
+
}
|
|
7457
|
+
const posts = raw.posts;
|
|
7458
|
+
return Array.isArray(posts) ? posts : [];
|
|
7459
|
+
}
|
|
7460
|
+
function tagTypes(text) {
|
|
7461
|
+
const body = text.trim();
|
|
7462
|
+
const raw = body ? JSON.parse(body) : {};
|
|
7463
|
+
const tags = raw.tags ?? {};
|
|
7464
|
+
return new Map(Object.entries(tags).map(([name, type]) => [name, String(type)]));
|
|
7465
|
+
}
|
|
7466
|
+
function version(url, width, height) {
|
|
7467
|
+
const file = absolute(String(url ?? ""));
|
|
7468
|
+
return { file, width: Number(width) || 0, height: Number(height) || 0, ext: extension(file) };
|
|
7394
7469
|
}
|
|
7395
|
-
function post(p,
|
|
7396
|
-
const preview = absolute(String(
|
|
7397
|
-
if (!file || !preview || !Number(p.id)) {
|
|
7470
|
+
function post(p, raw) {
|
|
7471
|
+
const preview = absolute(String(raw.preview ?? ""));
|
|
7472
|
+
if (!raw.file || !preview || !Number(p.id)) {
|
|
7398
7473
|
return [];
|
|
7399
7474
|
}
|
|
7475
|
+
const width = Number(p.width ?? p.image_width) || 0;
|
|
7476
|
+
const height = Number(p.height ?? p.image_height) || 0;
|
|
7400
7477
|
return [
|
|
7401
7478
|
{
|
|
7402
7479
|
id: Number(p.id),
|
|
7403
|
-
width
|
|
7404
|
-
height
|
|
7405
|
-
file,
|
|
7480
|
+
width,
|
|
7481
|
+
height,
|
|
7482
|
+
file: raw.file,
|
|
7406
7483
|
preview,
|
|
7407
|
-
ext,
|
|
7408
|
-
owner: String(owner ?? ""),
|
|
7484
|
+
ext: raw.ext,
|
|
7485
|
+
owner: String(raw.owner ?? ""),
|
|
7486
|
+
artist: raw.artist,
|
|
7487
|
+
score: Number(p.score) || 0,
|
|
7409
7488
|
rating: String(p.rating ?? ""),
|
|
7410
|
-
md5: String(md5 ?? ""),
|
|
7489
|
+
md5: String(raw.md5 ?? ""),
|
|
7411
7490
|
source: String(p.source ?? ""),
|
|
7412
|
-
tags:
|
|
7491
|
+
tags: raw.tags.split(/\s+/).filter(Boolean),
|
|
7492
|
+
smaller: raw.versions.filter((v) => v.file && v.width > 0 && v.height > 0 && v.width * v.height < width * height)
|
|
7413
7493
|
}
|
|
7414
7494
|
];
|
|
7415
7495
|
}
|
|
@@ -7417,54 +7497,239 @@ function parseGelbooru(text, origin = "https://safebooru.org") {
|
|
|
7417
7497
|
return records(text).flatMap((p) => {
|
|
7418
7498
|
const stored = p.directory && p.image ? `${origin}/images/${p.directory}/${p.image}` : "";
|
|
7419
7499
|
const file = absolute(String(p.file_url || stored));
|
|
7420
|
-
return post(p,
|
|
7500
|
+
return post(p, {
|
|
7501
|
+
preview: p.preview_url,
|
|
7502
|
+
file,
|
|
7503
|
+
ext: extension(file),
|
|
7504
|
+
owner: p.owner,
|
|
7505
|
+
artist: "",
|
|
7506
|
+
md5: p.hash ?? p.md5,
|
|
7507
|
+
tags: String(p.tags ?? ""),
|
|
7508
|
+
versions: [version(p.sample_url, p.sample_width, p.sample_height)]
|
|
7509
|
+
});
|
|
7421
7510
|
});
|
|
7422
7511
|
}
|
|
7423
7512
|
function parseMoebooru(text) {
|
|
7513
|
+
const types = tagTypes(text);
|
|
7424
7514
|
return records(text).flatMap((p) => {
|
|
7425
7515
|
const file = absolute(String(p.file_url ?? ""));
|
|
7426
|
-
|
|
7516
|
+
const tags = String(p.tags ?? "");
|
|
7517
|
+
return post(p, {
|
|
7518
|
+
preview: p.preview_url,
|
|
7519
|
+
file,
|
|
7520
|
+
ext: String(p.file_ext || extension(file)).toLowerCase(),
|
|
7521
|
+
owner: p.author,
|
|
7522
|
+
artist: tags.split(/\s+/).find((tag) => types.get(tag) === "artist") ?? "",
|
|
7523
|
+
md5: p.md5,
|
|
7524
|
+
tags,
|
|
7525
|
+
versions: [
|
|
7526
|
+
version(p.jpeg_url, p.jpeg_width, p.jpeg_height),
|
|
7527
|
+
version(p.sample_url, p.sample_width, p.sample_height)
|
|
7528
|
+
]
|
|
7529
|
+
});
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7532
|
+
function parseDanbooru(text) {
|
|
7533
|
+
return records(text).flatMap((p) => {
|
|
7534
|
+
const file = absolute(String(p.file_url ?? ""));
|
|
7535
|
+
const asset = p.media_asset ?? {};
|
|
7536
|
+
return post(p, {
|
|
7537
|
+
preview: p.preview_file_url,
|
|
7538
|
+
file,
|
|
7539
|
+
ext: String(p.file_ext || extension(file)).toLowerCase(),
|
|
7540
|
+
owner: "",
|
|
7541
|
+
artist: String(p.tag_string_artist ?? "").split(/\s+/)[0] ?? "",
|
|
7542
|
+
md5: p.md5,
|
|
7543
|
+
tags: String(p.tag_string ?? ""),
|
|
7544
|
+
versions: (asset.variants ?? []).map((v) => version(v.url, v.width, v.height)).filter((v) => v.ext === "jpg" || v.ext === "png").sort((a, b) => b.width * b.height - a.width * a.height)
|
|
7545
|
+
});
|
|
7427
7546
|
});
|
|
7428
7547
|
}
|
|
7429
7548
|
function parseCount(xml) {
|
|
7430
7549
|
return Number(/count="(\d+)"/.exec(xml)?.[1] ?? 0);
|
|
7431
7550
|
}
|
|
7432
|
-
function
|
|
7433
|
-
const
|
|
7551
|
+
function parseCounts(text) {
|
|
7552
|
+
const raw = text.trim() ? JSON.parse(text) : {};
|
|
7553
|
+
return Number(raw.counts?.posts) || 0;
|
|
7554
|
+
}
|
|
7555
|
+
function cutoutMap(spec) {
|
|
7556
|
+
const cutouts = new Map;
|
|
7557
|
+
for (const entry of (spec ?? "").split(/\s+/).filter(Boolean)) {
|
|
7558
|
+
const at2 = entry.indexOf("=");
|
|
7559
|
+
if (at2 > 0) {
|
|
7560
|
+
cutouts.set(entry.slice(0, at2), entry.slice(at2 + 1).split(",").filter(Boolean));
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
return cutouts;
|
|
7564
|
+
}
|
|
7565
|
+
function hostMap(spec) {
|
|
7566
|
+
const hosts = new Map;
|
|
7567
|
+
for (const entry of (spec ?? "").split(/[\s,]+/).filter(Boolean)) {
|
|
7568
|
+
const at2 = entry.indexOf("=");
|
|
7569
|
+
const key = entry.slice(0, at2);
|
|
7570
|
+
try {
|
|
7571
|
+
const url = new URL(entry.slice(at2 + 1));
|
|
7572
|
+
if (key && url.protocol === "https:") {
|
|
7573
|
+
hosts.set(key, url.origin);
|
|
7574
|
+
}
|
|
7575
|
+
} catch {}
|
|
7576
|
+
}
|
|
7577
|
+
return hosts;
|
|
7578
|
+
}
|
|
7579
|
+
function chosen(value, all, fallback, none) {
|
|
7580
|
+
const words = (value ?? "").split(/\s+/);
|
|
7581
|
+
if (none !== undefined && words.includes(none)) {
|
|
7582
|
+
return [];
|
|
7583
|
+
}
|
|
7584
|
+
const picked = all.filter((item) => words.includes(item));
|
|
7585
|
+
return picked.length > 0 ? picked : [...fallback];
|
|
7586
|
+
}
|
|
7587
|
+
function ratingSet(value) {
|
|
7588
|
+
return chosen(value, RATINGS, ["safe"]);
|
|
7589
|
+
}
|
|
7590
|
+
function blockSet(value) {
|
|
7591
|
+
return chosen(value, BLOCKS, BLOCKS, "none");
|
|
7592
|
+
}
|
|
7593
|
+
function tiers(safe, questionable, explicit) {
|
|
7594
|
+
return { safe: new Set(safe), questionable: new Set(questionable), explicit: new Set(explicit) };
|
|
7595
|
+
}
|
|
7596
|
+
function based(spec) {
|
|
7597
|
+
const origin = HOSTS.get(spec.key);
|
|
7598
|
+
const cutouts = CUTOUTS.get(spec.key);
|
|
7434
7599
|
return {
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
origin,
|
|
7438
|
-
cutouts,
|
|
7439
|
-
vouched
|
|
7440
|
-
|
|
7600
|
+
...spec,
|
|
7601
|
+
origin: origin ?? spec.origin,
|
|
7602
|
+
moved: origin !== undefined && origin !== spec.origin,
|
|
7603
|
+
cutouts: cutouts ?? spec.cutouts,
|
|
7604
|
+
vouched: spec.vouched && cutouts === undefined
|
|
7605
|
+
};
|
|
7606
|
+
}
|
|
7607
|
+
function anyOf(tags, grouped) {
|
|
7608
|
+
if (tags.length < 2) {
|
|
7609
|
+
return tags.join("");
|
|
7610
|
+
}
|
|
7611
|
+
return grouped ? `( ${tags.join(" ~ ")} )` : tags.map((tag) => `~${tag}`).join(" ");
|
|
7612
|
+
}
|
|
7613
|
+
function gelbooru(raw) {
|
|
7614
|
+
const spec = based(raw);
|
|
7615
|
+
const api = (params) => `${spec.origin}/index.php?${new URLSearchParams({ page: "dapi", s: "post", q: "index", ...params })}`;
|
|
7616
|
+
return {
|
|
7617
|
+
...spec,
|
|
7618
|
+
cutouts: anyOf(spec.cutouts, true),
|
|
7619
|
+
ratings: tiers(["safe", "general"], ["questionable"], ["explicit"]),
|
|
7620
|
+
rate: () => "",
|
|
7621
|
+
best: "sort:score:desc",
|
|
7622
|
+
tagBudget: Number.POSITIVE_INFINITY,
|
|
7441
7623
|
postsUrl: (tags, page) => api({ json: "1", limit: String(PAGE), pid: String(page), tags }),
|
|
7442
7624
|
countUrl: (tags) => api({ limit: "0", tags }),
|
|
7443
7625
|
postUrl: (id) => api({ json: "1", id: String(id) }),
|
|
7444
|
-
pageUrl: (id) => `${origin}/index.php?page=post&s=view&id=${id}`,
|
|
7445
|
-
parse: (text) => parseGelbooru(text, origin)
|
|
7626
|
+
pageUrl: (id) => `${spec.origin}/index.php?page=post&s=view&id=${id}`,
|
|
7627
|
+
parse: (text) => parseGelbooru(text, spec.origin),
|
|
7628
|
+
count: parseCount
|
|
7446
7629
|
};
|
|
7447
7630
|
}
|
|
7448
|
-
function moebooru(
|
|
7449
|
-
const
|
|
7631
|
+
function moebooru(raw) {
|
|
7632
|
+
const spec = based(raw);
|
|
7633
|
+
const params = (rest) => new URLSearchParams({ api_version: "2", include_tags: "1", ...rest });
|
|
7450
7634
|
return {
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7635
|
+
...spec,
|
|
7636
|
+
cutouts: anyOf(spec.cutouts, false),
|
|
7637
|
+
ratings: tiers([MOEBOORU.safe], [MOEBOORU.questionable], [MOEBOORU.explicit]),
|
|
7638
|
+
rate: (levels) => {
|
|
7639
|
+
const [only] = levels;
|
|
7640
|
+
if (levels.length === 1 && only) {
|
|
7641
|
+
return `rating:${MOEBOORU[only]}`;
|
|
7642
|
+
}
|
|
7643
|
+
const missing = RATINGS.filter((level) => !levels.includes(level));
|
|
7644
|
+
const [left] = missing;
|
|
7645
|
+
return missing.length === 1 && left ? `-rating:${MOEBOORU[left]}` : "";
|
|
7646
|
+
},
|
|
7647
|
+
best: "order:score",
|
|
7648
|
+
tagBudget: Number.POSITIVE_INFINITY,
|
|
7649
|
+
postsUrl: (tags, page) => `${spec.origin}/post.json?${params({ limit: String(PAGE), page: String(page + 1), tags })}`,
|
|
7650
|
+
countUrl: (tags) => `${spec.origin}/post.xml?${new URLSearchParams({ limit: "1", tags })}`,
|
|
7651
|
+
postUrl: (id) => `${spec.origin}/post.json?${params({ tags: `id:${id}` })}`,
|
|
7652
|
+
pageUrl: (id) => `${spec.origin}/post/show/${id}`,
|
|
7653
|
+
parse: parseMoebooru,
|
|
7654
|
+
count: parseCount
|
|
7655
|
+
};
|
|
7656
|
+
}
|
|
7657
|
+
function danbooru(raw) {
|
|
7658
|
+
const spec = based(raw);
|
|
7659
|
+
return {
|
|
7660
|
+
...spec,
|
|
7661
|
+
cutouts: anyOf(spec.cutouts, false),
|
|
7662
|
+
ratings: tiers(["g"], ["s", "q"], ["e"]),
|
|
7663
|
+
rate: () => "",
|
|
7664
|
+
best: "order:score",
|
|
7665
|
+
tagBudget: 2,
|
|
7666
|
+
postsUrl: (tags, page) => `${spec.origin}/posts.json?${new URLSearchParams({ limit: String(PAGE), page: String(page + 1), tags })}`,
|
|
7667
|
+
countUrl: (tags) => `${spec.origin}/counts/posts.json?${new URLSearchParams({ tags })}`,
|
|
7668
|
+
postUrl: (id) => `${spec.origin}/posts.json?${new URLSearchParams({ limit: "1", tags: `id:${id}` })}`,
|
|
7669
|
+
pageUrl: (id) => `${spec.origin}/posts/${id}`,
|
|
7670
|
+
parse: parseDanbooru,
|
|
7671
|
+
count: parseCounts
|
|
7462
7672
|
};
|
|
7463
7673
|
}
|
|
7674
|
+
function siteNamed(name) {
|
|
7675
|
+
return SITES.find((site) => site.key === name || site.name === name || new URL(site.origin).host === name);
|
|
7676
|
+
}
|
|
7677
|
+
function tagsOf(query) {
|
|
7678
|
+
return query.split(/\s+/).filter(Boolean).length;
|
|
7679
|
+
}
|
|
7680
|
+
function postRef(text, fallback) {
|
|
7681
|
+
const query = text.trim();
|
|
7682
|
+
if (/^\d+$/.test(query)) {
|
|
7683
|
+
return { site: fallback, id: Number(query) };
|
|
7684
|
+
}
|
|
7685
|
+
const named = /^([\w.]+):(\d+)$/.exec(query);
|
|
7686
|
+
if (named) {
|
|
7687
|
+
const site2 = SITES.find((s) => s.key === named[1] || s.name === named[1]);
|
|
7688
|
+
return site2 && { site: site2, id: Number(named[2]) };
|
|
7689
|
+
}
|
|
7690
|
+
let url;
|
|
7691
|
+
try {
|
|
7692
|
+
url = new URL(query);
|
|
7693
|
+
} catch {
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
const site = siteNamed(url.host);
|
|
7697
|
+
const id = Number(url.searchParams.get("id") ?? url.pathname.split("/").filter(Boolean).at(-1));
|
|
7698
|
+
return site && id > 0 ? { site, id } : undefined;
|
|
7699
|
+
}
|
|
7464
7700
|
var SITES = [
|
|
7465
|
-
gelbooru(
|
|
7466
|
-
|
|
7467
|
-
|
|
7701
|
+
gelbooru({
|
|
7702
|
+
key: "safebooru",
|
|
7703
|
+
name: "safebooru",
|
|
7704
|
+
origin: "https://safebooru.org",
|
|
7705
|
+
cutouts: ["transparent_background", "vector_trace"],
|
|
7706
|
+
vouched: false,
|
|
7707
|
+
ansi: 4
|
|
7708
|
+
}),
|
|
7709
|
+
moebooru({
|
|
7710
|
+
key: "yande",
|
|
7711
|
+
name: "yande.re",
|
|
7712
|
+
origin: "https://yande.re",
|
|
7713
|
+
cutouts: ["transparent_png"],
|
|
7714
|
+
vouched: true,
|
|
7715
|
+
ansi: 5
|
|
7716
|
+
}),
|
|
7717
|
+
moebooru({
|
|
7718
|
+
key: "konachan",
|
|
7719
|
+
name: "konachan",
|
|
7720
|
+
origin: "https://konachan.net",
|
|
7721
|
+
cutouts: ["transparent", "vector"],
|
|
7722
|
+
vouched: false,
|
|
7723
|
+
ansi: 6
|
|
7724
|
+
}),
|
|
7725
|
+
danbooru({
|
|
7726
|
+
key: "danbooru",
|
|
7727
|
+
name: "danbooru",
|
|
7728
|
+
origin: "https://safebooru.donmai.us",
|
|
7729
|
+
cutouts: ["transparent_background"],
|
|
7730
|
+
vouched: false,
|
|
7731
|
+
ansi: 2
|
|
7732
|
+
})
|
|
7468
7733
|
];
|
|
7469
7734
|
function originHost(source) {
|
|
7470
7735
|
let host;
|
|
@@ -7475,8 +7740,17 @@ function originHost(source) {
|
|
|
7475
7740
|
}
|
|
7476
7741
|
return host.split(".").slice(-2).join(".");
|
|
7477
7742
|
}
|
|
7478
|
-
function
|
|
7479
|
-
return
|
|
7743
|
+
function exposed(post2, blocks = BLOCKS) {
|
|
7744
|
+
return post2.tags.filter((tag) => blocks.some((block2) => EXPOSED[block2].has(tag)));
|
|
7745
|
+
}
|
|
7746
|
+
function rated(site, post2, levels = ["safe"]) {
|
|
7747
|
+
return levels.some((level) => site.ratings[level].has(post2.rating));
|
|
7748
|
+
}
|
|
7749
|
+
function rendition(post2) {
|
|
7750
|
+
return [post2, ...post2.smaller].find((v) => v.width * v.height <= MAX_PIXELS);
|
|
7751
|
+
}
|
|
7752
|
+
function mirrored(owner) {
|
|
7753
|
+
return MIRRORS.has(owner);
|
|
7480
7754
|
}
|
|
7481
7755
|
function mates(owners) {
|
|
7482
7756
|
const byOwner = new Map;
|
|
@@ -7491,21 +7765,50 @@ function mates(owners) {
|
|
|
7491
7765
|
function cacheDir(site) {
|
|
7492
7766
|
return join4(process.env.XDG_CACHE_HOME ?? join4(homedir(), ".cache"), "ttheme", site.key);
|
|
7493
7767
|
}
|
|
7768
|
+
function retryAfter(value, now) {
|
|
7769
|
+
if (value !== null && /^\s*\d+\s*$/.test(value)) {
|
|
7770
|
+
return Number(value) * 1000;
|
|
7771
|
+
}
|
|
7772
|
+
const at2 = value === null ? Number.NaN : Date.parse(value);
|
|
7773
|
+
return Number.isNaN(at2) ? MAX_WAIT : Math.max(0, at2 - now);
|
|
7774
|
+
}
|
|
7775
|
+
function pausedUntil(site) {
|
|
7776
|
+
return paused.get(site.key) ?? 0;
|
|
7777
|
+
}
|
|
7778
|
+
function challenged(response) {
|
|
7779
|
+
return (response.headers.get("server") ?? "").startsWith("cloudflare") && response.headers.get("cf-mitigated") === "challenge";
|
|
7780
|
+
}
|
|
7494
7781
|
async function get(site, url, signal, headers = {}, timeout = TIMEOUT) {
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7782
|
+
for (let tries = 1;; tries++) {
|
|
7783
|
+
const wait = pausedUntil(site) - Date.now();
|
|
7784
|
+
if (wait > 0) {
|
|
7785
|
+
await sleep(wait, undefined, { signal });
|
|
7786
|
+
}
|
|
7787
|
+
const response = await fetch(url, {
|
|
7788
|
+
headers: { "User-Agent": AGENT, Referer: `${site.origin}/`, ...headers },
|
|
7789
|
+
signal: timeout ? AbortSignal.any([signal, AbortSignal.timeout(timeout)]) : signal
|
|
7790
|
+
});
|
|
7791
|
+
if (response.status === 429 && tries < TRIES) {
|
|
7792
|
+
await response.body?.cancel();
|
|
7793
|
+
const delay = retryAfter(response.headers.get("retry-after"), Date.now());
|
|
7794
|
+
if (delay > MAX_WAIT) {
|
|
7795
|
+
throw new Error(`${site.name} asks to wait ${Math.ceil(delay / 1000)}s`);
|
|
7796
|
+
}
|
|
7797
|
+
paused.set(site.key, Math.max(pausedUntil(site), Date.now() + delay));
|
|
7798
|
+
continue;
|
|
7799
|
+
}
|
|
7800
|
+
if (!response.ok) {
|
|
7801
|
+
await response.body?.cancel();
|
|
7802
|
+
throw new Error(challenged(response) ? `${site.name} is behind a Cloudflare challenge` : `${site.name} answered ${response.status}`);
|
|
7803
|
+
}
|
|
7804
|
+
return response;
|
|
7501
7805
|
}
|
|
7502
|
-
return response;
|
|
7503
7806
|
}
|
|
7504
7807
|
async function fetchPosts(site, tags, page, signal) {
|
|
7505
7808
|
return site.parse(await (await get(site, site.postsUrl(tags, page), signal)).text());
|
|
7506
7809
|
}
|
|
7507
7810
|
async function fetchCount(site, tags, signal) {
|
|
7508
|
-
return
|
|
7811
|
+
return site.count(await (await get(site, site.countUrl(tags), signal)).text());
|
|
7509
7812
|
}
|
|
7510
7813
|
async function fetchPost(site, id, signal) {
|
|
7511
7814
|
const [found] = site.parse(await (await get(site, site.postUrl(id), signal)).text());
|
|
@@ -7620,8 +7923,66 @@ function search(palettes, query) {
|
|
|
7620
7923
|
return palettes.filter((p) => [p.name, p.group, p.native ?? "", p.ansiSource].some((field) => field.toLowerCase().includes(needle)));
|
|
7621
7924
|
}
|
|
7622
7925
|
|
|
7926
|
+
// src/cutout.ts
|
|
7927
|
+
import { spawn } from "node:child_process";
|
|
7928
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7929
|
+
var TIMEOUT2 = 30000;
|
|
7930
|
+
var LEAST = 3;
|
|
7931
|
+
var MOST = 97;
|
|
7932
|
+
var SCRIPT = `ObjC.import('Vision')
|
|
7933
|
+
ObjC.import('CoreImage')
|
|
7934
|
+
ObjC.import('CoreGraphics')
|
|
7935
|
+
function run(argv) {
|
|
7936
|
+
const error = Ref()
|
|
7937
|
+
const image = $.CIImage.imageWithContentsOfURL($.NSURL.fileURLWithPath(argv[0]))
|
|
7938
|
+
if (!image) throw new Error('unreadable image')
|
|
7939
|
+
const handler = $.VNImageRequestHandler.alloc.initWithCIImageOptions(image, $({}))
|
|
7940
|
+
const request = $.VNGenerateForegroundInstanceMaskRequest.alloc.init
|
|
7941
|
+
if (!handler.performRequestsError($([request]), error)) throw new Error('vision failed')
|
|
7942
|
+
if (request.results.count === 0) throw new Error('no foreground')
|
|
7943
|
+
const found = request.results.objectAtIndex(0)
|
|
7944
|
+
const buffer = found.generateMaskedImageOfInstancesFromRequestHandlerCroppedToInstancesExtentError(
|
|
7945
|
+
found.allInstances, handler, false, error)
|
|
7946
|
+
if (!buffer) throw new Error('mask failed')
|
|
7947
|
+
const written = $.CIContext.context.writePNGRepresentationOfImageToURLFormatColorSpaceOptionsError(
|
|
7948
|
+
$.CIImage.imageWithCVPixelBuffer(buffer), $.NSURL.fileURLWithPath(argv[1]), $.kCIFormatRGBA8,
|
|
7949
|
+
$.CGColorSpaceCreateWithName($.kCGColorSpaceSRGB), $({}), error)
|
|
7950
|
+
if (!written) throw new Error('write failed')
|
|
7951
|
+
}`;
|
|
7952
|
+
function canRemoveBackground() {
|
|
7953
|
+
return process.platform === "darwin";
|
|
7954
|
+
}
|
|
7955
|
+
function keepable(clear) {
|
|
7956
|
+
return clear >= LEAST && clear <= MOST;
|
|
7957
|
+
}
|
|
7958
|
+
function removeBackground(input, output, signal) {
|
|
7959
|
+
return new Promise((resolve, reject) => {
|
|
7960
|
+
const child = spawn("osascript", ["-l", "JavaScript", "-e", SCRIPT, input, output], {
|
|
7961
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
7962
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT2)])
|
|
7963
|
+
});
|
|
7964
|
+
let failure = "";
|
|
7965
|
+
child.stderr.on("data", (chunk) => {
|
|
7966
|
+
failure += chunk;
|
|
7967
|
+
});
|
|
7968
|
+
child.on("error", reject);
|
|
7969
|
+
child.on("close", (code) => {
|
|
7970
|
+
if (code !== 0) {
|
|
7971
|
+
reject(new Error(failure.trim().split(`
|
|
7972
|
+
`).at(-1) || `osascript exited ${code}`));
|
|
7973
|
+
return;
|
|
7974
|
+
}
|
|
7975
|
+
try {
|
|
7976
|
+
resolve(decodePng(new Uint8Array(readFileSync3(output))));
|
|
7977
|
+
} catch (error) {
|
|
7978
|
+
reject(error);
|
|
7979
|
+
}
|
|
7980
|
+
});
|
|
7981
|
+
});
|
|
7982
|
+
}
|
|
7983
|
+
|
|
7623
7984
|
// src/find-screen.ts
|
|
7624
|
-
var TILE = { pitch: 25, cols: 22, rows: 9, height:
|
|
7985
|
+
var TILE = { pitch: 25, cols: 22, rows: 9, height: 13 };
|
|
7625
7986
|
var MIN = { cols: 25, rows: 16 };
|
|
7626
7987
|
var TRY_ID = 2 ** 31;
|
|
7627
7988
|
var BELOW_BG = -1073741826;
|
|
@@ -7658,14 +8019,14 @@ function decodeKeys(input) {
|
|
|
7658
8019
|
if (m) {
|
|
7659
8020
|
keys.push(CSI[`${m[1] ?? ""}${m[2] ?? m[3] ?? ""}`] ?? "nop");
|
|
7660
8021
|
i += 1 + m[0].length;
|
|
7661
|
-
|
|
7662
|
-
keys.push("esc");
|
|
7663
|
-
i++;
|
|
8022
|
+
continue;
|
|
7664
8023
|
}
|
|
8024
|
+
keys.push("esc");
|
|
8025
|
+
i++;
|
|
7665
8026
|
continue;
|
|
7666
8027
|
}
|
|
7667
8028
|
keys.push(c === "\r" || c === `
|
|
7668
|
-
` ? "enter" : c === "\t" ? "tab" : c === "\x03" ? "ctrl-c" : c);
|
|
8029
|
+
` ? "enter" : c === "\t" ? "tab" : c === "\x03" ? "ctrl-c" : c === "" || c === "\b" ? "backspace" : c);
|
|
7669
8030
|
i++;
|
|
7670
8031
|
}
|
|
7671
8032
|
return keys;
|
|
@@ -7736,8 +8097,14 @@ function progress(got, size) {
|
|
|
7736
8097
|
}
|
|
7737
8098
|
return size >= 1e6 ? `${(got / 1e6).toFixed(1)}/${(size / 1e6).toFixed(1)} MB` : `${Math.round(got / 1000)}/${Math.round(size / 1000)} KB`;
|
|
7738
8099
|
}
|
|
8100
|
+
function plain(artist) {
|
|
8101
|
+
return artist.replace(/_\([^)]*\)?$/, "");
|
|
8102
|
+
}
|
|
7739
8103
|
function dims(tile) {
|
|
7740
|
-
return [
|
|
8104
|
+
return [
|
|
8105
|
+
`${tile.reduced ? "↓" : ""}${tile.width}×${tile.height}`,
|
|
8106
|
+
Math.max(tile.width, tile.height) < SMALL ? YELLOW : D
|
|
8107
|
+
];
|
|
7741
8108
|
}
|
|
7742
8109
|
function foot(line, cols, accent, spec) {
|
|
7743
8110
|
const keys = [...spec.keys ?? []];
|
|
@@ -7860,23 +8227,61 @@ function frameBox(lines, r0, c0, h, w, sgr) {
|
|
|
7860
8227
|
}
|
|
7861
8228
|
lines[r0 + h - 1]?.put(c0, `╰${"─".repeat(w - 2)}╯`, sgr);
|
|
7862
8229
|
}
|
|
7863
|
-
function badge(view) {
|
|
7864
|
-
return [` ${
|
|
8230
|
+
function badge(view, label = view.site) {
|
|
8231
|
+
return [` ${label} `, `\x1B[7;${30 + view.siteAnsi}m`];
|
|
8232
|
+
}
|
|
8233
|
+
function tabs(line, cols, view) {
|
|
8234
|
+
const strip = SITES.flatMap((site, i) => {
|
|
8235
|
+
const label = `${site.name}${site.moved ? "*" : ""}`;
|
|
8236
|
+
const tab = site.name === view.site ? badge(view, label) : [` ${label} `, D];
|
|
8237
|
+
return i ? [[" ", ""], tab] : [tab];
|
|
8238
|
+
});
|
|
8239
|
+
const fits = strip.reduce((n, [text]) => n + width(text), 0) <= cols;
|
|
8240
|
+
line.run(0, fits ? strip : [badge(view)]);
|
|
7865
8241
|
}
|
|
7866
8242
|
function query(line, cols, view, accent) {
|
|
8243
|
+
if (view.editing !== undefined) {
|
|
8244
|
+
const c2 = line.run(0, [
|
|
8245
|
+
["⌕ ", accent],
|
|
8246
|
+
[view.editing, ""],
|
|
8247
|
+
["█", accent]
|
|
8248
|
+
]);
|
|
8249
|
+
line.put(c2, view.editing ? " enter searches" : ` a tag, a post url or an id — ${view.tag || "esc leaves"}`, D);
|
|
8250
|
+
return;
|
|
8251
|
+
}
|
|
7867
8252
|
const c = line.run(0, [
|
|
7868
8253
|
["⌕ ", accent],
|
|
7869
|
-
[view.tag, ""]
|
|
8254
|
+
[view.tag || "nothing yet — / searches", view.tag ? "" : D]
|
|
7870
8255
|
]);
|
|
7871
|
-
|
|
8256
|
+
const allowed = BLOCKS.filter((block2) => !view.block.includes(block2));
|
|
8257
|
+
const state = [
|
|
8258
|
+
view.preset,
|
|
8259
|
+
view.order,
|
|
8260
|
+
...view.rating.join("+") === "safe" ? [] : [view.rating.join("+")],
|
|
8261
|
+
...allowed.length > 0 ? [`allows ${allowed.join("+")}`] : [],
|
|
8262
|
+
...view.unblocked ? ["unblock"] : []
|
|
8263
|
+
];
|
|
8264
|
+
line.put(c, ` ${state.join(" ")}`, D);
|
|
7872
8265
|
if (view.total > 0 || !view.searching) {
|
|
7873
|
-
const counter = [
|
|
8266
|
+
const counter = [[`${view.tiles.length}/${view.checked}`, ""]];
|
|
7874
8267
|
if (view.checked < view.total) {
|
|
7875
8268
|
counter.push([` of ${view.total}`, D]);
|
|
7876
8269
|
}
|
|
7877
8270
|
line.right(cols, counter);
|
|
7878
8271
|
}
|
|
7879
8272
|
}
|
|
8273
|
+
function transparent(shown) {
|
|
8274
|
+
if (shown.cut === "on") {
|
|
8275
|
+
return [[`cut out · ${shown.clear}% transparent`, GREEN]];
|
|
8276
|
+
}
|
|
8277
|
+
if (shown.cut === "off") {
|
|
8278
|
+
return [["opaque · x cuts out", YELLOW]];
|
|
8279
|
+
}
|
|
8280
|
+
if (shown.cut === "failed") {
|
|
8281
|
+
return [["opaque · no cut-out", YELLOW]];
|
|
8282
|
+
}
|
|
8283
|
+
return [shown.clear > 0 ? [`${shown.clear}% transparent`, GREEN] : ["opaque", YELLOW]];
|
|
8284
|
+
}
|
|
7880
8285
|
function status(view) {
|
|
7881
8286
|
if (view.installing !== undefined) {
|
|
7882
8287
|
return [`installing ${view.palette} ← ${view.site} ${view.installing}`, YELLOW];
|
|
@@ -7884,18 +8289,27 @@ function status(view) {
|
|
|
7884
8289
|
if (view.error) {
|
|
7885
8290
|
return [view.error, YELLOW];
|
|
7886
8291
|
}
|
|
8292
|
+
if (view.waiting !== undefined) {
|
|
8293
|
+
return [`${view.site} asked to slow down · ${view.waiting}s`, YELLOW];
|
|
8294
|
+
}
|
|
7887
8295
|
if (view.fetching) {
|
|
7888
8296
|
return [`fetching ${view.fetching.id} · ${progress(view.fetching.got, view.fetching.size)}`, YELLOW];
|
|
7889
8297
|
}
|
|
8298
|
+
if (view.cutting !== undefined) {
|
|
8299
|
+
return [`cutting out ${view.cutting}`, YELLOW];
|
|
8300
|
+
}
|
|
7890
8301
|
if (view.preparing !== undefined) {
|
|
7891
8302
|
return [`preparing ${view.preparing}`, YELLOW];
|
|
7892
8303
|
}
|
|
8304
|
+
if (view.note) {
|
|
8305
|
+
return [view.note, D];
|
|
8306
|
+
}
|
|
7893
8307
|
return;
|
|
7894
8308
|
}
|
|
7895
8309
|
function grid(lines, images, cols, rows, view, accent) {
|
|
7896
8310
|
const { perRow, rowsVis } = gridShape(cols, rows);
|
|
7897
8311
|
query(lines[0], cols, view, accent);
|
|
7898
|
-
lines[1]
|
|
8312
|
+
tabs(lines[1], cols, view);
|
|
7899
8313
|
for (let k = 0;k < perRow * rowsVis; k++) {
|
|
7900
8314
|
const i = view.top * perRow + k;
|
|
7901
8315
|
const tile = view.tiles[i];
|
|
@@ -7915,6 +8329,20 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7915
8329
|
if (tile.mates.length > 0) {
|
|
7916
8330
|
lines[r0 + 10]?.put(c0 + TILE.cols, "≈", accent);
|
|
7917
8331
|
}
|
|
8332
|
+
const credit = tile.artist ? plain(tile.artist) : tile.owner && `@${tile.owner}`;
|
|
8333
|
+
if (credit) {
|
|
8334
|
+
lines[r0 + 11]?.put(c0 + 1, credit.slice(0, TILE.cols - 8), D);
|
|
8335
|
+
}
|
|
8336
|
+
const marks = [];
|
|
8337
|
+
if (tile.score > 0) {
|
|
8338
|
+
marks.push([`★${tile.score}`, D]);
|
|
8339
|
+
}
|
|
8340
|
+
if (tile.variants > 1) {
|
|
8341
|
+
marks.push([` ×${tile.variants}`, accent]);
|
|
8342
|
+
}
|
|
8343
|
+
if (marks.length > 0) {
|
|
8344
|
+
lines[r0 + 11]?.right(c0 + TILE.cols + 1, marks);
|
|
8345
|
+
}
|
|
7918
8346
|
}
|
|
7919
8347
|
const above = view.top * perRow;
|
|
7920
8348
|
const below = view.tiles.length - (view.top + rowsVis) * perRow;
|
|
@@ -7923,7 +8351,7 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7923
8351
|
lines[rows - 2]?.put(0, scroll.join(" "), D);
|
|
7924
8352
|
}
|
|
7925
8353
|
if (!view.searching && view.tiles.length === 0 && !view.error) {
|
|
7926
|
-
lines[3]?.put(0, view.preset === "cutouts" ? `no transparent cutouts of ${view.tag} on ${view.site} —
|
|
8354
|
+
lines[3]?.put(0, view.preset === "cutouts" ? `no transparent cutouts of ${view.tag} on ${view.site} — c searches every post, tab tries ${view.nextSite}` : `no posts of ${view.tag} on ${view.site} — tab tries ${view.nextSite}`, D);
|
|
7927
8355
|
}
|
|
7928
8356
|
const line = lines[rows - 1];
|
|
7929
8357
|
const lead = status(view);
|
|
@@ -7941,8 +8369,9 @@ function grid(lines, images, cols, rows, view, accent) {
|
|
|
7941
8369
|
keys: [
|
|
7942
8370
|
["←↑↓→", "move"],
|
|
7943
8371
|
["enter", "try on"],
|
|
7944
|
-
["tab",
|
|
7945
|
-
["
|
|
8372
|
+
["tab", "site"],
|
|
8373
|
+
["s", "settings"],
|
|
8374
|
+
["/", "search"],
|
|
7946
8375
|
["?", "keys"]
|
|
7947
8376
|
],
|
|
7948
8377
|
right: ["esc", "back"]
|
|
@@ -7955,6 +8384,12 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7955
8384
|
}
|
|
7956
8385
|
const shown = view.shown?.id === tile.id ? view.shown : undefined;
|
|
7957
8386
|
const meta2 = [badge(view), [" ", ""], [String(tile.id), B], [" ", ""], dims(tile)];
|
|
8387
|
+
if (tile.artist) {
|
|
8388
|
+
meta2.push([` · ${plain(tile.artist)}`, ""]);
|
|
8389
|
+
}
|
|
8390
|
+
if (tile.score > 0) {
|
|
8391
|
+
meta2.push([` · ★${tile.score}`, D]);
|
|
8392
|
+
}
|
|
7958
8393
|
if (shown) {
|
|
7959
8394
|
meta2.push([` · ${megabytes(shown.bytes)}`, D]);
|
|
7960
8395
|
}
|
|
@@ -7963,7 +8398,7 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7963
8398
|
meta2.push([` · ${tile.origin}`, D]);
|
|
7964
8399
|
}
|
|
7965
8400
|
if (shown) {
|
|
7966
|
-
meta2.push([" ", ""], shown
|
|
8401
|
+
meta2.push([" ", ""], ...transparent(shown));
|
|
7967
8402
|
}
|
|
7968
8403
|
lines[0]?.run(0, meta2);
|
|
7969
8404
|
if (tile.mates.length > 0) {
|
|
@@ -7983,6 +8418,7 @@ function trial(lines, images, cols, rows, view, accent) {
|
|
|
7983
8418
|
keys: view.installing === undefined ? [
|
|
7984
8419
|
["←→", "browse"],
|
|
7985
8420
|
["enter", "install"],
|
|
8421
|
+
...view.shown?.cut === "on" || view.shown?.cut === "off" ? [["x", "cut out"]] : [],
|
|
7986
8422
|
["?", "keys"]
|
|
7987
8423
|
] : [],
|
|
7988
8424
|
right: view.installing === undefined ? ["esc", "grid"] : undefined
|
|
@@ -7992,18 +8428,60 @@ var KEYS = {
|
|
|
7992
8428
|
grid: [
|
|
7993
8429
|
["move", "←↑↓→ home end pgup pgdn"],
|
|
7994
8430
|
["try on", "enter"],
|
|
7995
|
-
["
|
|
7996
|
-
["
|
|
8431
|
+
["site", `tab ${SITES.map((site) => site.name).join(", ")}`],
|
|
8432
|
+
["posts", "c cutouts or every post"],
|
|
8433
|
+
["search", "/ a tag, a post url or an id"],
|
|
8434
|
+
["unfold", "space a set of ×N"],
|
|
8435
|
+
["open", "o the post page in a browser"],
|
|
8436
|
+
["settings", "s rating, block, posts, order, sets, remove bg"],
|
|
7997
8437
|
["back", "esc returns to preview"],
|
|
7998
8438
|
["close", "? esc"]
|
|
7999
8439
|
],
|
|
8000
8440
|
try: [
|
|
8001
8441
|
["browse", "←→"],
|
|
8002
8442
|
["install", "enter"],
|
|
8443
|
+
["cut out", "x the background off or on, on an opaque picture"],
|
|
8444
|
+
["open", "o the post page in a browser"],
|
|
8003
8445
|
["grid", "esc"],
|
|
8004
8446
|
["close", "? esc"]
|
|
8005
8447
|
]
|
|
8006
8448
|
};
|
|
8449
|
+
function panel(lines, cols, rows, view, accent) {
|
|
8450
|
+
const at2 = view.panel ?? 0;
|
|
8451
|
+
const label = Math.max(...view.settings.map((row) => row.label.length));
|
|
8452
|
+
const widest = Math.max(...view.settings.map((row) => row.choices.reduce((n, c) => n + c.length + (row.multi ? 6 : 3), 0)));
|
|
8453
|
+
const w = Math.min(cols - 2, Math.max(28, label + widest + 8));
|
|
8454
|
+
const h = view.settings.length + 4;
|
|
8455
|
+
const x = Math.floor((cols - w) / 2);
|
|
8456
|
+
const y = Math.max(2, Math.floor((rows - h) / 2));
|
|
8457
|
+
lines[y]?.put(x, `╭─ settings ${"─".repeat(Math.max(0, w - 13))}╮`);
|
|
8458
|
+
for (let r = y + 1;r < y + h - 1; r++) {
|
|
8459
|
+
lines[r]?.put(x, `│${" ".repeat(w - 2)}│`);
|
|
8460
|
+
}
|
|
8461
|
+
view.settings.forEach((row, i) => {
|
|
8462
|
+
const line = lines[y + 2 + i];
|
|
8463
|
+
line.put(x + 3, row.label, i === at2 ? B : D);
|
|
8464
|
+
let c = x + 5 + label;
|
|
8465
|
+
const on = row.value.split(" ");
|
|
8466
|
+
row.choices.forEach((choice, k) => {
|
|
8467
|
+
const lit = on.includes(choice);
|
|
8468
|
+
const under = row.multi && i === at2 && k === row.cursor ? "4;" : "";
|
|
8469
|
+
const text = row.multi ? ` ${lit ? "[x]" : "[ ]"} ${choice} ` : ` ${choice} `;
|
|
8470
|
+
c = line.put(c, text, lit ? `\x1B[${under}7;${30 + view.siteAnsi}m` : under ? `\x1B[4m${D}` : D) + 1;
|
|
8471
|
+
});
|
|
8472
|
+
});
|
|
8473
|
+
lines[y + h - 1]?.put(x, `╰${"─".repeat(w - 2)}╯`);
|
|
8474
|
+
foot(lines[rows - 1], cols, accent, {
|
|
8475
|
+
badge: "SET",
|
|
8476
|
+
keys: [
|
|
8477
|
+
["↑↓", "setting"],
|
|
8478
|
+
["←→", "value"],
|
|
8479
|
+
["space", "toggle"],
|
|
8480
|
+
["enter", "save"]
|
|
8481
|
+
],
|
|
8482
|
+
right: ["esc", "undo"]
|
|
8483
|
+
});
|
|
8484
|
+
}
|
|
8007
8485
|
function help(lines, cols, rows, view, accent) {
|
|
8008
8486
|
const keys = KEYS[view.mode];
|
|
8009
8487
|
const w = Math.min(cols - 2, 50);
|
|
@@ -8036,19 +8514,23 @@ function renderFind(view, cols, rows) {
|
|
|
8036
8514
|
} else {
|
|
8037
8515
|
grid(lines, images, cols, rows, view, accent);
|
|
8038
8516
|
}
|
|
8039
|
-
if (view.help) {
|
|
8517
|
+
if (view.help || view.panel !== undefined) {
|
|
8040
8518
|
const keep = view.mode === "try" ? images : [];
|
|
8041
8519
|
for (let r = 0;r < rows; r++) {
|
|
8042
8520
|
lines[r] = new Line(cols);
|
|
8043
8521
|
}
|
|
8044
|
-
|
|
8522
|
+
if (view.help) {
|
|
8523
|
+
help(lines, cols, rows, view, accent);
|
|
8524
|
+
} else {
|
|
8525
|
+
panel(lines, cols, rows, view, accent);
|
|
8526
|
+
}
|
|
8045
8527
|
return { lines: lines.map((l) => l.render()), images: keep };
|
|
8046
8528
|
}
|
|
8047
8529
|
return { lines: lines.map((l) => l.render()), images };
|
|
8048
8530
|
}
|
|
8049
8531
|
|
|
8050
8532
|
// src/palettes.ts
|
|
8051
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as
|
|
8533
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8052
8534
|
import { homedir as homedir2 } from "node:os";
|
|
8053
8535
|
import { dirname as dirname2, join as join6 } from "node:path";
|
|
8054
8536
|
var EMITTERS = { ghostty, kitty, alacritty };
|
|
@@ -8063,7 +8545,7 @@ function readInstalled(configHome2) {
|
|
|
8063
8545
|
if (!existsSync2(path2)) {
|
|
8064
8546
|
throw new Error(`nothing installed yet at ${path2} — run \`ttheme init\` first`);
|
|
8065
8547
|
}
|
|
8066
|
-
const doc = JSON.parse(
|
|
8548
|
+
const doc = JSON.parse(readFileSync4(path2, "utf8"));
|
|
8067
8549
|
if (!Array.isArray(doc?.palettes) || !Array.isArray(doc.terminals)) {
|
|
8068
8550
|
throw new Error(`${path2} has no palettes or terminals`);
|
|
8069
8551
|
}
|
|
@@ -8158,7 +8640,7 @@ function sync(configHome2, catalog, state) {
|
|
|
8158
8640
|
}
|
|
8159
8641
|
}
|
|
8160
8642
|
const { file, body } = blockFor(terminal, configHome2, startup);
|
|
8161
|
-
const current = existsSync2(file) ?
|
|
8643
|
+
const current = existsSync2(file) ? readFileSync4(file, "utf8") : "";
|
|
8162
8644
|
if (terminal === "alacritty" && current !== "" && !current.includes("# ttheme begin")) {
|
|
8163
8645
|
continue;
|
|
8164
8646
|
}
|
|
@@ -8185,11 +8667,160 @@ function forget(configHome2, catalog, terminals, names) {
|
|
|
8185
8667
|
return removed;
|
|
8186
8668
|
}
|
|
8187
8669
|
|
|
8670
|
+
// src/unblock.ts
|
|
8671
|
+
import { connect, createServer } from "node:net";
|
|
8672
|
+
var SPLIT = 20;
|
|
8673
|
+
var HANDSHAKE = 22;
|
|
8674
|
+
function joined(parts) {
|
|
8675
|
+
const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
|
|
8676
|
+
let at2 = 0;
|
|
8677
|
+
for (const part of parts) {
|
|
8678
|
+
out.set(part, at2);
|
|
8679
|
+
at2 += part.length;
|
|
8680
|
+
}
|
|
8681
|
+
return out;
|
|
8682
|
+
}
|
|
8683
|
+
function reframe(record, at2 = SPLIT) {
|
|
8684
|
+
const body = record.subarray(5);
|
|
8685
|
+
if (record[0] !== HANDSHAKE || body.length < 2) {
|
|
8686
|
+
return [record];
|
|
8687
|
+
}
|
|
8688
|
+
const cut = Math.max(1, Math.min(at2, body.length - 1));
|
|
8689
|
+
const framed = (part) => joined([
|
|
8690
|
+
Uint8Array.from([
|
|
8691
|
+
record[0],
|
|
8692
|
+
record[1],
|
|
8693
|
+
record[2],
|
|
8694
|
+
part.length >> 8,
|
|
8695
|
+
part.length & 255
|
|
8696
|
+
]),
|
|
8697
|
+
part
|
|
8698
|
+
]);
|
|
8699
|
+
return [framed(body.subarray(0, cut)), framed(body.subarray(cut))];
|
|
8700
|
+
}
|
|
8701
|
+
function tunnelTo(client, host, port, rest) {
|
|
8702
|
+
const upstream = connect(port, host, () => {
|
|
8703
|
+
upstream.setNoDelay(true);
|
|
8704
|
+
client.write(`HTTP/1.1 200 Connection Established\r
|
|
8705
|
+
\r
|
|
8706
|
+
`);
|
|
8707
|
+
let held = rest;
|
|
8708
|
+
let framed = false;
|
|
8709
|
+
const forward = (chunk) => {
|
|
8710
|
+
if (framed) {
|
|
8711
|
+
upstream.write(chunk);
|
|
8712
|
+
return;
|
|
8713
|
+
}
|
|
8714
|
+
held = joined([held, chunk]);
|
|
8715
|
+
if (held.length < 5) {
|
|
8716
|
+
return;
|
|
8717
|
+
}
|
|
8718
|
+
const length = held[3] * 256 + held[4];
|
|
8719
|
+
if (held[0] !== HANDSHAKE) {
|
|
8720
|
+
framed = true;
|
|
8721
|
+
upstream.write(held);
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
if (held.length < 5 + length) {
|
|
8725
|
+
return;
|
|
8726
|
+
}
|
|
8727
|
+
framed = true;
|
|
8728
|
+
const [first, second] = reframe(held.subarray(0, 5 + length));
|
|
8729
|
+
upstream.write(first);
|
|
8730
|
+
setTimeout(() => {
|
|
8731
|
+
if (second) {
|
|
8732
|
+
upstream.write(second);
|
|
8733
|
+
}
|
|
8734
|
+
upstream.write(held.subarray(5 + length));
|
|
8735
|
+
}, 10);
|
|
8736
|
+
};
|
|
8737
|
+
client.on("data", forward);
|
|
8738
|
+
if (held.length > 0) {
|
|
8739
|
+
const pending = held;
|
|
8740
|
+
held = new Uint8Array(0);
|
|
8741
|
+
forward(pending);
|
|
8742
|
+
}
|
|
8743
|
+
upstream.pipe(client);
|
|
8744
|
+
});
|
|
8745
|
+
upstream.on("error", () => client.destroy());
|
|
8746
|
+
client.on("error", () => upstream.destroy());
|
|
8747
|
+
}
|
|
8748
|
+
function serve(client) {
|
|
8749
|
+
client.setNoDelay(true);
|
|
8750
|
+
let head = new Uint8Array(0);
|
|
8751
|
+
const onData = (chunk) => {
|
|
8752
|
+
head = joined([head, chunk]);
|
|
8753
|
+
const text = Buffer.from(head).toString("latin1");
|
|
8754
|
+
const end = text.indexOf(`\r
|
|
8755
|
+
\r
|
|
8756
|
+
`);
|
|
8757
|
+
if (end === -1) {
|
|
8758
|
+
return;
|
|
8759
|
+
}
|
|
8760
|
+
client.off("data", onData);
|
|
8761
|
+
const target = /^CONNECT ([^ :]+):(\d+)/.exec(text);
|
|
8762
|
+
if (!target) {
|
|
8763
|
+
client.end(`HTTP/1.1 405 Method Not Allowed\r
|
|
8764
|
+
\r
|
|
8765
|
+
`);
|
|
8766
|
+
return;
|
|
8767
|
+
}
|
|
8768
|
+
tunnelTo(client, target[1], Number(target[2]), head.subarray(end + 4));
|
|
8769
|
+
};
|
|
8770
|
+
client.on("data", onData);
|
|
8771
|
+
client.on("error", () => client.destroy());
|
|
8772
|
+
}
|
|
8773
|
+
function tunnel() {
|
|
8774
|
+
const server = createServer(serve);
|
|
8775
|
+
return new Promise((resolve2, reject) => {
|
|
8776
|
+
server.once("error", reject);
|
|
8777
|
+
server.listen(0, "127.0.0.1", () => {
|
|
8778
|
+
resolve2({
|
|
8779
|
+
port: server.address().port,
|
|
8780
|
+
close: () => {
|
|
8781
|
+
server.close();
|
|
8782
|
+
server.unref();
|
|
8783
|
+
}
|
|
8784
|
+
});
|
|
8785
|
+
});
|
|
8786
|
+
});
|
|
8787
|
+
}
|
|
8788
|
+
|
|
8188
8789
|
// src/find.ts
|
|
8790
|
+
var SETTINGS = [
|
|
8791
|
+
{ name: "TTHEME_FIND_RATING", label: "rating", choices: RATINGS, multi: { read: ratingSet } },
|
|
8792
|
+
{ name: "TTHEME_FIND_BLOCK", label: "block", choices: BLOCKS, multi: { read: blockSet, none: "none" } },
|
|
8793
|
+
{ name: "TTHEME_FIND_POSTS", label: "posts", choices: ["all", "cutouts"] },
|
|
8794
|
+
{ name: "TTHEME_FIND_ORDER", label: "order", choices: ["newest", "score"] },
|
|
8795
|
+
{ name: "TTHEME_FIND_SETS", label: "sets", choices: ["fold", "show"] },
|
|
8796
|
+
...canRemoveBackground() ? [{ name: "TTHEME_FIND_REMOVE_BG", label: "remove bg", choices: ["on", "off"] }] : []
|
|
8797
|
+
];
|
|
8798
|
+
function initial(setting, raw) {
|
|
8799
|
+
if (setting.multi) {
|
|
8800
|
+
return setting.multi.read(raw).join(" ") || setting.multi.none;
|
|
8801
|
+
}
|
|
8802
|
+
return setting.choices.find((choice) => choice === raw) ?? setting.choices[0];
|
|
8803
|
+
}
|
|
8189
8804
|
var WORKERS = 4;
|
|
8805
|
+
var PRELOAD = 2;
|
|
8190
8806
|
var SETTLE = 150;
|
|
8807
|
+
var ESCAPE = 30;
|
|
8191
8808
|
var TRY_WIDTH = 1280;
|
|
8192
8809
|
var LOOKAHEAD = 1;
|
|
8810
|
+
function blank(key, searching) {
|
|
8811
|
+
return {
|
|
8812
|
+
key,
|
|
8813
|
+
groups: [],
|
|
8814
|
+
sources: [],
|
|
8815
|
+
queue: [],
|
|
8816
|
+
seen: new Set,
|
|
8817
|
+
checked: 0,
|
|
8818
|
+
total: 0,
|
|
8819
|
+
focus: 0,
|
|
8820
|
+
top: 0,
|
|
8821
|
+
searching
|
|
8822
|
+
};
|
|
8823
|
+
}
|
|
8193
8824
|
function describe(error) {
|
|
8194
8825
|
if (!(error instanceof Error)) {
|
|
8195
8826
|
return String(error);
|
|
@@ -8201,14 +8832,34 @@ function describe(error) {
|
|
|
8201
8832
|
return describe(error.cause);
|
|
8202
8833
|
}
|
|
8203
8834
|
const code = error.code;
|
|
8204
|
-
|
|
8835
|
+
if (typeof code === "string") {
|
|
8836
|
+
return code;
|
|
8837
|
+
}
|
|
8838
|
+
const first = (error.message || error.name).split(".")[0] ?? "";
|
|
8839
|
+
return first.length > 56 ? `${first.slice(0, 55)}…` : first;
|
|
8205
8840
|
}
|
|
8206
8841
|
function reason(site, error) {
|
|
8207
8842
|
return `${site.name}: ${describe(error)}`;
|
|
8208
8843
|
}
|
|
8844
|
+
function incomplete(input) {
|
|
8845
|
+
const at2 = input.lastIndexOf("\x1B");
|
|
8846
|
+
return at2 !== -1 && /^\[?[0-9;]*$/.test(input.slice(at2 + 1));
|
|
8847
|
+
}
|
|
8209
8848
|
function tick() {
|
|
8210
8849
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
8211
8850
|
}
|
|
8851
|
+
function readCache(site, name) {
|
|
8852
|
+
try {
|
|
8853
|
+
return JSON.parse(readFileSync5(join7(cacheDir(site), name), "utf8"));
|
|
8854
|
+
} catch {
|
|
8855
|
+
return {};
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
function writeCache(site, name, data) {
|
|
8859
|
+
mkdirSync4(cacheDir(site), { recursive: true });
|
|
8860
|
+
writeFileSync4(join7(cacheDir(site), name), `${JSON.stringify(data)}
|
|
8861
|
+
`);
|
|
8862
|
+
}
|
|
8212
8863
|
|
|
8213
8864
|
class Finder {
|
|
8214
8865
|
view;
|
|
@@ -8219,12 +8870,18 @@ class Finder {
|
|
|
8219
8870
|
rows = process.stdout.rows || 24;
|
|
8220
8871
|
gen = 0;
|
|
8221
8872
|
pumping = 0;
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
seen = new Set;
|
|
8873
|
+
boards = new Map;
|
|
8874
|
+
board;
|
|
8225
8875
|
posts = new Map;
|
|
8876
|
+
thumbPath = new Map;
|
|
8226
8877
|
siteIndex = 0;
|
|
8878
|
+
values = new Map(SETTINGS.map((setting) => [setting.name, initial(setting, process.env[setting.name])]));
|
|
8879
|
+
direction = 1;
|
|
8880
|
+
prefetching = 0;
|
|
8881
|
+
inflight = new Map;
|
|
8227
8882
|
owners = new Map;
|
|
8883
|
+
probed = new Map;
|
|
8884
|
+
unsaved = new Set;
|
|
8228
8885
|
thumbQueue = [];
|
|
8229
8886
|
thumbing = 0;
|
|
8230
8887
|
sent = new Map;
|
|
@@ -8232,6 +8889,7 @@ class Finder {
|
|
|
8232
8889
|
current;
|
|
8233
8890
|
fetch;
|
|
8234
8891
|
settle;
|
|
8892
|
+
partial;
|
|
8235
8893
|
input = "";
|
|
8236
8894
|
probeWait;
|
|
8237
8895
|
done;
|
|
@@ -8251,17 +8909,32 @@ class Finder {
|
|
|
8251
8909
|
site: this.site.name,
|
|
8252
8910
|
siteAnsi: this.site.ansi,
|
|
8253
8911
|
nextSite: this.nextSite.name,
|
|
8254
|
-
preset: "cutouts",
|
|
8912
|
+
preset: this.setting("TTHEME_FIND_POSTS") === "all" ? "all" : "cutouts",
|
|
8913
|
+
order: this.setting("TTHEME_FIND_ORDER") === "score" ? "score" : "newest",
|
|
8914
|
+
rating: ratingSet(this.setting("TTHEME_FIND_RATING")),
|
|
8915
|
+
block: blockSet(this.setting("TTHEME_FIND_BLOCK")),
|
|
8916
|
+
sets: this.setting("TTHEME_FIND_SETS") === "show" ? "show" : "fold",
|
|
8917
|
+
unblocked: process.env.TTHEME_FIND_PROXY !== undefined,
|
|
8918
|
+
settings: SETTINGS.map((setting) => ({
|
|
8919
|
+
label: setting.label,
|
|
8920
|
+
choices: [...setting.choices],
|
|
8921
|
+
value: this.setting(setting.name),
|
|
8922
|
+
multi: setting.multi && { none: setting.multi.none },
|
|
8923
|
+
cursor: 0
|
|
8924
|
+
})),
|
|
8255
8925
|
colors: { cursor: entry.cursor, selection: entry.selection, ansi: entry.ansi },
|
|
8256
8926
|
tiles: [],
|
|
8257
8927
|
checked: 0,
|
|
8258
8928
|
total: 0,
|
|
8259
|
-
searching:
|
|
8929
|
+
searching: tag !== "",
|
|
8260
8930
|
focus: 0,
|
|
8261
8931
|
top: 0,
|
|
8262
8932
|
mode: "grid",
|
|
8263
|
-
help: false
|
|
8933
|
+
help: false,
|
|
8934
|
+
editing: tag === "" ? "" : undefined
|
|
8264
8935
|
};
|
|
8936
|
+
this.board = blank(this.boardKey, tag !== "");
|
|
8937
|
+
this.boards.set(this.board.key, this.board);
|
|
8265
8938
|
}
|
|
8266
8939
|
get signal() {
|
|
8267
8940
|
return this.session.signal;
|
|
@@ -8272,6 +8945,52 @@ class Finder {
|
|
|
8272
8945
|
get nextSite() {
|
|
8273
8946
|
return SITES[(this.siteIndex + 1) % SITES.length];
|
|
8274
8947
|
}
|
|
8948
|
+
setting(name) {
|
|
8949
|
+
return this.values.get(name);
|
|
8950
|
+
}
|
|
8951
|
+
get boardKey() {
|
|
8952
|
+
return `${this.site.key}|${this.view.preset}|${this.view.order}`;
|
|
8953
|
+
}
|
|
8954
|
+
mark(site, id) {
|
|
8955
|
+
return `${site.key}:${id}`;
|
|
8956
|
+
}
|
|
8957
|
+
origPath(site, id, ext) {
|
|
8958
|
+
return join7(this.scratch, "orig", `${site.key}-${id}.${ext}`);
|
|
8959
|
+
}
|
|
8960
|
+
tileOf(site, post2, variants) {
|
|
8961
|
+
const version2 = rendition(post2) ?? post2;
|
|
8962
|
+
return {
|
|
8963
|
+
id: post2.id,
|
|
8964
|
+
width: version2.width,
|
|
8965
|
+
height: version2.height,
|
|
8966
|
+
reduced: version2 !== post2,
|
|
8967
|
+
owner: mirrored(post2.owner) ? "" : post2.owner,
|
|
8968
|
+
artist: post2.artist,
|
|
8969
|
+
score: post2.score,
|
|
8970
|
+
variants,
|
|
8971
|
+
origin: originHost(post2.source),
|
|
8972
|
+
mates: this.owners.get(site.key)?.get(post2.owner) ?? [],
|
|
8973
|
+
thumb: this.thumbPath.get(this.mark(site, post2.id))
|
|
8974
|
+
};
|
|
8975
|
+
}
|
|
8976
|
+
show() {
|
|
8977
|
+
const site = this.site;
|
|
8978
|
+
const view = this.view;
|
|
8979
|
+
const board = this.board;
|
|
8980
|
+
view.tiles = board.groups.flatMap((group) => group.open ? group.posts.map((post2) => this.tileOf(site, post2, 0)) : [this.tileOf(site, group.posts[0], group.posts.length)]);
|
|
8981
|
+
view.checked = board.checked;
|
|
8982
|
+
view.total = board.total;
|
|
8983
|
+
view.searching = board.searching;
|
|
8984
|
+
view.note = board.note;
|
|
8985
|
+
view.focus = Math.max(0, Math.min(board.focus, view.tiles.length - 1));
|
|
8986
|
+
view.top = board.top;
|
|
8987
|
+
}
|
|
8988
|
+
syncSite() {
|
|
8989
|
+
const view = this.view;
|
|
8990
|
+
view.site = this.site.name;
|
|
8991
|
+
view.siteAnsi = this.site.ansi;
|
|
8992
|
+
view.nextSite = this.nextSite.name;
|
|
8993
|
+
}
|
|
8275
8994
|
async run() {
|
|
8276
8995
|
const { stdin, stdout } = process;
|
|
8277
8996
|
stdin.setRawMode(true);
|
|
@@ -8282,10 +9001,13 @@ class Finder {
|
|
|
8282
9001
|
const exit = new Promise((resolve2) => {
|
|
8283
9002
|
this.done = resolve2;
|
|
8284
9003
|
});
|
|
9004
|
+
const clock = setInterval(this.onClock, 250);
|
|
8285
9005
|
const cell = await this.probe();
|
|
8286
9006
|
if (cell) {
|
|
8287
9007
|
this.cell = cell;
|
|
8288
|
-
this.
|
|
9008
|
+
if (this.view.tag) {
|
|
9009
|
+
this.search();
|
|
9010
|
+
}
|
|
8289
9011
|
} else {
|
|
8290
9012
|
this.view.searching = false;
|
|
8291
9013
|
this.view.error = "this terminal does not report its cell size — find needs kitty graphics";
|
|
@@ -8295,6 +9017,8 @@ class Finder {
|
|
|
8295
9017
|
this.session.abort();
|
|
8296
9018
|
this.fetch?.abort();
|
|
8297
9019
|
clearTimeout(this.settle);
|
|
9020
|
+
clearTimeout(this.partial);
|
|
9021
|
+
clearInterval(clock);
|
|
8298
9022
|
stdin.off("data", this.onData);
|
|
8299
9023
|
stdout.off("resize", this.onResize);
|
|
8300
9024
|
this.write("\x1B_Ga=d,d=A,q=2\x1B\\\x1B[H\x1B[J");
|
|
@@ -8334,12 +9058,28 @@ class Finder {
|
|
|
8334
9058
|
this.input = this.input.slice(0, at2) + this.input.slice(at2 + 1 + m[0].length);
|
|
8335
9059
|
this.probeWait({ h: Number(m[1]), w: Number(m[2]) });
|
|
8336
9060
|
}
|
|
9061
|
+
clearTimeout(this.partial);
|
|
9062
|
+
if (incomplete(this.input)) {
|
|
9063
|
+
this.partial = setTimeout(this.flushKeys, ESCAPE);
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
this.flushKeys();
|
|
9067
|
+
};
|
|
9068
|
+
flushKeys = () => {
|
|
8337
9069
|
const keys = decodeKeys(this.input);
|
|
8338
9070
|
this.input = "";
|
|
8339
9071
|
for (const key of keys) {
|
|
8340
9072
|
this.key(key);
|
|
8341
9073
|
}
|
|
8342
9074
|
};
|
|
9075
|
+
onClock = () => {
|
|
9076
|
+
const left = Math.ceil((pausedUntil(this.site) - Date.now()) / 1000);
|
|
9077
|
+
const waiting = left > 0 ? left : undefined;
|
|
9078
|
+
if (waiting !== this.view.waiting) {
|
|
9079
|
+
this.view.waiting = waiting;
|
|
9080
|
+
this.draw();
|
|
9081
|
+
}
|
|
9082
|
+
};
|
|
8343
9083
|
onResize = () => {
|
|
8344
9084
|
this.cols = process.stdout.columns || this.cols;
|
|
8345
9085
|
this.rows = process.stdout.rows || this.rows;
|
|
@@ -8359,6 +9099,14 @@ class Finder {
|
|
|
8359
9099
|
if (view.installing !== undefined) {
|
|
8360
9100
|
return;
|
|
8361
9101
|
}
|
|
9102
|
+
if (view.editing !== undefined) {
|
|
9103
|
+
this.editKey(key);
|
|
9104
|
+
return;
|
|
9105
|
+
}
|
|
9106
|
+
if (view.panel !== undefined) {
|
|
9107
|
+
this.panelKey(key);
|
|
9108
|
+
return;
|
|
9109
|
+
}
|
|
8362
9110
|
if (view.help) {
|
|
8363
9111
|
if (key === "?" || key === "esc") {
|
|
8364
9112
|
view.help = false;
|
|
@@ -8407,29 +9155,265 @@ class Finder {
|
|
|
8407
9155
|
this.select();
|
|
8408
9156
|
return;
|
|
8409
9157
|
}
|
|
8410
|
-
if (key === "
|
|
9158
|
+
if (key === "c") {
|
|
8411
9159
|
view.preset = view.preset === "cutouts" ? "all" : "cutouts";
|
|
8412
|
-
this.
|
|
9160
|
+
this.turn();
|
|
8413
9161
|
return;
|
|
8414
9162
|
}
|
|
8415
|
-
if (key === "
|
|
9163
|
+
if (key === "tab") {
|
|
8416
9164
|
this.siteIndex = (this.siteIndex + 1) % SITES.length;
|
|
8417
|
-
this.
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
9165
|
+
this.syncSite();
|
|
9166
|
+
this.turn();
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
if (key === "/") {
|
|
9170
|
+
view.editing = "";
|
|
9171
|
+
this.draw();
|
|
9172
|
+
return;
|
|
9173
|
+
}
|
|
9174
|
+
if (key === " ") {
|
|
9175
|
+
this.unfold();
|
|
9176
|
+
return;
|
|
9177
|
+
}
|
|
9178
|
+
if (key === "o") {
|
|
9179
|
+
this.openPage();
|
|
9180
|
+
return;
|
|
9181
|
+
}
|
|
9182
|
+
if (key === "s") {
|
|
9183
|
+
view.panel = 0;
|
|
9184
|
+
this.draw();
|
|
8423
9185
|
return;
|
|
8424
9186
|
}
|
|
8425
9187
|
if (key === "esc") {
|
|
8426
9188
|
this.finish(2);
|
|
8427
9189
|
}
|
|
8428
9190
|
}
|
|
9191
|
+
panelKey(key) {
|
|
9192
|
+
const view = this.view;
|
|
9193
|
+
const at2 = view.panel ?? 0;
|
|
9194
|
+
const row = view.settings[at2];
|
|
9195
|
+
if (key === "up" || key === "down") {
|
|
9196
|
+
view.panel = Math.max(0, Math.min(view.settings.length - 1, at2 + (key === "up" ? -1 : 1)));
|
|
9197
|
+
this.draw();
|
|
9198
|
+
return;
|
|
9199
|
+
}
|
|
9200
|
+
if ((key === "left" || key === "right") && row) {
|
|
9201
|
+
const step = key === "left" ? -1 : 1;
|
|
9202
|
+
if (row.multi) {
|
|
9203
|
+
row.cursor = (row.cursor + step + row.choices.length) % row.choices.length;
|
|
9204
|
+
} else {
|
|
9205
|
+
const index = row.choices.indexOf(row.value);
|
|
9206
|
+
row.value = row.choices[(index + step + row.choices.length) % row.choices.length];
|
|
9207
|
+
}
|
|
9208
|
+
this.draw();
|
|
9209
|
+
return;
|
|
9210
|
+
}
|
|
9211
|
+
if (key === " " && row?.multi) {
|
|
9212
|
+
const on = row.value.split(" ");
|
|
9213
|
+
const next = row.choices.filter((choice, k) => k === row.cursor ? !on.includes(choice) : on.includes(choice));
|
|
9214
|
+
const empty = row.multi.none;
|
|
9215
|
+
if (next.length > 0 || empty !== undefined) {
|
|
9216
|
+
row.value = next.length > 0 ? next.join(" ") : empty;
|
|
9217
|
+
this.draw();
|
|
9218
|
+
}
|
|
9219
|
+
return;
|
|
9220
|
+
}
|
|
9221
|
+
if (key === "esc") {
|
|
9222
|
+
view.settings.forEach((setting, i) => {
|
|
9223
|
+
setting.value = this.setting(SETTINGS[i]?.name ?? "");
|
|
9224
|
+
});
|
|
9225
|
+
view.panel = undefined;
|
|
9226
|
+
this.draw();
|
|
9227
|
+
return;
|
|
9228
|
+
}
|
|
9229
|
+
if (key === "enter" || key === "alt-c") {
|
|
9230
|
+
view.panel = undefined;
|
|
9231
|
+
this.adopt();
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
adopt() {
|
|
9235
|
+
const view = this.view;
|
|
9236
|
+
const before = SETTINGS.map((setting) => this.setting(setting.name));
|
|
9237
|
+
view.settings.forEach((row, i) => {
|
|
9238
|
+
const setting = SETTINGS[i];
|
|
9239
|
+
if (setting) {
|
|
9240
|
+
this.values.set(setting.name, row.value);
|
|
9241
|
+
}
|
|
9242
|
+
});
|
|
9243
|
+
const changed = SETTINGS.filter((setting, i) => this.setting(setting.name) !== before[i]);
|
|
9244
|
+
if (changed.length === 0) {
|
|
9245
|
+
this.draw();
|
|
9246
|
+
return;
|
|
9247
|
+
}
|
|
9248
|
+
this.save(changed);
|
|
9249
|
+
view.rating = ratingSet(this.setting("TTHEME_FIND_RATING"));
|
|
9250
|
+
view.block = blockSet(this.setting("TTHEME_FIND_BLOCK"));
|
|
9251
|
+
view.sets = this.setting("TTHEME_FIND_SETS") === "show" ? "show" : "fold";
|
|
9252
|
+
view.preset = this.setting("TTHEME_FIND_POSTS") === "all" ? "all" : "cutouts";
|
|
9253
|
+
view.order = this.setting("TTHEME_FIND_ORDER") === "score" ? "score" : "newest";
|
|
9254
|
+
this.boards.clear();
|
|
9255
|
+
this.current = undefined;
|
|
9256
|
+
view.shown = undefined;
|
|
9257
|
+
view.mode = "grid";
|
|
9258
|
+
this.search();
|
|
9259
|
+
}
|
|
9260
|
+
save(changed) {
|
|
9261
|
+
const path2 = join7(this.home, "ttheme", "config.zsh");
|
|
9262
|
+
try {
|
|
9263
|
+
const before = existsSync3(path2) ? readFileSync5(path2, "utf8") : "";
|
|
9264
|
+
const after = changed.reduce((text, setting) => withSetting(text, setting.name, this.setting(setting.name)), before);
|
|
9265
|
+
writeFileSync4(path2, after);
|
|
9266
|
+
} catch (error) {
|
|
9267
|
+
this.view.error = error instanceof Error ? error.message : String(error);
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
editKey(key) {
|
|
9271
|
+
const view = this.view;
|
|
9272
|
+
const text = view.editing ?? "";
|
|
9273
|
+
if (key === "esc") {
|
|
9274
|
+
view.editing = undefined;
|
|
9275
|
+
if (view.tag === "") {
|
|
9276
|
+
this.finish(2);
|
|
9277
|
+
return;
|
|
9278
|
+
}
|
|
9279
|
+
this.draw();
|
|
9280
|
+
return;
|
|
9281
|
+
}
|
|
9282
|
+
if (key === "enter") {
|
|
9283
|
+
view.editing = undefined;
|
|
9284
|
+
if (text.trim()) {
|
|
9285
|
+
this.commit(text.trim());
|
|
9286
|
+
return;
|
|
9287
|
+
}
|
|
9288
|
+
if (view.tag === "") {
|
|
9289
|
+
this.finish(2);
|
|
9290
|
+
return;
|
|
9291
|
+
}
|
|
9292
|
+
this.draw();
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
9295
|
+
if (key === "backspace") {
|
|
9296
|
+
view.editing = text.slice(0, -1);
|
|
9297
|
+
this.draw();
|
|
9298
|
+
return;
|
|
9299
|
+
}
|
|
9300
|
+
if (key.length === 1 && key >= " ") {
|
|
9301
|
+
view.editing = text + key;
|
|
9302
|
+
this.draw();
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9305
|
+
async commit(text) {
|
|
9306
|
+
const ref = postRef(text, this.site);
|
|
9307
|
+
if (ref) {
|
|
9308
|
+
await this.jump(ref.site, ref.id);
|
|
9309
|
+
return;
|
|
9310
|
+
}
|
|
9311
|
+
this.view.tag = text;
|
|
9312
|
+
this.boards.clear();
|
|
9313
|
+
await this.search();
|
|
9314
|
+
}
|
|
9315
|
+
async jump(site, id) {
|
|
9316
|
+
const gen = ++this.gen;
|
|
9317
|
+
this.siteIndex = SITES.indexOf(site);
|
|
9318
|
+
this.syncSite();
|
|
9319
|
+
this.current = undefined;
|
|
9320
|
+
this.view.shown = undefined;
|
|
9321
|
+
this.view.error = undefined;
|
|
9322
|
+
const board = blank(`${site.key}|post:${id}`, true);
|
|
9323
|
+
this.board = board;
|
|
9324
|
+
this.boards.set(board.key, board);
|
|
9325
|
+
this.show();
|
|
9326
|
+
this.draw();
|
|
9327
|
+
try {
|
|
9328
|
+
const post2 = await fetchPost(site, id, this.signal);
|
|
9329
|
+
if (gen !== this.gen) {
|
|
9330
|
+
return;
|
|
9331
|
+
}
|
|
9332
|
+
board.searching = false;
|
|
9333
|
+
if (!post2) {
|
|
9334
|
+
board.error = `${site.name} has no post ${id}`;
|
|
9335
|
+
this.view.error = board.error;
|
|
9336
|
+
} else {
|
|
9337
|
+
this.posts.set(this.mark(site, post2.id), post2);
|
|
9338
|
+
board.groups = [{ posts: [post2], open: false }];
|
|
9339
|
+
board.checked = 1;
|
|
9340
|
+
board.total = 1;
|
|
9341
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
9342
|
+
this.thumbs();
|
|
9343
|
+
this.view.mode = "try";
|
|
9344
|
+
}
|
|
9345
|
+
this.show();
|
|
9346
|
+
if (post2) {
|
|
9347
|
+
this.select();
|
|
9348
|
+
}
|
|
9349
|
+
this.draw();
|
|
9350
|
+
} catch (error) {
|
|
9351
|
+
this.fail(gen, error);
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
async turn() {
|
|
9355
|
+
this.current = undefined;
|
|
9356
|
+
this.view.shown = undefined;
|
|
9357
|
+
const known = this.boards.get(this.boardKey);
|
|
9358
|
+
if (!known) {
|
|
9359
|
+
await this.search();
|
|
9360
|
+
return;
|
|
9361
|
+
}
|
|
9362
|
+
this.gen++;
|
|
9363
|
+
this.board = known;
|
|
9364
|
+
this.view.error = known.error;
|
|
9365
|
+
this.show();
|
|
9366
|
+
this.draw();
|
|
9367
|
+
await this.pump();
|
|
9368
|
+
}
|
|
9369
|
+
unfold() {
|
|
9370
|
+
const view = this.view;
|
|
9371
|
+
let index = 0;
|
|
9372
|
+
for (const group of this.board.groups) {
|
|
9373
|
+
const size = group.open ? group.posts.length : 1;
|
|
9374
|
+
if (view.focus < index + size) {
|
|
9375
|
+
if (group.posts.length < 2) {
|
|
9376
|
+
return;
|
|
9377
|
+
}
|
|
9378
|
+
group.open = !group.open;
|
|
9379
|
+
this.board.focus = index;
|
|
9380
|
+
for (const post2 of group.posts) {
|
|
9381
|
+
if (!this.thumbPath.has(this.mark(this.site, post2.id))) {
|
|
9382
|
+
this.thumbQueue.push({ site: this.site, post: post2 });
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
this.thumbs();
|
|
9386
|
+
this.show();
|
|
9387
|
+
this.scroll();
|
|
9388
|
+
this.draw();
|
|
9389
|
+
return;
|
|
9390
|
+
}
|
|
9391
|
+
index += size;
|
|
9392
|
+
}
|
|
9393
|
+
}
|
|
9394
|
+
openPage() {
|
|
9395
|
+
const tile = this.view.tiles[this.view.focus];
|
|
9396
|
+
if (!tile) {
|
|
9397
|
+
return;
|
|
9398
|
+
}
|
|
9399
|
+
const opener = process.platform === "darwin" ? "open" : "xdg-open";
|
|
9400
|
+
try {
|
|
9401
|
+
spawn2(opener, [this.site.pageUrl(tile.id)], { stdio: "ignore", detached: true }).unref();
|
|
9402
|
+
} catch {}
|
|
9403
|
+
}
|
|
8429
9404
|
tryKey(key) {
|
|
8430
9405
|
const view = this.view;
|
|
8431
9406
|
if (key === "left" || key === "right") {
|
|
8432
|
-
this.
|
|
9407
|
+
this.direction = key === "left" ? -1 : 1;
|
|
9408
|
+
this.focus(view.focus + this.direction);
|
|
9409
|
+
return;
|
|
9410
|
+
}
|
|
9411
|
+
if (key === "o") {
|
|
9412
|
+
this.openPage();
|
|
9413
|
+
return;
|
|
9414
|
+
}
|
|
9415
|
+
if (key === "x") {
|
|
9416
|
+
this.swap();
|
|
8433
9417
|
return;
|
|
8434
9418
|
}
|
|
8435
9419
|
if (key === "enter") {
|
|
@@ -8455,6 +9439,7 @@ class Finder {
|
|
|
8455
9439
|
return;
|
|
8456
9440
|
}
|
|
8457
9441
|
view.focus = next;
|
|
9442
|
+
this.board.focus = next;
|
|
8458
9443
|
this.scroll();
|
|
8459
9444
|
this.pump();
|
|
8460
9445
|
if (view.mode === "try") {
|
|
@@ -8472,28 +9457,50 @@ class Finder {
|
|
|
8472
9457
|
if (row > view.top + rowsVis - 1) {
|
|
8473
9458
|
view.top = row - rowsVis + 1;
|
|
8474
9459
|
}
|
|
9460
|
+
this.board.top = view.top;
|
|
9461
|
+
}
|
|
9462
|
+
query(site) {
|
|
9463
|
+
const view = this.view;
|
|
9464
|
+
const wanted = [
|
|
9465
|
+
view.tag,
|
|
9466
|
+
site.rate(this.view.rating),
|
|
9467
|
+
view.preset === "cutouts" ? site.cutouts : "",
|
|
9468
|
+
view.order === "score" ? site.best : ""
|
|
9469
|
+
].filter(Boolean);
|
|
9470
|
+
const kept = [];
|
|
9471
|
+
const dropped = [];
|
|
9472
|
+
for (const part of wanted) {
|
|
9473
|
+
if (tagsOf([...kept, part].join(" ")) <= site.tagBudget) {
|
|
9474
|
+
kept.push(part);
|
|
9475
|
+
} else {
|
|
9476
|
+
dropped.push(part);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
this.board.note = dropped.length > 0 ? `${site.name} takes ${site.tagBudget} tags — ${dropped.join(" ")} left out` : undefined;
|
|
9480
|
+
return kept.join(" ");
|
|
8475
9481
|
}
|
|
8476
9482
|
async search() {
|
|
8477
9483
|
const gen = ++this.gen;
|
|
8478
|
-
const view = this.view;
|
|
8479
|
-
Object.assign(view, { tiles: [], checked: 0, total: 0, searching: true, focus: 0, top: 0, error: undefined });
|
|
8480
|
-
this.seen.clear();
|
|
8481
|
-
this.queue = [];
|
|
8482
|
-
this.sources = [];
|
|
8483
|
-
this.thumbQueue = [];
|
|
8484
|
-
this.draw();
|
|
8485
9484
|
const site = this.site;
|
|
8486
|
-
const
|
|
9485
|
+
const board = blank(this.boardKey, true);
|
|
9486
|
+
this.board = board;
|
|
9487
|
+
this.boards.set(board.key, board);
|
|
9488
|
+
this.view.error = undefined;
|
|
9489
|
+
this.show();
|
|
9490
|
+
this.draw();
|
|
9491
|
+
const tags = this.query(site);
|
|
8487
9492
|
try {
|
|
8488
9493
|
const [total, owners] = await Promise.all([fetchCount(site, tags, this.signal), this.mateOwners(site)]);
|
|
8489
9494
|
if (gen !== this.gen) {
|
|
8490
9495
|
return;
|
|
8491
9496
|
}
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
9497
|
+
board.total = total;
|
|
9498
|
+
const room = tagsOf(tags) + 1 <= site.tagBudget;
|
|
9499
|
+
board.sources = [
|
|
9500
|
+
...room ? [...owners.keys()].map((owner) => ({ tags: `${tags} user:${owner}`, page: 0, done: false })) : [],
|
|
8495
9501
|
{ tags, page: 0, done: false }
|
|
8496
9502
|
];
|
|
9503
|
+
this.show();
|
|
8497
9504
|
this.draw();
|
|
8498
9505
|
await this.pump();
|
|
8499
9506
|
} catch (error) {
|
|
@@ -8504,62 +9511,64 @@ class Finder {
|
|
|
8504
9511
|
if (gen !== this.gen || this.signal.aborted) {
|
|
8505
9512
|
return;
|
|
8506
9513
|
}
|
|
8507
|
-
this.
|
|
8508
|
-
this.
|
|
9514
|
+
this.board.error = reason(this.site, error);
|
|
9515
|
+
this.board.searching = false;
|
|
9516
|
+
this.view.error = this.board.error;
|
|
9517
|
+
this.show();
|
|
8509
9518
|
this.draw();
|
|
8510
9519
|
}
|
|
8511
9520
|
wants() {
|
|
8512
|
-
const view = this.view;
|
|
8513
|
-
if (view.checked < Math.min(view.total, PAGE)) {
|
|
8514
|
-
return true;
|
|
8515
|
-
}
|
|
8516
9521
|
const { perRow, rowsVis } = gridShape(this.cols, this.rows);
|
|
8517
|
-
return view.tiles.length < (
|
|
9522
|
+
return this.view.tiles.length < (this.board.top + rowsVis + LOOKAHEAD) * perRow;
|
|
8518
9523
|
}
|
|
8519
9524
|
async pump() {
|
|
8520
9525
|
const gen = this.gen;
|
|
8521
|
-
|
|
9526
|
+
const board = this.board;
|
|
9527
|
+
if (this.pumping === gen || board.sources.length === 0) {
|
|
8522
9528
|
return;
|
|
8523
9529
|
}
|
|
8524
9530
|
this.pumping = gen;
|
|
8525
|
-
const
|
|
9531
|
+
const site = this.site;
|
|
8526
9532
|
try {
|
|
8527
|
-
|
|
9533
|
+
board.searching = true;
|
|
8528
9534
|
while (gen === this.gen && this.wants()) {
|
|
8529
|
-
if (
|
|
8530
|
-
const source =
|
|
9535
|
+
if (board.queue.length === 0) {
|
|
9536
|
+
const source = board.sources.find((s) => !s.done);
|
|
8531
9537
|
if (!source) {
|
|
8532
9538
|
break;
|
|
8533
9539
|
}
|
|
8534
|
-
const posts = await fetchPosts(
|
|
9540
|
+
const posts = await fetchPosts(site, source.tags, source.page, this.signal);
|
|
8535
9541
|
if (gen !== this.gen) {
|
|
8536
9542
|
return;
|
|
8537
9543
|
}
|
|
8538
9544
|
source.page++;
|
|
8539
9545
|
source.done = posts.length < PAGE;
|
|
8540
9546
|
for (const post2 of posts) {
|
|
8541
|
-
if (!
|
|
8542
|
-
|
|
8543
|
-
|
|
9547
|
+
if (!board.seen.has(post2.id)) {
|
|
9548
|
+
board.seen.add(post2.id);
|
|
9549
|
+
board.queue.push(post2);
|
|
8544
9550
|
}
|
|
8545
9551
|
}
|
|
8546
9552
|
continue;
|
|
8547
9553
|
}
|
|
8548
|
-
const batch =
|
|
8549
|
-
const passed = await Promise.all(batch.map((post2) => this.passes(post2)));
|
|
9554
|
+
const batch = board.queue.splice(0, WORKERS);
|
|
9555
|
+
const passed = await Promise.all(batch.map((post2) => this.passes(site, post2)));
|
|
9556
|
+
this.saveProbes(site);
|
|
8550
9557
|
if (gen !== this.gen) {
|
|
8551
9558
|
return;
|
|
8552
9559
|
}
|
|
8553
9560
|
batch.forEach((post2, i) => {
|
|
8554
|
-
|
|
9561
|
+
board.checked++;
|
|
8555
9562
|
if (passed[i]) {
|
|
8556
|
-
this.admit(post2);
|
|
9563
|
+
this.admit(site, post2);
|
|
8557
9564
|
}
|
|
8558
9565
|
});
|
|
9566
|
+
this.show();
|
|
8559
9567
|
this.draw();
|
|
8560
9568
|
}
|
|
8561
9569
|
if (gen === this.gen) {
|
|
8562
|
-
|
|
9570
|
+
board.searching = false;
|
|
9571
|
+
this.show();
|
|
8563
9572
|
this.draw();
|
|
8564
9573
|
}
|
|
8565
9574
|
} catch (error) {
|
|
@@ -8570,37 +9579,62 @@ class Finder {
|
|
|
8570
9579
|
}
|
|
8571
9580
|
}
|
|
8572
9581
|
}
|
|
8573
|
-
async passes(post2) {
|
|
8574
|
-
|
|
9582
|
+
async passes(site, post2) {
|
|
9583
|
+
const version2 = rendition(post2);
|
|
9584
|
+
if (!rated(site, post2, this.view.rating) || exposed(post2, this.view.block).length > 0 || !version2) {
|
|
8575
9585
|
return false;
|
|
8576
9586
|
}
|
|
8577
9587
|
if (this.view.preset === "all") {
|
|
8578
|
-
return ["png", "jpg", "jpeg"].includes(
|
|
9588
|
+
return ["png", "jpg", "jpeg"].includes(version2.ext);
|
|
8579
9589
|
}
|
|
8580
9590
|
if (post2.ext !== "png") {
|
|
8581
9591
|
return false;
|
|
8582
9592
|
}
|
|
8583
|
-
|
|
8584
|
-
|
|
9593
|
+
return site.vouched || this.transparent(site, post2);
|
|
9594
|
+
}
|
|
9595
|
+
probes(site) {
|
|
9596
|
+
let known = this.probed.get(site.key);
|
|
9597
|
+
if (!known) {
|
|
9598
|
+
known = readCache(site, "probes.json");
|
|
9599
|
+
this.probed.set(site.key, known);
|
|
9600
|
+
}
|
|
9601
|
+
return known;
|
|
9602
|
+
}
|
|
9603
|
+
async transparent(site, post2) {
|
|
9604
|
+
const known = this.probes(site);
|
|
9605
|
+
const cached = known[post2.id];
|
|
9606
|
+
if (cached !== undefined) {
|
|
9607
|
+
return cached;
|
|
8585
9608
|
}
|
|
8586
9609
|
try {
|
|
8587
|
-
|
|
9610
|
+
const alpha = (await headOf(site, post2.file, this.signal))?.alpha === true;
|
|
9611
|
+
known[post2.id] = alpha;
|
|
9612
|
+
this.unsaved.add(site.key);
|
|
9613
|
+
return alpha;
|
|
8588
9614
|
} catch {
|
|
8589
9615
|
return false;
|
|
8590
9616
|
}
|
|
8591
9617
|
}
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
9618
|
+
saveProbes(site) {
|
|
9619
|
+
if (this.unsaved.delete(site.key)) {
|
|
9620
|
+
writeCache(site, "probes.json", this.probes(site));
|
|
9621
|
+
}
|
|
9622
|
+
}
|
|
9623
|
+
admit(site, post2) {
|
|
9624
|
+
this.posts.set(this.mark(site, post2.id), post2);
|
|
9625
|
+
const last = this.view.sets === "fold" ? this.board.groups.at(-1) : undefined;
|
|
9626
|
+
const head = last?.posts[0];
|
|
9627
|
+
const credit = post2.owner || post2.artist;
|
|
9628
|
+
if (last && head && credit && (head.owner || head.artist) === credit && head.width === post2.width && head.height === post2.height) {
|
|
9629
|
+
last.posts.push(post2);
|
|
9630
|
+
if (last.open) {
|
|
9631
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
9632
|
+
this.thumbs();
|
|
9633
|
+
}
|
|
9634
|
+
return;
|
|
9635
|
+
}
|
|
9636
|
+
this.board.groups.push({ posts: [post2], open: false });
|
|
9637
|
+
this.thumbQueue.push({ site, post: post2 });
|
|
8604
9638
|
this.thumbs();
|
|
8605
9639
|
}
|
|
8606
9640
|
async mateOwners(site) {
|
|
@@ -8609,11 +9643,7 @@ class Finder {
|
|
|
8609
9643
|
return cached;
|
|
8610
9644
|
}
|
|
8611
9645
|
const found = origins(this.home);
|
|
8612
|
-
const
|
|
8613
|
-
let known = {};
|
|
8614
|
-
try {
|
|
8615
|
-
known = JSON.parse(readFileSync4(cachePath, "utf8"));
|
|
8616
|
-
} catch {}
|
|
9646
|
+
const known = readCache(site, "owners.json");
|
|
8617
9647
|
const byPalette = new Map;
|
|
8618
9648
|
for (const sibling of this.catalog.palettes) {
|
|
8619
9649
|
const origin = found.get(sibling.name);
|
|
@@ -8631,48 +9661,54 @@ class Finder {
|
|
|
8631
9661
|
}
|
|
8632
9662
|
byPalette.set(sibling.name, owner);
|
|
8633
9663
|
}
|
|
8634
|
-
|
|
8635
|
-
writeFileSync4(cachePath, `${JSON.stringify(known)}
|
|
8636
|
-
`);
|
|
9664
|
+
writeCache(site, "owners.json", known);
|
|
8637
9665
|
const owners = mates(byPalette);
|
|
8638
9666
|
this.owners.set(site.key, owners);
|
|
8639
9667
|
return owners;
|
|
8640
9668
|
}
|
|
8641
|
-
async cached(site, path2, url) {
|
|
9669
|
+
async cached(site, path2, url, progress2) {
|
|
8642
9670
|
if (existsSync3(path2)) {
|
|
8643
|
-
return new Uint8Array(
|
|
9671
|
+
return new Uint8Array(readFileSync5(path2));
|
|
9672
|
+
}
|
|
9673
|
+
const running = this.inflight.get(path2);
|
|
9674
|
+
if (running) {
|
|
9675
|
+
return running;
|
|
9676
|
+
}
|
|
9677
|
+
const job = (async () => {
|
|
9678
|
+
const bytes = await fetchBytes(site, url, this.signal, progress2);
|
|
9679
|
+
mkdirSync4(dirname3(path2), { recursive: true });
|
|
9680
|
+
writeFileSync4(path2, bytes);
|
|
9681
|
+
return bytes;
|
|
9682
|
+
})();
|
|
9683
|
+
this.inflight.set(path2, job);
|
|
9684
|
+
try {
|
|
9685
|
+
return await job;
|
|
9686
|
+
} finally {
|
|
9687
|
+
this.inflight.delete(path2);
|
|
8644
9688
|
}
|
|
8645
|
-
const bytes = await fetchBytes(site, url, this.signal);
|
|
8646
|
-
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8647
|
-
writeFileSync4(path2, bytes);
|
|
8648
|
-
return bytes;
|
|
8649
9689
|
}
|
|
8650
9690
|
thumbs() {
|
|
8651
9691
|
while (this.thumbing < WORKERS && this.thumbQueue.length > 0) {
|
|
8652
|
-
const
|
|
9692
|
+
const { site, post: post2 } = this.thumbQueue.shift();
|
|
8653
9693
|
this.thumbing++;
|
|
8654
|
-
this.thumb(
|
|
9694
|
+
this.thumb(site, post2).catch(() => {}).finally(() => {
|
|
8655
9695
|
this.thumbing--;
|
|
8656
9696
|
this.thumbs();
|
|
8657
9697
|
});
|
|
8658
9698
|
}
|
|
8659
9699
|
}
|
|
8660
|
-
async thumb(
|
|
8661
|
-
const post2 = this.posts.get(tile.id);
|
|
8662
|
-
const site = this.site;
|
|
8663
|
-
if (!post2) {
|
|
8664
|
-
return;
|
|
8665
|
-
}
|
|
9700
|
+
async thumb(site, post2) {
|
|
8666
9701
|
const w = TILE.cols * this.cell.w;
|
|
8667
9702
|
const h = TILE.rows * this.cell.h;
|
|
8668
|
-
const path2 = join7(
|
|
9703
|
+
const path2 = join7(this.scratch, "tile", `${site.key}-${post2.id}-${w}x${h}.png`);
|
|
8669
9704
|
if (!existsSync3(path2)) {
|
|
8670
|
-
const thumb = join7(cacheDir(site), "thumb", `${
|
|
9705
|
+
const thumb = join7(cacheDir(site), "thumb", `${post2.id}.${extension(post2.preview)}`);
|
|
8671
9706
|
const bytes = await this.cached(site, thumb, post2.preview);
|
|
8672
9707
|
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8673
|
-
writeFileSync4(path2, encodePng(contain(decodeImage(bytes), w, h)));
|
|
9708
|
+
writeFileSync4(path2, encodePng(contain(decodeImage(bytes, MAX_PIXELS), w, h)));
|
|
8674
9709
|
}
|
|
8675
|
-
|
|
9710
|
+
this.thumbPath.set(this.mark(site, post2.id), path2);
|
|
9711
|
+
this.show();
|
|
8676
9712
|
this.draw();
|
|
8677
9713
|
}
|
|
8678
9714
|
select() {
|
|
@@ -8694,31 +9730,26 @@ class Finder {
|
|
|
8694
9730
|
}
|
|
8695
9731
|
async load(tile) {
|
|
8696
9732
|
const view = this.view;
|
|
8697
|
-
const post2 = this.posts.get(tile.id);
|
|
8698
|
-
|
|
9733
|
+
const post2 = this.posts.get(this.mark(this.site, tile.id));
|
|
9734
|
+
const version2 = post2 && rendition(post2);
|
|
9735
|
+
if (!version2) {
|
|
8699
9736
|
return;
|
|
8700
9737
|
}
|
|
8701
9738
|
const site = this.site;
|
|
8702
9739
|
const control = new AbortController;
|
|
8703
9740
|
this.fetch = control;
|
|
8704
|
-
const signal = AbortSignal.any([this.signal, control.signal]);
|
|
8705
9741
|
try {
|
|
8706
|
-
const path2 =
|
|
8707
|
-
|
|
8708
|
-
if (existsSync3(path2)) {
|
|
8709
|
-
bytes = new Uint8Array(readFileSync4(path2));
|
|
8710
|
-
} else {
|
|
9742
|
+
const path2 = this.origPath(site, tile.id, version2.ext);
|
|
9743
|
+
if (!existsSync3(path2)) {
|
|
8711
9744
|
view.fetching = { id: tile.id, got: 0, size: 0 };
|
|
8712
9745
|
this.draw();
|
|
8713
|
-
bytes = await fetchBytes(site, post2.file, signal, (got, size) => {
|
|
8714
|
-
if (view.fetching?.id === tile.id) {
|
|
8715
|
-
view.fetching = { id: tile.id, got, size };
|
|
8716
|
-
this.draw();
|
|
8717
|
-
}
|
|
8718
|
-
});
|
|
8719
|
-
mkdirSync4(dirname3(path2), { recursive: true });
|
|
8720
|
-
writeFileSync4(path2, bytes);
|
|
8721
9746
|
}
|
|
9747
|
+
const bytes = await this.cached(site, path2, version2.file, (got, size) => {
|
|
9748
|
+
if (view.fetching?.id === tile.id) {
|
|
9749
|
+
view.fetching = { id: tile.id, got, size };
|
|
9750
|
+
this.draw();
|
|
9751
|
+
}
|
|
9752
|
+
});
|
|
8722
9753
|
if (control.signal.aborted) {
|
|
8723
9754
|
return;
|
|
8724
9755
|
}
|
|
@@ -8726,14 +9757,41 @@ class Finder {
|
|
|
8726
9757
|
view.preparing = tile.id;
|
|
8727
9758
|
this.flush();
|
|
8728
9759
|
await tick();
|
|
8729
|
-
const image = decodeImage(bytes);
|
|
9760
|
+
const image = decodeImage(bytes, MAX_PIXELS);
|
|
8730
9761
|
if (site !== this.site) {
|
|
8731
9762
|
return;
|
|
8732
9763
|
}
|
|
8733
|
-
|
|
9764
|
+
const current = {
|
|
9765
|
+
site,
|
|
9766
|
+
id: tile.id,
|
|
9767
|
+
image,
|
|
9768
|
+
plain: image,
|
|
9769
|
+
failed: false,
|
|
9770
|
+
bytes,
|
|
9771
|
+
ext: version2.ext,
|
|
9772
|
+
clear: transparency(image)
|
|
9773
|
+
};
|
|
9774
|
+
if (current.clear === 0 && canRemoveBackground() && this.setting("TTHEME_FIND_REMOVE_BG") !== "off") {
|
|
9775
|
+
view.preparing = undefined;
|
|
9776
|
+
view.cutting = tile.id;
|
|
9777
|
+
this.flush();
|
|
9778
|
+
const cut = await this.cutOut(site, tile.id, path2, control.signal);
|
|
9779
|
+
if (control.signal.aborted || site !== this.site) {
|
|
9780
|
+
return;
|
|
9781
|
+
}
|
|
9782
|
+
if (cut) {
|
|
9783
|
+
current.cut = cut;
|
|
9784
|
+
current.image = cut;
|
|
9785
|
+
current.clear = transparency(cut);
|
|
9786
|
+
} else {
|
|
9787
|
+
current.failed = true;
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
this.current = current;
|
|
8734
9791
|
if (view.tiles[view.focus]?.id === tile.id) {
|
|
8735
9792
|
this.present();
|
|
8736
9793
|
}
|
|
9794
|
+
this.preload();
|
|
8737
9795
|
} catch (error) {
|
|
8738
9796
|
if (!control.signal.aborted && !this.signal.aborted) {
|
|
8739
9797
|
view.error = reason(site, error);
|
|
@@ -8745,9 +9803,59 @@ class Finder {
|
|
|
8745
9803
|
if (view.preparing === tile.id) {
|
|
8746
9804
|
view.preparing = undefined;
|
|
8747
9805
|
}
|
|
9806
|
+
if (view.cutting === tile.id) {
|
|
9807
|
+
view.cutting = undefined;
|
|
9808
|
+
}
|
|
8748
9809
|
this.draw();
|
|
8749
9810
|
}
|
|
8750
9811
|
}
|
|
9812
|
+
async cutOut(site, id, path2, signal) {
|
|
9813
|
+
const out = join7(this.scratch, "cut", `${site.key}-${id}.png`);
|
|
9814
|
+
try {
|
|
9815
|
+
mkdirSync4(dirname3(out), { recursive: true });
|
|
9816
|
+
const cut = existsSync3(out) ? decodeImage(new Uint8Array(readFileSync5(out)), MAX_PIXELS) : await removeBackground(path2, out, signal);
|
|
9817
|
+
return keepable(transparency(cut)) ? cut : undefined;
|
|
9818
|
+
} catch {
|
|
9819
|
+
rmSync4(out, { force: true });
|
|
9820
|
+
return;
|
|
9821
|
+
}
|
|
9822
|
+
}
|
|
9823
|
+
swap() {
|
|
9824
|
+
const current = this.current;
|
|
9825
|
+
if (!current?.cut || this.view.shown?.id !== current.id) {
|
|
9826
|
+
return;
|
|
9827
|
+
}
|
|
9828
|
+
current.image = current.image === current.cut ? current.plain : current.cut;
|
|
9829
|
+
current.clear = transparency(current.image);
|
|
9830
|
+
this.present();
|
|
9831
|
+
this.draw();
|
|
9832
|
+
}
|
|
9833
|
+
preload() {
|
|
9834
|
+
const view = this.view;
|
|
9835
|
+
if (view.mode !== "try") {
|
|
9836
|
+
return;
|
|
9837
|
+
}
|
|
9838
|
+
const site = this.site;
|
|
9839
|
+
for (const index of [view.focus + this.direction, view.focus - this.direction]) {
|
|
9840
|
+
if (this.prefetching >= PRELOAD) {
|
|
9841
|
+
return;
|
|
9842
|
+
}
|
|
9843
|
+
const tile = view.tiles[index];
|
|
9844
|
+
const post2 = tile && this.posts.get(this.mark(site, tile.id));
|
|
9845
|
+
const version2 = post2 && rendition(post2);
|
|
9846
|
+
if (!version2) {
|
|
9847
|
+
continue;
|
|
9848
|
+
}
|
|
9849
|
+
const path2 = this.origPath(site, post2.id, version2.ext);
|
|
9850
|
+
if (existsSync3(path2)) {
|
|
9851
|
+
continue;
|
|
9852
|
+
}
|
|
9853
|
+
this.prefetching++;
|
|
9854
|
+
this.cached(site, path2, version2.file).catch(() => {}).finally(() => {
|
|
9855
|
+
this.prefetching--;
|
|
9856
|
+
});
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
8751
9859
|
present() {
|
|
8752
9860
|
const current = this.current;
|
|
8753
9861
|
if (!current) {
|
|
@@ -8757,11 +9865,12 @@ class Finder {
|
|
|
8757
9865
|
const H = this.rows * this.cell.h;
|
|
8758
9866
|
const width2 = Math.min(W, TRY_WIDTH);
|
|
8759
9867
|
const height = Math.max(1, Math.round(H * width2 / W));
|
|
8760
|
-
const
|
|
9868
|
+
const cut = current.cut ? current.image === current.cut ? "on" : "off" : current.failed ? "failed" : "none";
|
|
9869
|
+
const path2 = join7(this.scratch, `${current.id}${cut === "on" ? "c" : ""}-${width2}x${height}.png`);
|
|
8761
9870
|
if (!existsSync3(path2)) {
|
|
8762
9871
|
writeFileSync4(path2, encodePng(tryOn(current.image, this.entry, this.tone, width2, height)));
|
|
8763
9872
|
}
|
|
8764
|
-
this.view.shown = { id: current.id, clear: current.clear, bytes: current.bytes.length, path: path2 };
|
|
9873
|
+
this.view.shown = { id: current.id, clear: current.clear, bytes: current.bytes.length, path: path2, cut };
|
|
8765
9874
|
}
|
|
8766
9875
|
async install() {
|
|
8767
9876
|
const view = this.view;
|
|
@@ -8831,17 +9940,45 @@ class Finder {
|
|
|
8831
9940
|
this.write(`${out}\x1B[?2026l`);
|
|
8832
9941
|
}
|
|
8833
9942
|
}
|
|
9943
|
+
function routable() {
|
|
9944
|
+
if (process.versions.bun) {
|
|
9945
|
+
return true;
|
|
9946
|
+
}
|
|
9947
|
+
const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
|
|
9948
|
+
return major > 22 || major === 22 && minor >= 21;
|
|
9949
|
+
}
|
|
9950
|
+
async function relaunch() {
|
|
9951
|
+
if (!routable()) {
|
|
9952
|
+
process.stderr.write(`unblock needs node 22.21 or newer — this is ${process.versions.node}
|
|
9953
|
+
`);
|
|
9954
|
+
return 1;
|
|
9955
|
+
}
|
|
9956
|
+
const proxy = await tunnel();
|
|
9957
|
+
const child = spawn2(process.execPath, process.argv.slice(1), {
|
|
9958
|
+
stdio: "inherit",
|
|
9959
|
+
env: {
|
|
9960
|
+
...process.env,
|
|
9961
|
+
NODE_USE_ENV_PROXY: "1",
|
|
9962
|
+
NODE_NO_WARNINGS: "1",
|
|
9963
|
+
HTTPS_PROXY: `http://127.0.0.1:${proxy.port}`,
|
|
9964
|
+
TTHEME_FIND_PROXY: String(proxy.port)
|
|
9965
|
+
}
|
|
9966
|
+
});
|
|
9967
|
+
const code = await new Promise((resolve2) => child.on("exit", (status2) => resolve2(status2 ?? 1)));
|
|
9968
|
+
proxy.close();
|
|
9969
|
+
return code;
|
|
9970
|
+
}
|
|
8834
9971
|
async function runFind(name) {
|
|
9972
|
+
if (process.env.TTHEME_FIND_UNBLOCK === "1" && !process.env.TTHEME_FIND_PROXY) {
|
|
9973
|
+
return relaunch();
|
|
9974
|
+
}
|
|
8835
9975
|
const home = configHome();
|
|
8836
9976
|
const catalog = readCatalog(home);
|
|
8837
9977
|
const entry = find2(catalog.palettes, name);
|
|
8838
|
-
if (!entry.booru) {
|
|
8839
|
-
throw new Error(`${name} has no booru tag to search for`);
|
|
8840
|
-
}
|
|
8841
9978
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
8842
9979
|
throw new Error("ttheme find needs a terminal");
|
|
8843
9980
|
}
|
|
8844
|
-
return new Finder(home, catalog, entry, entry.booru).run();
|
|
9981
|
+
return new Finder(home, catalog, entry, entry.booru ?? "").run();
|
|
8845
9982
|
}
|
|
8846
9983
|
|
|
8847
9984
|
// src/init.ts
|
|
@@ -8851,7 +9988,7 @@ import {
|
|
|
8851
9988
|
existsSync as existsSync5,
|
|
8852
9989
|
mkdirSync as mkdirSync5,
|
|
8853
9990
|
readdirSync as readdirSync3,
|
|
8854
|
-
readFileSync as
|
|
9991
|
+
readFileSync as readFileSync6,
|
|
8855
9992
|
rmSync as rmSync5,
|
|
8856
9993
|
writeFileSync as writeFileSync5
|
|
8857
9994
|
} from "node:fs";
|
|
@@ -9970,34 +11107,6 @@ class a extends V {
|
|
|
9970
11107
|
});
|
|
9971
11108
|
}
|
|
9972
11109
|
}
|
|
9973
|
-
var n$1 = class n extends V {
|
|
9974
|
-
options;
|
|
9975
|
-
cursor = 0;
|
|
9976
|
-
get _selectedValue() {
|
|
9977
|
-
return this.options[this.cursor];
|
|
9978
|
-
}
|
|
9979
|
-
changeValue() {
|
|
9980
|
-
const e = this._selectedValue;
|
|
9981
|
-
this.value = e === undefined ? undefined : e.value;
|
|
9982
|
-
}
|
|
9983
|
-
constructor(e) {
|
|
9984
|
-
super(e, false), this.options = e.options;
|
|
9985
|
-
const o2 = this.options.findIndex(({ value: s }) => s === e.initialValue), t2 = o2 === -1 ? 0 : o2;
|
|
9986
|
-
this.cursor = this.options[t2]?.disabled ? findCursor(t2, 1, this.options) : t2, this.changeValue(), this.on("cursor", (s) => {
|
|
9987
|
-
switch (s) {
|
|
9988
|
-
case "left":
|
|
9989
|
-
case "up":
|
|
9990
|
-
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
9991
|
-
break;
|
|
9992
|
-
case "down":
|
|
9993
|
-
case "right":
|
|
9994
|
-
this.cursor = findCursor(this.cursor, 1, this.options);
|
|
9995
|
-
break;
|
|
9996
|
-
}
|
|
9997
|
-
this.changeValue();
|
|
9998
|
-
});
|
|
9999
|
-
}
|
|
10000
|
-
};
|
|
10001
11110
|
|
|
10002
11111
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
10003
11112
|
import { styleText as styleText2, stripVTControlCharacters as stripVTControlCharacters3 } from "node:util";
|
|
@@ -10100,7 +11209,7 @@ var limitOptions = ({
|
|
|
10100
11209
|
d && g2++, c2 && g2++;
|
|
10101
11210
|
const T3 = f2 + (d ? 1 : 0), y = W - (c2 ? 1 : 0);
|
|
10102
11211
|
for (let t2 = T3;t2 < y; t2++) {
|
|
10103
|
-
const
|
|
11212
|
+
const n2 = e[t2], o2 = n2 ? w(n2, t2 === l) : "", h2 = wrapAnsi(o2, i, {
|
|
10104
11213
|
hard: true,
|
|
10105
11214
|
trim: false
|
|
10106
11215
|
}).split(`
|
|
@@ -10108,17 +11217,17 @@ var limitOptions = ({
|
|
|
10108
11217
|
s.push(h2), g2 += h2.length;
|
|
10109
11218
|
}
|
|
10110
11219
|
if (g2 > v) {
|
|
10111
|
-
let t2 = 0,
|
|
11220
|
+
let t2 = 0, n2 = 0, o2 = g2;
|
|
10112
11221
|
const h2 = l - T3;
|
|
10113
11222
|
let u3 = v;
|
|
10114
11223
|
const L = () => I(s, o2, 0, h2, u3), E = () => I(s, o2, h2 + 1, s.length, u3, true);
|
|
10115
|
-
d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u3 && (c2 || (u3 -= 1), { lineCount: o2, removals:
|
|
11224
|
+
d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u3 && (c2 || (u3 -= 1), { lineCount: o2, removals: n2 } = E())) : (c2 || (u3 -= 1), { lineCount: o2, removals: n2 } = E(), o2 > u3 && (u3 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n2 > 0 && (c2 = true, s.splice(s.length - n2, n2));
|
|
10116
11225
|
}
|
|
10117
11226
|
const x = [];
|
|
10118
11227
|
d && x.push(M2);
|
|
10119
11228
|
for (const t2 of s)
|
|
10120
|
-
for (const
|
|
10121
|
-
x.push(
|
|
11229
|
+
for (const n2 of t2)
|
|
11230
|
+
x.push(n2);
|
|
10122
11231
|
return c2 && x.push(M2), x;
|
|
10123
11232
|
};
|
|
10124
11233
|
var confirm = (i) => {
|
|
@@ -10185,58 +11294,58 @@ ${styleText2("reset", styleText2("dim", `Press ${styleText2(["gray", "bgWhite",
|
|
|
10185
11294
|
render() {
|
|
10186
11295
|
const t2 = i.withGuide ?? settings.withGuide, a2 = wrapTextWithPrefix(i.output, i.message, t2 ? `${symbolBar(this.state)} ` : "", `${symbol(this.state)} `), r2 = `${t2 ? `${styleText2("gray", S_BAR)}
|
|
10187
11296
|
` : ""}${a2}
|
|
10188
|
-
`, o2 = this.value ?? [], p2 = (
|
|
10189
|
-
if (
|
|
10190
|
-
return u3(
|
|
10191
|
-
const s = o2.includes(
|
|
10192
|
-
return l && s ? u3(
|
|
11297
|
+
`, o2 = this.value ?? [], p2 = (n2, l) => {
|
|
11298
|
+
if (n2.disabled)
|
|
11299
|
+
return u3(n2, "disabled");
|
|
11300
|
+
const s = o2.includes(n2.value);
|
|
11301
|
+
return l && s ? u3(n2, "active-selected") : s ? u3(n2, "selected") : u3(n2, l ? "active" : "inactive");
|
|
10193
11302
|
};
|
|
10194
11303
|
switch (this.state) {
|
|
10195
11304
|
case "submit": {
|
|
10196
|
-
const
|
|
11305
|
+
const n2 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "submitted")).join(styleText2("dim", ", ")) || styleText2("dim", "none"), l = wrapTextWithPrefix(i.output, n2, t2 ? `${styleText2("gray", S_BAR)} ` : "");
|
|
10197
11306
|
return `${r2}${l}`;
|
|
10198
11307
|
}
|
|
10199
11308
|
case "cancel": {
|
|
10200
|
-
const
|
|
10201
|
-
if (
|
|
11309
|
+
const n2 = this.options.filter(({ value: s }) => o2.includes(s)).map((s) => u3(s, "cancelled")).join(styleText2("dim", ", "));
|
|
11310
|
+
if (n2.trim() === "")
|
|
10202
11311
|
return `${r2}${styleText2("gray", S_BAR)}`;
|
|
10203
|
-
const l = wrapTextWithPrefix(i.output,
|
|
11312
|
+
const l = wrapTextWithPrefix(i.output, n2, t2 ? `${styleText2("gray", S_BAR)} ` : "");
|
|
10204
11313
|
return `${r2}${l}${t2 ? `
|
|
10205
11314
|
${styleText2("gray", S_BAR)}` : ""}`;
|
|
10206
11315
|
}
|
|
10207
11316
|
case "error": {
|
|
10208
|
-
const
|
|
11317
|
+
const n2 = t2 ? `${styleText2("yellow", S_BAR)} ` : "", l = this.error.split(`
|
|
10209
11318
|
`).map(($, C2) => C2 === 0 ? `${t2 ? `${styleText2("yellow", S_BAR_END)} ` : ""}${styleText2("yellow", $)}` : ` ${$}`).join(`
|
|
10210
11319
|
`), s = r2.split(`
|
|
10211
11320
|
`).length, h2 = l.split(`
|
|
10212
11321
|
`).length + 1;
|
|
10213
|
-
return `${r2}${
|
|
11322
|
+
return `${r2}${n2}${limitOptions({
|
|
10214
11323
|
output: i.output,
|
|
10215
11324
|
options: this.options,
|
|
10216
11325
|
cursor: this.cursor,
|
|
10217
11326
|
maxItems: i.maxItems,
|
|
10218
|
-
columnPadding:
|
|
11327
|
+
columnPadding: n2.length,
|
|
10219
11328
|
rowPadding: s + h2,
|
|
10220
11329
|
style: p2
|
|
10221
11330
|
}).join(`
|
|
10222
|
-
${
|
|
11331
|
+
${n2}`)}
|
|
10223
11332
|
${l}
|
|
10224
11333
|
`;
|
|
10225
11334
|
}
|
|
10226
11335
|
default: {
|
|
10227
|
-
const
|
|
11336
|
+
const n2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l = r2.split(`
|
|
10228
11337
|
`).length, s = v ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS, t2) : t2 ? [styleText2("cyan", S_BAR_END)] : [], h2 = s.join(`
|
|
10229
11338
|
`), $ = s.length + 1;
|
|
10230
|
-
return `${r2}${
|
|
11339
|
+
return `${r2}${n2}${limitOptions({
|
|
10231
11340
|
output: i.output,
|
|
10232
11341
|
options: this.options,
|
|
10233
11342
|
cursor: this.cursor,
|
|
10234
11343
|
maxItems: i.maxItems,
|
|
10235
|
-
columnPadding:
|
|
11344
|
+
columnPadding: n2.length,
|
|
10236
11345
|
rowPadding: l + $,
|
|
10237
11346
|
style: p2
|
|
10238
11347
|
}).join(`
|
|
10239
|
-
${
|
|
11348
|
+
${n2}`)}
|
|
10240
11349
|
${h2}
|
|
10241
11350
|
`;
|
|
10242
11351
|
}
|
|
@@ -10268,18 +11377,18 @@ var C2 = (o2, e, s) => {
|
|
|
10268
11377
|
hard: true,
|
|
10269
11378
|
trim: false
|
|
10270
11379
|
}, i = wrapAnsi(o2, e, a2).split(`
|
|
10271
|
-
`), c2 = i.reduce((
|
|
11380
|
+
`), c2 = i.reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), u3 = i.map(s).reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), g2 = e - (u3 - c2);
|
|
10272
11381
|
return wrapAnsi(o2, g2, a2);
|
|
10273
11382
|
};
|
|
10274
11383
|
var note = (o2 = "", e = "", s) => {
|
|
10275
11384
|
const a2 = s?.output ?? process$1.stdout, i = s?.withGuide ?? settings.withGuide, c2 = s?.format ?? W$1, g2 = ["", ...C2(o2, getColumns(a2) - 6, c2).split(`
|
|
10276
|
-
`).map(c2), ""],
|
|
11385
|
+
`).map(c2), ""], n2 = dist_default2(e), t2 = Math.max(g2.reduce((m3, F) => {
|
|
10277
11386
|
const O = dist_default2(F);
|
|
10278
11387
|
return O > m3 ? O : m3;
|
|
10279
|
-
}, 0),
|
|
11388
|
+
}, 0), n2) + 2, h2 = g2.map((m3) => `${styleText2("gray", S_BAR)} ${m3}${" ".repeat(t2 - dist_default2(m3))}${styleText2("gray", S_BAR)}`).join(`
|
|
10280
11389
|
`), T3 = i ? `${styleText2("gray", S_BAR)}
|
|
10281
11390
|
` : "", l$1 = i ? S_CONNECT_LEFT : S_CORNER_BOTTOM_LEFT;
|
|
10282
|
-
a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(t2 -
|
|
11391
|
+
a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(t2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT)}
|
|
10283
11392
|
${h2}
|
|
10284
11393
|
${styleText2("gray", l$1 + S_BAR_H.repeat(t2 + 2) + S_CORNER_BOTTOM_RIGHT)}
|
|
10285
11394
|
`);
|
|
@@ -10293,69 +11402,6 @@ var SELECT_INSTRUCTIONS = [
|
|
|
10293
11402
|
`${styleText2("dim", "↑/↓")} to navigate`,
|
|
10294
11403
|
`${styleText2("dim", "Enter:")} confirm`
|
|
10295
11404
|
];
|
|
10296
|
-
var c2 = (t2, o2) => t2.includes(`
|
|
10297
|
-
`) ? t2.split(`
|
|
10298
|
-
`).map((d) => o2(d)).join(`
|
|
10299
|
-
`) : o2(t2);
|
|
10300
|
-
var select = (t2) => {
|
|
10301
|
-
const o2 = (n3, m3) => {
|
|
10302
|
-
if (n3 === undefined)
|
|
10303
|
-
return "";
|
|
10304
|
-
const s = n3.label ?? String(n3.value);
|
|
10305
|
-
switch (m3) {
|
|
10306
|
-
case "disabled":
|
|
10307
|
-
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (i) => styleText2("gray", i))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
|
|
10308
|
-
case "selected":
|
|
10309
|
-
return `${c2(s, (i) => styleText2("dim", i))}`;
|
|
10310
|
-
case "active":
|
|
10311
|
-
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
|
|
10312
|
-
case "cancelled":
|
|
10313
|
-
return `${c2(s, (i) => styleText2(["strikethrough", "dim"], i))}`;
|
|
10314
|
-
default:
|
|
10315
|
-
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (i) => styleText2("dim", i))}`;
|
|
10316
|
-
}
|
|
10317
|
-
}, d = t2.showInstructions ?? true;
|
|
10318
|
-
return new n$1({
|
|
10319
|
-
options: t2.options,
|
|
10320
|
-
signal: t2.signal,
|
|
10321
|
-
input: t2.input,
|
|
10322
|
-
output: t2.output,
|
|
10323
|
-
initialValue: t2.initialValue,
|
|
10324
|
-
render() {
|
|
10325
|
-
const n3 = t2.withGuide ?? settings.withGuide, m3 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t2.output, t2.message, s, m3), u4 = `${n3 ? `${styleText2("gray", S_BAR)}
|
|
10326
|
-
` : ""}${i}
|
|
10327
|
-
`;
|
|
10328
|
-
switch (this.state) {
|
|
10329
|
-
case "submit": {
|
|
10330
|
-
const r2 = n3 ? `${styleText2("gray", S_BAR)} ` : "", a2 = wrapTextWithPrefix(t2.output, o2(this.options[this.cursor], "selected"), r2);
|
|
10331
|
-
return `${u4}${a2}`;
|
|
10332
|
-
}
|
|
10333
|
-
case "cancel": {
|
|
10334
|
-
const r2 = n3 ? `${styleText2("gray", S_BAR)} ` : "", a2 = wrapTextWithPrefix(t2.output, o2(this.options[this.cursor], "cancelled"), r2);
|
|
10335
|
-
return `${u4}${a2}${n3 ? `
|
|
10336
|
-
${styleText2("gray", S_BAR)}` : ""}`;
|
|
10337
|
-
}
|
|
10338
|
-
default: {
|
|
10339
|
-
const r2 = n3 ? `${styleText2("cyan", S_BAR)} ` : "", a2 = u4.split(`
|
|
10340
|
-
`).length, p2 = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n3) : n3 ? [styleText2("cyan", S_BAR_END)] : [], b2 = p2.join(`
|
|
10341
|
-
`), f2 = p2.length + 1;
|
|
10342
|
-
return `${u4}${r2}${limitOptions({
|
|
10343
|
-
output: t2.output,
|
|
10344
|
-
cursor: this.cursor,
|
|
10345
|
-
options: this.options,
|
|
10346
|
-
maxItems: t2.maxItems,
|
|
10347
|
-
columnPadding: r2.length,
|
|
10348
|
-
rowPadding: a2 + f2,
|
|
10349
|
-
style: (g2, x) => o2(g2, g2.disabled ? "disabled" : x ? "active" : "inactive")
|
|
10350
|
-
}).join(`
|
|
10351
|
-
${r2}`)}
|
|
10352
|
-
${b2}
|
|
10353
|
-
`;
|
|
10354
|
-
}
|
|
10355
|
-
}
|
|
10356
|
-
}
|
|
10357
|
-
}).prompt();
|
|
10358
|
-
};
|
|
10359
11405
|
var i = `${styleText2("gray", S_BAR)} `;
|
|
10360
11406
|
|
|
10361
11407
|
// src/market.ts
|
|
@@ -10366,7 +11412,7 @@ import { openSync, writeSync } from "node:fs";
|
|
|
10366
11412
|
import { ReadStream } from "node:tty";
|
|
10367
11413
|
var QUERY_CODES = ["10", "11", "12", "17", ...Array.from({ length: 16 }, (_2, i2) => `4;${i2}`)];
|
|
10368
11414
|
function paletteOsc(entry) {
|
|
10369
|
-
const osc4 = entry.ansi.map((
|
|
11415
|
+
const osc4 = entry.ansi.map((c2, i2) => `;${i2};${c2}`).join("");
|
|
10370
11416
|
return [
|
|
10371
11417
|
`\x1B]11;${entry.background}\x1B\\`,
|
|
10372
11418
|
`\x1B]10;${entry.foreground}\x1B\\`,
|
|
@@ -10422,7 +11468,7 @@ async function queryTerminalColors() {
|
|
|
10422
11468
|
// src/ansi.ts
|
|
10423
11469
|
function rgb2(hex2) {
|
|
10424
11470
|
const h2 = hex2.replace("#", "");
|
|
10425
|
-
return [h2.slice(0, 2), h2.slice(2, 4), h2.slice(4, 6)].map((
|
|
11471
|
+
return [h2.slice(0, 2), h2.slice(2, 4), h2.slice(4, 6)].map((c2) => Number.parseInt(c2, 16)).join(";");
|
|
10426
11472
|
}
|
|
10427
11473
|
function ansiChip(text, bg2) {
|
|
10428
11474
|
return `\x1B[48;2;${rgb2(bg2)}m ${text} \x1B[0m`;
|
|
@@ -10434,7 +11480,7 @@ function ansiFg(color2) {
|
|
|
10434
11480
|
return `\x1B[38;2;${rgb2(color2)}m`;
|
|
10435
11481
|
}
|
|
10436
11482
|
function ansiSwatch(colors, bg2) {
|
|
10437
|
-
return `\x1B[48;2;${rgb2(bg2)}m${colors.map((
|
|
11483
|
+
return `\x1B[48;2;${rgb2(bg2)}m${colors.map((c2) => `\x1B[38;2;${rgb2(c2)}m▄`).join("")} \x1B[0m`;
|
|
10438
11484
|
}
|
|
10439
11485
|
|
|
10440
11486
|
// src/palette-prompt.ts
|
|
@@ -10485,6 +11531,7 @@ var DIM = "\x1B[2m";
|
|
|
10485
11531
|
var BOLD = "\x1B[1m";
|
|
10486
11532
|
var BOLD_INVERSE = "\x1B[1;7m";
|
|
10487
11533
|
var CYAN = "\x1B[36m";
|
|
11534
|
+
var YELLOW2 = "\x1B[33m";
|
|
10488
11535
|
function promptFx(value) {
|
|
10489
11536
|
return value === "decode" || value === "glitch" ? value : "typewriter";
|
|
10490
11537
|
}
|
|
@@ -10511,7 +11558,12 @@ class PalettePrompt extends V {
|
|
|
10511
11558
|
fxSeed = 0;
|
|
10512
11559
|
fxTimer;
|
|
10513
11560
|
constructor(opts) {
|
|
10514
|
-
super({
|
|
11561
|
+
super({
|
|
11562
|
+
render: () => this.draw(),
|
|
11563
|
+
input: opts.input,
|
|
11564
|
+
output: opts.output,
|
|
11565
|
+
validate: opts.required ? () => this.picked.size === 0 ? `pick at least one ${this.scope}` : undefined : undefined
|
|
11566
|
+
}, true);
|
|
10515
11567
|
this.entries = opts.entries;
|
|
10516
11568
|
this.scope = opts.scope ?? "palette";
|
|
10517
11569
|
this.picked = new Set(opts.installed ?? []);
|
|
@@ -10552,26 +11604,32 @@ class PalettePrompt extends V {
|
|
|
10552
11604
|
}
|
|
10553
11605
|
});
|
|
10554
11606
|
this.on("key", (_char, key) => {
|
|
10555
|
-
if (key?.name === "
|
|
11607
|
+
if (key?.name === "space") {
|
|
10556
11608
|
this.pick(this.rows[this.cursor]);
|
|
10557
11609
|
}
|
|
10558
11610
|
});
|
|
10559
11611
|
}
|
|
11612
|
+
_isActionKey(char) {
|
|
11613
|
+
return char === "\t" || char === " ";
|
|
11614
|
+
}
|
|
10560
11615
|
members(group) {
|
|
10561
11616
|
const filter = this.scope === "palette" ? this.userInput.trim() : "";
|
|
10562
11617
|
return this.named.filter((e) => e.group === group && (filter === "" || matchesPalette(e, filter)));
|
|
10563
11618
|
}
|
|
11619
|
+
everyone(rows) {
|
|
11620
|
+
return rows.flatMap((r2) => r2.kind === "group" ? this.members(r2.name) : []);
|
|
11621
|
+
}
|
|
10564
11622
|
pick(row) {
|
|
10565
|
-
const names = row?.kind === "palette" ? [row.entry.name] : row?.kind === "group" ? this.members(row.name).map((e) => e.name) : [];
|
|
11623
|
+
const names = row?.kind === "palette" ? [row.entry.name] : row?.kind === "group" ? this.members(row.name).map((e) => e.name) : row?.kind === "all" ? this.everyone(this.rows).map((e) => e.name) : [];
|
|
10566
11624
|
if (names.length === 0) {
|
|
10567
11625
|
return;
|
|
10568
11626
|
}
|
|
10569
|
-
const add = names.some((
|
|
10570
|
-
for (const
|
|
11627
|
+
const add = names.some((n2) => !this.picked.has(n2));
|
|
11628
|
+
for (const n2 of names) {
|
|
10571
11629
|
if (add) {
|
|
10572
|
-
this.picked.add(
|
|
11630
|
+
this.picked.add(n2);
|
|
10573
11631
|
} else {
|
|
10574
|
-
this.picked.delete(
|
|
11632
|
+
this.picked.delete(n2);
|
|
10575
11633
|
}
|
|
10576
11634
|
}
|
|
10577
11635
|
}
|
|
@@ -10625,18 +11683,23 @@ class PalettePrompt extends V {
|
|
|
10625
11683
|
}
|
|
10626
11684
|
rebuild(snap) {
|
|
10627
11685
|
const focused = this.rows[this.cursor];
|
|
10628
|
-
|
|
11686
|
+
const body = this.scope === "series" ? seriesRows(this.entries, this.userInput) : pickerRows(this.entries, this.expanded, this.userInput);
|
|
11687
|
+
const start = body.length > 0 ? firstPalette(body) + 1 : 0;
|
|
11688
|
+
this.rows = body.length > 0 ? [{ kind: "all", count: this.allCount(body) }, ...body] : [];
|
|
10629
11689
|
if (snap === "first") {
|
|
10630
|
-
this.cursor =
|
|
11690
|
+
this.cursor = start;
|
|
10631
11691
|
} else if (snap === "keep") {
|
|
10632
11692
|
const name = focused?.kind === "palette" ? focused.entry.name : "";
|
|
10633
11693
|
const kept = name ? this.rows.findIndex((r2) => r2.kind === "palette" && r2.entry.name === name) : -1;
|
|
10634
|
-
this.cursor = kept === -1 ?
|
|
11694
|
+
this.cursor = kept === -1 ? start : kept;
|
|
10635
11695
|
} else if (this.cursor >= this.rows.length) {
|
|
10636
11696
|
this.cursor = Math.max(0, this.rows.length - 1);
|
|
10637
11697
|
}
|
|
10638
11698
|
this.sync();
|
|
10639
11699
|
}
|
|
11700
|
+
allCount(body) {
|
|
11701
|
+
return this.scope === "series" ? body.length : this.everyone(body).length;
|
|
11702
|
+
}
|
|
10640
11703
|
move(delta) {
|
|
10641
11704
|
const next = this.cursor + delta;
|
|
10642
11705
|
if (next >= 0 && next < this.rows.length) {
|
|
@@ -10674,6 +11737,11 @@ class PalettePrompt extends V {
|
|
|
10674
11737
|
}
|
|
10675
11738
|
renderRow(row, focused) {
|
|
10676
11739
|
const marker = focused ? "▶ " : " ";
|
|
11740
|
+
if (row.kind === "all") {
|
|
11741
|
+
const box2 = this.everyone(this.rows).every((e2) => this.picked.has(e2.name)) ? "● " : "○ ";
|
|
11742
|
+
const tail = `(${row.count})`;
|
|
11743
|
+
return `${marker}${box2}select all ${this.color ? `${DIM}${tail}${RESET}` : tail}`;
|
|
11744
|
+
}
|
|
10677
11745
|
if (row.kind === "group" && this.scope === "series") {
|
|
10678
11746
|
const box2 = this.pickedIn(row.name) === row.count ? "● " : "○ ";
|
|
10679
11747
|
const name = row.name.padEnd(this.seriesPad);
|
|
@@ -10714,7 +11782,7 @@ class PalettePrompt extends V {
|
|
|
10714
11782
|
}
|
|
10715
11783
|
const filter = this.userInput.trim();
|
|
10716
11784
|
const total = this.scope === "series" ? this.series.length : this.named.length;
|
|
10717
|
-
const matched = this.scope === "series" ? this.rows.length : filter ? this.named.filter((e) => matchesPalette(e, filter)).length : total;
|
|
11785
|
+
const matched = this.scope === "series" ? this.rows.filter((r2) => r2.kind === "group").length : filter ? this.named.filter((e) => matchesPalette(e, filter)).length : total;
|
|
10718
11786
|
const head = `${bar("◆")} ${title} ${dim(`(${matched}/${total} · ${this.pickedCount()} picked)`)}`;
|
|
10719
11787
|
const search2 = `${bar("│")} ${this.userInput ? `⌕ ${this.userInput}_` : dim(`⌕ search…${this.example ? ` e.g. ${this.animatedExample()}` : ""}`)}`;
|
|
10720
11788
|
if (this.cursor < this.top) {
|
|
@@ -10732,7 +11800,8 @@ class PalettePrompt extends V {
|
|
|
10732
11800
|
const below = this.rows.length - this.top - window2.length;
|
|
10733
11801
|
const more = below > 0 ? `${bar("│")} ${dim(`↓ ${below} more`)}` : bar("│");
|
|
10734
11802
|
const fold = this.scope === "series" ? "" : " · ←→ fold";
|
|
10735
|
-
const
|
|
11803
|
+
const go = this.scope === "series" ? "continue" : "install";
|
|
11804
|
+
const hint = this.state === "error" ? `${bar("└")} ${this.color ? `${YELLOW2}${this.error}${RESET}` : this.error}` : `${bar("└")} ${dim(`↑↓ move${fold} · space pick · type to filter · enter ${go} · esc cancel`)}`;
|
|
10736
11805
|
return [head, search2, ...body, more, hint].join(`
|
|
10737
11806
|
`);
|
|
10738
11807
|
}
|
|
@@ -10747,8 +11816,8 @@ function runAdd(names) {
|
|
|
10747
11816
|
const home = configHome();
|
|
10748
11817
|
const catalog = readCatalog(home);
|
|
10749
11818
|
const state = readInstalled(home);
|
|
10750
|
-
const already = names.filter((
|
|
10751
|
-
const fresh = names.filter((
|
|
11819
|
+
const already = names.filter((n2) => state.palettes.includes(n2));
|
|
11820
|
+
const fresh = names.filter((n2) => !state.palettes.includes(n2));
|
|
10752
11821
|
if (fresh.length === 0) {
|
|
10753
11822
|
console.log(`already installed: ${already.join(", ")}`);
|
|
10754
11823
|
return;
|
|
@@ -10765,12 +11834,12 @@ function runRemove(names) {
|
|
|
10765
11834
|
const home = configHome();
|
|
10766
11835
|
const catalog = readCatalog(home);
|
|
10767
11836
|
const state = readInstalled(home);
|
|
10768
|
-
const gone = names.filter((
|
|
11837
|
+
const gone = names.filter((n2) => state.palettes.includes(n2));
|
|
10769
11838
|
if (gone.length === 0) {
|
|
10770
11839
|
console.log(`not installed: ${names.join(", ")}`);
|
|
10771
11840
|
return;
|
|
10772
11841
|
}
|
|
10773
|
-
const next = { ...state, palettes: state.palettes.filter((
|
|
11842
|
+
const next = { ...state, palettes: state.palettes.filter((n2) => !gone.includes(n2)) };
|
|
10774
11843
|
sync(home, catalog, next);
|
|
10775
11844
|
forget(home, catalog, state.terminals, gone);
|
|
10776
11845
|
writeInstalled(home, next);
|
|
@@ -10813,17 +11882,15 @@ async function runUpdate() {
|
|
|
10813
11882
|
const added = catalog.palettes.length - before;
|
|
10814
11883
|
console.log(`catalog ${catalog.version} — ${catalog.palettes.length} palettes${added > 0 ? ` (+${added})` : ""}`);
|
|
10815
11884
|
}
|
|
10816
|
-
async function
|
|
10817
|
-
const home = configHome();
|
|
10818
|
-
const catalog = readCatalog(home);
|
|
10819
|
-
const state = readInstalled(home);
|
|
11885
|
+
async function pickPalettes(catalog, installed, scope, required = false) {
|
|
10820
11886
|
const entries = process.env.TTHEME_SORT === "series" ? catalog.palettes : alphabetical(catalog.palettes);
|
|
10821
11887
|
const live = process.stdout.isTTY === true && !process.env.NO_COLOR;
|
|
10822
11888
|
const saved = live ? await queryTerminalColors() : new Map;
|
|
10823
11889
|
const prompt = new PalettePrompt({
|
|
10824
11890
|
entries,
|
|
10825
11891
|
scope,
|
|
10826
|
-
installed
|
|
11892
|
+
installed,
|
|
11893
|
+
required,
|
|
10827
11894
|
color: !process.env.NO_COLOR,
|
|
10828
11895
|
fx: promptFx(process.env.TTHEME_FX),
|
|
10829
11896
|
onFocus: live ? (entry) => process.stdout.write(paletteOsc(entry)) : undefined
|
|
@@ -10831,12 +11898,21 @@ async function runBrowse(scope = "palette") {
|
|
|
10831
11898
|
const done = await prompt.prompt();
|
|
10832
11899
|
process.stdout.write(restoreOsc(saved));
|
|
10833
11900
|
if (isCancel(done)) {
|
|
11901
|
+
return;
|
|
11902
|
+
}
|
|
11903
|
+
return catalog.palettes.filter((e) => prompt.picked.has(e.name)).map((e) => e.name);
|
|
11904
|
+
}
|
|
11905
|
+
async function runBrowse() {
|
|
11906
|
+
const home = configHome();
|
|
11907
|
+
const catalog = readCatalog(home);
|
|
11908
|
+
const state = readInstalled(home);
|
|
11909
|
+
const wanted = await pickPalettes(catalog, state.palettes, "palette");
|
|
11910
|
+
if (!wanted) {
|
|
10834
11911
|
console.log("nothing changed");
|
|
10835
11912
|
return;
|
|
10836
11913
|
}
|
|
10837
|
-
const
|
|
10838
|
-
const
|
|
10839
|
-
const added = wanted.filter((n3) => !state.palettes.includes(n3));
|
|
11914
|
+
const dropped = state.palettes.filter((n2) => !wanted.includes(n2));
|
|
11915
|
+
const added = wanted.filter((n2) => !state.palettes.includes(n2));
|
|
10840
11916
|
if (added.length === 0 && dropped.length === 0) {
|
|
10841
11917
|
console.log("nothing changed");
|
|
10842
11918
|
return;
|
|
@@ -10860,6 +11936,9 @@ function copyDir(copies, from, to) {
|
|
|
10860
11936
|
copies.push({ from: join8(from, f2), to: join8(to, f2) });
|
|
10861
11937
|
}
|
|
10862
11938
|
}
|
|
11939
|
+
function loadManifest(root2) {
|
|
11940
|
+
return JSON.parse(readFileSync6(join8(root2, "dist", "manifest.json"), "utf8"));
|
|
11941
|
+
}
|
|
10863
11942
|
function planInit(opts, paths) {
|
|
10864
11943
|
const dist = join8(paths.root, "dist");
|
|
10865
11944
|
const home = join8(paths.configHome, "ttheme");
|
|
@@ -10874,7 +11953,7 @@ function planInit(opts, paths) {
|
|
|
10874
11953
|
const configPath = join8(home, "config.zsh");
|
|
10875
11954
|
const settings2 = {
|
|
10876
11955
|
file: configPath,
|
|
10877
|
-
content: configFile(existsSync5(configPath) ?
|
|
11956
|
+
content: configFile(existsSync5(configPath) ? readFileSync6(configPath, "utf8") : "")
|
|
10878
11957
|
};
|
|
10879
11958
|
const notes = [];
|
|
10880
11959
|
if (opts.terminals.includes("ghostty")) {
|
|
@@ -10882,22 +11961,21 @@ function planInit(opts, paths) {
|
|
|
10882
11961
|
}
|
|
10883
11962
|
if (opts.terminals.includes("alacritty")) {
|
|
10884
11963
|
const config = join8(paths.configHome, "alacritty", "alacritty.toml");
|
|
10885
|
-
if (existsSync5(config) && !
|
|
11964
|
+
if (existsSync5(config) && !readFileSync6(config, "utf8").includes("# ttheme begin")) {
|
|
10886
11965
|
notes.push("alacritty.toml already exists — ttheme left it alone; add its themes/ import yourself");
|
|
10887
11966
|
}
|
|
10888
11967
|
}
|
|
10889
11968
|
notes.push("wezterm and iterm2: import the palettes you install from the release archive");
|
|
10890
|
-
const
|
|
10891
|
-
|
|
10892
|
-
return { copies, edits, settings: settings2, catalog, installed, notes };
|
|
11969
|
+
const installed = { terminals: opts.terminals, palettes: opts.palettes };
|
|
11970
|
+
return { copies, edits, settings: settings2, catalog: loadManifest(paths.root), installed, notes };
|
|
10893
11971
|
}
|
|
10894
11972
|
function applyInit(plan) {
|
|
10895
|
-
for (const
|
|
10896
|
-
mkdirSync5(dirname4(
|
|
10897
|
-
rmSync5(
|
|
10898
|
-
copyFileSync(
|
|
10899
|
-
if (
|
|
10900
|
-
chmodSync(
|
|
11973
|
+
for (const c2 of plan.copies) {
|
|
11974
|
+
mkdirSync5(dirname4(c2.to), { recursive: true });
|
|
11975
|
+
rmSync5(c2.to, { force: true });
|
|
11976
|
+
copyFileSync(c2.from, c2.to);
|
|
11977
|
+
if (c2.executable) {
|
|
11978
|
+
chmodSync(c2.to, 493);
|
|
10901
11979
|
}
|
|
10902
11980
|
}
|
|
10903
11981
|
mkdirSync5(dirname4(plan.settings.file), { recursive: true });
|
|
@@ -10908,7 +11986,7 @@ function applyInit(plan) {
|
|
|
10908
11986
|
sync(configHome2, plan.catalog, plan.installed);
|
|
10909
11987
|
for (const e of plan.edits) {
|
|
10910
11988
|
mkdirSync5(dirname4(e.file), { recursive: true });
|
|
10911
|
-
const current = existsSync5(e.file) ?
|
|
11989
|
+
const current = existsSync5(e.file) ? readFileSync6(e.file, "utf8") : "";
|
|
10912
11990
|
writeFileSync5(e.file, upsertBlock(current, e.block));
|
|
10913
11991
|
}
|
|
10914
11992
|
}
|
|
@@ -10919,36 +11997,59 @@ function accepted(value) {
|
|
|
10919
11997
|
}
|
|
10920
11998
|
return value;
|
|
10921
11999
|
}
|
|
10922
|
-
async function
|
|
10923
|
-
|
|
10924
|
-
const terminals = accepted(await multiselect({
|
|
12000
|
+
async function askTerminals(detected, preselected) {
|
|
12001
|
+
return accepted(await multiselect({
|
|
10925
12002
|
message: "wire which terminals?",
|
|
10926
12003
|
options: INIT_TERMINALS.map((t2) => ({ value: t2, hint: t2 === detected ? "detected" : undefined })),
|
|
10927
12004
|
initialValues: preselected,
|
|
10928
12005
|
required: true
|
|
10929
12006
|
}));
|
|
10930
|
-
const tabPalette = accepted(await select({
|
|
10931
|
-
message: "new tabs",
|
|
10932
|
-
options: [
|
|
10933
|
-
{ value: "seq", label: "rotate through the palettes", hint: "default" },
|
|
10934
|
-
{ value: "off", label: "inherit the window colors" }
|
|
10935
|
-
],
|
|
10936
|
-
initialValue: "seq"
|
|
10937
|
-
}));
|
|
10938
|
-
const announce = accepted(await confirm({ message: 'show the palette name under "Last login:"?', initialValue: true }));
|
|
10939
|
-
return { terminals, tabPalette, announce };
|
|
10940
12007
|
}
|
|
10941
|
-
function
|
|
12008
|
+
function verify(plan) {
|
|
10942
12009
|
const missing = [
|
|
10943
|
-
...plan.copies.filter((
|
|
12010
|
+
...plan.copies.filter((c2) => !existsSync5(c2.to)).map((c2) => c2.to),
|
|
10944
12011
|
...existsSync5(plan.settings.file) ? [] : [plan.settings.file],
|
|
10945
|
-
...plan.edits.filter((e) => !
|
|
12012
|
+
...plan.edits.filter((e) => !readFileSync6(e.file, "utf8").includes("# ttheme begin")).map((e) => e.file)
|
|
10946
12013
|
];
|
|
10947
12014
|
if (missing.length > 0) {
|
|
10948
12015
|
throw new Error(`init left gaps:
|
|
10949
12016
|
${missing.join(`
|
|
10950
12017
|
`)}`);
|
|
10951
12018
|
}
|
|
12019
|
+
}
|
|
12020
|
+
function seriesOf(catalog, names) {
|
|
12021
|
+
return [...new Set(catalog.palettes.filter((e) => names.includes(e.name)).map((e) => e.group))];
|
|
12022
|
+
}
|
|
12023
|
+
function paintStartup(catalog, installed) {
|
|
12024
|
+
const startup = catalog.palettes.find((e) => e.name === startupPalette(installed));
|
|
12025
|
+
const live = process.stdout.isTTY === true && !process.env.NO_COLOR && !process.env.TMUX;
|
|
12026
|
+
if (!startup || !live) {
|
|
12027
|
+
return false;
|
|
12028
|
+
}
|
|
12029
|
+
process.stdout.write(paletteOsc(startup));
|
|
12030
|
+
return true;
|
|
12031
|
+
}
|
|
12032
|
+
function receipt(plan, opts, painted) {
|
|
12033
|
+
const series = seriesOf(plan.catalog, opts.palettes);
|
|
12034
|
+
const startup = startupPalette(plan.installed);
|
|
12035
|
+
note([
|
|
12036
|
+
`${series.join(", ")} (${opts.palettes.length})`,
|
|
12037
|
+
`startup ${startup}${painted ? " — this tab wears it already" : ""}`,
|
|
12038
|
+
"new tabs rotate through the palettes — change with `ttheme config`"
|
|
12039
|
+
].join(`
|
|
12040
|
+
`), `installed ${opts.palettes.length} palettes`);
|
|
12041
|
+
const next = ["exec zsh the ttheme command in this tab"];
|
|
12042
|
+
if (opts.terminals.includes("ghostty")) {
|
|
12043
|
+
next.push("restart ghostty new tabs pick up its config");
|
|
12044
|
+
}
|
|
12045
|
+
if (opts.terminals.includes("kitty")) {
|
|
12046
|
+
next.push("new kitty window picks up its config");
|
|
12047
|
+
}
|
|
12048
|
+
note([...next, ...plan.notes].join(`
|
|
12049
|
+
`), "next");
|
|
12050
|
+
outro("done");
|
|
12051
|
+
}
|
|
12052
|
+
function report(plan, opts) {
|
|
10952
12053
|
const lines = [
|
|
10953
12054
|
`placed ${plan.copies.length} files`,
|
|
10954
12055
|
`settings in ${plan.settings.file} — edit later with \`ttheme config\``,
|
|
@@ -10961,15 +12062,9 @@ ${missing.join(`
|
|
|
10961
12062
|
lines.push("open a new kitty window to pick up its config");
|
|
10962
12063
|
}
|
|
10963
12064
|
lines.push(...plan.notes);
|
|
10964
|
-
lines.push("no palettes yet — `ttheme browse` picks them from the catalog");
|
|
10965
|
-
|
|
10966
|
-
note(lines.join(`
|
|
10967
|
-
`), "done");
|
|
10968
|
-
} else {
|
|
10969
|
-
console.log(lines.join(`
|
|
12065
|
+
lines.push("no palettes yet — open a new shell (`exec zsh`), then `ttheme browse` picks them from the catalog");
|
|
12066
|
+
console.log(lines.join(`
|
|
10970
12067
|
`));
|
|
10971
|
-
console.log("run `ttheme browse` to pick your palettes");
|
|
10972
|
-
}
|
|
10973
12068
|
}
|
|
10974
12069
|
async function runInit(flags = {}) {
|
|
10975
12070
|
const root2 = join8(import.meta.dirname, "..");
|
|
@@ -10989,40 +12084,40 @@ async function runInit(flags = {}) {
|
|
|
10989
12084
|
const detected = detectTerminal(process.env);
|
|
10990
12085
|
const preselected = INIT_TERMINALS.filter((t2) => t2 === detected || existsSync5(join8(configHome2, t2)));
|
|
10991
12086
|
const interactive = !flags.yes && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
10992
|
-
|
|
10993
|
-
if (interactive) {
|
|
10994
|
-
opts = await ask(detected, preselected);
|
|
10995
|
-
} else {
|
|
12087
|
+
if (!interactive) {
|
|
10996
12088
|
if (preselected.length === 0) {
|
|
10997
12089
|
throw new Error("no supported terminal detected — run this inside ghostty, kitty or alacritty");
|
|
10998
12090
|
}
|
|
10999
|
-
|
|
12091
|
+
const opts2 = { terminals: preselected, palettes: [] };
|
|
12092
|
+
const plan2 = planInit(opts2, paths);
|
|
12093
|
+
applyInit(plan2);
|
|
12094
|
+
verify(plan2);
|
|
12095
|
+
report(plan2, opts2);
|
|
12096
|
+
return;
|
|
12097
|
+
}
|
|
12098
|
+
intro("ttheme init");
|
|
12099
|
+
const terminals = await askTerminals(detected, preselected);
|
|
12100
|
+
const palettes = await pickPalettes(loadManifest(root2), [], "series", true);
|
|
12101
|
+
if (!palettes) {
|
|
12102
|
+
cancel("nothing changed");
|
|
12103
|
+
process.exit(1);
|
|
11000
12104
|
}
|
|
12105
|
+
const opts = { terminals, palettes };
|
|
11001
12106
|
const plan = planInit(opts, paths);
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
`), `wiring ${
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
process.exit(1);
|
|
11013
|
-
}
|
|
12107
|
+
note([
|
|
12108
|
+
`install ${palettes.length} palettes — ${seriesOf(plan.catalog, palettes).join(", ")}`,
|
|
12109
|
+
`copy ${plan.copies.length} files under ${configHome2}`,
|
|
12110
|
+
`write ${plan.settings.file}`,
|
|
12111
|
+
...plan.edits.map((e) => `edit ${e.file}`)
|
|
12112
|
+
].join(`
|
|
12113
|
+
`), `wiring ${terminals.join(", ")}`);
|
|
12114
|
+
if (!accepted(await confirm({ message: "apply these changes?" }))) {
|
|
12115
|
+
cancel("nothing changed");
|
|
12116
|
+
process.exit(1);
|
|
11014
12117
|
}
|
|
11015
12118
|
applyInit(plan);
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
return;
|
|
11019
|
-
}
|
|
11020
|
-
const browse = accepted(await confirm({ message: "pick the series to install now?", initialValue: true }));
|
|
11021
|
-
if (browse) {
|
|
11022
|
-
await runBrowse("series");
|
|
11023
|
-
} else {
|
|
11024
|
-
outro("run `ttheme browse` when you are ready");
|
|
11025
|
-
}
|
|
12119
|
+
verify(plan);
|
|
12120
|
+
receipt(plan, opts, paintStartup(plan.catalog, plan.installed));
|
|
11026
12121
|
}
|
|
11027
12122
|
|
|
11028
12123
|
// src/cli.ts
|