This repository is build for Bi - directional conversion Notion ⇄ Markdown
Project description
Notion Markdown Converter (Python)
A toolkit to convert Markdown content into Notion API block structures with high-fidelity formatting and block-type mapping.
Project Objective
Develop a Python package that parses Markdown and outputs a list of Notion API-compatible block objects, faithfully preserving:
- All supported Notion block types (basic, media, advanced, etc.)
- Rich text formatting (bold, italic, code, links, color, equations)
- Inline and block equations (LaTeX)
- Nested structures (lists, toggles, columns, etc.)
- Annotation context splitting (bold + inline math, code in list items, etc.)
Core Features
1. Markdown to Notion Block Conversion
- Parse Markdown and generate an ordered list of Notion API blocks (
dictformat). - Preserve structure and nesting as per Markdown input.
2. Rich Text Segmentation
- Segment text runs by formatting boundary:
- Bold, italic, underline, code, color
- Inline equations (
$...$) - Inline code (
`...`)
- Mixing formats:
- Example:
**States ($S$):** All possible situations...- Must become:
"States ("(bold)"S"(equation + bold)"):"(bold)" All possible situations..."(normal)
- Must become:
- Example:
3. Block Type Coverage
Support the following block types (with example input → output mapping):
Basic Blocks
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
Text |
paragraph |
|
# Heading 1 |
heading_1 |
|
## Heading 2 |
heading_2 |
|
### Heading 3 |
heading_3 |
|
- List item |
bulleted_list_item |
|
1. List item |
numbered_list_item |
Toggle Blocks
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
<details><summary>Summary</summary>Content</details> or ??? Toggle |
toggle |
Toggle heading 1/2/3, Toggle list |
Quote / Callout
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
> Quote |
quote |
|
> [!NOTE] Callout |
callout |
Custom icon/color support |
Dividers and Tables
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
--- |
divider |
|
| Markdown Table | table |
Media Blocks
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
 |
