From 456a69bb8d3547ae039b0e9e32e5526541c2c255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Fri, 16 Oct 2026 14:23:45 +0000 Subject: [PATCH] fix(claude): render markdown tables in notes --- claude/skills/notes/notes.py | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/claude/skills/notes/notes.py b/claude/skills/notes/notes.py index 72f4272..d9c9fed 100755 --- a/claude/skills/notes/notes.py +++ b/claude/skills/notes/notes.py @@ -249,6 +249,41 @@ def inline_plain(text: str, site: Site, at: str) -> str: return re.sub(r"\x00(\d+)\x00", lambda m: links[int(m[1])], text) +# gfm pipe tables: a header row, a row of dashes, then body rows up to a line without a pipe +PIPE = re.compile(r"(? list[str]: + s = line.strip() + s = s.removeprefix("|") + s = s[:-1] if s.endswith("|") and not s.endswith("\\|") else s + return [c.strip().replace("\\|", "|") for c in PIPE.split(s)] + + +def table_start(lines: list[str], i: int) -> list[str] | None: + """the column alignments if a table starts at lines[i], else None""" + if i + 1 >= len(lines) or not (PIPE.search(lines[i]) and PIPE.search(lines[i + 1])): + return None + seps = table_cells(lines[i + 1]) + if len(table_cells(lines[i])) != len(seps): + return None + if not all(SEPARATOR_CELL.match(c) for c in seps): + return None + return [ALIGN.get((c[0] == ":", c[-1] == ":"), "") for c in seps] + + +def table_row(line: str, aligns: list[str], tag: str, site: Site, at: str) -> str: + # short rows are padded, extra cells dropped, as gfm does + cells = (table_cells(line) + [""] * len(aligns))[: len(aligns)] + row = [] + for cell, align in zip(cells, aligns): + style = f' style="text-align: {align}"' if align else "" + row.append(f"<{tag}{style}>{inline(cell, site, at)}") + return f"{''.join(row)}" + + def markdown(text: str, site: Site, at: str) -> str: lines = text.splitlines() out: list[str] = [] @@ -294,6 +329,20 @@ def markdown(text: str, site: Site, at: str) -> str: out.append(f"{inline(m[2], site, at)}") i += 1 continue + if aligns := table_start(lines, i): + flush_para() + close_lists() + head = table_row(line, aligns, "th", site, at) + i += 2 + body = [] + while i < len(lines) and PIPE.search(lines[i]): + body.append(table_row(lines[i], aligns, "td", site, at)) + i += 1 + tbody = f"{''.join(body)}" if body else "" + out.append( + f'
{head}{tbody}
' + ) + continue if m := re.match(r"^(\s*)([-*]|\d+\.)\s+(.*)$", line): flush_para() indent, tag = len(m[1]), "ol" if m[2][0].isdigit() else "ul" @@ -314,6 +363,7 @@ def markdown(text: str, site: Site, at: str) -> str: i < len(lines) and lines[i].strip() and not re.match(r"^\s*([-*]|\d+\.)\s+|^\s*```|^#{1,4}\s", lines[i]) + and not table_start(lines, i) ): item.append(lines[i].strip()) i += 1