Skip to content

Add LaTeX, super/subscript, pull quotes, callouts, and Markdown docs#65

Merged
ma2za merged 7 commits into
ma2za:mainfrom
MattFisher:markdown-latex-and-others
Jul 18, 2026
Merged

Add LaTeX, super/subscript, pull quotes, callouts, and Markdown docs#65
ma2za merged 7 commits into
ma2za:mainfrom
MattFisher:markdown-latex-and-others

Conversation

@MattFisher

@MattFisher MattFisher commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Builds on the from_markdown work from #61, adding several Markdown constructs that map to Substack nodes, plus a documentation page. Node shapes were confirmed by round-tripping editor-authored drafts through the live API.

Closes #63

New Markdown features

  • LaTeX math — inline $…$ (→ latex node) and block $$…$$ (→ latex_block), via mdit_py_plugins.dollarmath.
  • Superscript (^x^) and subscript (~x~) inline marks. Subscript's single-tilde syntax coexists with ~~strikethrough~~.
  • Pull quotes and callouts via fenced-container syntax (:::pullquote / :::callout), since they have no native Markdown equivalent.

Tables: intentionally not supported

An earlier iteration added a table node, but Substack has no table renderer or editor UI — the node stored but never displayed. It's been removed to avoid producing silently-broken drafts. docs/markdown.md documents this and points users at Datawrapper embeds for tabular data.

Docs

New docs/markdown.md documents every supported construct with examples, plus what's intentionally unsupported. Linked from the README's Markdown section.

Dependencies

The sub/superscript plugins require mdit-py-plugins >= 0.5; the constraint (>=0.5,<0.7) and lockfile are updated accordingly (resolves 0.6.1).

Testing

  • Unit tests cover each new construct in test_from_markdown_features.py.
  • The end-to-end fixture and golden file were regenerated from a live round-trip, so every feature here is verified against the real Substack API (RUN_SUBSTACK_E2E=1).

MattFisher and others added 5 commits July 10, 2026 13:16
GFM tables (`| col | col |`) map to Substack's table/table_row/
table_header/table_cell schema. Table node shape was confirmed by
probing the live API.

LaTeX math blocks ($$...$$) map to latex_block via mdit_py_plugins'
dollarmath plugin. Both features are covered by unit tests and the
e2e fixture/golden file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline math ($x$ and $$x$$ via dollarmath's double_inline) now maps to
Substack's inline `latex` node, confirmed against the live API. Previously
inline math tokens were silently dropped.

Also restores the [^unused] and [^listnote] footnote definitions in the
e2e fixture, which had been corrupted, dropping footnote ma2za#4 from coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop dollarmath's double_inline option so $$...$$ is only ever a display
block, matching the common Markdown convention. Inline math stays $...$.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Substack has no table renderer or insert UI, so the table node (added
earlier) stored but never displayed. Removed it in favor of features
Substack actually supports, confirmed by round-tripping editor-authored
nodes through the live API:

- pullquote and calloutBlock via `:::pullquote` / `:::callout` containers
- superscript (`^x^`) and subscript (`~x~`) inline marks

Subscript's single-tilde syntax coexists with `~~strikethrough~~`.

The sub/superscript plugins require mdit-py-plugins >= 0.5, so the
constraint and lockfile are updated (now resolves 0.6.1).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add docs/markdown.md covering every Markdown construct from_markdown()
converts (formatting, headings, lists, images, footnotes, math, pull
quotes, callouts) plus what is intentionally unsupported (tables,
editor-only widgets). Link it from the README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

ma2za commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Thanks for adding these Markdown features. I reproduced two edge cases against the PR head:

  1. Ordinary paired dollar signs are parsed as math. Revenue grew from $5 million to $10 million this year. becomes text, latex, text.
  2. Labeled display math is dropped. For $$ ... $$ (mass-energy), the plugin emits math_block_label, but the renderer only handles math_block.

These regression tests currently both fail:

def test_currency_dollar_signs_are_not_parsed_as_latex(self):
    post = make_post()
    markdown = "Revenue grew from $5 million to $10 million this year."
    post.from_markdown(markdown)

    paragraph = body(post)[0]
    assert [node["type"] for node in paragraph["content"]] == ["text"]
    assert paragraph["content"][0]["text"] == markdown


def test_labeled_latex_block_is_preserved(self):
    post = make_post()
    post.from_markdown(
        "$$\nE=mc^2\n$$ (mass-energy)\n\nAfter the equation."
    )

    blocks = body(post)
    assert [block["type"] for block in blocks] == ["latex_block", "paragraph"]
    assert blocks[0]["attrs"]["persistentExpression"] == "E=mc^2"

The focused run reported 2 failed, 26 deselected.

Address PR ma2za#65 review feedback from ma2za:

- Configure dollarmath with allow_space=False and allow_digits=False
  (Pandoc-style delimiters), so paired dollar amounts like
  "$5 million to $10 million" stay plain text instead of being parsed
  as inline math.
- Render math_block_label tokens ("$$ ... $$ (label)") as latex_block,
  discarding the label, instead of silently dropping the whole block.
- Add both of the reviewer's regression tests and document the
  delimiter rules in docs/markdown.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JfNdp5q3sjjY62Zd6ZMzg3
@MattFisher

Copy link
Copy Markdown
Contributor Author

Good catches! Both fixed. The dollarmath plugin is now configured with allow_space=False, allow_digits=False (Pandoc-style delimiters), so paired currency amounts stay plain text, and the renderer now handles math_block_label the same as math_block, discarding the label since Substack has no equation labels. Both of your regression tests are added to the suite and pass.

@ma2za
ma2za merged commit fd0aefb into ma2za:main Jul 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support tables and LaTeX in markdown

3 participants