feat: 提交详情能看 diff,也能丢给 AI 审这一次改动。文件检查可单独排除路径,TODO 只认大写,避免把 todo 表和模块当成待办标记。

热力图按容器宽度铺满一年,不再横向滚动。托盘右键换成可换皮肤的弹层;更新下载显示进度,退出后再由脚本拉起安装器,避免还占着 exe。
This commit is contained in:
李琦
2026-08-19 15:46:03 +08:00
parent 1694397245
commit 7c4109f687
38 changed files with 2618 additions and 129 deletions

View File

@@ -620,9 +620,28 @@ func (s *Store) ProjectRules(projectID int64) ([]ExclusionRule, error) {
return s.queryRules(`SELECT id,pattern,category,builtin,project_id FROM exclusion_rules WHERE project_id=? ORDER BY category,pattern`, projectID)
}
// RulesForProject 扫描用:全局规则 + 项目专属规则叠加。
// RulesForProject 扫描用:全局规则 + 项目专属规则叠加(不含检查专用排除)
func (s *Store) RulesForProject(projectID int64) ([]ExclusionRule, error) {
return s.queryRules(`SELECT id,pattern,category,builtin,project_id FROM exclusion_rules WHERE project_id IN (0,?) ORDER BY project_id,category,builtin DESC,pattern`, projectID)
all, e := s.queryRules(`SELECT id,pattern,category,builtin,project_id FROM exclusion_rules WHERE project_id IN (0,?) ORDER BY project_id,category,builtin DESC,pattern`, projectID)
if e != nil {
return nil, e
}
return withoutRuleCategory(all, "insight"), nil
}
// InsightExcludes 仅用于文件检查的项目排除规则,不影响行数统计。
func (s *Store) InsightExcludes(projectID int64) ([]ExclusionRule, error) {
return s.queryRules(`SELECT id,pattern,category,builtin,project_id FROM exclusion_rules WHERE project_id=? AND category='insight' ORDER BY pattern`, projectID)
}
func withoutRuleCategory(rules []ExclusionRule, category string) []ExclusionRule {
out := []ExclusionRule{}
for _, r := range rules {
if r.Category != category {
out = append(out, r)
}
}
return out
}
func (s *Store) queryRules(q string, args ...any) ([]ExclusionRule, error) {
@@ -659,6 +678,9 @@ func (s *Store) AddProjectRule(projectID int64, pattern, category string) (Exclu
}
r, e := s.db.Exec(`INSERT INTO exclusion_rules(pattern,category,builtin,project_id) VALUES(?,?,0,?)`, pattern, category, projectID)
if e != nil {
if strings.Contains(strings.ToLower(e.Error()), "unique") {
return ExclusionRule{}, errors.New("PATTERN_EXISTS")
}
return ExclusionRule{}, e
}
id, _ := r.LastInsertId()
@@ -788,7 +810,7 @@ func (s *Store) LogCategories() ([]string, error) {
}
func (s *Store) Settings() (AppSettings, error) {
x := AppSettings{DatabasePath: s.path, Theme: "dark", Locale: "zh-CN", GitScope: "current", AutoRefresh: true, GlassOpacity: 55, LoadingStyle: "fullscreen-orbit",
MinimizeToTray: true, AutoUpdateMode: "daily", AutoUpdateInterval: 1, AutoUpdateTime: "09:00", AIProvider: "spark", ImageMode: "base64", ProjectSyncMode: "auto"}
MinimizeToTray: true, TrayMenuStyle: "native", AutoUpdateMode: "daily", AutoUpdateInterval: 1, AutoUpdateTime: "09:00", AIProvider: "spark", ImageMode: "base64", ProjectSyncMode: "auto"}
r, e := s.db.Query(`SELECT key,value FROM settings`)
if e != nil {
return x, e
@@ -816,6 +838,10 @@ func (s *Store) Settings() (AppSettings, error) {
}
case "minimizeToTray":
x.MinimizeToTray = v == "true"
case "trayMenuStyle":
if validTrayMenuStyle(v) {
x.TrayMenuStyle = normalizeTrayMenuStyle(v)
}
case "autoUpdateEnabled":
x.AutoUpdateEnabled = v == "true"
case "autoUpdateMode":
@@ -865,6 +891,11 @@ func (s *Store) SaveSettings(x AppSettings) error {
if !validLoadingStyle(x.LoadingStyle) {
x.LoadingStyle = "fullscreen-orbit"
}
if !validTrayMenuStyle(x.TrayMenuStyle) {
x.TrayMenuStyle = "native"
} else {
x.TrayMenuStyle = normalizeTrayMenuStyle(x.TrayMenuStyle)
}
if x.AutoUpdateInterval < 1 || x.AutoUpdateInterval > 720 {
x.AutoUpdateInterval = 1
}
@@ -884,7 +915,7 @@ func (s *Store) SaveSettings(x AppSettings) error {
x.ProjectSyncMode = "auto"
}
vals := map[string]string{"theme": x.Theme, "locale": x.Locale, "gitScope": x.GitScope, "autoRefresh": fmt.Sprint(x.AutoRefresh), "glassOpacity": strconv.Itoa(x.GlassOpacity), "loadingStyle": x.LoadingStyle, "loadingStyleSet": "true",
"minimizeToTray": fmt.Sprint(x.MinimizeToTray), "autoUpdateEnabled": fmt.Sprint(x.AutoUpdateEnabled), "autoUpdateMode": x.AutoUpdateMode, "autoUpdateInterval": strconv.Itoa(x.AutoUpdateInterval), "autoUpdateTime": x.AutoUpdateTime,
"minimizeToTray": fmt.Sprint(x.MinimizeToTray), "trayMenuStyle": x.TrayMenuStyle, "autoUpdateEnabled": fmt.Sprint(x.AutoUpdateEnabled), "autoUpdateMode": x.AutoUpdateMode, "autoUpdateInterval": strconv.Itoa(x.AutoUpdateInterval), "autoUpdateTime": x.AutoUpdateTime,
"aiProvider": x.AIProvider, "sparkKey": x.SparkKey, "deepSeekKey": x.DeepSeekKey, "syncApiKeys": fmt.Sprint(x.SyncAPIKeys),
"avatarMode": x.AvatarMode, "avatarValue": x.AvatarValue, "imageMode": x.ImageMode, "projectSyncMode": x.ProjectSyncMode}
if x.AvatarMode != "base64" && x.AvatarMode != "url" && x.AvatarMode != "path" {
@@ -933,6 +964,26 @@ func validLoadingStyle(v string) bool {
return false
}
}
func validTrayMenuStyle(v string) bool {
switch v {
case "native", "liquid", "glass", "compact", "minimal", "status", "neon":
return true
default:
return false
}
}
func normalizeTrayMenuStyle(v string) string {
if v == "minimal" {
return "compact"
}
if !validTrayMenuStyle(v) {
return "native"
}
return v
}
func (s *Store) ClearData(mode string, projectID int64) error {
tx, e := s.db.Begin()
if e != nil {