jsmdcui 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/README.md +54 -0
- package/package.json +1 -1
- package/runmd.mjs +14 -2
- package/runtime/help/help.md +20 -0
- package/single-exe/README.md +114 -2
- package/single-exe/assetsHelper.js +89 -0
- package/single-exe/compiled.js +7 -1
- package/src/build-args.js +40 -0
- package/src/cui/kitty-images.mjs +10 -3
- package/src/cui/kitty-mode.mjs +6 -0
- package/src/cui/server.mjs +1 -0
- package/src/index.js +27 -6
- package/src/lua/engine.js +1 -1
- package/src/plugins/js-bridge.js +1 -1
- package/src/plugins/manager.js +1 -1
- package/src/runtime/registry.js +1 -1
- package/tests/assets-helper.test.js +116 -0
- package/tests/compiled-runtime.test.js +64 -0
- package/tests/kitty-images.test.js +22 -0
- package/tests/kitty-mode.test.js +14 -0
- package/tests/wui-responsive-images.test.js +2 -0
- package/single-exe/README.md-rpc.js +0 -623
- package/single-exe/README.md-server.js +0 -127
- package/single-exe/README.md.back.js +0 -2
- package/single-exe/README.md.front.js +0 -27
- package/single-exe/README.md.html +0 -325
- package/src/runtime/assets.js +0 -90
package/src/index.js
CHANGED
|
@@ -94,17 +94,17 @@ import { accessSync, closeSync, constants, existsSync, openSync, readSync, readd
|
|
|
94
94
|
import { mkdir } from "node:fs/promises";
|
|
95
95
|
import { dirname, basename, join, resolve, sep } from "node:path";
|
|
96
96
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
97
|
-
import process from "node:process";
|
|
98
97
|
import { toggleTaskCheckboxBeforeColumn, updateAnsiTaskCheckbox } from "./cui/task-checkbox.mjs";
|
|
99
98
|
import { fenceEventMap, inlineFenceEventCode } from "./cui/fence-events.mjs";
|
|
100
99
|
import { checkMarkdownIdCollisions, formatMarkdownIdCheckAnsi } from "./cui/id-collision.mjs";
|
|
101
100
|
import { fitKittyImageToWidth, prepareKittyImages } from "./cui/kitty-images.mjs";
|
|
101
|
+
import { kittyModeFromEnvironment } from "./cui/kitty-mode.mjs";
|
|
102
102
|
import { logKittyPlacement } from "./cui/kitty-debug.mjs";
|
|
103
103
|
import { Config } from "./config/config.js";
|
|
104
104
|
import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
|
|
105
105
|
import { cleanConfig } from "./config/clean.js";
|
|
106
106
|
import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
|
|
107
|
-
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "
|
|
107
|
+
import { assetPath, buildHtmlBundleImageMap, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "../single-exe/assetsHelper.js";
|
|
108
108
|
//import { PluginManager } from "./plugins/manager.js";
|
|
109
109
|
import { JsPluginManager, buildMicroGlobal, buildTuiBlockIndex, findTuiBlockInIndex, insertTuiTextareaNewline, mergeTuiTextareaBackward, mergeTuiTextareaForward, runAction, listActions } from "./plugins/js-bridge.js";
|
|
110
110
|
import { Colorscheme } from "./config/colorscheme.js";
|
|
@@ -124,16 +124,25 @@ import { writeBackup, removeBackup, applyBackup } from "./buffer/backup.js";
|
|
|
124
124
|
let kittyImageMode = "off";
|
|
125
125
|
let allowRemoteKittyImages = false;
|
|
126
126
|
const remoteMarkdownSources = new Map();
|
|
127
|
+
let bundledImageMapPromise = null;
|
|
127
128
|
import { isHex3Encoding, isMdcuiEncoding } from "./runtime/encodings.js";
|
|
128
129
|
import { createInterface } from "node:readline/promises";
|
|
129
130
|
|
|
130
131
|
import pkg from "../package.json" with { type: "json" };
|
|
131
132
|
import { REPO_ROOT,IS_COMPILED, buildExecutable,buildEarlyExit,stringifyNonPrimitiveDefineValues } from "../single-exe/compiled.js";
|
|
133
|
+
import { expandBuildMdAliases } from "./build-args.js";
|
|
132
134
|
|
|
133
135
|
|
|
134
136
|
const __filename = fileURLToPath(import.meta.url);
|
|
135
137
|
const __dirname = dirname(__filename);
|
|
136
138
|
|
|
139
|
+
function getBundledImageMap() {
|
|
140
|
+
if (!IS_COMPILED || !global.MDCUI_MAIN) return null;
|
|
141
|
+
bundledImageMapPromise ??= import(global.MDCUI_MAIN + "-server.js")
|
|
142
|
+
.then((module) => buildHtmlBundleImageMap(module.homepage));
|
|
143
|
+
return bundledImageMapPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
|
|
138
147
|
if(!globalThis.Bun)
|
|
139
148
|
{
|
|
@@ -379,6 +388,7 @@ async function renderMdcui(markdown, width = process.stdout.columns || 80, mdpat
|
|
|
379
388
|
: await prepareKittyImages(tui, resolvedImageBasePath, width, {
|
|
380
389
|
allowUrl: allowRemoteKittyImages,
|
|
381
390
|
kittyMode: kittyImageMode,
|
|
391
|
+
getBundledImageMap,
|
|
382
392
|
});
|
|
383
393
|
return {
|
|
384
394
|
rendered: prepared.rendered,
|
|
@@ -1118,7 +1128,7 @@ function parseArgs(argv) {
|
|
|
1118
1128
|
cdpMaze: false,
|
|
1119
1129
|
cdpPort: 0,
|
|
1120
1130
|
cdpAddress: "",
|
|
1121
|
-
kittyMode:
|
|
1131
|
+
kittyMode: kittyModeFromEnvironment(process.env.JSMDCUI_KITTY_MODE),
|
|
1122
1132
|
settings: new Map(),
|
|
1123
1133
|
};
|
|
1124
1134
|
const files = [];
|
|
@@ -1268,6 +1278,9 @@ Modes:
|
|
|
1268
1278
|
Display Markdown images with Kitty graphics and the jsgotty MIME extension
|
|
1269
1279
|
--kitty-compat
|
|
1270
1280
|
Display Markdown images with Kitty graphics without the non-standard MIME U field
|
|
1281
|
+
|
|
1282
|
+
JSMDCUI_KITTY_MODE=off|compat|extended
|
|
1283
|
+
Set the Kitty image mode without command-line flags; explicit Kitty flags override it
|
|
1271
1284
|
|
|
1272
1285
|
--edit
|
|
1273
1286
|
Open files as editable UTF-8 text
|
|
@@ -1349,14 +1362,20 @@ Remote Markdown:
|
|
|
1349
1362
|
Download HTTP(S) Markdown and, with Kitty mode, its HTTP(S) images; allow its code to run
|
|
1350
1363
|
|
|
1351
1364
|
Experimental single-exe:
|
|
1365
|
+
--build-md-exe <file.md> [BUN_ARGS...]
|
|
1366
|
+
Build with <file.md> embedded as global.MDCUI_MAIN & exit
|
|
1367
|
+
--build-md-for <platform> <file.md> [BUN_ARGS...]
|
|
1368
|
+
Build for <platform> with <file.md> embedded & exit
|
|
1369
|
+
|
|
1352
1370
|
--build-exe [BUN_ARGS...]
|
|
1353
1371
|
Build a Bun single-file executable & exit
|
|
1354
1372
|
--build-for <target> [BUN_ARGS...]
|
|
1355
1373
|
Build a Bun single-file executable for target & exit
|
|
1356
1374
|
|
|
1357
1375
|
[BUN_ARGS...]
|
|
1358
|
-
--define
|
|
1359
|
-
|
|
1376
|
+
--define MDCUI_OVERWRITE_DEMO=1
|
|
1377
|
+
--define global.MDCUI_MAIN=myapp.md
|
|
1378
|
+
More info in --readme
|
|
1360
1379
|
https://bun.com/docs/bundler/executables
|
|
1361
1380
|
`
|
|
1362
1381
|
].join("\n");
|
|
@@ -5024,7 +5043,7 @@ class App {
|
|
|
5024
5043
|
const name = buf?.name ?? "No name";
|
|
5025
5044
|
const filename = /^[^\s"'\\]+$/.test(name) ? name : JSON.stringify(name);
|
|
5026
5045
|
this.openCommandMode(`save ${filename}`);
|
|
5027
|
-
break;
|
|
5046
|
+
break; //"
|
|
5028
5047
|
}
|
|
5029
5048
|
case "row":
|
|
5030
5049
|
if (isTerm) {
|
|
@@ -8257,6 +8276,8 @@ function stringDefineFromArgv(argv, name) {
|
|
|
8257
8276
|
async function main() {
|
|
8258
8277
|
addCheckpoint("Argument Parsing");
|
|
8259
8278
|
|
|
8279
|
+
expandBuildMdAliases(process.argv);
|
|
8280
|
+
stringifyNonPrimitiveDefineValues(process.argv, "process.env.JSMDCUI_KITTY_MODE");
|
|
8260
8281
|
stringifyNonPrimitiveDefineValues(process.argv, "global.MDCUI_MAIN");
|
|
8261
8282
|
const argvMdcuiMain = stringDefineFromArgv(
|
|
8262
8283
|
process.argv,
|
package/src/lua/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { readInternalAssetBytes } from "
|
|
2
|
+
import { readInternalAssetBytes } from "../../single-exe/assetsHelper.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { REPO_ROOT } from "../../single-exe/compiled.js";
|
package/src/plugins/js-bridge.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readdir } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, join, basename, extname } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetBytes } from "
|
|
6
|
+
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetBytes } from "../../single-exe/assetsHelper.js";
|
|
7
7
|
import { isMdcuiEncoding } from "../runtime/encodings.js";
|
|
8
8
|
import { newMessage, newMessageAtLine, MTError, MTWarning, MTInfo } from "../buffer/message.js";
|
|
9
9
|
import { Loc } from "../buffer/loc.js";
|
package/src/plugins/manager.js
CHANGED
|
@@ -4,7 +4,7 @@ import { basename, dirname, extname, join, sep } from "node:path";
|
|
|
4
4
|
import { fetchHttp, downloadFile } from "../platform/commands.js";
|
|
5
5
|
import { extractAndStrip } from "../platform/archive.js";
|
|
6
6
|
import { createLuaEngine } from "../lua/engine.js";
|
|
7
|
-
import { assetPath, hasInternalAssets, internalAssetSource, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "
|
|
7
|
+
import { assetPath, hasInternalAssets, internalAssetSource, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "../../single-exe/assetsHelper.js";
|
|
8
8
|
import { execCommand, runBackgroundShell, runCommand } from "../shell/shell.js";
|
|
9
9
|
import { Loc } from "../buffer/loc.js";
|
|
10
10
|
import { BTDefault, BTHelp, BTInfo, BTLog, BTRaw, BTScratch, byteOffset, BufferCore } from "../buffer/buffer.js";
|
package/src/runtime/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, extname, join } from "node:path";
|
|
4
|
-
import { assetPath, hasInternalAssets, listInternalAssetPaths, readInternalAssetBytes } from "
|
|
4
|
+
import { assetPath, hasInternalAssets, listInternalAssetPaths, readInternalAssetBytes } from "../../single-exe/assetsHelper.js";
|
|
5
5
|
|
|
6
6
|
export const RTColorscheme = 0;
|
|
7
7
|
export const RTSyntax = 1;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
buildHtmlBundleImageMap,
|
|
7
|
+
canonicalHtmlBundleImageHref,
|
|
8
|
+
decodeHtmlAttribute,
|
|
9
|
+
findHtmlBundleAsset,
|
|
10
|
+
findHtmlBundleImageAsset,
|
|
11
|
+
htmlBundleImageAssetPath,
|
|
12
|
+
} from "../single-exe/assetsHelper.js";
|
|
13
|
+
|
|
14
|
+
const homepage = {
|
|
15
|
+
files: [
|
|
16
|
+
{
|
|
17
|
+
input: "../../images/pixel.png",
|
|
18
|
+
path: "/$bunfs/root/pixel-abc123.png",
|
|
19
|
+
loader: "file",
|
|
20
|
+
headers: { "content-type": "image/png" },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
input: "scripts/app.js",
|
|
24
|
+
path: "/$bunfs/root/app-test.js",
|
|
25
|
+
loader: "file",
|
|
26
|
+
headers: { "content-type": "text/javascript" },
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
test("HTML bundle asset lookup matches the compiled public asset path", () => {
|
|
32
|
+
expect(findHtmlBundleAsset(homepage, "/pixel-abc123.png"))
|
|
33
|
+
.toBe(homepage.files[0]);
|
|
34
|
+
expect(htmlBundleImageAssetPath(homepage, "/pixel-abc123.png?version=2"))
|
|
35
|
+
.toBe("/$bunfs/root/pixel-abc123.png");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("HTML bundle image lookup rejects non-images and unavailable development bundles", () => {
|
|
39
|
+
expect(findHtmlBundleImageAsset(homepage, "/app-test.js")).toBeNull();
|
|
40
|
+
expect(findHtmlBundleAsset({}, "/pixel-abc123.png")).toBeNull();
|
|
41
|
+
expect(findHtmlBundleAsset(null, "/pixel-abc123.png")).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("HTML bundle image map joins preserved Markdown hrefs to compiled bunfs assets", async () => {
|
|
45
|
+
const directory = await mkdtemp(join(tmpdir(), "jsmdcui-html-assets-"));
|
|
46
|
+
const htmlPath = join(directory, "app.html");
|
|
47
|
+
await writeFile(htmlPath, [
|
|
48
|
+
'<img src="/dms-ajghf5bd.jpg" data-mdcui-src="jsmdcui/single-exe/dms.jpg">',
|
|
49
|
+
'<img src="/unicode-xyz.png" data-mdcui-src="%E5%9C%96%E7%89%87/%E6%B8%AC%E8%A9%A6%20%E5%9C%96.png">',
|
|
50
|
+
'<img src="/query-qwe.png" data-mdcui-src="./query.png?x=1&y=2#preview">',
|
|
51
|
+
].join("\n"));
|
|
52
|
+
const compiledHomepage = {
|
|
53
|
+
index: htmlPath,
|
|
54
|
+
files: [
|
|
55
|
+
{
|
|
56
|
+
input: "dms.jpg",
|
|
57
|
+
path: "/$bunfs/root/dms-ajghf5bd.jpg",
|
|
58
|
+
loader: "file",
|
|
59
|
+
headers: { "content-type": "image/jpeg" },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
input: "../../unicode.png",
|
|
63
|
+
path: "/$bunfs/root/unicode-xyz.png",
|
|
64
|
+
loader: "file",
|
|
65
|
+
headers: { "content-type": "image/png" },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
input: "../query.png",
|
|
69
|
+
path: "/$bunfs/root/query-qwe.png",
|
|
70
|
+
loader: "file",
|
|
71
|
+
headers: { "content-type": "image/png" },
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const images = await buildHtmlBundleImageMap(compiledHomepage);
|
|
77
|
+
expect(images.get("jsmdcui/single-exe/dms.jpg"))
|
|
78
|
+
.toBe("/$bunfs/root/dms-ajghf5bd.jpg");
|
|
79
|
+
expect(images.get("圖片/測試 圖.png"))
|
|
80
|
+
.toBe("/$bunfs/root/unicode-xyz.png");
|
|
81
|
+
expect(images.get("./query.png?x=1&y=2#preview"))
|
|
82
|
+
.toBe("/$bunfs/root/query-qwe.png");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("HTML bundle href canonicalization decodes serialization without resolving paths", () => {
|
|
86
|
+
expect(decodeHtmlAttribute("./a.png?x=1&y=2")).toBe("./a.png?x=1&y=2");
|
|
87
|
+
expect(canonicalHtmlBundleImageHref("%E5%9C%96%E7%89%87/a%20b.png"))
|
|
88
|
+
.toBe("圖片/a b.png");
|
|
89
|
+
expect(canonicalHtmlBundleImageHref("./a.png?x=1&y=2", { htmlAttribute: true }))
|
|
90
|
+
.toBe("./a.png?x=1&y=2");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("HTML bundle image map accepts a custom source attribute", async () => {
|
|
94
|
+
const directory = await mkdtemp(join(tmpdir(), "jsmdcui-html-assets-custom-"));
|
|
95
|
+
const htmlPath = join(directory, "custom.html");
|
|
96
|
+
await writeFile(
|
|
97
|
+
htmlPath,
|
|
98
|
+
'<img src="/photo-custom123.jpg" data-original-image="./photos/a.jpg">',
|
|
99
|
+
);
|
|
100
|
+
const customHomepage = {
|
|
101
|
+
index: htmlPath,
|
|
102
|
+
files: [{
|
|
103
|
+
input: "../../photos/a.jpg",
|
|
104
|
+
path: "/$bunfs/root/photo-custom123.jpg",
|
|
105
|
+
loader: "file",
|
|
106
|
+
headers: { "content-type": "image/jpeg" },
|
|
107
|
+
}],
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const images = await buildHtmlBundleImageMap(
|
|
111
|
+
customHomepage,
|
|
112
|
+
"data-original-image",
|
|
113
|
+
);
|
|
114
|
+
expect(images.get("./photos/a.jpg"))
|
|
115
|
+
.toBe("/$bunfs/root/photo-custom123.jpg");
|
|
116
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
|
+
import { expandBuildMdAliases } from "../src/build-args.js";
|
|
2
3
|
import {
|
|
3
4
|
isCompiledBinary as isSingleExeCompiled,
|
|
4
5
|
stringifyNonPrimitiveDefineValues,
|
|
@@ -10,6 +11,69 @@ test("single-exe recognizes Bun compiled virtual paths", () => {
|
|
|
10
11
|
expect(isSingleExeCompiled(["bun", "/project/src/index.js"])).toBe(false);
|
|
11
12
|
});
|
|
12
13
|
|
|
14
|
+
test("--build-md-exe expands before the existing build flow", () => {
|
|
15
|
+
const argv = [
|
|
16
|
+
"bun",
|
|
17
|
+
"src/index.js",
|
|
18
|
+
"--build-md-exe",
|
|
19
|
+
"../中文工具.md",
|
|
20
|
+
"--sourcemap",
|
|
21
|
+
];
|
|
22
|
+
expect(expandBuildMdAliases(argv)).toBe(true);
|
|
23
|
+
expect(argv).toEqual([
|
|
24
|
+
"bun",
|
|
25
|
+
"src/index.js",
|
|
26
|
+
"--build-exe",
|
|
27
|
+
"--define",
|
|
28
|
+
"global.MDCUI_MAIN=../中文工具.md",
|
|
29
|
+
"--sourcemap",
|
|
30
|
+
]);
|
|
31
|
+
expect(expandBuildMdAliases(argv)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("--build-md-exe requires a Markdown path", () => {
|
|
35
|
+
expect(() => expandBuildMdAliases(["bun", "index.js", "--build-md-exe"]))
|
|
36
|
+
.toThrow("Missing Markdown path for --build-md-exe");
|
|
37
|
+
expect(() => expandBuildMdAliases([
|
|
38
|
+
"bun",
|
|
39
|
+
"index.js",
|
|
40
|
+
"--build-md-exe",
|
|
41
|
+
"--sourcemap",
|
|
42
|
+
])).toThrow("Missing Markdown path for --build-md-exe");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("--build-md-for expands before the existing cross-build flow", () => {
|
|
46
|
+
const argv = [
|
|
47
|
+
"bun",
|
|
48
|
+
"src/index.js",
|
|
49
|
+
"--build-md-for",
|
|
50
|
+
"bun-linux-x64",
|
|
51
|
+
"../中文工具.md",
|
|
52
|
+
"--sourcemap",
|
|
53
|
+
];
|
|
54
|
+
expect(expandBuildMdAliases(argv)).toBe(true);
|
|
55
|
+
expect(argv).toEqual([
|
|
56
|
+
"bun",
|
|
57
|
+
"src/index.js",
|
|
58
|
+
"--build-for",
|
|
59
|
+
"bun-linux-x64",
|
|
60
|
+
"--define",
|
|
61
|
+
"global.MDCUI_MAIN=../中文工具.md",
|
|
62
|
+
"--sourcemap",
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("--build-md-for requires a platform and Markdown path", () => {
|
|
67
|
+
expect(() => expandBuildMdAliases(["bun", "index.js", "--build-md-for"]))
|
|
68
|
+
.toThrow("Missing platform for --build-md-for");
|
|
69
|
+
expect(() => expandBuildMdAliases([
|
|
70
|
+
"bun",
|
|
71
|
+
"index.js",
|
|
72
|
+
"--build-md-for",
|
|
73
|
+
"bun-linux-x64",
|
|
74
|
+
])).toThrow("Missing Markdown path for --build-md-for");
|
|
75
|
+
});
|
|
76
|
+
|
|
13
77
|
test("non-primitive MDCUI_MAIN define values become strings", () => {
|
|
14
78
|
const split = [
|
|
15
79
|
"bun",
|
|
@@ -26,6 +26,28 @@ test("Bun-rendered Markdown image links reserve rows and retain Kitty data", asy
|
|
|
26
26
|
expect(Bun.stripANSI(result.rendered)).toContain("📷 pixel");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
+
test("Kitty images prefer lazily supplied compiled HTML bundle assets", async () => {
|
|
30
|
+
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-kitty-bundled-"));
|
|
31
|
+
const bundledPath = join(dir, "pixel-bundled.png");
|
|
32
|
+
await writeFile(bundledPath, ONE_PIXEL_PNG);
|
|
33
|
+
const ansi = Bun.markdown.ansi("", {
|
|
34
|
+
hyperlinks: true,
|
|
35
|
+
columns: 40,
|
|
36
|
+
});
|
|
37
|
+
let imageMapRequests = 0;
|
|
38
|
+
const result = await prepareKittyImages(ansi, join(dir, "missing-app.md"), 40, {
|
|
39
|
+
getBundledImageMap: async () => {
|
|
40
|
+
imageMapRequests++;
|
|
41
|
+
return new Map([["./images/pixel.png?version=2#preview", bundledPath]]);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(imageMapRequests).toBe(1);
|
|
46
|
+
expect(result.images).toHaveLength(1);
|
|
47
|
+
expect(result.images[0].path).toBe(bundledPath);
|
|
48
|
+
expect(result.images[0].data.equals(ONE_PIXEL_PNG)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
29
51
|
test("Kitty compat converts compressed images to standard PNG payloads", async () => {
|
|
30
52
|
const dir = await mkdtemp(join(tmpdir(), "jsmdcui-kitty-compat-"));
|
|
31
53
|
const markdownPath = join(dir, "app.md");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { kittyModeFromEnvironment } from "../src/cui/kitty-mode.mjs";
|
|
3
|
+
|
|
4
|
+
test("Kitty mode accepts supported environment values case-insensitively", () => {
|
|
5
|
+
expect(kittyModeFromEnvironment("compat")).toBe("compat");
|
|
6
|
+
expect(kittyModeFromEnvironment(" EXTENDED ")).toBe("extended");
|
|
7
|
+
expect(kittyModeFromEnvironment("off")).toBe("off");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("Kitty mode defaults invalid or missing environment values to off", () => {
|
|
11
|
+
expect(kittyModeFromEnvironment()).toBe("off");
|
|
12
|
+
expect(kittyModeFromEnvironment("yes")).toBe("off");
|
|
13
|
+
expect(kittyModeFromEnvironment("")).toBe("off");
|
|
14
|
+
});
|
|
@@ -19,6 +19,7 @@ test("createWui constrains images to their content width", async () => {
|
|
|
19
19
|
const html = await createWui("", markdownPath);
|
|
20
20
|
|
|
21
21
|
expect(html).toContain("img {\n max-width: 100%;\n height: auto;\n}");
|
|
22
|
+
expect(html).toContain('src="wide.png" alt="wide" data-mdcui-src="wide.png"');
|
|
22
23
|
expect(html.match(/max-width: 100%/g)).toHaveLength(1);
|
|
23
24
|
expect(html.match(/image\.md\.front\.js/g)).toHaveLength(1);
|
|
24
25
|
});
|
|
@@ -31,6 +32,7 @@ test("createWui injects the image rule into an existing document head", async ()
|
|
|
31
32
|
const html = await createWui(source, markdownPath);
|
|
32
33
|
|
|
33
34
|
expect(html.indexOf("max-width: 100%")).toBeLessThan(html.indexOf("</head>"));
|
|
35
|
+
expect(html).toContain('src="wide.png" data-mdcui-src="wide.png"');
|
|
34
36
|
expect(html.match(/document\.md\.front\.js/g)).toHaveLength(1);
|
|
35
37
|
});
|
|
36
38
|
|