Files
firefly-ui/src/lib/components/receipt/notes.ts
T
hh 349a6a0b32 feat(transactions): the transaction page as a receipt with context
The amount is the headline, notes come first at full width, then the
last twelve visits to the same place as a dot timeline next to the
paying account's days around it with the running balance. Fields edit in
place: j and k move, Enter opens and saves, Esc cancels, s c, s b, s t
and s s jump to classification. A matching subscription shows as a
suggestion, L links it.
2026-10-16 15:51:00 +00:00

68 lines
2.0 KiB
TypeScript

export type Inline =
| { kind: "text" | "bold" | "code"; text: string }
| { kind: "link"; text: string; href: string };
export type Block =
| { kind: "head"; line: Inline[] }
| { kind: "list"; ordered: boolean; items: Inline[][] }
| { kind: "para"; lines: Inline[][] };
const TOKEN =
/\*\*([^*]+)\*\*|`([^`]+)`|\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)|(https?:\/\/[^\s<>()]*[^\s<>().,;:!?'"])/g;
const BULLET = /^\s*(?:[-*•]|(\d+)[.)])\s+(.*)$/;
const HEAD = /^#{1,6}\s+(.*)$/;
const LINES = /\r?\n/;
export function inline(text: string): Inline[] {
const out: Inline[] = [];
let last = 0;
for (const match of text.matchAll(TOKEN)) {
const [whole, bold, code, label, href, url] = match;
if (match.index > last) {
out.push({ kind: "text", text: text.slice(last, match.index) });
}
if (bold) {
out.push({ kind: "bold", text: bold });
} else if (code) {
out.push({ kind: "code", text: code });
} else {
out.push({ href: href ?? url, kind: "link", text: label ?? url });
}
last = match.index + whole.length;
}
if (last < text.length) {
out.push({ kind: "text", text: text.slice(last) });
}
return out;
}
export function blocks(notes: string): Block[] {
const result: Block[] = [];
let open: Block | null = null;
for (const raw of notes.split(LINES)) {
const line = raw.trimEnd();
const head = line.match(HEAD);
const bullet = line.match(BULLET);
if (!line.trim()) {
open = null;
} else if (head) {
result.push({ kind: "head", line: inline(head[1]) });
open = null;
} else if (bullet) {
const ordered = Boolean(bullet[1]);
if (open?.kind !== "list" || open.ordered !== ordered) {
open = { items: [], kind: "list", ordered };
result.push(open);
}
open.items.push(inline(bullet[2]));
} else {
if (open?.kind !== "para") {
open = { kind: "para", lines: [] };
result.push(open);
}
open.lines.push(inline(line));
}
}
return result;
}