isorite 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.js +84 -10
- package/dist/main.js +363 -32
- package/notes/algorithms/the-quadtree/subdivision.svg +15 -0
- package/notes/algorithms/the-quadtree.md +2 -0
- package/package.json +1 -1
- package/plugin/server.ts +1 -1
- package/web/_app/immutable/assets/{0.CTbO0owi.css → 0.CPW9fjPu.css} +1 -1
- package/web/_app/immutable/assets/NoteEditor.Wt5vU9Tb.css +1 -0
- package/web/_app/immutable/chunks/{8wVGBFSs.js → -2Meesge.js} +3 -3
- package/web/_app/immutable/chunks/{DDuWClBR.js → 6RXH8WsD.js} +1 -1
- package/web/_app/immutable/chunks/{BL3mLym4.js → 9z49435t.js} +1 -1
- package/web/_app/immutable/chunks/B0A4Snxv.js +6 -0
- package/web/_app/immutable/chunks/B8Qvvm53.js +1 -0
- package/web/_app/immutable/chunks/{DMAYXnpq.js → BA8Hpxhg.js} +1 -1
- package/web/_app/immutable/chunks/{CKSRTWxH.js → BAclEN0O.js} +1 -1
- package/web/_app/immutable/chunks/BJo70SOc.js +1 -0
- package/web/_app/immutable/chunks/{DECNRCf2.js → BLmF1B2i.js} +1 -1
- package/web/_app/immutable/chunks/{CiVjR8gq.js → BTclWEfx.js} +1 -1
- package/web/_app/immutable/chunks/{Cp_JbuGh.js → BWVMfAMh.js} +1 -1
- package/web/_app/immutable/chunks/{D4fLEcOQ.js → BXAOBcPb.js} +1 -1
- package/web/_app/immutable/chunks/BYSDTLqw.js +1 -0
- package/web/_app/immutable/chunks/{B3aPQAYq.js → BcgO-fBX.js} +1 -1
- package/web/_app/immutable/chunks/BimZp6R0.js +523 -0
- package/web/_app/immutable/chunks/{CO2Y5y64.js → Bq2GBAJ0.js} +1 -1
- package/web/_app/immutable/chunks/BqrMZ9MX.js +1 -0
- package/web/_app/immutable/chunks/{BN6xhG8e.js → Bt7VOT5H.js} +1 -1
- package/web/_app/immutable/chunks/{BGbXxNFa.js → BuUzVkG2.js} +1 -1
- package/web/_app/immutable/chunks/{Brxvm_b1.js → By4S_gy5.js} +1 -1
- package/web/_app/immutable/chunks/{CRIvE37b.js → BymwHy7o.js} +1 -1
- package/web/_app/immutable/chunks/{GRrQoDGJ.js → C5MPRibb.js} +1 -1
- package/web/_app/immutable/chunks/CBkjDkEN.js +151 -0
- package/web/_app/immutable/chunks/{BrqJKzQU.js → CD6xPodA.js} +1 -1
- package/web/_app/immutable/chunks/{CEufJyoj.js → CQd9D8Nc.js} +1 -1
- package/web/_app/immutable/chunks/{BrGmYbJT.js → CT_Ry4q8.js} +1 -1
- package/web/_app/immutable/chunks/{CQadubS9.js → C_amgxV1.js} +1 -1
- package/web/_app/immutable/chunks/CiU6tYJv.js +1 -0
- package/web/_app/immutable/chunks/{spNKdrPy.js → CjtKPNlv.js} +1 -1
- package/web/_app/immutable/chunks/{DxLkKbK2.js → Cl5zRP_n.js} +1 -1
- package/web/_app/immutable/chunks/{BXF2XoRC.js → CsRPJjQS.js} +1 -1
- package/web/_app/immutable/chunks/{CETSRtnV.js → CyFLqlvP.js} +1 -1
- package/web/_app/immutable/chunks/{Bq5hCPe1.js → Czz9qOh8.js} +1 -1
- package/web/_app/immutable/chunks/{BguXRgvc.js → D3-rqfsM.js} +1 -1
- package/web/_app/immutable/chunks/{bcQIrid8.js → D5XlJBy8.js} +1 -1
- package/web/_app/immutable/chunks/{TuFg0KqZ.js → D5vocsTl.js} +1 -1
- package/web/_app/immutable/chunks/{CTscJvp6.js → D95RKsvN.js} +1 -1
- package/web/_app/immutable/chunks/{BzI3yqBQ.js → DBC2xjOo.js} +1 -1
- package/web/_app/immutable/chunks/{DII7jNnq.js → DCD8_jrW.js} +1 -1
- package/web/_app/immutable/chunks/{BsLNwr5A.js → DF89zyH-.js} +1 -1
- package/web/_app/immutable/chunks/{DacMdKjW.js → DQjIi5Jv.js} +1 -1
- package/web/_app/immutable/chunks/{C-dfcq9W.js → DRX-TQzj.js} +1 -1
- package/web/_app/immutable/chunks/{BunrC_8P.js → DX4bJJO0.js} +1 -1
- package/web/_app/immutable/chunks/{EQVRMnZU.js → DXK7PB4g.js} +1 -1
- package/web/_app/immutable/chunks/{mbLqADPr.js → DXTK-eyo.js} +1 -1
- package/web/_app/immutable/chunks/{wCTXGvXm.js → DbNETcPs.js} +1 -1
- package/web/_app/immutable/chunks/{CX-xckzu.js → DfCZPwp1.js} +1 -1
- package/web/_app/immutable/chunks/{DKNOMI4Y.js → Dh1lBxfx.js} +1 -1
- package/web/_app/immutable/chunks/{BgczuZKA.js → DkxkvOjx.js} +1 -1
- package/web/_app/immutable/chunks/DlKQFacy.js +1 -0
- package/web/_app/immutable/chunks/{0M1khDs2.js → DlXrOzlh.js} +1 -1
- package/web/_app/immutable/chunks/{B85D_PlV.js → Dpa4Syyy.js} +1 -1
- package/web/_app/immutable/chunks/DshHzxgH.js +1 -0
- package/web/_app/immutable/chunks/{CdY1YAit.js → EMo1IeTW.js} +1 -1
- package/web/_app/immutable/chunks/Iqr2CudW.js +3 -0
- package/web/_app/immutable/chunks/{CoyQYage.js → RFxekmX-.js} +1 -1
- package/web/_app/immutable/chunks/{vk5Wkr31.js → RlApGeew.js} +1 -1
- package/web/_app/immutable/chunks/{CLVUDRRk.js → Xm0YTnxV.js} +1 -1
- package/web/_app/immutable/chunks/c7PFFMl4.js +1 -0
- package/web/_app/immutable/chunks/{CZ8wTb_M.js → gPtCGHiy.js} +1 -1
- package/web/_app/immutable/chunks/{D4SKbj9K.js → oWM_kgYq.js} +1 -1
- package/web/_app/immutable/chunks/{wj7G6pU5.js → rghE0CVz.js} +1 -1
- package/web/_app/immutable/chunks/{B9YLdnR_.js → tUSDgbpy.js} +1 -1
- package/web/_app/immutable/chunks/{DTdlqvEb.js → vfDvz3qP.js} +1 -1
- package/web/_app/immutable/chunks/{BRKS-CCX.js → whhiiiga.js} +1 -1
- package/web/_app/immutable/entry/app.iw6ZIOh2.js +2 -0
- package/web/_app/immutable/entry/start.CFnKMSWs.js +1 -0
- package/web/_app/immutable/nodes/0.BzEgwgsa.js +26 -0
- package/web/_app/immutable/nodes/1.rzuq9VRh.js +1 -0
- package/web/_app/immutable/nodes/2.D_jWsQLW.js +1 -0
- package/web/_app/immutable/nodes/3.BzRx3SiD.js +1 -0
- package/web/_app/immutable/nodes/4.DP2MGav1.js +2 -0
- package/web/_app/immutable/nodes/5.BPS-CCQT.js +5 -0
- package/web/_app/immutable/nodes/6.DQMCyNJ3.js +1 -0
- package/web/_app/immutable/nodes/7.MWEn-qPC.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +17 -17
- package/web/_app/immutable/assets/NoteEditor.8wWtZK_A.css +0 -1
- package/web/_app/immutable/chunks/CBWiKnLz.js +0 -1
- package/web/_app/immutable/chunks/CHbKgHy3.js +0 -1
- package/web/_app/immutable/chunks/CJ1dhP0p.js +0 -1
- package/web/_app/immutable/chunks/CLkY0zVi.js +0 -1
- package/web/_app/immutable/chunks/D-aFnd2T.js +0 -1
- package/web/_app/immutable/chunks/DRQEUdad.js +0 -1
- package/web/_app/immutable/chunks/DZYaqCwi.js +0 -1
- package/web/_app/immutable/chunks/D_19S_Iu.js +0 -1
- package/web/_app/immutable/chunks/DcgxJ8bF.js +0 -673
- package/web/_app/immutable/chunks/Ddgejj-u.js +0 -1
- package/web/_app/immutable/chunks/nMarIA7J.js +0 -3
- package/web/_app/immutable/chunks/vUsuwDSf.js +0 -6
- package/web/_app/immutable/entry/app.CswuY3sh.js +0 -2
- package/web/_app/immutable/entry/start.D8ZvSyBD.js +0 -1
- package/web/_app/immutable/nodes/0.DBoa3NAQ.js +0 -26
- package/web/_app/immutable/nodes/1.DrwFerb8.js +0 -1
- package/web/_app/immutable/nodes/2.DPmbppXV.js +0 -1
- package/web/_app/immutable/nodes/3.BW7AbIrS.js +0 -1
- package/web/_app/immutable/nodes/4.B5HAyMKs.js +0 -2
- package/web/_app/immutable/nodes/5.BvScSoCM.js +0 -5
- package/web/_app/immutable/nodes/6.Bna6qY2G.js +0 -1
- package/web/_app/immutable/nodes/7.BxQaF3lD.js +0 -1
package/dist/main.js
CHANGED
|
@@ -18,7 +18,7 @@ import { Hono } from "hono";
|
|
|
18
18
|
import { streamSSE } from "hono/streaming";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { OpenCode } from "@opencode/client";
|
|
21
|
-
import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
21
|
+
import { appendFile, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
22
22
|
import { createInterface } from "node:readline/promises";
|
|
23
23
|
import { createServer } from "node:net";
|
|
24
24
|
import { isSea } from "node:sea";
|
|
@@ -142,7 +142,7 @@ function normpath(p) {
|
|
|
142
142
|
}
|
|
143
143
|
return "/".repeat(initial) + out.join("/") || ".";
|
|
144
144
|
}
|
|
145
|
-
/** SHA-256 as hex, synchronous and dependency-free, so the core runs anywhere. */
|
|
145
|
+
/** SHA-256 as hex, synchronous and dependency-free, so the core runs anywhere: of text (as UTF-8), or of bytes. */
|
|
146
146
|
function sha256(message) {
|
|
147
147
|
const K = new Uint32Array([
|
|
148
148
|
1116352408,
|
|
@@ -210,7 +210,7 @@ function sha256(message) {
|
|
|
210
210
|
3204031479,
|
|
211
211
|
3329325298
|
|
212
212
|
]);
|
|
213
|
-
const data = new TextEncoder().encode(message);
|
|
213
|
+
const data = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
214
214
|
const len = data.length;
|
|
215
215
|
const padded = new Uint8Array(len + 9 + 63 >> 6 << 6);
|
|
216
216
|
padded.set(data);
|
|
@@ -377,7 +377,7 @@ function linkTexts(body) {
|
|
|
377
377
|
return out;
|
|
378
378
|
}
|
|
379
379
|
/** Every link and image in a body, in order: where it points, its title, and whether it is an image
|
|
380
|
-
* (``, which for an artifact or a picture means "shown here", an embed). */
|
|
380
|
+
* (``, which for an artifact or a picture means "shown here", an embed), with an image's alt text. */
|
|
381
381
|
function citeRefs(body) {
|
|
382
382
|
const { tokens } = parse$1(body);
|
|
383
383
|
const out = [];
|
|
@@ -391,7 +391,8 @@ function citeRefs(body) {
|
|
|
391
391
|
else if (c.type === "image") out.push({
|
|
392
392
|
href: c.attrGet("src") ?? "",
|
|
393
393
|
title: c.attrGet("title"),
|
|
394
|
-
embed: true
|
|
394
|
+
embed: true,
|
|
395
|
+
alt: c.content
|
|
395
396
|
});
|
|
396
397
|
}
|
|
397
398
|
return out;
|
|
@@ -446,6 +447,64 @@ var init_markdown = __esmMin((() => {
|
|
|
446
447
|
SPACES = /[\s_]+/gu;
|
|
447
448
|
}));
|
|
448
449
|
//#endregion
|
|
450
|
+
//#region packages/core/src/picture.ts
|
|
451
|
+
/** Width and height in pixels, or null. */
|
|
452
|
+
function pictureSize(bytes, type) {
|
|
453
|
+
const v = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), n = bytes.length;
|
|
454
|
+
const at = (i, s) => [...s].every((c, k) => bytes[i + k] === c.charCodeAt(0));
|
|
455
|
+
const ok = (w, h) => w > 0 && h > 0 ? {
|
|
456
|
+
width: w,
|
|
457
|
+
height: h
|
|
458
|
+
} : null;
|
|
459
|
+
try {
|
|
460
|
+
if (type === "image/png" && n >= 24 && at(12, "IHDR")) return ok(v.getUint32(16), v.getUint32(20));
|
|
461
|
+
if (type === "image/gif" && n >= 10 && at(0, "GIF")) return ok(v.getUint16(6, true), v.getUint16(8, true));
|
|
462
|
+
if (type === "image/jpeg") {
|
|
463
|
+
for (let i = 2; i + 9 < n;) {
|
|
464
|
+
if (bytes[i] !== 255) return null;
|
|
465
|
+
const m = bytes[i + 1];
|
|
466
|
+
if (m >= 192 && m <= 207 && m !== 196 && m !== 200 && m !== 204) return ok(v.getUint16(i + 7), v.getUint16(i + 5));
|
|
467
|
+
i += 2 + v.getUint16(i + 2);
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
if (type === "image/webp" && n >= 30 && at(0, "RIFF") && at(8, "WEBP")) {
|
|
472
|
+
if (at(12, "VP8 ")) return ok(v.getUint16(26, true) & 16383, v.getUint16(28, true) & 16383);
|
|
473
|
+
if (at(12, "VP8L")) {
|
|
474
|
+
const b = v.getUint32(21, true);
|
|
475
|
+
return ok((b & 16383) + 1, (b >> 14 & 16383) + 1);
|
|
476
|
+
}
|
|
477
|
+
if (at(12, "VP8X")) return ok(1 + (bytes[24] | bytes[25] << 8 | bytes[26] << 16), 1 + (bytes[27] | bytes[28] << 8 | bytes[29] << 16));
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
if (type === "image/svg+xml") {
|
|
481
|
+
const head = new TextDecoder().decode(bytes.subarray(0, 4096)), tag = /<svg\b[^>]*>/i.exec(head)?.[0] ?? "";
|
|
482
|
+
const attr = (name) => new RegExp(`\\s${name}\\s*=\\s*["']\\s*([\\d.]+)\\s*(px)?\\s*["']`, "i").exec(tag);
|
|
483
|
+
const w = attr("width"), h = attr("height");
|
|
484
|
+
if (w && h) return ok(Math.round(Number(w[1])), Math.round(Number(h[1])));
|
|
485
|
+
const box = /\sviewBox\s*=\s*["']\s*[-\d.]+[\s,]+[-\d.]+[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(tag);
|
|
486
|
+
return box ? ok(Math.round(Number(box[1])), Math.round(Number(box[2]))) : null;
|
|
487
|
+
}
|
|
488
|
+
} catch {}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
var TYPES$1, pictureType;
|
|
492
|
+
var init_picture = __esmMin((() => {
|
|
493
|
+
TYPES$1 = {
|
|
494
|
+
png: "image/png",
|
|
495
|
+
jpg: "image/jpeg",
|
|
496
|
+
jpeg: "image/jpeg",
|
|
497
|
+
gif: "image/gif",
|
|
498
|
+
svg: "image/svg+xml",
|
|
499
|
+
webp: "image/webp",
|
|
500
|
+
avif: "image/avif"
|
|
501
|
+
};
|
|
502
|
+
pictureType = (path) => {
|
|
503
|
+
const dot = path.lastIndexOf(".");
|
|
504
|
+
return dot < 0 || path.lastIndexOf("/") > dot ? null : TYPES$1[path.slice(dot + 1).toLowerCase()] ?? null;
|
|
505
|
+
};
|
|
506
|
+
}));
|
|
507
|
+
//#endregion
|
|
449
508
|
//#region packages/core/src/kinds.ts
|
|
450
509
|
function kindOf(type) {
|
|
451
510
|
const t = type.trim().toLowerCase();
|
|
@@ -518,11 +577,25 @@ function outwardLinks(body) {
|
|
|
518
577
|
}
|
|
519
578
|
seen.add(href);
|
|
520
579
|
}
|
|
580
|
+
for (const { href, embed, alt } of citeRefs(body)) {
|
|
581
|
+
if (!embed || seen.has(href)) continue;
|
|
582
|
+
const c = codeTarget(href);
|
|
583
|
+
if (!c || !pictureType(c.path)) continue;
|
|
584
|
+
out.push({
|
|
585
|
+
kind: "code",
|
|
586
|
+
href,
|
|
587
|
+
text: alt || c.path.split("/").at(-1),
|
|
588
|
+
path: c.path,
|
|
589
|
+
picture: true
|
|
590
|
+
});
|
|
591
|
+
seen.add(href);
|
|
592
|
+
}
|
|
521
593
|
return out;
|
|
522
594
|
}
|
|
523
595
|
var ABOUT, DONE, EMBED;
|
|
524
596
|
var init_kinds = __esmMin((() => {
|
|
525
597
|
init_markdown();
|
|
598
|
+
init_picture();
|
|
526
599
|
init_text();
|
|
527
600
|
ABOUT = {
|
|
528
601
|
question: "about",
|
|
@@ -725,7 +798,7 @@ var init_bundle = __esmMin((() => {
|
|
|
725
798
|
concept.links = links;
|
|
726
799
|
concept.outward = outwardLinks(concept.body);
|
|
727
800
|
for (const link of links) if (link.broken) this.issue(concept.path, "warning", "broken-link", `broken link to ${link.target}`);
|
|
728
|
-
for (const { href, title, embed } of citeRefs(concept.body)) {
|
|
801
|
+
for (const { href, title, embed, alt } of citeRefs(concept.body)) {
|
|
729
802
|
const path = this.filePath(concept, href), kind = path === null ? null : citeKind(path);
|
|
730
803
|
if (path === null || kind === null) continue;
|
|
731
804
|
const t = strip((title ?? "").toLowerCase());
|
|
@@ -735,10 +808,11 @@ var init_bundle = __esmMin((() => {
|
|
|
735
808
|
embed,
|
|
736
809
|
broken: !this.files.has(path),
|
|
737
810
|
rel: rating(title),
|
|
738
|
-
align: t === "left" ? "left" : t === "center" || t === "centre" ? "center" : null
|
|
811
|
+
align: t === "left" ? "left" : t === "center" || t === "centre" ? "center" : null,
|
|
812
|
+
...embed && alt ? { alt } : {}
|
|
739
813
|
};
|
|
740
814
|
concept.cites.push(cite);
|
|
741
|
-
if (cite.broken) this.issue(concept.path, "warning", "broken-link", `broken link to ${path}`);
|
|
815
|
+
if (cite.broken) this.issue(concept.path, "warning", "broken-link", kind === "image" ? `picture not in the notes: ${path}` : `broken link to ${path}`);
|
|
742
816
|
}
|
|
743
817
|
}
|
|
744
818
|
const byName = /* @__PURE__ */ new Map();
|
|
@@ -1809,13 +1883,16 @@ function mapModel(base) {
|
|
|
1809
1883
|
s
|
|
1810
1884
|
]);
|
|
1811
1885
|
}
|
|
1886
|
+
const holdsFiles = /* @__PURE__ */ new Set();
|
|
1887
|
+
for (const f of base.files) holdsFiles.add(f.includes("/") ? f.slice(0, f.lastIndexOf("/")) : "");
|
|
1812
1888
|
const dir = (id) => {
|
|
1813
1889
|
const d = base.directories.get(id);
|
|
1814
|
-
|
|
1890
|
+
const children = [...[...d.children].sort().map(dir).filter((c) => c !== null), ...[...d.concepts].sort().map(note)];
|
|
1891
|
+
return id && !children.length && (holdsFiles.has(id) || d.children.length) ? null : {
|
|
1815
1892
|
kind: "dir",
|
|
1816
1893
|
id: "d:" + id,
|
|
1817
1894
|
ref: id,
|
|
1818
|
-
children
|
|
1895
|
+
children
|
|
1819
1896
|
};
|
|
1820
1897
|
};
|
|
1821
1898
|
return {
|
|
@@ -1830,6 +1907,7 @@ var init_map = __esmMin((() => {
|
|
|
1830
1907
|
//#region packages/core/src/index.ts
|
|
1831
1908
|
var init_src$1 = __esmMin((() => {
|
|
1832
1909
|
init_bundle();
|
|
1910
|
+
init_picture();
|
|
1833
1911
|
init_frontmatter();
|
|
1834
1912
|
init_source();
|
|
1835
1913
|
init_markdown();
|
|
@@ -1884,8 +1962,9 @@ function loadBundle(root) {
|
|
|
1884
1962
|
}
|
|
1885
1963
|
//#endregion
|
|
1886
1964
|
//#region packages/engine/src/api.ts
|
|
1887
|
-
var EngineError, Conflict;
|
|
1965
|
+
var MAX_FILE_BYTES, EngineError, Conflict;
|
|
1888
1966
|
var init_api = __esmMin((() => {
|
|
1967
|
+
MAX_FILE_BYTES = 2e7;
|
|
1889
1968
|
EngineError = class extends Error {
|
|
1890
1969
|
status;
|
|
1891
1970
|
constructor(message, status = 400) {
|
|
@@ -2699,6 +2778,9 @@ something at one place in the note they have open.`;
|
|
|
2699
2778
|
is drawn as the diagram. Draw one where a picture of how things stand to
|
|
2700
2779
|
each other helps, or when asked; not by habit. Maths in a label goes
|
|
2701
2780
|
between $$ and $$.
|
|
2781
|
+
- A picture is shown as . You can't see it: what
|
|
2782
|
+
you know of it is the words in the brackets. Keep a picture's line as it is
|
|
2783
|
+
when you rewrite around it, and never make up a picture's path.
|
|
2702
2784
|
- Ordinary Markdown otherwise. No HTML.`;
|
|
2703
2785
|
FORMAT_RULES = FORMAT;
|
|
2704
2786
|
LOOKUP = (rounds, meaning) => `# Looking things up
|
|
@@ -2807,7 +2889,7 @@ var init_lookup = __esmMin((() => {
|
|
|
2807
2889
|
...str$1,
|
|
2808
2890
|
description: "The note's id or path: basics/entropy or /basics/entropy.md."
|
|
2809
2891
|
} }, ["id"]),
|
|
2810
|
-
fn$1("read_note", "Read one section of a note, by its heading, or the whole note when no section is named. Prefer a section.", {
|
|
2892
|
+
fn$1("read_note", "Read one section of a note, by its heading, or the whole note when no section is named. Prefer a section. A picture in it is : you have its words, not the picture.", {
|
|
2811
2893
|
id: str$1,
|
|
2812
2894
|
section: {
|
|
2813
2895
|
...str$1,
|
|
@@ -4030,18 +4112,41 @@ var init_engine = __esmMin((() => {
|
|
|
4030
4112
|
const notes = {}, broken = await this.brokenCode();
|
|
4031
4113
|
for (const c of this.bundle.concepts.values()) {
|
|
4032
4114
|
const bad = broken.get(c.id) ?? [];
|
|
4033
|
-
const links = c.outward.map((o) => ({
|
|
4115
|
+
const links = c.outward.filter((o) => !(o.kind === "code" && o.picture)).map((o) => ({
|
|
4034
4116
|
kind: o.kind,
|
|
4035
4117
|
href: o.href,
|
|
4036
4118
|
text: o.text,
|
|
4037
4119
|
...bad.includes(o.href) ? { broken: true } : {}
|
|
4038
4120
|
}));
|
|
4121
|
+
const pictures = [], shown = /* @__PURE__ */ new Set();
|
|
4122
|
+
for (const p of c.cites) {
|
|
4123
|
+
if (p.kind !== "image" || !p.embed || shown.has(p.target)) continue;
|
|
4124
|
+
shown.add(p.target);
|
|
4125
|
+
pictures.push({
|
|
4126
|
+
href: p.target,
|
|
4127
|
+
path: p.target,
|
|
4128
|
+
from: "notes",
|
|
4129
|
+
text: p.alt || p.target.split("/").at(-1),
|
|
4130
|
+
...p.broken ? { broken: true } : {}
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
for (const o of c.outward) {
|
|
4134
|
+
if (o.kind !== "code" || !o.picture) continue;
|
|
4135
|
+
pictures.push({
|
|
4136
|
+
href: o.href,
|
|
4137
|
+
path: o.path,
|
|
4138
|
+
from: "code",
|
|
4139
|
+
text: o.text,
|
|
4140
|
+
...bad.includes(o.href) ? { broken: true } : {}
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4039
4143
|
notes[c.id] = {
|
|
4040
4144
|
title: c.title,
|
|
4041
4145
|
tokens: estimate(c.body.trim()),
|
|
4042
4146
|
...c.kind !== "note" ? { kind: c.kind } : {},
|
|
4043
4147
|
...c.done ? { done: c.done } : {},
|
|
4044
|
-
...links.length ? { links } : {}
|
|
4148
|
+
...links.length ? { links } : {},
|
|
4149
|
+
...pictures.length ? { pictures } : {}
|
|
4045
4150
|
};
|
|
4046
4151
|
}
|
|
4047
4152
|
return {
|
|
@@ -4438,6 +4543,107 @@ var init_engine = __esmMin((() => {
|
|
|
4438
4543
|
this.queue = run.catch(() => void 0);
|
|
4439
4544
|
return run;
|
|
4440
4545
|
}
|
|
4546
|
+
/** A picture (M22), in the notes or in the base's repository. The path must be a picture's, within its folder,
|
|
4547
|
+
* and (in the notes) never in a hidden folder such as `.isorite/`: nothing but a picture is given this way. */
|
|
4548
|
+
async file(path, from = "notes") {
|
|
4549
|
+
const type = pictureType(path);
|
|
4550
|
+
if (!type) return null;
|
|
4551
|
+
let files, at;
|
|
4552
|
+
if (from === "code") {
|
|
4553
|
+
const target = codeTarget(`code:${path}`), where = (await this.settingsOfBase()).repository;
|
|
4554
|
+
files = target && where ? this.opts.repository?.(where) ?? null : null;
|
|
4555
|
+
at = target?.path ?? "";
|
|
4556
|
+
} else {
|
|
4557
|
+
at = path.replace(/^\/+/, "");
|
|
4558
|
+
files = at.split("/").some((p) => !p || p === "." || p === ".." || p.startsWith(".")) ? null : this.opts.notes;
|
|
4559
|
+
}
|
|
4560
|
+
const bytes = files ? await files.readBytes(at) : null;
|
|
4561
|
+
if (!bytes) return null;
|
|
4562
|
+
if (bytes.length > 2e7) throw new EngineError(`${path} is larger than isorite gives (${MAX_FILE_BYTES / 1e6} MB)`, 413);
|
|
4563
|
+
return {
|
|
4564
|
+
bytes,
|
|
4565
|
+
type,
|
|
4566
|
+
version: sha256(bytes).slice(0, 16)
|
|
4567
|
+
};
|
|
4568
|
+
}
|
|
4569
|
+
/** What was read of each picture, kept while its size and time are the same. */
|
|
4570
|
+
pictureCache = /* @__PURE__ */ new Map();
|
|
4571
|
+
/** Every picture in the notes (M22), found afresh: one put in the folder by hand since the base was opened is
|
|
4572
|
+
* among them, and from then on in the bundle too, so a link to it isn't broken. A hidden folder is never looked in. */
|
|
4573
|
+
async pictures() {
|
|
4574
|
+
const found = [];
|
|
4575
|
+
const visit = async (dir) => {
|
|
4576
|
+
for (const f of await this.opts.notes.list(dir)) {
|
|
4577
|
+
if (f.name.startsWith(".")) continue;
|
|
4578
|
+
const path = dir ? `${dir}/${f.name}` : f.name;
|
|
4579
|
+
if (f.dir) await visit(path);
|
|
4580
|
+
else if (pictureType(f.name)) found.push({
|
|
4581
|
+
path,
|
|
4582
|
+
...f.size === void 0 ? {} : { size: f.size },
|
|
4583
|
+
...f.mtime === void 0 ? {} : { mtime: f.mtime }
|
|
4584
|
+
});
|
|
4585
|
+
}
|
|
4586
|
+
};
|
|
4587
|
+
await visit("");
|
|
4588
|
+
const out = [], seen = /* @__PURE__ */ new Set();
|
|
4589
|
+
for (const f of found) {
|
|
4590
|
+
seen.add(f.path);
|
|
4591
|
+
const kept = this.pictureCache.get(f.path);
|
|
4592
|
+
if (kept && f.size !== void 0 && kept.size === f.size && kept.mtime === f.mtime) {
|
|
4593
|
+
out.push(kept.info);
|
|
4594
|
+
continue;
|
|
4595
|
+
}
|
|
4596
|
+
const bytes = f.size !== void 0 && f.size > 2e7 ? null : await this.opts.notes.readBytes(f.path);
|
|
4597
|
+
if (!bytes) continue;
|
|
4598
|
+
const px = pictureSize(bytes, pictureType(f.path));
|
|
4599
|
+
const info = {
|
|
4600
|
+
path: f.path,
|
|
4601
|
+
bytes: bytes.length,
|
|
4602
|
+
width: px?.width ?? null,
|
|
4603
|
+
height: px?.height ?? null,
|
|
4604
|
+
sha256: sha256(bytes)
|
|
4605
|
+
};
|
|
4606
|
+
this.pictureCache.set(f.path, {
|
|
4607
|
+
...f.size === void 0 ? {} : { size: f.size },
|
|
4608
|
+
...f.mtime === void 0 ? {} : { mtime: f.mtime },
|
|
4609
|
+
info
|
|
4610
|
+
});
|
|
4611
|
+
out.push(info);
|
|
4612
|
+
}
|
|
4613
|
+
for (const path of this.pictureCache.keys()) if (!seen.has(path)) this.pictureCache.delete(path);
|
|
4614
|
+
let changed = false;
|
|
4615
|
+
for (const path of seen) if (!this.files.has(path)) {
|
|
4616
|
+
this.files.set(path, { path });
|
|
4617
|
+
changed = true;
|
|
4618
|
+
}
|
|
4619
|
+
for (const path of [...this.files.keys()]) if (pictureType(path) && !seen.has(path)) {
|
|
4620
|
+
this.files.delete(path);
|
|
4621
|
+
changed = true;
|
|
4622
|
+
}
|
|
4623
|
+
if (changed) this.built = this.index = null;
|
|
4624
|
+
return out.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
4625
|
+
}
|
|
4626
|
+
/** Put a picture in the notes (M22). The path must be a picture's, out of any hidden folder, and free: a file
|
|
4627
|
+
* that's there is never replaced. Not journaled: a picture has no history, and stays when its link goes. */
|
|
4628
|
+
savePicture(path, bytes) {
|
|
4629
|
+
return this.one(async () => {
|
|
4630
|
+
const type = pictureType(path), parts = path.split("/");
|
|
4631
|
+
if (!type || parts.some((p) => !p || p === "." || p === ".." || p.startsWith(".")) || /[\\\x00-\x1f]/.test(path)) throw new EngineError(`not a picture's path: ${path}`, 400);
|
|
4632
|
+
if (bytes.length > 2e7) throw new EngineError(`the picture is larger than isorite takes (${MAX_FILE_BYTES / 1e6} MB)`, 413);
|
|
4633
|
+
if (await this.opts.notes.readBytes(path) !== null) throw new Conflict(`${path} is there already`, null);
|
|
4634
|
+
await this.opts.notes.writeBytes(path, bytes);
|
|
4635
|
+
this.files.set(path, { path });
|
|
4636
|
+
this.built = this.index = null;
|
|
4637
|
+
const px = pictureSize(bytes, type);
|
|
4638
|
+
return {
|
|
4639
|
+
path,
|
|
4640
|
+
bytes: bytes.length,
|
|
4641
|
+
width: px?.width ?? null,
|
|
4642
|
+
height: px?.height ?? null,
|
|
4643
|
+
sha256: sha256(bytes)
|
|
4644
|
+
};
|
|
4645
|
+
});
|
|
4646
|
+
}
|
|
4441
4647
|
/** Where a note's file is: drafts and Understanding notes are among isorite's own files. */
|
|
4442
4648
|
where(id) {
|
|
4443
4649
|
if (!inBase(id)) return {
|
|
@@ -6491,6 +6697,8 @@ var init_ports = __esmMin((() => {
|
|
|
6491
6697
|
systemClock = { now: () => Date.now() };
|
|
6492
6698
|
MemoryFiles = class {
|
|
6493
6699
|
files = /* @__PURE__ */ new Map();
|
|
6700
|
+
/** Files held as bytes (pictures), apart from text. */
|
|
6701
|
+
bytes = /* @__PURE__ */ new Map();
|
|
6494
6702
|
constructor(files = {}) {
|
|
6495
6703
|
for (const [path, text] of Object.entries(files)) this.files.set(path, text);
|
|
6496
6704
|
}
|
|
@@ -6503,22 +6711,42 @@ var init_ports = __esmMin((() => {
|
|
|
6503
6711
|
async append(path, text) {
|
|
6504
6712
|
this.files.set(path, (this.files.get(path) ?? "") + text);
|
|
6505
6713
|
}
|
|
6714
|
+
async readBytes(path) {
|
|
6715
|
+
const b = this.bytes.get(path), t = this.files.get(path);
|
|
6716
|
+
return b ?? (t === void 0 ? null : new TextEncoder().encode(t));
|
|
6717
|
+
}
|
|
6718
|
+
async writeBytes(path, bytes) {
|
|
6719
|
+
this.files.delete(path);
|
|
6720
|
+
this.bytes.set(path, bytes);
|
|
6721
|
+
}
|
|
6506
6722
|
async remove(path) {
|
|
6507
6723
|
this.files.delete(path);
|
|
6724
|
+
this.bytes.delete(path);
|
|
6508
6725
|
}
|
|
6509
6726
|
async list(dir) {
|
|
6510
6727
|
const prefix = dir ? dir + "/" : "";
|
|
6511
6728
|
const found = /* @__PURE__ */ new Map();
|
|
6512
|
-
for (const path of this.files.keys()) {
|
|
6729
|
+
for (const path of [...this.files.keys(), ...this.bytes.keys()]) {
|
|
6513
6730
|
if (!path.startsWith(prefix)) continue;
|
|
6514
6731
|
const rest = path.slice(prefix.length), cut = rest.indexOf("/");
|
|
6515
|
-
if (cut
|
|
6516
|
-
|
|
6732
|
+
if (cut >= 0) {
|
|
6733
|
+
found.set(rest.slice(0, cut), {
|
|
6734
|
+
name: rest.slice(0, cut),
|
|
6735
|
+
dir: true
|
|
6736
|
+
});
|
|
6737
|
+
continue;
|
|
6738
|
+
}
|
|
6739
|
+
const b = this.bytes.get(path);
|
|
6740
|
+
found.set(rest, b ? {
|
|
6741
|
+
name: rest,
|
|
6742
|
+
dir: false,
|
|
6743
|
+
size: b.length
|
|
6744
|
+
} : {
|
|
6745
|
+
name: rest,
|
|
6746
|
+
dir: false
|
|
6747
|
+
});
|
|
6517
6748
|
}
|
|
6518
|
-
return [...found
|
|
6519
|
-
name,
|
|
6520
|
-
dir: isDir
|
|
6521
|
-
}));
|
|
6749
|
+
return [...found.values()];
|
|
6522
6750
|
}
|
|
6523
6751
|
};
|
|
6524
6752
|
MemorySecrets = class {
|
|
@@ -7978,6 +8206,7 @@ var Terminal = class {
|
|
|
7978
8206
|
};
|
|
7979
8207
|
//#endregion
|
|
7980
8208
|
//#region packages/server/src/http.ts
|
|
8209
|
+
init_src$1();
|
|
7981
8210
|
init_src();
|
|
7982
8211
|
const LOOPBACK = /* @__PURE__ */ new Set([
|
|
7983
8212
|
"localhost",
|
|
@@ -7999,6 +8228,8 @@ const TYPES = {
|
|
|
7999
8228
|
".map": "application/json",
|
|
8000
8229
|
".txt": "text/plain; charset=utf-8"
|
|
8001
8230
|
};
|
|
8231
|
+
/** What a picture may do when opened on its own: be drawn, and nothing else (no script, no fetch, no form). */
|
|
8232
|
+
const PICTURE_CSP = "sandbox; default-src 'none'; img-src data:; style-src 'unsafe-inline'";
|
|
8002
8233
|
const Edit = z.object({
|
|
8003
8234
|
base: z.string().max(64),
|
|
8004
8235
|
text: z.string()
|
|
@@ -8221,6 +8452,42 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
|
|
|
8221
8452
|
app.get("/api/map", (c) => answer(c, () => api.map()));
|
|
8222
8453
|
app.get("/api/timeline", (c) => answer(c, () => api.timeline()));
|
|
8223
8454
|
app.get("/api/search", (c) => answer(c, () => api.search(c.req.query("q") ?? "")));
|
|
8455
|
+
app.get("/api/pictures", (c) => answer(c, () => api.pictures()));
|
|
8456
|
+
app.post("/api/file", async (c) => {
|
|
8457
|
+
let origin = "";
|
|
8458
|
+
try {
|
|
8459
|
+
origin = new URL(c.req.header("origin") ?? "").host;
|
|
8460
|
+
} catch {}
|
|
8461
|
+
if (!c.req.header("origin") || origin !== c.req.header("host")) return refuse(c, 403, "cross-origin request refused");
|
|
8462
|
+
if (c.req.header("x-isorite-token") !== token) return refuse(c, 403, "bad token");
|
|
8463
|
+
const path = c.req.query("path") ?? "", type = pictureType(path);
|
|
8464
|
+
if (!type || (c.req.header("content-type") ?? "").split(";")[0].trim().toLowerCase() !== type) return refuse(c, 415, "a picture, of the type its name says");
|
|
8465
|
+
if (Number(c.req.header("content-length") ?? 0) > 2e7) return refuse(c, 413, "picture too large");
|
|
8466
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
8467
|
+
if (bytes.length > 2e7) return refuse(c, 413, "picture too large");
|
|
8468
|
+
return answer(c, () => api.savePicture(path, bytes));
|
|
8469
|
+
});
|
|
8470
|
+
app.get("/api/file", async (c) => {
|
|
8471
|
+
const code = c.req.query("code"), path = code ?? c.req.query("path") ?? "";
|
|
8472
|
+
let found;
|
|
8473
|
+
try {
|
|
8474
|
+
found = await api.file(path, code === void 0 ? "notes" : "code");
|
|
8475
|
+
} catch (e) {
|
|
8476
|
+
if (e instanceof EngineError) return refuse(c, e.status, e.message);
|
|
8477
|
+
throw e;
|
|
8478
|
+
}
|
|
8479
|
+
if (!found) return refuse(c, 404, `no picture at ${path}`);
|
|
8480
|
+
const etag = `"${found.version}"`;
|
|
8481
|
+
const headers = {
|
|
8482
|
+
"Content-Type": found.type,
|
|
8483
|
+
"Cache-Control": "no-cache",
|
|
8484
|
+
ETag: etag,
|
|
8485
|
+
"X-Content-Type-Options": "nosniff",
|
|
8486
|
+
"Content-Security-Policy": PICTURE_CSP
|
|
8487
|
+
};
|
|
8488
|
+
if (c.req.header("if-none-match") === etag) return c.body(null, 304, headers);
|
|
8489
|
+
return c.body(found.bytes, 200, headers);
|
|
8490
|
+
});
|
|
8224
8491
|
app.put("/api/note", async (c) => {
|
|
8225
8492
|
const refused = writeRefused(c);
|
|
8226
8493
|
if (refused) return refused;
|
|
@@ -8555,12 +8822,26 @@ var NodeFiles = class {
|
|
|
8555
8822
|
throw e;
|
|
8556
8823
|
}
|
|
8557
8824
|
}
|
|
8558
|
-
|
|
8825
|
+
async readBytes(path) {
|
|
8826
|
+
try {
|
|
8827
|
+
return new Uint8Array(await readFile(this.at(path)));
|
|
8828
|
+
} catch (e) {
|
|
8829
|
+
if (gone(e)) return null;
|
|
8830
|
+
throw e;
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8559
8833
|
async write(path, text) {
|
|
8834
|
+
await this.replace(path, text);
|
|
8835
|
+
}
|
|
8836
|
+
async writeBytes(path, bytes) {
|
|
8837
|
+
await this.replace(path, bytes);
|
|
8838
|
+
}
|
|
8839
|
+
/** Written beside the file and renamed over it, so a reader never sees half a note. */
|
|
8840
|
+
async replace(path, data) {
|
|
8560
8841
|
const full = this.at(path), beside = `${full}.${process.pid}.${this.n++}.tmp`;
|
|
8561
8842
|
await mkdir(dirname(full), { recursive: true });
|
|
8562
8843
|
try {
|
|
8563
|
-
await writeFile(beside,
|
|
8844
|
+
await writeFile(beside, data, typeof data === "string" ? "utf8" : void 0);
|
|
8564
8845
|
await rename(beside, full);
|
|
8565
8846
|
} catch (e) {
|
|
8566
8847
|
await rm(beside, { force: true });
|
|
@@ -8574,10 +8855,27 @@ var NodeFiles = class {
|
|
|
8574
8855
|
}
|
|
8575
8856
|
async list(dir) {
|
|
8576
8857
|
try {
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8858
|
+
const at = this.at(dir), entries = await readdir(at, { withFileTypes: true });
|
|
8859
|
+
return (await Promise.all(entries.filter((e) => e.isDirectory() || e.isFile()).map(async (e) => {
|
|
8860
|
+
if (e.isDirectory()) return {
|
|
8861
|
+
name: e.name,
|
|
8862
|
+
dir: true
|
|
8863
|
+
};
|
|
8864
|
+
if (e.name.endsWith(".md")) return {
|
|
8865
|
+
name: e.name,
|
|
8866
|
+
dir: false
|
|
8867
|
+
};
|
|
8868
|
+
const s = await stat(resolve(at, e.name)).catch(() => null);
|
|
8869
|
+
return s ? {
|
|
8870
|
+
name: e.name,
|
|
8871
|
+
dir: false,
|
|
8872
|
+
size: s.size,
|
|
8873
|
+
mtime: s.mtimeMs
|
|
8874
|
+
} : {
|
|
8875
|
+
name: e.name,
|
|
8876
|
+
dir: false
|
|
8877
|
+
};
|
|
8878
|
+
}))).sort((a, b) => a.name < b.name ? -1 : 1);
|
|
8581
8879
|
} catch (e) {
|
|
8582
8880
|
if (gone(e)) return [];
|
|
8583
8881
|
throw e;
|
|
@@ -8836,7 +9134,7 @@ var Setup = class {
|
|
|
8836
9134
|
};
|
|
8837
9135
|
//#endregion
|
|
8838
9136
|
//#region packages/server/src/version.ts
|
|
8839
|
-
const VERSION = "0.
|
|
9137
|
+
const VERSION = "0.10.0";
|
|
8840
9138
|
//#endregion
|
|
8841
9139
|
//#region packages/server/src/git.ts
|
|
8842
9140
|
/** The top of the repository `folder` is in, or null: the nearest folder holding `.git`, a directory or, in a
|
|
@@ -11079,6 +11377,19 @@ const finderHttp = (pace = 0) => scriptedHttp(finderAnswer, pace);
|
|
|
11079
11377
|
//#region packages/guide/src/node.ts
|
|
11080
11378
|
/** In a checkout, the package's notes/; in the npm package, where this is bundled into dist/, the guide/ beside it. */
|
|
11081
11379
|
const GUIDE_DIR = [join(import.meta.dirname, "..", "guide"), join(import.meta.dirname, "..", "notes")].find((d) => existsSync(join(d, "start-here.md"))) ?? join(import.meta.dirname, "..", "notes");
|
|
11380
|
+
/** The guide's pictures (M22), by path, as bytes. */
|
|
11381
|
+
function loadGuidePictures(dir = GUIDE_DIR) {
|
|
11382
|
+
const out = {};
|
|
11383
|
+
const visit = (at) => {
|
|
11384
|
+
for (const name of readdirSync(at)) {
|
|
11385
|
+
const path = join(at, name);
|
|
11386
|
+
if (statSync(path).isDirectory()) visit(path);
|
|
11387
|
+
else if (/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(name)) out[relative(dir, path).split(sep).join("/")] = new Uint8Array(readFileSync(path));
|
|
11388
|
+
}
|
|
11389
|
+
};
|
|
11390
|
+
visit(dir);
|
|
11391
|
+
return out;
|
|
11392
|
+
}
|
|
11082
11393
|
/** The guide's files as they stand in the package: its notes and its AGENTS.md, by path. */
|
|
11083
11394
|
function loadGuide(dir = GUIDE_DIR) {
|
|
11084
11395
|
const out = {};
|
|
@@ -11106,7 +11417,7 @@ function writeGuide(folder) {
|
|
|
11106
11417
|
fresh: false
|
|
11107
11418
|
};
|
|
11108
11419
|
if (existsSync(at) && readdirSync(at).some((f) => f !== ".isorite")) throw new StudyError(`${at} has files in it that are not the guide: name another folder, isorite guide <folder>`);
|
|
11109
|
-
for (const [path, text] of Object.entries(loadGuide())) {
|
|
11420
|
+
for (const [path, text] of [...Object.entries(loadGuide()), ...Object.entries(loadGuidePictures())]) {
|
|
11110
11421
|
mkdirSync(dirname(join(at, path)), { recursive: true });
|
|
11111
11422
|
writeFileSync(join(at, path), text);
|
|
11112
11423
|
}
|
|
@@ -11155,8 +11466,21 @@ async function writeExample(folder, now = Date.now()) {
|
|
|
11155
11466
|
mkdirSync(dirname(at), { recursive: true });
|
|
11156
11467
|
writeFileSync(at, text);
|
|
11157
11468
|
}
|
|
11469
|
+
copyPictures(NOTES_DIR, notes);
|
|
11158
11470
|
return { notes };
|
|
11159
11471
|
}
|
|
11472
|
+
/** The pictures among a folder's notes (M22), copied as they are: they have no history. */
|
|
11473
|
+
function copyPictures(from, to, dir = from) {
|
|
11474
|
+
for (const name of readdirSync(dir)) {
|
|
11475
|
+
const path = join(dir, name);
|
|
11476
|
+
if (statSync(path).isDirectory()) copyPictures(from, to, path);
|
|
11477
|
+
else if (citeKind(name) === "image") {
|
|
11478
|
+
const at = join(to, relative(from, path));
|
|
11479
|
+
mkdirSync(dirname(at), { recursive: true });
|
|
11480
|
+
copyFileSync(path, at);
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11160
11484
|
const GEOMETRY_DIR = join(import.meta.dirname, "..", "geometry");
|
|
11161
11485
|
/** The geometry example's notes, and the code beside them, as they stand today, by path. */
|
|
11162
11486
|
function loadGeometry(dir = GEOMETRY_DIR) {
|
|
@@ -11287,7 +11611,8 @@ init_model();
|
|
|
11287
11611
|
const USAGE = `isorite: an agentic harness for understanding
|
|
11288
11612
|
|
|
11289
11613
|
isorite new <folder> start a knowledge base: the folder, empty, with isorite's .isorite/
|
|
11290
|
-
isorite check <notes>
|
|
11614
|
+
isorite check <notes> [--unused] check a folder of notes: frontmatter, links, cycles; --unused lists
|
|
11615
|
+
the pictures no note shows
|
|
11291
11616
|
isorite serve <notes> [--port <n>] [--allow-host <name>]
|
|
11292
11617
|
open it in the browser, on this machine only
|
|
11293
11618
|
isorite migrate <study base> [--merge] move a study base (format 1) into its notes' .isorite/; exercises
|
|
@@ -11324,7 +11649,7 @@ const USAGE = `isorite: an agentic harness for understanding
|
|
|
11324
11649
|
the browser; run again, as you left it. Iso is your model, or with
|
|
11325
11650
|
none set up, the guide's finder (also: isorite --tutorial)
|
|
11326
11651
|
`;
|
|
11327
|
-
function check(notes, out = console.log) {
|
|
11652
|
+
function check(notes, out = console.log, unused = false) {
|
|
11328
11653
|
const root = resolve(notes);
|
|
11329
11654
|
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
11330
11655
|
out(`no folder at ${root}`);
|
|
@@ -11335,12 +11660,18 @@ function check(notes, out = console.log) {
|
|
|
11335
11660
|
for (const c of cycles) out(`warning requires cycle: ${c.join(" -> ")}`);
|
|
11336
11661
|
const errors = issues.filter((i) => i.level === "error").length;
|
|
11337
11662
|
out(`${bundle.concepts.size} notes in ${bundle.directories.size} folders; ${errors} errors, ${issues.length - errors} warnings, ${cycles.length} requires cycles`);
|
|
11663
|
+
if (unused) {
|
|
11664
|
+
const cited = new Set([...bundle.concepts.values()].flatMap((c) => c.cites.map((x) => x.target)));
|
|
11665
|
+
const idle = [...bundle.files].filter((f) => citeKind(f) === "image" && !cited.has(f)).sort();
|
|
11666
|
+
for (const f of idle) out(`unused ${f}`);
|
|
11667
|
+
out(idle.length ? `${idle.length} ${idle.length === 1 ? "picture" : "pictures"} no note shows` : "every picture is shown by a note");
|
|
11668
|
+
}
|
|
11338
11669
|
return errors ? 1 : 0;
|
|
11339
11670
|
}
|
|
11340
11671
|
function main(argv) {
|
|
11341
11672
|
const [command, ...rest] = argv;
|
|
11342
11673
|
try {
|
|
11343
|
-
if (command === "check" && rest
|
|
11674
|
+
if (command === "check" && rest.find((r) => !r.startsWith("--"))) return check(rest.find((r) => !r.startsWith("--")), console.log, rest.includes("--unused"));
|
|
11344
11675
|
if (command === "new" && rest[0]) {
|
|
11345
11676
|
const made = newBase(rest[0]);
|
|
11346
11677
|
console.log(`${made}: a knowledge base, empty. Open it in the browser, or beside your code in the terminal:\n isorite serve ${made}\n cd <your code> && isorite term ${made}`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 260" width="260" height="260" font-family="sans-serif">
|
|
2
|
+
<rect x="2" y="2" width="256" height="256" fill="none" stroke="#7a8794" stroke-width="2"/>
|
|
3
|
+
<g fill="none" stroke="#7a8794" stroke-width="1.5">
|
|
4
|
+
<path d="M130 2V258M2 130H258"/>
|
|
5
|
+
<path d="M66 2V130M2 66H130"/>
|
|
6
|
+
<path d="M194 130V258M130 194H258"/>
|
|
7
|
+
<path d="M34 2V66M2 34H66"/>
|
|
8
|
+
</g>
|
|
9
|
+
<g fill="#3f7fbf">
|
|
10
|
+
<circle cx="16" cy="18" r="5"/><circle cx="50" cy="48" r="5"/><circle cx="20" cy="52" r="5"/>
|
|
11
|
+
<circle cx="98" cy="40" r="5"/><circle cx="96" cy="104" r="5"/><circle cx="200" cy="70" r="5"/>
|
|
12
|
+
<circle cx="40" cy="190" r="5"/><circle cx="160" cy="160" r="5"/><circle cx="226" cy="168" r="5"/>
|
|
13
|
+
<circle cx="170" cy="226" r="5"/>
|
|
14
|
+
</g>
|
|
15
|
+
</svg>
|
|
@@ -6,4 +6,6 @@ description: The plane split into squares, and each square into four, until each
|
|
|
6
6
|
|
|
7
7
|
A tree over the bodies in 2D. The root is a square holding them all; a square with more than one body is split into four. Each node also keeps its [centre of mass](/algorithms/centre-of-mass.md "requires"). In 3D it is an octree, with eight children.
|
|
8
8
|
|
|
9
|
+

|
|
10
|
+
|
|
9
11
|
How is a body inserted, and what does each node keep? I don't have this straight yet.
|
package/package.json
CHANGED