mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 09:56:47 +08:00
* feat(server): accept JSON Lines in the attachment text preview proxy The viewer now shows .jsonl / .ndjson files as a tree, so the text proxy's whitelist takes them too (extension and application/x-ndjson). The whitelist test gains the CSV / TSV / log / JSON Lines cases and says it is mirrored by the client-side table in packages/views/editor/utils/preview.test.ts. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(views): table, tree, numbered-line and viewport views in the attachment viewer MUL-7650, step 3 of MUL-7642. Fills in how the full-window viewer shows the file types agents produce most. - CSV / TSV (`table` kind): parsed to a table on the shared DataTable — pinned header, virtualized rows, resizable columns, a frozen row-number column. Clicking a header sorts ascending / descending / file order; numbers sort numerically, empty cells stay last, all-number columns align right. `;`-separated exports are sniffed. DataTable headers now carry aria-sort when the table sorts through TanStack's own state. - JSON / JSON Lines / YAML (`structured` kind): a collapsible tree with inline previews of collapsed records and paged long lists; Tree / Raw in the top bar. A file that does not parse falls back to the source and says why. YAML alias expansion stays capped by the parser. - Code, text and logs: numbered lines (the number is a pseudo-element, so copying skips it) with a sticky gutter, and a wrap toggle. Logs and plain text wrap by default, code does not; the reader's choice carries across the sequence. - HTML: Fit / Desktop 1440 / Tablet 768 / Phone 390. A device width lays the document out at that width and scales it down when the stage is narrower, with the size and scale shown under it; switching never remounts the iframe. A source toggle shows the HTML with line numbers. - Open in new tab works for every previewable kind: /{slug}/attachments/{id} /preview loads the record by id and renders the viewer's own top bar and stage (AttachmentPreviewStandalone), keeping the desktop scroll restore for HTML. - Whitelist: .jsonl / .ndjson join the text types on both sides, and the client test mirrors the server's case table. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]>
66 lines
2.0 KiB
TypeScript
66 lines
2.0 KiB
TypeScript
// @vitest-environment node
|
|
import { describe, expect, it } from "vitest";
|
|
import { parseStructured } from "./parse-structured";
|
|
|
|
describe("parseStructured", () => {
|
|
it("parses JSON", () => {
|
|
expect(parseStructured('{"a":[1,true,null]}', "json")).toEqual({
|
|
ok: true,
|
|
value: { a: [1, true, null] },
|
|
});
|
|
});
|
|
|
|
it("reports why JSON failed", () => {
|
|
const result = parseStructured('{"a":', "json");
|
|
expect(result.ok).toBe(false);
|
|
expect(!result.ok && result.message).toBeTruthy();
|
|
});
|
|
|
|
it("parses JSON Lines into a list, skipping blank lines", () => {
|
|
expect(parseStructured('{"n":1}\n\n{"n":2}\r\n', "jsonl")).toEqual({
|
|
ok: true,
|
|
value: [{ n: 1 }, { n: 2 }],
|
|
});
|
|
});
|
|
|
|
it("names the JSON Lines line that failed", () => {
|
|
const result = parseStructured('{"n":1}\n{oops}\n', "jsonl");
|
|
expect(result.ok).toBe(false);
|
|
expect(!result.ok && result.message).toMatch(/^Line 2: /);
|
|
});
|
|
|
|
it("parses YAML", () => {
|
|
expect(parseStructured("name: web\nports:\n - 80\n - 443\n", "yaml")).toEqual({
|
|
ok: true,
|
|
value: { name: "web", ports: [80, 443] },
|
|
});
|
|
});
|
|
|
|
it("shows a multi-document YAML stream as a list of documents", () => {
|
|
expect(parseStructured("kind: A\n---\nkind: B\n", "yaml")).toEqual({
|
|
ok: true,
|
|
value: [{ kind: "A" }, { kind: "B" }],
|
|
});
|
|
});
|
|
|
|
it("treats an empty YAML file as null", () => {
|
|
expect(parseStructured("", "yaml")).toEqual({ ok: true, value: null });
|
|
});
|
|
|
|
it("reports YAML syntax errors", () => {
|
|
const result = parseStructured("a: [1, 2\nb: 3", "yaml");
|
|
expect(result.ok).toBe(false);
|
|
});
|
|
|
|
it("refuses runaway alias expansion instead of hanging", () => {
|
|
const bomb = [
|
|
"a: &a [x, x, x, x, x, x, x, x, x]",
|
|
"b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a]",
|
|
"c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b]",
|
|
"d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c]",
|
|
"e: [*d, *d, *d, *d, *d, *d, *d, *d, *d]",
|
|
].join("\n");
|
|
expect(parseStructured(bomb, "yaml").ok).toBe(false);
|
|
});
|
|
});
|