跳到主要内容

选择上下文

当用户在 Zotero 中选中条目后,插件会构建一个结构化的选择上下文(SelectionContext),描述用户选择了什么、选中的条目各属于哪种类型。这个上下文是 buildRequest Hook 的输入基础。

选择类型

根据用户选中的条目类型组合,selectionContext.selectionType 返回以下值之一:

类型说明
"parent"选中的都是父条目(顶层条目)
"child"选中的都是子条目(非顶层条目)
"attachment"选中的都是附件
"note"选中的都是笔记
"mixed"选中的混合了多种类型
"none"没有选中任何条目

上下文结构

selectionContext = {
selectionType: "parent", // 选择类型
items: {
parents: [ /* 父条目列表 */ ],
children: [ /* 子条目列表 */ ],
attachments: [ /* 附件列表 */ ],
notes: [ /* 笔记列表 */ ],
},
summary: {
parentCount: 2,
childCount: 0,
attachmentCount: 0,
noteCount: 0,
},
warnings: [], // 警告信息
sampledAt: "2026-01-15T...", // 上下文创建时间
}

每个类型条目都包含丰富的上下文信息。

父条目(ParentContext)

父条目是 Zotero 库中的顶层条目(如期刊论文、书籍、网页等)。每个父条目上下文包含:

{
item: Zotero.Item, // 条目对象
id: number, // 条目 ID
title: string, // 标题
attachments: [ // 该条目下的子附件
{ type, filePath, mimeType, dateAdded, ... }
],
notes: [ // 该条目下的子笔记
{ id, content, ... }
],
tags: string[], // 标签列表
collections: string[], // 所属合集
children: [ // 其他子条目
{ id, type, ... }
],
}

附件(AttachmentContext)

附件是条目的文件附件(PDF、Markdown 等)。每个附件上下文包含:

{
item: Zotero.Item, // 附件条目对象
id: number, // 条目 ID
filePath: string, // 本地文件路径
fileName: string, // 文件名
mimeType: string, // MIME 类型(如 "application/pdf")
dateAdded: Date, // 添加日期
parentItem: { // 所属父条目
id: number,
key: string,
libraryID: number,
},
tags: string[],
collections: string[],
}

笔记(NoteContext)

{
item: Zotero.Item,
id: number,
content: string, // 笔记内容(HTML)
parentItem: { id, key, libraryID },
tags: string[],
}

在 Hook 中使用选择上下文

获取选中的附件

export function buildRequest({ selectionContext, runtime }) {
const attachments = selectionContext.items.attachments;

return {
kind: "skillrunner.job.v1",
create: { skill_id: "my-skill" },
input: {
files: attachments.map((attachment) => ({
path: runtime.helpers.getAttachmentFilePath(attachment),
name: runtime.helpers.getAttachmentFileName(attachment),
})),
},
};
}

获取选中的父条目及其子内容

export function buildRequest({ selectionContext, runtime }) {
const parents = selectionContext.items.parents;

for (const parent of parents) {
const title = parent.item.getField("title");
const attachments = parent.attachments; // 该父条目下的附件
const notes = parent.notes; // 该父条目下的笔记
}

// ...
}

检查选择类型决定行为

export function preflight({ selectionContext }) {
const { selectionType } = selectionContext;

if (selectionType === "none") {
// 没有选中任何条目,跳过
return { kind: "skip", reason: "未选中条目" };
}

if (selectionType === "attachment") {
// 用户选中的都是附件,走附件处理逻辑
} else if (selectionType === "parent") {
// 用户选中的都是父条目,展开第一个符合条件的附件
}

return { kind: "continue", context: { selectionType } };
}

过滤附件

使用声明式 validateSelection 处理常见的附件过滤:

{
"validateSelection": {
"select": { "policy": "pdf-attachment" },
"require": {
"counts": { "attachments": 1 },
"allowMixed": false
}
}
}

对于需要依赖已解析单元的运行时决策,使用 preflight 来跳过或继续:

export function preflight({ selectionContext, runtime }) {
const { helpers } = runtime;

const hasPdf = selectionContext.items.attachments.some(
a => helpers.isPdfAttachment(a)
);

if (!hasPdf) {
return { kind: "skip", reason: "没有 PDF 附件" };
}

return { kind: "continue" };
}

选中无条目时的 workflow

inputs.unit: "workflow"trigger.requiresSelection: false 时,workflow 可以在不选中任何条目的情况下触发。此时 selectionContext.selectionType"none"items 中所有数组为空。这种模式适合创建全局操作(如"创建 Topic 综合")。

声明式选择验证

如果你的 workflow 只需要跳过已有结果的条目过滤特定类型的输入,使用声明式 validateSelection 字段,无需编写 JavaScript hook。

{
"validateSelection": {
"select": { "policy": "literature-source" },
"exclude": [
{ "kind": "generated-notes-all", "noteKinds": ["digest"] }
]
}
}

详见清单文件编写中的完整说明。

选择指南: 尽可能使用声明式 validateSelection——零 JS 代码、零维护成本。复杂的筛选逻辑可以在 buildRequest Hook 中实现。

下一步