image |
|
[video](url) |
video |
|
[audio](url) |
audio |
|
[File](url) |
file |
|
[Bookmark](url) |
bookmark |
Web bookmarks |
Code
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
```python ... ``` |
code |
Language support, code caption |
`inline code` |
Inline text |
annotations.code = true |
Equations
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
$$ ... $$ |
equation (block) |
|
$...$ (inline) |
Inline equation | Must be in .text array with context |
Columns
| Markdown Example | Notion Block Type | Notes |
|---|---|---|
:::columns ... ::: |
column_list |
Nested blocks as column children |
Synced, Breadcrumb, Button, Table of Contents, AI Block
- Allow simple syntax or special markers for:
- Synced block (
:::synced) - Breadcrumb (
:::breadcrumb) - Button (
[Button](action)) - Table of Contents (
[TOC]) - AI Block (
:::ai-block) - Mermaid code block for diagrams
- Synced block (
Rich Text Handling and Edge Cases
- Split any block into multiple
textruns as needed:- Example:
Markdown:**States ($S$):** ...
Notion:[ {"type": "text", "text": {"content": "States ("}, "annotations": {"bold": true}}, {"type": "equation", "equation": {"expression": "S"}, "annotations": {"bold": true}}, {"type": "text", "text": {"content": "):"}, "annotations": {"bold": true}}, {"type": "text", "text": {"content": " All possible situations..."}, "annotations": {"bold": false}} ]
- Example:
- Handle multiple annotations (bold+italic+code) per segment.
- Support hyperlinks, mentions (with special markdown syntax or tag).
Nested & Complex Structures
- Nested lists:
Markdown’s nested lists → children in Notion block structure - Toggle blocks, column blocks, table blocks:
Parse and create nested children lists as per Notion API.
Examples for Each Block
Paragraph with Mixed Content
Markdown:
- States ($S$): All possible situations… Output:
bulleted_list_itemwith 4textsegments, including inline equation.
Heading with Inline Equation
Markdown: Policy ($\pi$) Output:
heading_2with rich text:"Policy (", equation"\\pi",")"
Code Block
Markdown:
python print("Hello")
Output:
codeblock, language = python, content = print("Hello")
Image
Markdown:
Output:
imageblock with external URL
Table
Markdown: | A | B | |---|---| | 1 | 2 | Output:
tableblock, rows/columns parsed accordingly
Inline Code
Markdown:
Here is inline code.
Output:
paragraphwith text run (normal), text run (code), text run (normal)
Advanced Features
- Customizable mapping:
Allow user to define/extend block type mapping or handle unknown blocks gracefully. - Extensible:
Support for new Notion block types as Notion updates API.
Non-Goals
- Does not attempt to round-trip Notion → Markdown (unless explicitly developed for this).
- Does not handle Notion database pages (just blocks/pages for content).
Summary
This package should serve as a high-fidelity Markdown-to-Notion converter with fine-grained control over rich text and block types, designed for programmatic page building via the Notion API. All text formatting, equations, code, lists, tables, and advanced block types (AI, Mermaid, columns, etc.) are supported.
Ledu — Development Guide
Purpose: This document walks you (the implementer) through the recommended build order, core responsibilities, and testing rhythm for each module in the Ledu Markdown → Notion toolkit.
1 High‑Level Architecture
Markdown (raw string)
│ (1)
▼
MarkdownParser ──► Token list (markdown‑it‑py)
│ (2)
▼
PageBuilder ──► BlockConverter registry ──► Notion‑style JSON blocks
│ (3) ▲
│ │
└──► NotionClient (optional upload) ─┘
- MarkdownParser — Converts raw Markdown into a stable token stream.
- PageBuilder — Walks the token stream, dispatching each token to the correct BlockConverter subclass (paragraph, heading, list, …).
- BlockConverters — Transform tokens into Notion‑ready block dictionaries; rely on RichTextSegmenter for inline splitting.
All singletons (settings, registry) live in config.py so the rest of the code stays stateless.
2 Implementation Milestones
| Phase | Goal | Key modules | Tests to add |
|---|---|---|---|
| 0 | Skeleton compiles | all __init__.py, utils.typing |
just pytest -q smoke test |
| 1 | Paragraph & Heading blocks | rich_text.py, paragraph.py, heading.py |
inline mix fixture, heading fixture |
| 2 | Lists (bulleted/numbered) & nested depth handling | list_item.py, expand ConversionContext |
deep‑nest fixture |
| 3 | Code & Quote/Callout blocks | code.py, advanced.py (partial) |
code block language, caption |
| 4 | Tables & Divider | table.py |
simple table fixture |
| 5 | Media (image/audio/video/file/bookmark) | media.py |
remote vs. data‑url fixture |
| 6 | Toggles, Columns, Synced, TOC | advanced.py (full) |
toggle inside list fixture |
| 7 | CLI upload path | cli.py, notion/client.py |
record HTTP calls with pytest‑vcr |
| 8 | Extension hooks + config overrides | entry‑point loading, config.py |
plugin test package |
Complete each phase before moving forward—small PRs keep the mental load low.
3 Coding Rhythm
- Write a failing fixture in
tests/fixtures/(e.g.paragraph.md+ expected JSON). - Implement the minimal logic to pass that fixture.
- Run
pytest -q; commit. - Refactor if needed (🐥 → 🐓).
Why fixtures? They double as living documentation; future Notion API changes will surface as diff failures.
4 Rich‑Text Gotchas
- Inline equations: treat
$...$before other annotations to avoid$\pi$appearing inside**bold**regex hits. - Overlapping styles: split into smallest non‑overlapping segments left → right.
- Mentions & links: decide on a custom Markdown extension (e.g.
@useror<mention:page‑id>).
5 Testing Strategy
- Unit – each converter’s
to_notionon controlled token lists. - Integration – end‑to‑end
PageBuilder.convertagainst full‑page fixtures. - Live smoke – optional: flag‐guarded test that pushes to a dummy Notion page when
NOTION_TOKENenv var is set.
6 Release & Distribution
- Bump version in
pyproject.toml→poetry build→poetry publish. - Git tag using
vX.Y.Z(semver). - Draft a GitHub release that links the CHANGELOG.md (generate via
towncrieronce repo stabilises).
7 Future Ideas
- Round‑trip support (Notion → Markdown) using reversible AST annotations.
- Mermaid preview: detect ```mermaid blocks and upload rendered SVG via Notion image block.
- Watch mode: CLI subcommand that watches a Markdown file for changes and syncs automatically.
Happy building!
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ledu-0.1.3.tar.gz.
File metadata
- Download URL: ledu-0.1.3.tar.gz
- Upload date:
- Size: 18.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.4 CPython/3.12.7 Darwin/23.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad5a19e481e65276ac818fb7f41fd9dca78900e1c4ed7b6ebdc58985bdac4322
|
|
| MD5 |
0a324f422e4593ba16467d23a608f5d1
|
|
| BLAKE2b-256 |
f32d28de6fa2d881165b429493b57e9c54f375091d243de2d57872eee63536e7
|
File details
Details for the file ledu-0.1.3-py3-none-any.whl.
File metadata
- Download URL: ledu-0.1.3-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.4 CPython/3.12.7 Darwin/23.6.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63dd9750cf8e640ccf4a802079f5dca76800b45ac7c2b599d4e9c42b39d48f13
|
|
| MD5 |
0ea09461335a5a3f038f288509d1877b
|
|
| BLAKE2b-256 |
54deb034d3b2d89d2f0aa875f6a8ff0cb123c30ead1cbfdd19e4f0575b59b609
|