Compare changes

Choose any two refs to compare.

+177 -39
+8 -1
appview/db/db.go
··· 1078 1078 // transfer data, constructing pull_at from pulls table 1079 1079 _, err = tx.Exec(` 1080 1080 insert into pull_submissions_new (id, pull_at, round_number, patch, created) 1081 - select 1081 + select 1082 1082 ps.id, 1083 1083 'at://' || p.owner_did || '/sh.tangled.repo.pull/' || p.rkey, 1084 1084 ps.round_number, ··· 1173 1173 return err 1174 1174 }) 1175 1175 1176 + orm.RunMigration(conn, logger, "add-punchcard-setting-profile", func(tx *sql.Tx) error { 1177 + _, err := tx.Exec(` 1178 + alter table profile add column punchard_setting string; 1179 + `) 1180 + return err 1181 + }) 1182 + 1176 1183 return &DB{ 1177 1184 db, 1178 1185 logger,
+31 -21
appview/db/profile.go
··· 16 16 17 17 const TimeframeMonths = 7 18 18 19 - func MakeProfileTimeline(e Execer, forDid string) (*models.ProfileTimeline, error) { 19 + func MakeProfileTimeline(e Execer, forDid string, includePunchcard bool) (*models.ProfileTimeline, error) { 20 20 timeline := models.ProfileTimeline{ 21 21 ByMonth: make([]models.ByMonth, TimeframeMonths), 22 22 } ··· 98 98 }) 99 99 } 100 100 101 - punchcard, err := MakePunchcard( 102 - e, 103 - orm.FilterEq("did", forDid), 104 - orm.FilterGte("date", time.Now().AddDate(0, -TimeframeMonths, 0)), 105 - ) 106 - if err != nil { 107 - return nil, fmt.Errorf("error getting commits by did: %w", err) 108 - } 109 - for _, punch := range punchcard.Punches { 110 - if punch.Date.After(now) { 111 - continue 101 + if includePunchcard { 102 + punchcard, err := MakePunchcard( 103 + e, 104 + orm.FilterEq("did", forDid), 105 + orm.FilterGte("date", time.Now().AddDate(0, -TimeframeMonths, 0)), 106 + ) 107 + if err != nil { 108 + return nil, fmt.Errorf("error getting commits by did: %w", err) 112 109 } 110 + for _, punch := range punchcard.Punches { 111 + if punch.Date.After(now) { 112 + continue 113 + } 113 114 114 - monthsAgo := monthsBetween(punch.Date, now) 115 - if monthsAgo >= TimeframeMonths { 116 - // shouldn't happen; but times are weird 117 - continue 118 - } 115 + monthsAgo := monthsBetween(punch.Date, now) 116 + if monthsAgo >= TimeframeMonths { 117 + // shouldn't happen; but times are weird 118 + continue 119 + } 119 120 120 - idx := monthsAgo 121 - timeline.ByMonth[idx].Commits += punch.Count 121 + idx := monthsAgo 122 + timeline.ByMonth[idx].Commits += punch.Count 123 + } 122 124 } 123 125 124 126 return &timeline, nil ··· 353 355 includeBluesky := 0 354 356 355 357 err := e.QueryRow( 356 - `select description, include_bluesky, location, pronouns from profile where did = ?`, 358 + `select description, include_bluesky, location, pronouns, punchard_setting from profile where did = ?`, 357 359 did, 358 - ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns) 360 + ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &profile.PunchardSetting) 359 361 if err == sql.ErrNoRows { 360 362 profile := models.Profile{} 361 363 profile.Did = did ··· 536 538 } 537 539 return nil 538 540 } 541 + 542 + func SetProfilePunchcardStatus(e Execer, did string, punchcard models.ProfilePunchcardOption) error { 543 + _, err := e.Exec( 544 + `update profile set punchard_setting = ? where did = ?`, 545 + punchcard, did, 546 + ) 547 + return err 548 + }
+26 -7
appview/models/profile.go
··· 7 7 "tangled.org/core/api/tangled" 8 8 ) 9 9 10 + type ProfilePunchcardOption string 11 + 12 + const ( 13 + ProfilePunchcardOptionHideMine ProfilePunchcardOption = "HIDE_MINE" 14 + ProfilePunchcardOptionHideAll ProfilePunchcardOption = "HIDE_ALL" 15 + ) 16 + 17 + func ProfilePunchcardFromString(s string) ProfilePunchcardOption { 18 + switch s { 19 + case "HIDE_MINE": 20 + return ProfilePunchcardOptionHideMine 21 + case "HIDE_ALL": 22 + return ProfilePunchcardOptionHideAll 23 + default: 24 + return "" 25 + } 26 + } 27 + 10 28 type Profile struct { 11 29 // ids 12 30 ID int 13 31 Did string 14 32 15 33 // data 16 - Description string 17 - IncludeBluesky bool 18 - Location string 19 - Links [5]string 20 - Stats [2]VanityStat 21 - PinnedRepos [6]syntax.ATURI 22 - Pronouns string 34 + Description string 35 + IncludeBluesky bool 36 + Location string 37 + Links [5]string 38 + Stats [2]VanityStat 39 + PinnedRepos [6]syntax.ATURI 40 + Pronouns string 41 + PunchardSetting ProfilePunchcardOption 23 42 } 24 43 25 44 func (p Profile) IsLinksEmpty() bool {
+5 -3
appview/pages/pages.go
··· 337 337 } 338 338 339 339 type UserProfileSettingsParams struct { 340 - LoggedInUser *oauth.MultiAccountUser 341 - Tabs []map[string]any 342 - Tab string 340 + LoggedInUser *oauth.MultiAccountUser 341 + Tabs []map[string]any 342 + Tab string 343 + PunchcardSetting models.ProfilePunchcardOption 343 344 } 344 345 345 346 func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { ··· 537 538 ProfileTimeline *models.ProfileTimeline 538 539 Card *ProfileCard 539 540 Active string 541 + ShowPunchcard bool 540 542 } 541 543 542 544 func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error {
+4 -2
appview/pages/templates/layouts/profilebase.html
··· 10 10 <meta property="og:image" content="{{ $avatarUrl }}" /> 11 11 <meta property="og:image:width" content="512" /> 12 12 <meta property="og:image:height" content="512" /> 13 - 13 + 14 14 <meta name="twitter:card" content="summary" /> 15 15 <meta name="twitter:title" content="{{ $handle }}" /> 16 16 <meta name="twitter:description" content="{{ or .Card.Profile.Description $handle }}" /> ··· 28 28 <div class="{{ $style }} order-1 order-1"> 29 29 <div class="flex flex-col gap-4"> 30 30 {{ template "user/fragments/profileCard" .Card }} 31 - {{ block "punchcard" .Card.Punchcard }} {{ end }} 31 + {{ if .ShowPunchcard }} 32 + {{ block "punchcard" .Card.Punchcard }} {{ end }} 33 + {{ end }} 32 34 </div> 33 35 </div> 34 36
+31
appview/pages/templates/user/settings/profile.html
··· 59 59 </div> 60 60 </div> 61 61 </div> 62 + <div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full"> 63 + <div class="flex items-center justify-between p-4"> 64 + <div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]"> 65 + <div class="flex flex-wrap text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 66 + <span>Punchcard settings</span> 67 + </div> 68 + <form hx-post="/profile/punchcard" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch"> 69 + <select 70 + id="punchcard-setting" 71 + name="punchcard-setting" 72 + required 73 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"> 74 + {{/* For some reason, we can't use an empty string in a <select> in all scenarios unless it is preceded by a disabled select?? No idea, could just be a Firefox thing? */}} 75 + <option value="[[none]]" class="py-1" {{ if not $.PunchcardSetting }}selected{{ end }}> 76 + Show all 77 + </option> 78 + <option value="HIDE_MINE" class="py-1" {{ if eq "HIDE_MINE" $.PunchcardSetting }}selected{{ end }}> 79 + Hide mine for others 80 + </option> 81 + <option value="HIDE_ALL" class="py-1" {{ if eq "HIDE_ALL" $.PunchcardSetting }}selected{{ end }}> 82 + Hide everyones for me 83 + </option> 84 + </select> 85 + <button class="btn flex gap-2 items-center" type="submit"> 86 + {{ i "check" "size-4" }} 87 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 88 + </button> 89 + </form> 90 + </div> 91 + </div> 92 + </div> 62 93 {{ end }}
+11 -2
appview/settings/settings.go
··· 82 82 83 83 func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) { 84 84 user := s.OAuth.GetMultiAccountUser(r) 85 + profile, err := db.GetProfile(s.Db, user.Did()) 86 + if err != nil { 87 + log.Printf("failed to get profile to check punchcard settings: %s", err) 88 + } 85 89 86 - s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{ 90 + params := pages.UserProfileSettingsParams{ 87 91 LoggedInUser: user, 88 92 Tabs: settingsTabs, 89 93 Tab: "profile", 90 - }) 94 + } 95 + if profile != nil { 96 + params.PunchcardSetting = profile.PunchardSetting 97 + } 98 + 99 + s.Pages.UserProfileSettings(w, params) 91 100 } 92 101 93 102 func (s *Settings) notificationsSettings(w http.ResponseWriter, r *http.Request) {
+60 -3
appview/state/profile.go
··· 157 157 } 158 158 } 159 159 160 - timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid) 160 + loggedInUser := s.oauth.GetMultiAccountUser(r) 161 + 162 + forProfile, err := db.GetProfile(s.db, profile.UserDid) 163 + if err != nil { 164 + l.Error("failed to get for profile to check punchcard settings", "err", err) 165 + } 166 + requesterProfile, err := db.GetProfile(s.db, loggedInUser.Did()) 167 + if err != nil { 168 + l.Error("failed to get requester profile to check punchcard settings", "err", err) 169 + } 170 + 171 + showPunchcard := true 172 + if forProfile != nil && forProfile.PunchardSetting == models.ProfilePunchcardOptionHideMine { 173 + showPunchcard = false 174 + } 175 + if requesterProfile != nil && requesterProfile.PunchardSetting == models.ProfilePunchcardOptionHideAll { 176 + showPunchcard = false 177 + } 178 + 179 + timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid, showPunchcard) 161 180 if err != nil { 162 181 l.Error("failed to create timeline", "err", err) 163 182 } 164 183 184 + loggedInUserProfile, err := db.GetProfile(s.db, loggedInUser.Did()) 185 + if err != nil { 186 + l.Error("failed to get logged in user profile to check punchcard settings", "err", err) 187 + } 188 + 189 + if loggedInUserProfile != nil && loggedInUserProfile.PunchardSetting == models.ProfilePunchcardOptionHideAll { 190 + 191 + } 192 + 165 193 s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ 166 - LoggedInUser: s.oauth.GetMultiAccountUser(r), 194 + LoggedInUser: loggedInUser, 167 195 Card: profile, 168 196 Repos: pinnedRepos, 169 197 CollaboratingRepos: pinnedCollaboratingRepos, 170 198 ProfileTimeline: timeline, 199 + ShowPunchcard: showPunchcard, 171 200 }) 172 201 } 173 202 ··· 404 433 } 405 434 406 435 func (s *State) getProfileFeed(ctx context.Context, id *identity.Identity) (*feeds.Feed, error) { 407 - timeline, err := db.MakeProfileTimeline(s.db, id.DID.String()) 436 + timeline, err := db.MakeProfileTimeline(s.db, id.DID.String(), false) 408 437 if err != nil { 409 438 return nil, err 410 439 } ··· 728 757 AllRepos: allRepos, 729 758 }) 730 759 } 760 + 761 + func (s *State) UpdateProfilePunchcardSetting(w http.ResponseWriter, r *http.Request) { 762 + err := r.ParseForm() 763 + if err != nil { 764 + log.Println("invalid profile update form", err) 765 + return 766 + } 767 + user := s.oauth.GetUser(r) 768 + 769 + profile, err := db.GetProfile(s.db, user.Did) 770 + if err != nil { 771 + log.Printf("getting profile data for %s: %s", user.Did, err) 772 + } 773 + 774 + if profile == nil { 775 + return 776 + } 777 + 778 + punchcard := r.Form.Get("punchcard-setting") 779 + 780 + err = db.SetProfilePunchcardStatus(s.db, profile.Did, models.ProfilePunchcardFromString(punchcard)) 781 + if err != nil { 782 + log.Println("failed to update profile", err) 783 + return 784 + } 785 + 786 + s.pages.HxRefresh(w) 787 + }
+1
appview/state/router.go
··· 165 165 r.Get("/edit-pins", s.EditPinsFragment) 166 166 r.Post("/bio", s.UpdateProfileBio) 167 167 r.Post("/pins", s.UpdateProfilePins) 168 + r.Post("/punchcard", s.UpdateProfilePunchcardSetting) 168 169 }) 169 170 170 171 r.Mount("/settings", s.SettingsRouter())