@yaebal/preview
render a telegram-style chat from plain objects to an svg string
experimental / wip. the api and rendering may change without notice. not ready
for production — pin a version if you depend on the output.
install
pnpm add @yaebal/previewusage
renderChat(messages, options) returns an svg string — rich text, every common
media type, the lot. zero runtime, no <foreignObject> (so it rasterizes and survives
github's svg sanitizer). drop the result into docs, a readme, or a landing page.
import { renderChat } from "@yaebal/preview";
import { md } from "@yaebal/fmt"; // optional — produces { text, entities }
import { writeFile } from "node:fs/promises";
const svg = renderChat(
[
{ from: "user", text: "/start", time: "23:33", status: "read" },
{ from: "bot", name: "yaebal", ...md`Hello, **unknown** person`, time: "23:33" },
{ from: "bot", name: "yaebal", photo: [], src: "cat.jpg", caption: "a cat" },
{ from: "bot", name: "yaebal", voice: { duration: 7 } },
{ from: "bot", name: "yaebal", buttons: [["Useless button"]] },
],
{ theme: "light", width: 400 },
);
await writeFile("chat.svg", svg); // it's just a stringmessages
each entry is a ChatMessage. from is the only required field: "user" renders outgoing (right-aligned, with ticks), "bot" renders incoming
(left-aligned, with an avatar).
| field | type | description |
|---|---|---|
from | Side ("user" | "bot") | required. outgoing vs incoming |
name | string | sender label (incoming); also drives the avatar initial + colour |
time | string | timestamp shown in the message meta |
status | TickStatus ("sent" | "delivered" | "read") | outgoing read receipt (ticks). ignored for incoming |
buttons | string[][] | keyboard rows rendered as buttons under the message |
text | string | message text. wrapped automatically |
entities | MessageEntity[] | entities for text (bold/italic/code/link/spoiler/…). spread @yaebal/fmt's md/html to get these for free |
caption | string | caption for a media message |
captionEntities | MessageEntity[] | entities for caption |
src | string | real image/thumb url or data-uri for the picture-like media (a file_id can't render) |
spoiler | boolean | cover the media with a spoiler |
photo | PhotoSize[] | image (or placeholder) + optional caption |
sticker | Sticker | standalone image, or its emoji big |
animation | Animation | image + GIF badge |
video | Video | image + play button + duration |
voice | Voice | waveform + duration |
audio | Audio | play disc + title / performer |
document | Document | file icon + name + size |
venue | Venue | map tile + pin + title/address |
location | Location | map tile + pin |
contact | Contact | avatar + name + phone |
poll | Poll | question + options with percentage bars |
media
all media fields use the real @yaebal/types shapes (the array/objects you'd get off an Update). add spoiler: true to cover picture media.
// real @yaebal/types shapes — hand it a ctx.message almost verbatim.
// for picture-like media add `src` (URL/data-URI) to show real pixels;
// a file_id has none, so without `src` you get a clean placeholder.
renderChat([
{ from: "bot", name: "yaebal", photo: [], src: "cat.jpg", spoiler: true },
{ from: "bot", name: "yaebal", video: { width: 640, height: 360, duration: 42 } },
{ from: "bot", name: "yaebal", sticker: { emoji: "🎈" } },
{ from: "bot", name: "yaebal", document: { file_name: "report.pdf", file_size: 81920 } },
{ from: "bot", name: "yaebal", contact: { first_name: "Ann", phone_number: "+1 555" } },
{
from: "bot",
name: "yaebal",
poll: {
question: "tabs?",
options: [
{ text: "yes", voter_count: 7 },
{ text: "no", voter_count: 1 },
],
},
},
]);api
| export | signature | description |
|---|---|---|
renderChat | (messages: ChatMessage[], options?: RenderOptions) => string | render a telegram-style chat to an svg string |
ChatMessage | interface | one message — see the table above |
RenderOptions | interface | render-wide options — see below |
Side | "user" | "bot" | message direction |
TickStatus | "sent" | "delivered" | "read" | outgoing read receipt |
RenderOptions
| option | type | default | description |
|---|---|---|---|
theme | "dark" | "light" | "light" | the green-wallpaper look, or dark |
width | number | 380 | canvas width in px |
avatar | string | name initial | override the avatar glyph for incoming messages |