個案研究*Electron

如何 Notion 複製
文字和HTML。

跟蹤剪貼簿呼叫從Notion的頁面API,透過預載入和主程序,到Electron的剪貼簿編寫器。

Notion桌面7.6.1·JavaScript*REA

Notion 桌面包中的橋接程式碼

  1. 01 · 面向頁面的API clipboard.write(data) 預載入公開了一個可呼叫的方法。
  2. 02 · 預載入包裝器 ipcRenderer.invoke 包裝器傳送通道和引數。
  3. 03 · 主程序處理程式 ipcMain.handle 接收器檢查哪個頁面傳送它。
  4. 04 · 系統剪貼簿 clipboard.write(data) Electron 寫入提供的文字和HTML。

一個通道連線雙方: notion:clipboard:write

預載入指令碼和主程序使用同一個通道。解析包裝函式後,就能看出這些名稱背後實際呼叫的 Electron 函式。

複製操作去哪裡?

頁面可以呼叫名為 clipboard.write 的方法。它在預載入指令碼中的實現是一個包裝函式。關鍵問題是:它傳送了什麼,又由哪段程式碼接收?

你的程式設計助手

使用REA跟蹤Notion如何將文字和HTML複製到系統剪貼簿。 在每個步驟顯示程式碼。

一個示例提示與Notion的本地應用程式包可供你的程式設計助手.

REA查詢從哪裡開始閱讀

用 REA 分析標籤頁的預載入指令碼,可以找到暴露給頁面的 API,以及傳送 IPC 請求的呼叫。兩個結果都給出了在壓縮程式碼中的位置:

REA 分析結果 → 兩個原始碼位置

公開頁面API
api_key: "__electronApi"
api_status: "dynamic"
source: preload.js:1:22764–22828
渲染器IPC呼叫
operation: "invoke"
channel_expression: "e"
source: preload.js:1:6486–6522
這是新一次 analyze_javascript_application 分析結果中的部分欄位,來自 Notion 標籤頁的預載入指令碼,使用 REA 4.1.0 記錄。位置對應原始壓縮包中的行號和從零開始的列號。

助手開啟這些位置,在組裝的 API 中找到剪貼簿成員,再讀取 invokerInMain。呼叫處傳入 notion:clipboard:write,讓助手知道要在主程序程式碼中搜尋哪個名稱。下面四段程式碼展示這條呼叫鏈。

四段簡短程式碼

從打包的JavaScript簡化,具有可讀的變數名稱。 不相關的成員被省略。

  1. 從公開到頁面的API開始

    tab_browser_view/preload.js · API 成員
    const clipboardApi = {
      write: invokerInMain("notion:clipboard:write")
    };
    
    // Included in the API exposed through contextBridge:
    // window.__electronApi.clipboard

    此成員提供通道名稱。 的實施 invokerInMain 告訴我們請求如何離開頁面的程序。

  2. 解析預載入的包裝器

    tab_browser_view/preload.js · IPC 輔助函式
    function invokerInMain(channel) {
      return (...args) => invokeInMainAndReturnResult(channel, ...args);
    }
    
    function invokeInMainAndReturnResult(channel, ...args) {
      return ipcRenderer.invoke(channel, ...args);
    }

    返回的函式透過 Electron 的請求/響應 IPC 轉發引數。接下來可以在主程序中查詢 notion:clipboard:write。

  3. 檢查接收器是如何註冊的

    主要/index.js · 處理程式助手
    const handleRequestFromRenderer = {
      addListener(channel, handler) {
        ipcMain.handle(channel, handler);
      }
    };

    主程序使用另一個包裝器。 其 addListener 方法註冊一個 ipcMain.handle 那個頻道的接收器。

  4. 查詢寫入及其發件人檢查

    主要/index.js · 剪貼簿接收器
    handleRequestFromRenderer.addListener(
      "notion:clipboard:write",
      (event, data) =>
        isNotionWebContents(event.sender)
          ? clipboard.write(data)
          : undefined
    );

    這就是呼叫的終點。處理函式檢查傳送方後,把收到的資料交給 Electron 的剪貼簿 API。通道名和兩個解析後的包裝函式,把暴露給頁面的方法連線到了這裡。

複製的塊如何保持其結構?

Notion 的網頁編輯器透過 electronApi.clipboard.write 傳送文字和 HTML。它在本地保留內容塊資料,再在複製的 HTML 中放入一個 ID,將二者關聯起來。

儲存的塊資料的HTML連結

傳送到系統剪貼簿
text: "Project notes"
html: "<p>Project notes</p>"
    + "<!-- notionvc: <copy-id> -->"
Notion本地儲存
id: <copy-id>
mimeTypes:
  text/_notion-blocks-v3-production
    → structured block data
  1. 讀取HTML ID
  2. 查詢相同的本地ID
  3. 恢復Notion塊資料
文字和 ID 為示意值。已記錄的模組測試顯示:ID 匹配時可以取回內容塊資料;條目失效時只返回文字和 HTML。

在貼上時,Notion查詢HTML ID並根據儲存的複製進行檢查。 匹配的Id恢復所述塊資料。

從2026年7月13日快取的web資產中記錄的剪貼簿格式。

更多示例和來源

另一個例子:Markdown 表格

Notion將Markdown解析為塊資料。 此示例顯示錶如何成為標題和資料行。

透過Notion的Markdown解析器的表格

Markdown 輸入
| A | B |
| - | - |
| 1 | 2 |
結構化解析器輸出 · 摘錄
table · headerRow: true
  table_row
    cells: ["A"], ["B"]
  table_row
    cells: ["1"], ["2"]
從提取的Markdown解析器記錄輸出。 表變為行和單元格。
另一條路徑:離線SQLite連線

Notion 的渲染程序透過 IPC 請求 getSqliteMeta。主程序建立訊息通道,把一個埠交給渲染程序,另一個交給 SQLite 工具程序。後續的語句批次和結果直接透過這個埠傳輸。

追蹤這次交接,就能知道資料庫操作在哪個程序執行,也能解釋為什麼只跟蹤最初的 IPC 請求會漏掉後續通訊。

資料來源及如何閱讀摘錄

剪貼簿橋接程式碼來自 Notion Desktop 7.6.1 的 app.asar,包括標籤頁預載入指令碼和 main/index.js。節選中展開了壓縮後的變數名,並省略無關程式碼;傳送方檢查輔助函式展示了底層的 isNotionWebContents 呼叫。

虛擬剪貼簿和 Markdown 示例來自 2026 年 7 月 13 日快取的網頁資源,包括 747001、58169、736810 和 422457 模組。這些資源有獨立版本。儲存的測試執行提取出的模組和小型測試程式;輸出反映的是這些測試,沒有記錄桌面 UI 操作。

這裡的 REA 結果是對儲存的標籤頁預載入指令碼進行的專項分析,使用已釋出的 REA 4.1.0 記錄。它定位了 API 和呼叫操作。讀取包中的包裝函式實現後,就能把剪貼簿成員與匹配的接收方連線起來。

Markdown 示例使用解析器模組 422457。儲存的模組測試顯示,頁面和聊天過濾器都會保留這張表格。貼上/匯入、結構化標記和 AI/聊天解析器各自有不同入口與中間格式。

頂部