✨ 新建脚本模板可自定义 - #1731
Conversation
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。
|
注意两点
再具体一点,我这里主要担心两个行为:
这里不是说有内容就能存,而是至少要保证它还是一个完整的 userscript 模板。 即使用户把里面的 metadata 都删掉,最少也应该保留完整的 UserScript block。空白内容、只有一半 block、或者已经不能作为 userscript 模板使用的内容,都不应该覆盖当前有效配置。 [optional] 验证失败时可以保留用户正在编辑的草稿,但实际新建脚本应该继续使用上一次有效模板。
这个和一些 JSON setting 的情况类似。 如果直接把“完整模板内容”保存成用户配置,那么用户只要修改过一次,以后 ScriptCat 默认模板新增字段、修改推荐写法、修正默认值,这个用户就永远收不到了,除非主动恢复默认。 这里最好区分:
不建议升级时直接覆盖用户内容。 可以提示用户默认模板有新版,让用户自己选择继续保留当前模板,或者恢复到新的默认模板。 “恢复默认”也应该表示重新回到“跟随 ScriptCat 默认模板”的状态,而不是把当前版本默认模板复制一份保存成用户自定义。 这样以后默认模板继续更新时,没有自定义的用户可以自动获得更新;有自定义的用户也不会被强制覆盖,同时不会永久和默认模板的发展脱节。 |
|
既有 vitest timeout 问题见 #1734 |
处理了的
当前没做处理,感觉未来很长一段时间内应该都不会动这个模板 非绝对的必要,不应该动用户调整后的配置,对 json setting 那边,我的看法也是这样的,应该只做新增项目的添加 比如:未来新增了一个必要的属性,可以检测当前的模板中有没有这个,没有就往模板中添加上这个新增的属性 其它情况:当前的配置已经有属性了,然后默认模板修改为了另外的值,哪怕用户没有动过也不应该去调整,你不知道用户是不是因为认可原来的值而没有变动,除非非常的必要,否则不应该去动原有属性 |
Checklist / 检查清单
背景
普通 / 后台 / 定时三种新建脚本模板写死在
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}}。activeTabUrl由{ url }扩为{ url, title },为{{title}}提供数据源。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」的旧兼容路径,避免两条路径各自累加编号。旧写法保留,老用户把模板抄成字面量时仍会自动编号。已知限制
@grant、@match等字段是否合理。建议审查重点
src/pkg/utils/script_template.ts的空值删行规则:{{icon}}原本就是「取不到就删整行」,本 PR 把它推广到所有变量。若某个变量为空时更希望保留空指令行,这里是要改的地方。src/pkg/config/config.ts中lazyScriptName的签名变更(新增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.ts补handleCreateScript写入{ url, title }(删掉title后该用例转红,确认其真的守着这一行)。node e2e/session.mjs,构建后加载dist/ext)确认:真实 Monaco 编辑后落盘、新建脚本套用自定义模板、?target=initial时按活动标签页替换{{match}}/{{domain}}、普通模板含@crontab被拦下且旧值未被覆盖、恢复默认、明暗双主题。popup 点击「新建脚本」那一步在无头会话中没有真实网页标签可用,改由上述单元测试覆盖。Screenshots / 截图