Files
apes-sidebar/docs/plans/2026-08-15-url-target-browser-settings-design.md
T
Menghuan1918 29f162dac0 feat: 服务化 API 新增 urlTarget 外链认领 + 浏览器 HTTP/HTTPS 二级设置开关
基于 PR #74 的颗粒度讨论向前兼容拓展服务化 API:
- TabDescriptor 新增可选 urlTarget(url) 谓词:聊天/界面外链被拦截时按注册
  顺序路由到第一个命中的插件 tab 类型(openTab({ type, url, title })),
  内置 browser 退居隐式兜底;SIDEBAR_FEATURES 追加 'urlTarget'
- 浏览器链接设置改为二级菜单(齿轮弹窗):新增 browserInterceptHttp
  (默认开)+ browserInterceptHttps(默认关);browserInterceptLinks
  保留为总闸(默认开,旧文档零迁移)
- 拦截管线:registerLinkInterception 的 takeoverEnabled 改收解析后的
  URL,协议/同源纯判定(shouldInterceptLink)签名不变
- 测试:matchUrlTarget(注册顺序/抛错吞掉/browser 永不命中)、jsdom
  点击接管门控、parsePrefs 新字段默认值;全默认字面量同步
- 文档:设计文档 + AGENTS.md(字段/示例/设置键)+ README(外链分流)
2026-08-15 15:05:06 +08:00

5.8 KiB
Raw Blame History

外链点击路由到插件 tab(urlTarget)+ 浏览器 HTTP/HTTPS 二级设置

日期:2026-08-15 状态:已实施(本文档含实施偏差记录) 目标版本:v0.13.0(API 兼容层,版本号未随本变更 bump,见 §7)

1. 目标

基于 PR #74(聊天区外链开关颗粒度讨论)向前兼容地拓展服务化 API:

  1. 插件声明「点击打开的类型」:TabDescriptor 新增可选 urlTarget?: (url: URL) => boolean——聊天区外链被拦截时,路由到第一个声明的、命中的、未被设置禁用的插件 tab 类型(openTab({ type, url, title: hostname }),URL 即全部载荷),内置浏览器 tab 退居隐式兜底。
  2. 浏览器链接设置改为二级菜单(浏览器卡片齿轮弹窗内):
    • 侧边打开 HTTP 网页(默认开)
    • 侧边打开 HTTPS 网页(默认关)
    • browserInterceptLinks 保留为总闸(默认开,零迁移:旧文档显式 false 语义完整保留)。

2. 非目标

  • 不实施 PR #74 的「外链开关提升到常规区」(用户确认:二级设置留在浏览器卡片齿轮弹窗,常规区不加行)。
  • 不做 per-host / per-domain 路由规则(只有 per-protocol 开关 + 插件谓词)。
  • 不改 + 菜单、不改手动打开的浏览器 tab、不改同源/修饰键点击放行规则。
  • 不改 host 半逻辑(仅 PrefsSchema 加字段);不修改 DSH 源码。
  • 不做版本号 bump(发布时决定;向前兼容机制 = SIDEBAR_FEATURES 追加 'urlTarget')。

3. 现状回顾

  • link-intercept.ts 的 registerLinkInterception 无参 takeoverEnabled() 只做布尔门;index.tsx 硬编码 openTab({ type: 'browser', url, title: hostname })。
  • browserInterceptLinks(默认 true)与 browserNoSandbox 并列于浏览器卡片的 settings.toggles;拦截 gating = 该 pref && tabsEnabled['browser']。
  • openTab seed 已有 url 字段(新建 tab 的 path 预填),插件 tab 无需新渲染管道。

4. 设计

4.1 Prefs(prefs-shared.ts / config.ts / client/prefs.ts)

interface SidebarPrefs {
  // …
  browserInterceptLinks: boolean   // 总闸,默认 true(保留,零迁移)
  browserInterceptHttp: boolean    // HTTP 外链侧边栏接管,默认 true
  browserInterceptHttps: boolean   // HTTPS 外链侧边栏接管,默认 false
}

三处同步维护(类型 + schemastery 默认 + parsePrefs 校验),旧文档无需迁移:总闸保留使显式关闭过的用户升级后仍不拦截。

4.2 服务 API(service.ts)

interface TabDescriptor {
  // …
  urlTarget?: (url: URL) => boolean
}

/** 注册顺序先到先得;谓词抛错吞掉(console.error)继续;返回 undefined 由调用方回退 browser。 */
export function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDescriptor | undefined
  • 匹配函数为模块级纯函数(不进 BetterSidebarService 接口,KISS:不扩公开面);启用态过滤由调用方(index.tsx)按 tabsEnabled 过滤后传入。
  • 内置 browser 不声明 urlTarget,永远不可能遮蔽插件声明;插件多 URL 并存需用 createTab 铸造 per-URL id(browser 同款模式),否则 id 安全网聚焦既有 tab 不覆写 path。
  • SIDEBAR_FEATURES 追加 'urlTarget'(消费者按能力 gate)。
  • shouldInterceptLink 签名不变(协议/同源纯判定);takeoverEnabled 改为 (url: URL) => boolean,onClick 顺序:纯判定 → gate(持 URL)→ preventDefault → open。
  • index.tsx gate:总闸 browserInterceptLinks → 协议开关(https 读 browserInterceptHttps,否则读 browserInterceptHttp)→ 目标可开(插件命中即目标,否则 browser 的 tabsEnabled)。open 时再次解析目标并 openTab({ type, url, title: hostname })。

4.4 设置 UI(builtins/tabs.tsx / locales.ts)

浏览器卡片 settings.toggles 收敛为四行:browserNoSandbox / browserInterceptLinks(总闸)/ browserInterceptHttp / browserInterceptHttps;新增 zh/en 文案(HTTP 默认开、HTTPS 默认关及理由)。

5. 行为变化

  • 默认设置下:HTTP 外链 → 侧边栏(不变);HTTPS 外链 → 系统浏览器新窗口(变化,符合「默认关闭」)。
  • 声明 urlTarget 的插件 tab 认领被拦截的链接(优先于浏览器兜底);插件被禁用/谓词抛错时跳过。
  • 总闸关闭 → 一律不拦截(旧文档语义保留)。

6. 测试

  • tests/builtins.spec.ts:浏览器 toggles 断言收敛为四行 + 各行 title/desc 存在。
  • tests/service.spec.ts:matchUrlTarget(无声明 → undefined / 注册顺序 / 抛错吞掉 / browser 永不命中)。
  • tests/link-intercept.spec.ts(新增 jsdom):takeoverEnabled(url) 收到解析后的 URL、gate 决定是否接管、同源/非 http(s) 不 consult gate、修饰键绕过。
  • tests/unit.spec.ts:parsePrefs 新字段默认值(http true / https false / 非法回退);总闸与协议开关相互独立。
  • tests/plugin-shape.spec.ts / tests/smoke.spec.ts:全默认字面量补两个字段。

7. 实施偏差记录

  • 计划曾考虑把 matchUrlTarget 作为 BetterSidebarService 接口方法(镜像 matchFileViewer);按 KISS 收敛为模块级导出纯函数,启用态过滤由调用方完成(匹配函数不感知 prefs),公开 API 面只剩 urlTarget 字段 + 'urlTarget' feature 字符串。
  • 版本号未 bump(保持 0.12.1):SIDEBAR_SERVICE_VERSION 与 package.json 的配对断言继续成立;'urlTarget' feature 已入 SIDEBAR_FEATURES,发布时再决定版本。
  • PR #74 的 diff(常规区移动 + 其测试改动)被本方案取代,未合入。