md2clip.py
每次要把 markdown 貼到 Notion 的時候,我都會卡在同一個問題:直接貼,它就變成 code block。原本的 markdown syntax 直接成了 Notion 裡的一個灰色框,heading 沒有了,table 也沒有了,就是一整塊等寬字。
儘管 Notion 有提供 import 的功能,但它很慢,而且會讓 code block 排成奇怪的樣子,換行有時候會不見,程式碼擠成一堆。所以 import 不是個好選項。
我之前有一個 workaround,就是把 markdown 丟給 Claude Artifacts,讓它轉成 HTML 顯示出來,然後用 Artifacts 右上角的 copy 按鈕。這樣貼到 Notion 格式就對了。但每次都要開一個 Artifacts、等它生成、再按 copy,光是為了貼個文件就得花一輪 token,雖然有點浪費但確實有用。
冷靜想想,Claude Artifacts 做得到,那應該可以寫成工具,就可以不浪費 token 來完成同樣的事。所以我想做一個本地的 CLI 工具,cat file.md | md2clip.py,然後直接貼到 Notion 就好。
為什麼 Claude Artifacts 複製可以,土炮版本卻亂成一團呢?
觀察到這個現象之後,反過來想:既然 Artifacts copy 出來的東西 Notion 吃得下去,那背後一定有什麼機制在運作,搞清楚的話應該可以自己做一個。起手式很單純,截圖給 Claude Code 說:
你看我用 Artifacts 產生 HTML,然後按 copy,貼到 Notion 格式就是對的,我也想要一樣的功能,幫我做。
Claude Code 第一版做出來的東西,就是把 markdown 轉成 HTML 放進 clipboard。但貼到 Notion 之後,出來的只是簡單的 rich text,效果完全不像 Artifacts copy 的結果,heading 變 bold,table 壓成一行。同樣是 HTML,從 Artifacts copy 出來的可以,自己放進 clipboard 的就不行。
這引起了我的好奇,又跟 Claude Code 來回了幾版,都不成功。copy 出來的可以,我們自己做的放進 clipboard 就不行。於是我換了個方向,讓 Claude Code 去觀察 Artifacts copy 之後 clipboard 裡到底放了什麼。它分析完之後,又生了一版新的 POC 給我實驗。
Claude Code 分析完 clipboard 的內容後,發現 Artifacts copy 出來的 clipboard 裡,除了 HTML 本身,還多帶了幾個 metadata。其中一個叫 org.chromium.source-url,這是 Chromium 系瀏覽器放東西到 clipboard 時會自動帶上的標記。Notion 在貼上的時候會先看有沒有這個標記,有的話才會把 HTML 裡的 heading、list、table 各自轉成獨立的 Notion block;沒有的話,就整坨攤平成一段文字。
我們自己用程式放 HTML 進 clipboard,只放了 public.html,沒帶這個標記,所以 Notion 根本不走 block 轉換。除了 org.chromium.source-url,clipboard 裡還有其他幾個 Chromium 相關的設定,把這些都補上之後,就是下一節歸納出來的成果。
取得關鍵設定後,剩下就是湊答案的時間了
儘管我們沒辦法知道 Notion 內部是怎麼處理 clipboard 格式的,但從實驗歸納出來,可用的版本就是在 clipboard 裡同時放這四個東西:
用 macOS 的 NSPasteboard:
public.html:markdown 轉出來的 HTMLpublic.utf8-plain-text:原始 markdown 文字,純文字貼上時用org.chromium.source-url:放一個隨便的 URL,讓 Notion 以為這是從 Chromium 來的org.chromium.internal.source-rfh-token:Chromium 會附帶的 token,值放什麼都可以
完整的 md2clip.py 原始碼可以在 qty-claude-skills 取得,這個工具也發布在同一個 Claude Code plugin repo,安裝後可以透過 skill 直接使用。
cat file.md | md2clip.py
跑完之後直接去 Notion 貼上,heading、list、table 全部正確。拿一份有多層 heading、有序列表、有 table 的 markdown 測過,格式完全正確。
思路剖析:正向不順時,就試試逆向吧
這次能解掉這個問題,不是因為我一開始就知道 Notion 怎麼判斷 clipboard 格式。正向去猜 HTML 該怎麼寫,跟 Claude Code 反覆測了幾回都不是可以接受的結果。真正的轉折是換了方向:不再猜 Notion 要什麼,而是回頭去看「已經成功的東西長什麼樣子」。
讓 Claude Code 去分析 Artifacts copy 之後的 clipboard 內容,等於是把一個黑箱問題變成觀察題。儘管不知道如何產出能被 Notion 正確顯示的結果,但我們已經可以穩定產出可靠的範本,就是 Claude Artifacts 的 copy。那就用逆向的思維,觀察範本跟我們自己實作的差異在哪裡。
回頭看,這個思路其實蠻通用的。遇到「為什麼同樣的東西,從 A 來可以、從 B 來不行」的問題,與其正面去猜接收方的邏輯,不如先把 A 的輸出完整攤開來看。差異通常就藏在那些你沒注意到的 metadata 裡面。