> ## Documentation Index
> Fetch the complete documentation index at: https://dev.marginnote.com.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 数据交互

<Warning>
  本文由 AI 撰写，暂未经过人工校对，请谨慎参考。
</Warning>

面板是一个网页。页面里向 `/bridge/v1` 发请求，就能读写 MarginNote 里的数据。这一页说明请求的写法、可以调用的接口范围、写操作里不必填写的参数，以及判断失败响应的方法。

# 请求的写法

`mn.api(path, opts)` 的实现只有一行：

```javascript theme={null}
fetch('/bridge/v1' + path, opts).then(r => r.json())
```

`path` 从接口路径写起，前面不必重复 `/bridge/v1`。读取数据与写入数据用的是同一个方法：

<CodeGroup>
  ```javascript 读取 theme={null}
  const state = await mn.api('/ui-state');
  const topicid = state.state.topicid;
  ```

  ```javascript 写入 theme={null}
  await mn.api('/notes/addon-values', {
    method: 'POST',
    body: { noteIds: [noteid], values: { status: 'done' } }
  });
  ```
</CodeGroup>

`mn.api` 把响应体一律按 JSON 解析。响应的内容不是 JSON 的接口，用 `mn.api` 会抛出 `SyntaxError`，这类接口改用 `fetch`：

* `GET /guide` 返回 Markdown 正文。
* `GET /notes/{noteid}/render` 返回卡片长图或 PDF。
* `GET /documents/{bookmd5}/pages/{index}/content/{contentId}` 返回图片。
* `GET /bundles/{bundleId}/files` 带 `path` 时返回该文件的原始字节。

```javascript theme={null}
// 读取运行时规程，响应的内容是 Markdown
const text = await (await fetch('/bridge/v1/guide')).text();

// 获取一张卡的长图
const shot = await fetch('/bridge/v1/notes/' + noteid + '/render?format=png&width=880');
const blob = await shot.blob();
```

# 可以调用的接口范围

页面能调用的接口范围由模板与 MarginNote 共同决定。模板里的 `plugins` 名单连同当前是否处于沉浸态，决定面板能否建立；网页面板白名单决定页面能调用的接口。请求落在白名单之外时返回 `403`，错误码为 `IN_PROCESS_NOT_ALLOWED`。

`GET /capabilities` 里的 `requiresMax` 只约束外部 Agent。面板的请求在 MarginNote 进程内处理，不受令牌、订阅状态与跨设备批准的限制，接口能否调用只看该接口是否在白名单里。

可以调用的接口、各接口的请求体与响应字段、各接口的调用示例，见文档站的 `API 参考` 分组。

# 写操作里不必填写的参数

面板写数据时，`addon` 与 `confirm` 由 MarginNote 代办。`POST /notes/addon-values` 与 `POST /notes/{noteid}/edits` 的 `addon` 会被改成该面板所属插件的 id，页面填写的其他值不被采纳，因此写不进其他插件的命名空间。同一条路径上的 `confirm` 由 MarginNote 补上，页面填写的值不被采纳，也不必填写。其余写接口是否需要 `confirm`，见各接口自己的请求体说明。

写入本插件的数据时，请求体里不必出现 `addon` 与 `confirm`：

```javascript theme={null}
// 给若干张卡写入本插件的数据
await mn.api('/notes/addon-values', {
  method: 'POST',
  body: {
    noteIds: ['5880F73A-3A6D-4464-9D6F-88F60285401E'],
    values: { status: 'done', pick: 2 }
  }
});
```

响应里的 `persisted` 表示 MarginNote 已重新读取并核对一致。写入是否完成，依据该字段判断。

# 失败响应的判断

MarginNote 把错误对象当作正常的响应体返回，因此结果落在 resolve 里，Promise 不会进入 reject：

```json theme={null}
{
  "error": {
    "code": "IN_PROCESS_NOT_ALLOWED",
    "message": "该接口不在网页面板的白名单上。",
    "retryable": false
  }
}
```

判断一次调用的结果，看响应里是否带 `error`：

```javascript theme={null}
const r = await mn.api('/notes/' + noteid + '/edits', {
  method: 'POST',
  body: { ops: [{ op: 'setTitle', text: '新的标题' }] }
});
if (r.error) {
  mn.log(r.error.code, r.error.message);
} else if (r.persisted) {
  mn.log('已写入并核对通过');
}
```

`retryable` 只有真与假，表达不了结果未知。写入操作的失败分为明确拒绝、结果未知与可以重试。落在结果未知一类时不要自动重发，先读取当前数据再决定。

# 完整示例

用户在文档上划线时，插件要把摘录内容写进自己的数据。整个过程依次是获取当前上下文、获取卡片数据、写入插件字段。

```javascript theme={null}
mn.on('excerpt', async d => {
  // 一、获取当前界面状态，得到学习集与聚焦的卡
  const ui = await mn.api('/ui-state');
  const topicid = ui.state.topicid;

  // 二、获取该卡的标题、正文、标签与插件字段
  const got = await mn.api('/notes/batch-get', {
    method: 'POST',
    body: { noteIds: [d.noteid], addons: ['mn.hello'] }
  });
  const card = got.items[0];
  if (!card.found) return;

  // 三、把结果写回本插件自己的字段
  await mn.api('/notes/addon-values', {
    method: 'POST',
    body: { noteIds: [d.noteid], values: { status: 'done', topicid } }
  });

  mn.fit();
});
```

卡片命令的回调写法、面板模式的切换与事件声明，见[事件系统](/zh/events)与[使用 window.mn 与 MN 进行交互](/zh/sdk)。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.