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

cyfung1031 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

注意两点

  1. 不能让空的userscript metablock储存。至少要有 userscript开和合 (==UserScript== ==/UserScript== )
  2. 注意会不会用户储存后,就永远不会有新更新生效 (类似json setting情况)

再具体一点,我这里主要担心两个行为:

  1. 模板不能接受“空配置”

这里不是说有内容就能存,而是至少要保证它还是一个完整的 userscript 模板。

即使用户把里面的 metadata 都删掉,最少也应该保留完整的 UserScript block。空白内容、只有一半 block、或者已经不能作为 userscript 模板使用的内容,都不应该覆盖当前有效配置。

[optional] 验证失败时可以保留用户正在编辑的草稿,但实际新建脚本应该继续使用上一次有效模板。

  1. 用户自定义后,不要永远失去默认模板的后续更新

这个和一些 JSON setting 的情况类似。

如果直接把“完整模板内容”保存成用户配置,那么用户只要修改过一次,以后 ScriptCat 默认模板新增字段、修改推荐写法、修正默认值,这个用户就永远收不到了,除非主动恢复默认。

这里最好区分:

  • 没有自定义:始终跟随当前版本的默认模板
  • 用户有自定义:继续使用用户自己的模板
  • 用户自定义以后,默认模板又更新过:至少让用户知道默认模板已有更新

不建议升级时直接覆盖用户内容。

可以提示用户默认模板有新版,让用户自己选择继续保留当前模板,或者恢复到新的默认模板。

“恢复默认”也应该表示重新回到“跟随 ScriptCat 默认模板”的状态,而不是把当前版本默认模板复制一份保存成用户自定义。

这样以后默认模板继续更新时,没有自定义的用户可以自动获得更新;有自定义的用户也不会被强制覆盖,同时不会永久和默认模板的发展脱节。

@cyfung1031

Copy link
Copy Markdown
Collaborator

既有 vitest timeout 问题见 #1734

@CodFrm

CodFrm commented Sep 9, 2026

Copy link
Copy Markdown
Member Author
  1. 不能让空的userscript metablock储存。至少要有 userscript开和合 (==UserScript== ==/UserScript== )

处理了的

  1. 注意会不会用户储存后,就永远不会有新更新生效 (类似json setting情况)

当前没做处理,感觉未来很长一段时间内应该都不会动这个模板

非绝对的必要,不应该动用户调整后的配置,对 json setting 那边,我的看法也是这样的,应该只做新增项目的添加

比如:未来新增了一个必要的属性,可以检测当前的模板中有没有这个,没有就往模板中添加上这个新增的属性

其它情况:当前的配置已经有属性了,然后默认模板修改为了另外的值,哪怕用户没有动过也不应该去调整,你不知道用户是不是因为认可原来的值而没有变动,除非非常的必要,否则不应该去动原有属性

#1538 (comment)

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