Skip to content

✨ 新建脚本模板可自定义 - #1731

Open
CodFrm wants to merge 1 commit into
mainfrom
feat/custom-script-template
Open

✨ 新建脚本模板可自定义#1731
CodFrm wants to merge 1 commit into
mainfrom
feat/custom-script-template

Conversation

@CodFrm

@CodFrm CodFrm commented Sep 8, 2026

Copy link
Copy Markdown
Member

Checklist / 检查清单

  • Fixes mentioned issues / 修复已提及的问题
  • Code reviewed by human / 代码通过人工检查
  • Changes tested / 已完成测试

背景

普通 / 后台 / 定时三种新建脚本模板写死在 src/template/*.tpl,用户每次新建脚本都要手改 @author@license 等固定字段(#1722)。Violentmonkey 有可配置的新建模板,脚本猫没有。

维护者在 issue 里定了口径:仍然只保留三个模板,把内置模板当默认值、允许用户设置,并在保存前校验模板类型是否自洽。本 PR 按此实现。

本次改动

用户可见行为:设置 → 开发工具 → 脚本模板 新增一页,按脚本类型分别编辑模板;失焦即保存,与同页 ESLint 规则 / 编辑器配置一致;「恢复默认值」按类型清除覆写;已覆写的类型在切换条上标「已修改」。未覆写的类型仍使用内置模板,改动只影响之后新建的脚本。

实现:

  • 新增 src/pkg/utils/script_template.ts,承担模板解析的三件事:默认模板表、变量渲染、类型校验。
  • 变量:{{name}}(自动编号脚本名)、{{match}}{{icon}}{{domain}}{{title}}{{date}}(可写 {{date:YYYY-MM-DD}},格式串复用既有的 dayFormat)。取值为空的变量连同所在行删除,未识别的占位符原样保留。
  • emptyScript() 改为读配置里的模板并渲染;三个 .tpl@name 由字面量 New Userscript 改为 {{name}}
  • popup 写入的 activeTabUrl{ url } 扩为 { url, title },为 {{title}} 提供数据源。
  • 新增 i18n key(10 个 locale):模板页文案、变量说明、两条校验错误;脚本类型标签复用 script:background_script / script:scheduled_script,并补一个顶层 script:normal_script

实现考虑

  • 校验复用既有解析器:模板按「没有活动标签页」(即从设置页新建的上下文,也是页面变量全为空的最差情形)渲染后交给 parseScriptFromCode,再比对得到的脚本类型。这样 UserScript 头是否可解析、@name 是否为空、cron 表达式是否合法都由既有实现判定,不另写一套元数据校验;模板层只负责「类型对不对」这一条判断。校验不通过则不写入配置,保留草稿并在编辑器上方标红说明原因。
  • chrome.storage.local 而非 sync:模板是用户手写的代码,三份合计不难超过 chrome.storage.sync 单项 8KB 配额,而 ChromeStorage.set 不检查 chrome.runtime.lastError,超额会静默丢失——保存成功的 toast 会骗人。代价是模板不跨设备同步,这是本 PR 有意接受的取舍(src/pkg/config/consts.ts 里已注明理由)。若要改成跨设备同步,需要先给保存路径加体积校验或让 ChromeStorage 上报写入失败。
  • 变量语法沿用 {{}}:仓库原有的 {{match}} / {{icon}} 就是这个写法,Violentmonkey 也是(其 {{name}} {{url}} {{icon}} {{date}})。issue 原文提的 %name% 不采用。未识别占位符原样保留同样与 VM 一致,避免吃掉脚本正文里本来就有的 {{...}}
  • 脚本名只生成一次lazyScriptName 拆出 nextScriptName(),同一个名字同时喂给 {{name}} 和「模板里写死 New Userscript」的旧兼容路径,避免两条路径各自累加编号。旧写法保留,老用户把模板抄成字面量时仍会自动编号。

已知限制

  • 模板不跨设备同步(理由见上)。
  • 只能编辑既有的三种模板,不支持新增/命名自定义模板 —— 按 issue 中维护者的口径刻意不做。
  • 校验只保证模板能生成对应类型的脚本,不检查 @grant@match 等字段是否合理。

建议审查重点

  • src/pkg/utils/script_template.ts 的空值删行规则:{{icon}} 原本就是「取不到就删整行」,本 PR 把它推广到所有变量。若某个变量为空时更希望保留空指令行,这里是要改的地方。
  • 存储位置的取舍(local vs sync)。
  • src/pkg/config/config.tslazyScriptName 的签名变更(新增 name 参数),调用方只有 emptyScript

关联

close #1722

验证

  • pnpm lint:prettier / tsc / check:i18n / check:issue-templates / eslint 全通过。
  • pnpm test:ci:4613 passed。另有 1 例 scripts/git-staged-snapshot.test.mjs 在满载下超出 340ms 预算而失败,单独运行 306ms 通过,与本次改动无关(该用例 spawn git,不涉及本 PR 触及的文件)。
  • 新增用例:src/pkg/utils/script_template.test.ts(渲染 / 回落 / 校验)、src/pages/options/routes/ScriptEditor/editorScriptLoaders.test.ts(新建脚本装配、活动标签页变量、消费 activeTabUrl)、src/pages/options/routes/Setting/sections/ScriptTemplateSettings.test.tsx(保存 / 拦截 / 恢复默认 / 切换类型)、src/pages/popup/usePopupData.test.tshandleCreateScript 写入 { url, title }(删掉 title 后该用例转红,确认其真的守着这一行)。
  • 真实扩展会话(node e2e/session.mjs,构建后加载 dist/ext)确认:真实 Monaco 编辑后落盘、新建脚本套用自定义模板、?target=initial 时按活动标签页替换 {{match}}/{{domain}}、普通模板含 @crontab 被拦下且旧值未被覆盖、恢复默认、明暗双主题。popup 点击「新建脚本」那一步在无头会话中没有真实网页标签可用,改由上述单元测试覆盖。

Screenshots / 截图

image

close #1722

普通/后台/定时三种新建脚本模板原本写死在 src/template/*.tpl,用户每次新建都要手改
@author / @license。现在把内置模板降级为默认值,用户可在「设置 → 开发工具 → 脚本模板」
里按类型覆写,未覆写的类型仍回落到内置模板。

- 渲染与校验内核 src/pkg/utils/script_template.ts:支持 {{name}} {{match}} {{icon}}
  {{domain}} {{title}} {{date}}(可写 {{date:YYYY-MM-DD}} 指定格式);取值为空的变量连同
  所在行删除(沿用原 @ICON 行的处理),未识别的占位符原样保留(与 Violentmonkey 一致)。
- 保存前按类型校验:渲染后交给既有 parseScriptFromCode,普通脚本模板不得含
  @background/@crontab,后台/定时模板必须含对应指令且 cron 表达式合法;不通过则不落库,
  编辑器标红并给出原因。
- 模板落 chrome.storage.local 而非 sync:用户手写代码三份合计容易超过 sync 单项 8KB 配额,
  而 ChromeStorage.set 不检查 lastError,超额会静默丢失。
- {{title}} 需要标签页标题,popup 的 activeTabUrl 由 { url } 扩为 { url, title }。
- lazyScriptName 拆出 nextScriptName:脚本名只生成一次,同时喂给 {{name}} 与旧的
  「模板里写死 New Userscript」兼容路径。

本地验证(真实 Monaco + chrome.storage + 新建脚本流程)见 e2e/scratch/tpl-1722/report.md。
@cyfung1031

Copy link
Copy Markdown
Collaborator

注意兩點

  1. 不能讓空的userscript metablock儲存。至少要有 userscript開和合
  2. 注意會不會用戶儲存後,就永遠不會有新更新生效 (類似json setting情況)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Custom userscript header template

2 participants