diff --git a/admin/assets/css/main.css b/admin/assets/css/main.css index 7d9adaf..09bd7ff 100644 --- a/admin/assets/css/main.css +++ b/admin/assets/css/main.css @@ -387,6 +387,17 @@ main { background: var(--accent-dim); } +.icon-btn:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.icon-btn:disabled:hover { + color: var(--muted); + border-color: var(--border); + background: transparent; +} + .icon-btn.danger:hover { color: var(--bad); border-color: var(--bad); diff --git a/admin/components/RouteCard.vue b/admin/components/RouteCard.vue index 8d0eed5..b246606 100644 --- a/admin/components/RouteCard.vue +++ b/admin/components/RouteCard.vue @@ -19,6 +19,22 @@ {{ t("route.fallback") }}
+ + @@ -67,11 +83,12 @@ import { fmtMatch } from "~/types"; const { t } = useI18n(); -const props = defineProps<{ route: Route }>(); +const props = defineProps<{ route: Route; atFirst?: boolean; atLast?: boolean }>(); const emit = defineEmits<{ (e: "toggle", route: Route): void; (e: "edit", route: Route): void; (e: "delete", route: Route): void; + (e: "move", route: Route, dir: -1 | 1): void; }>(); function onToggle(event: Event): void { diff --git a/admin/composables/useI18n.ts b/admin/composables/useI18n.ts index 17ea390..77b6fc3 100644 --- a/admin/composables/useI18n.ts +++ b/admin/composables/useI18n.ts @@ -37,6 +37,8 @@ const en: Dict = { "route.chat": "CHAT", "route.topic": "TOPIC", "route.fallback": "fallback", + "route.moveUp": "Move up", + "route.moveDown": "Move down", "filter.event": "Event", "filter.repo": "Repo", "filter.actor": "Actor", @@ -70,7 +72,7 @@ const en: Dict = { "routeEditor.langHint": "en or zh; custom via KV i18n:", "routeEditor.enabled": "Route enabled", "routeEditor.fallback": "Fallback route", - "routeEditor.fallbackHint": "Only fires when no other route matched; its filters are ignored", + "routeEditor.fallbackHint": "Only fires when no other route matched; filters still apply", "routeEditor.filters": "Filters", "routeEditor.filtersNote": "(all must match · AND)", "routeEditor.matchPlaceholder": "match value", @@ -192,6 +194,8 @@ const zh: Dict = { "route.chat": "群组", "route.topic": "话题", "route.fallback": "兜底", + "route.moveUp": "上移", + "route.moveDown": "下移", "filter.event": "事件", "filter.repo": "仓库", "filter.actor": "操作者", @@ -225,7 +229,7 @@ const zh: Dict = { "routeEditor.langHint": "en 或 zh;可通过 KV i18n: 自定义", "routeEditor.enabled": "启用路由", "routeEditor.fallback": "兜底路由", - "routeEditor.fallbackHint": "仅当没有其它路由匹配该事件时才发送,其过滤器会被忽略", + "routeEditor.fallbackHint": "仅当没有其它路由匹配该事件时才发送,其过滤器仍会生效", "routeEditor.filters": "过滤器", "routeEditor.filtersNote": "(全部匹配 · AND)", "routeEditor.matchPlaceholder": "匹配值", diff --git a/admin/pages/admin.vue b/admin/pages/admin.vue index f8f16f5..3712ac8 100644 --- a/admin/pages/admin.vue +++ b/admin/pages/admin.vue @@ -56,10 +56,13 @@ v-for="(r, i) in groupRoutes" :key="r.id" :route="r" + :at-first="i === 0" + :at-last="i === groupRoutes.length - 1" :style="{ animationDelay: i * 45 + 'ms' }" @toggle="onToggle" @edit="openEdit" @delete="onDelete" + @move="onMove" /> @@ -271,6 +274,22 @@ async function onSave(route: Route): Promise { } } +async function onMove(route: Route, dir: -1 | 1): Promise { + const group = selectedGroup.value; + if (!group) return; + const i = groupRoutes.value.findIndex((r) => r.id === route.id); + const j = i + dir; + if (i < 0 || j < 0 || j >= groupRoutes.value.length) return; + const next = [...groupRoutes.value]; + [next[i], next[j]] = [next[j]!, next[i]!]; + try { + await saveGroupRoutes(group.id, next); + } catch (err) { + push(t("toast.saveFailed", { msg: err instanceof Error ? err.message : String(err) }), "bad"); + loadGroupRoutes(group.id); + } +} + async function onToggle(route: Route): Promise { const group = selectedGroup.value; if (!group) return;