Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The chat composer formats as you write: a bar for bold, italic, strikethrough, links, lists, quotes, code and code blocks that can be shown or hidden, shortcuts listed under its keyboard button (Ctrl or Cmd with B, I, Shift+X, K, Shift+C, Alt+Shift+C, Shift+7, 8 and 9), and Markdown typed inline (*bold*, _italic_, ~strike~, `code`, ``` blocks, - and 1. lists, > quotes) turning into its formatting. Enter sends, Shift+Enter starts a line, Enter adds a list item or a code line where it should, Ctrl+Enter sends from anywhere, pasting a link over words links them, and plain text pasted in is read as Markdown. Mentions, emoji completion, drafts and typing work as before, messages are still sent and kept as Markdown, the editor loads only with the composer, and your own messages are edited in place with the same composer on a computer (Edit message in the message's menu) and in the sheet on a phone. | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| 4 | import { type DocNode, docToMarkdown, escapeText, markdownToDoc } from "./chat-compose.ts"; | |
| 5 | ||
| 6 | const text = (value: string, ...marks: (string | { type: string; attrs?: Record<string, unknown> })[]): DocNode => ({ | |
| 7 | type: "text", | |
| 8 | text: value, | |
| 9 | ...(marks.length ? { marks: marks.map((mark) => (typeof mark === "string" ? { type: mark } : mark)) } : {}), | |
| 10 | }); | |
| 11 | const p = (...content: DocNode[]): DocNode => (content.length ? { type: "paragraph", content } : { type: "paragraph" }); | |
| 12 | const doc = (...content: DocNode[]): DocNode => ({ type: "doc", content }); | |
| 13 | ||
| 14 | test("formatting is written as Markdown", () => { | |
| 15 | assert.equal( | |
| 16 | docToMarkdown(doc(p(text("bold", "bold"), text(" "), text("it", "italic"), text(" "), text("gone", "strike"), text(" "), text("a*b", "code")))), | |
| 17 | "**bold** _it_ ~~gone~~ `a*b`", | |
| 18 | ); | |
| 19 | // Marks that overlap open and close once. | |
| 20 | assert.equal(docToMarkdown(doc(p(text("a", "bold"), text("b", "bold", "italic"), text("c", "bold")))), "**a_b_c**"); | |
| 21 | // Spaces at a mark's edges sit outside it. | |
| 22 | assert.equal(docToMarkdown(doc(p(text("say "), text(" hi ", "bold"), text("there")))), "say **hi** there"); | |
| 23 | assert.equal(docToMarkdown(doc(p(text("tick ` in", "code")))), "`` tick ` in ``"); | |
| 24 | }); | |
| 25 | ||
| 26 | test("links: with their text, or bare when the text is the address", () => { | |
| 27 | const link = (href: string) => ({ type: "link", attrs: { href } }); | |
| 28 | assert.equal(docToMarkdown(doc(p(text("the docs", link("https://g1t.sh/docs"))))), "[the docs](https://g1t.sh/docs)"); | |
| 29 | assert.equal(docToMarkdown(doc(p(text("https://g1t.sh", link("https://g1t.sh"))))), "https://g1t.sh"); | |
| 30 | assert.equal(docToMarkdown(doc(p(text("x", link("https://a.example/a b"))))), "[x](<https://a.example/a%20b>)"); | |
| 31 | }); | |
| 32 | ||
| 33 | test("each paragraph is a line; an empty one is a blank line", () => { | |
| 34 | assert.equal(docToMarkdown(doc(p(text("one")), p(text("two")), p(), p(text("three")))), "one\ntwo\n\nthree"); | |
| 35 | assert.equal(docToMarkdown(doc(p(), p(text("x")), p())), "x"); | |
| 36 | }); | |
| 37 | ||
| 38 | test("text that only looks like Markdown is escaped, and mentions stay mentions", () => { | |
| 39 | assert.equal(escapeText("2*3 and snake_case and _edge_ and [x] and `t` and ~5"), "2\\*3 and snake_case and \\_edge\\_ and \\[x\\] and \\`t\\` and \\~5"); | |
| 40 | assert.equal(docToMarkdown(doc(p(text("- not a list")), p(text("1. not either")), p(text("# nor this")), p(text("> or this")))), "\\- not a list\n1\\. not either\n\\# nor this\n\\> or this"); | |
| 41 | assert.equal(docToMarkdown(doc(p(text("ask @ana in #web about #12")))), "ask @ana in #web about #12"); | |
| 42 | }); | |
| 43 | ||
| 44 | test("lists, quotes and code blocks", () => { | |
| 45 | const item = (...content: DocNode[]): DocNode => ({ type: "listItem", content }); | |
| 46 | assert.equal( | |
| 47 | docToMarkdown( | |
| 48 | doc( | |
| 49 | p(text("Plan:")), | |
| 50 | { type: "bulletList", content: [item(p(text("one"))), item(p(text("two")), { type: "orderedList", attrs: { start: 1 }, content: [item(p(text("deep")))] })] }, | |
| 51 | p(text("after")), | |
| 52 | ), | |
| 53 | ), | |
| 54 | "Plan:\n\n- one\n- two\n 1. deep\n\nafter", | |
| 55 | ); | |
| 56 | assert.equal(docToMarkdown(doc({ type: "orderedList", attrs: { start: 3 }, content: [item(p(text("c"))), item(p(text("d")))] })), "3. c\n4. d"); | |
| 57 | assert.equal(docToMarkdown(doc({ type: "blockquote", content: [p(text("quoted")), p(), p(text("more"))] })), "> quoted\n>\n> more"); | |
| 58 | assert.equal(docToMarkdown(doc({ type: "codeBlock", attrs: { language: "ts" }, content: [{ type: "text", text: "const a = 1;\n\n\nconst b = `x`;" }] })), "```ts\nconst a = 1;\n\n\nconst b = `x`;\n```"); | |
| 59 | assert.equal(docToMarkdown(doc({ type: "codeBlock", attrs: { language: null }, content: [{ type: "text", text: "```\nfence\n```" }] })), "````\n```\nfence\n```\n````"); | |
| 60 | }); | |
| 61 | ||
| 62 | test("Markdown comes back into the composer as it was written", () => { | |
| 63 | const samples = [ | |
| 64 | "**bold** _it_ ~~gone~~ `code`", | |
| 65 | "one\ntwo\n\nthree", | |
| 66 | "Plan:\n\n- one\n- two\n 1. deep\n\nafter", | |
| 67 | "3. c\n4. d", | |
| 68 | "> quoted\n>\n> more", | |
| 69 | "```ts\nconst a = 1;\n\nconst b = 2;\n```", | |
| 70 | "[the docs](https://g1t.sh/docs) and https://g1t.sh", | |
| 71 | "ask @ana in #web about g1t#12", | |
| 72 | "\\- not a list and 2\\*3 and snake_case", | |
| 73 | "**a _b_ c**", | |
| 74 | ]; | |
| 75 | for (const sample of samples) assert.equal(docToMarkdown(markdownToDoc(sample)), sample, sample); | |
| 76 | }); | |
| 77 | ||
| 78 | test("Markdown the composer cannot write still comes in", () => { | |
| 79 | // A heading is a bold line; a rule is left out; an image is a link. | |
| 80 | assert.equal(docToMarkdown(markdownToDoc("## Notes\n---\ntext")), "**Notes**\ntext"); | |
| 81 | assert.equal(docToMarkdown(markdownToDoc("*star em* and __strong__ and ~one~")), "_star em_ and **strong** and ~~one~~"); | |
| 82 | assert.deepEqual(markdownToDoc(""), { type: "doc", content: [{ type: "paragraph" }] }); | |
| 83 | }); |