From a001efea5fe6be3d5a56f66b89698f2388c2f63d Mon Sep 17 00:00:00 2001 From: RhenCloud Date: Mon, 3 Aug 2026 08:16:34 +0800 Subject: [PATCH] feat(admin): add route templates to editor --- admin/assets/css/main.css | 33 +++++++++++++ admin/components/RouteEditor.vue | 33 ++++++++++++- admin/composables/useI18n.ts | 30 ++++++++++++ admin/types.ts | 83 ++++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+), 1 deletion(-) diff --git a/admin/assets/css/main.css b/admin/assets/css/main.css index 091b355..7d9adaf 100644 --- a/admin/assets/css/main.css +++ b/admin/assets/css/main.css @@ -619,6 +619,39 @@ main { color: var(--faint); } +.templates { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.template-chip { + padding: 5px 12px; + background: var(--surface-2); + border: 1px solid var(--border-strong); + border-radius: 999px; + color: var(--muted); + font-family: var(--ui); + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: + border-color 0.15s, + color 0.15s, + background 0.15s; +} + +.template-chip:hover { + border-color: var(--accent); + color: var(--accent); +} + +.template-chip.active { + background: var(--accent-dim); + border-color: var(--accent); + color: var(--accent); +} + .field input[type="text"] { width: 100%; padding: 10px 12px; diff --git a/admin/components/RouteEditor.vue b/admin/components/RouteEditor.vue index c277377..b60dbcb 100644 --- a/admin/components/RouteEditor.vue +++ b/admin/components/RouteEditor.vue @@ -10,6 +10,24 @@
+
+ +
+ +
+
import { reactive, watch } from "vue"; import type { Filter, Route, RouteTarget } from "~/types"; -import { FILTER_TYPES, fmtMatch } from "~/types"; +import { FILTER_TYPES, ROUTE_TEMPLATES, fmtMatch } from "~/types"; interface FilterForm extends Filter { matchText: string; @@ -199,6 +217,19 @@ function addFilter(): void { filterError.value = ""; } +function applyTemplate(tmpl: (typeof ROUTE_TEMPLATES)[number]): void { + form.id = tmpl.id; + form.name = t(tmpl.nameKey); + form.filters = tmpl.filters.map((f) => ({ + ...f, + matchText: fmtMatch(f.match), + })) as FilterForm[]; + form.targets = [blankTarget()]; + filterError.value = ""; + targetError.value = ""; + formError.value = ""; +} + function addTarget(): void { form.targets.push(blankTarget()); targetError.value = ""; diff --git a/admin/composables/useI18n.ts b/admin/composables/useI18n.ts index f296c3c..17ea390 100644 --- a/admin/composables/useI18n.ts +++ b/admin/composables/useI18n.ts @@ -46,6 +46,21 @@ const en: Dict = { "routeEditor.editTitle": "Edit route", "routeEditor.newTitle": "New route", "routeEditor.close": "Close", + "routeEditor.templates": "Templates", + "routeEditor.templatesNote": "(click to prefill filters)", + "templates.push": "Push", + "templates.pullRequest": "Pull Request", + "templates.issues": "Issues", + "templates.release": "Release", + "templates.workflow": "Workflow", + "templates.checks": "Check Suite", + "templates.deployment": "Deployment", + "templates.comments": "Comments", + "templates.reviews": "Review", + "templates.starFork": "Star / Fork", + "templates.createDelete": "Create / Delete", + "templates.member": "Member", + "templates.commitComment": "Commit Comment", "routeEditor.name": "Name", "routeEditor.namePlaceholder": "My Route", "routeEditor.id": "ID", @@ -186,6 +201,21 @@ const zh: Dict = { "routeEditor.editTitle": "编辑路由", "routeEditor.newTitle": "新建路由", "routeEditor.close": "关闭", + "routeEditor.templates": "模板", + "routeEditor.templatesNote": "(点击可预填过滤器)", + "templates.push": "推送", + "templates.pullRequest": "拉取请求", + "templates.issues": "议题", + "templates.release": "发布", + "templates.workflow": "工作流", + "templates.checks": "检查套件", + "templates.deployment": "部署", + "templates.comments": "评论", + "templates.reviews": "评审", + "templates.starFork": "星标 / 复刻", + "templates.createDelete": "创建 / 删除", + "templates.member": "协作者", + "templates.commitComment": "提交评论", "routeEditor.name": "名称", "routeEditor.namePlaceholder": "我的路由", "routeEditor.id": "ID", diff --git a/admin/types.ts b/admin/types.ts index 5fe8d2b..3622aa2 100644 --- a/admin/types.ts +++ b/admin/types.ts @@ -38,6 +38,89 @@ export interface Me { groups: Group[]; } +export interface RouteTemplate { + id: string; + nameKey: string; + filters: Filter[]; +} + +export const ROUTE_TEMPLATES: RouteTemplate[] = [ + { + id: "push-events", + nameKey: "templates.push", + filters: [{ type: "event", match: "push" }], + }, + { + id: "pull-requests", + nameKey: "templates.pullRequest", + filters: [ + { type: "event", match: "pull_request" }, + { type: "action", match: ["opened", "synchronize", "reopened", "closed"] }, + ], + }, + { + id: "issues", + nameKey: "templates.issues", + filters: [ + { type: "event", match: "issues" }, + { type: "action", match: ["opened", "edited", "closed", "reopened"] }, + ], + }, + { + id: "releases", + nameKey: "templates.release", + filters: [ + { type: "event", match: "release" }, + { type: "action", match: ["published"] }, + ], + }, + { + id: "workflows", + nameKey: "templates.workflow", + filters: [{ type: "event", match: ["workflow_job", "workflow_run"] }], + }, + { + id: "checks", + nameKey: "templates.checks", + filters: [{ type: "event", match: ["check_suite", "check_run"] }], + }, + { + id: "deployments", + nameKey: "templates.deployment", + filters: [{ type: "event", match: "deployment" }], + }, + { + id: "comments", + nameKey: "templates.comments", + filters: [{ type: "event", match: ["issue_comment", "pull_request_review_comment"] }], + }, + { + id: "reviews", + nameKey: "templates.reviews", + filters: [{ type: "event", match: "pull_request_review" }], + }, + { + id: "star-fork", + nameKey: "templates.starFork", + filters: [{ type: "event", match: ["star", "fork"] }], + }, + { + id: "create-delete", + nameKey: "templates.createDelete", + filters: [{ type: "event", match: ["create", "delete"] }], + }, + { + id: "members", + nameKey: "templates.member", + filters: [{ type: "event", match: "member" }], + }, + { + id: "commit-comments", + nameKey: "templates.commitComment", + filters: [{ type: "event", match: "commit_comment" }], + }, +]; + export const FILTER_TYPES = ["event", "repo", "actor", "action", "branch", "keyword"] as const; export const FILTER_LABELS: Record = {