惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
腾讯CDC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
L
LINUX DO - 热门话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
V
Vulnerabilities – Threatpost
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
C
Cisco Blogs
A
Arctic Wolf
月光博客
月光博客
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
量子位
小众软件
小众软件
Latest news
Latest news
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Microsoft Security Blog
Microsoft Security Blog
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
N
Netflix TechBlog - Medium
K
Kaspersky official blog
人人都是产品经理
人人都是产品经理
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
Y
Y Combinator Blog
P
Proofpoint News Feed
H
Hackread – Cybersecurity News, Data Breaches, AI and More
M
MIT News - Artificial intelligence
T
Threat Research - Cisco Blogs
S
Schneier on Security
D
Docker
Scott Helme
Scott Helme
MyScale Blog
MyScale Blog
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
GbyAI
GbyAI
有赞技术团队
有赞技术团队
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
H
Help Net Security
Simon Willison's Weblog
Simon Willison's Weblog
J
Java Code Geeks
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tenable Blog
B
Blog
Know Your Adversary
Know Your Adversary
IT之家
IT之家

Yuexun J

Native macOS Updates in Tauri Why MCP Matters How to Make Your Tauri Dev Faster Developing an App with My AI Intern My Journey with Vim The Vim Guide for VS Code Users
How the Notion Editor Works
Yuexun Jiang · 2021-03-25 · via Yuexun J

Written in December 2020. Some details may be outdated.

Notion is everywhere. Note-taking, knowledge management, project management — all in one tool.

I’ve used it for years. But I never looked at how it works. The editor, specifically.

So I opened DevTools, dug through the minified code, and figured it out.

Here’s what I found.

TL;DR

  • Everything is a Block. Blocks are independent. Some have editable regions, some don’t.
  • Layout uses CSS Flexbox. Simple, but limited.
  • Editable regions use contenteditable divs. But Notion doesn’t use document.execCommand.
  • React renders the view, but Notion implements its own render queue.
  • Every edit creates a transaction with operations. Operations modify block data.
  • Text formatting is stored as structured data, not HTML. The editor rebuilds DOM from data.
  • Selection is custom. Cross-block selection triggers block-level highlighting, not text selection.
  • Copy-paste is inconsistent. Single-line pastes lose formatting. Multi-line pastes parse HTML.
  • Undo/redo uses a stack with inverted operations.

Editable Regions

contenteditable implementation

Each text block is a contenteditable div. Independent from other blocks.

This means cross-block selection is tricky. You can’t just drag across multiple blocks like in Google Docs.

Non-text blocks (images, embeds) are just DOM. No contenteditable. No cursor logic needed.

DOM rendering

This separation simplifies things. No zero-width characters for cursor positioning. Each block handles its own editing.

Layout

Notion’s layout is basic. Flexbox.

Flexbox layout

Drag two blocks side by side, they split evenly. Resize, and the width ratio saves to block data.

Want right-aligned content? Add an empty block on the left and adjust widths.

It works. But complex layouts? Not really possible.

Text Input

When you type, Notion listens to events on the contenteditable div. But it doesn’t let the browser handle the edit.

Instead:

  1. Event fires
  2. Notion creates a transaction with operations
  3. Operations modify block data
  4. View re-renders from data

Notion uses React, but not React’s normal rendering. It maintains a custom queue. Components call forceUpdate, Notion batches and executes them.

Every edit produces a transaction:

Transaction structure

Type “4” after “123”? The transaction contains a set operation that updates the block’s properties.title to “1234”.

The entire block value is replaced. Every keystroke.

This means long blocks get slow. That’s why pressing Enter always creates a new block.

Line Breaks Create Blocks

Press Enter, Notion intercepts it. Creates a new block. Moves focus.

Block creation operations

Four operations:

  1. set — create new block with fresh ID
  2. update — link to parent document
  3. listAfter — position after current block
  4. set — initialize with empty content

Search for “commit” or “createAndCommit” in Notion’s code to see transactions in action.

Formatting

Formatting works similarly. Intercept keyboard event, generate transaction, update data.

Notion listens globally for key combinations. Cmd+B for bold, etc.

Formatting call stack

The transaction:

Formatting data structure

Bold “bold” in “this is a bold text” produces:

[["this is a "], ["bold", [["b"]]], [" text"]]

The format: [[TEXT, [[FORMAT], [FORMAT]]], [TEXT], ...]

Formatting metadata lives with the text. Not in HTML attributes.

This avoids document.execCommand entirely. Better cross-browser consistency.

Format markers

b for bold, i for italic, a for links, and so on.

Selection

Inside a block: browser’s native selection.

Across blocks: Notion takes over.

Block selection

Drag beyond a block’s boundary, and Notion adds div.notion-selectable-halo to highlight the entire block. Continue dragging, more blocks highlight.

Selection highlight

It’s not true text selection. It’s block selection.

There’s also a quirk: box selection behavior changes based on x-position at the same y-coordinate.

Selection quirk

Not intuitive.

Copy-Paste

Notion handles multiple formats:

  • text/plain
  • text/html
  • text/uri-list
  • text/_notion-blocks-v2-production (internal)
  • text/_notion-text-production (internal)

Paste handling

External paste logic:

  • Multi-line? Parse text/html, restore formatting.
  • Single-line? Treat as plain text. All formatting lost.

This is a significant limitation. Paste one styled line, you get plain text.

Internal Block Copy

Copy a block in Notion, and the clipboard gets:

  1. An HTML link placeholder
  2. A text/_notion-blocks-v2-production JSON blob

Internal copy

Pasting creates a reference, then fetches block data via API (api/v3/syncRecordValues).

This means internal block paste requires network.

Network dependency

Offline web? Block paste fails. Mobile apps cache data locally, so they work offline.

Internal Text Copy

Text copy is simpler. Full data lives in text/_notion-text-production. No network needed.

Text copy

Undo-Redo

Every transaction includes invertedOperations — the reverse of what was done.

Undo-redo stack

Notion maintains a revisionStack. Pointer at the top.

Undo: execute invertedOperations from current transaction, move pointer down.

Redo: execute operations from next transaction, move pointer up.

Edit after undo: slice the stack, push new transaction, pointer to top.

Standard undo-redo, cleanly implemented.

The Takeaway

Notion’s block-based architecture is elegant. Every row in a database is a block. Every embed is a block. Views (kanban, calendar, timeline) are just different renderings of the same blocks.

Extensibility is built in. Third-party embeds fit naturally.

But the details reveal rough edges:

  • Single-line paste loses formatting
  • Internal paste needs network
  • Selection behavior is inconsistent
  • Long blocks get slow

The foundation is solid. The execution has room to grow.

I might dig deeper later. There’s more to find.