appview: allows a default knot to be configured #858

open
opened by willdot.net targeting master from willdot.net/tangled-core: master

This follows on from the work carried out in #836

I've added a select box in the Knots settings page which pulls in the users knots and also adds in knot1.tangled.sh. When the user selects one of these options, it will save to their profile in the database. NOTE: I haven't yet implemented adding that to the AT record because I'm not sure on how the lexicon setup works yet!

Then when users go to create a new repo / fork, if there is a value in their profile for the default knot, then that will pre select the knot to use for the new repo / fork.

Changed files
+113 -8
appview
+7
appview/db/db.go
··· 1174 1174 return err 1175 1175 }) 1176 1176 1177 + runMigration(conn, logger, "add-default-knot-profile", func(tx *sql.Tx) error { 1178 + _, err := tx.Exec(` 1179 + alter table profile add column default_knot text; 1180 + `) 1181 + return err 1182 + }) 1183 + 1177 1184 return &DB{ 1178 1185 db, 1179 1186 logger,
+11 -4
appview/db/profile.go
··· 130 130 description, 131 131 include_bluesky, 132 132 location, 133 - pronouns 133 + pronouns, 134 + default_knot 134 135 ) 135 - values (?, ?, ?, ?, ?)`, 136 + values (?, ?, ?, ?, ?, ?)`, 136 137 profile.Did, 137 138 profile.Description, 138 139 includeBskyValue, 139 140 profile.Location, 140 141 profile.Pronouns, 142 + profile.DefaultKnot, 141 143 ) 142 144 143 145 if err != nil { ··· 311 313 func GetProfile(e Execer, did string) (*models.Profile, error) { 312 314 var profile models.Profile 313 315 var pronouns sql.Null[string] 316 + var defaultKnot sql.Null[string] 314 317 315 318 profile.Did = did 316 319 317 320 includeBluesky := 0 318 321 319 322 err := e.QueryRow( 320 - `select description, include_bluesky, location, pronouns from profile where did = ?`, 323 + `select description, include_bluesky, location, pronouns, default_knot from profile where did = ?`, 321 324 did, 322 - ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns) 325 + ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &defaultKnot) 323 326 if err == sql.ErrNoRows { 324 327 profile := models.Profile{} 325 328 profile.Did = did ··· 338 341 profile.Pronouns = pronouns.V 339 342 } 340 343 344 + if defaultKnot.Valid { 345 + profile.DefaultKnot = defaultKnot.V 346 + } 347 + 341 348 rows, err := e.Query(`select link from profile_links where did = ?`, did) 342 349 if err != nil { 343 350 return nil, err
+10
appview/knots/knots.go
··· 80 80 return 81 81 } 82 82 83 + defaultKnot := "" 84 + profile, err := db.GetProfile(k.Db, user.Did) 85 + if err != nil { 86 + k.Logger.Warn("gettings user profile to get default knot", "error", err) 87 + } 88 + if profile != nil { 89 + defaultKnot = profile.DefaultKnot 90 + } 91 + 83 92 k.Pages.Knots(w, pages.KnotsParams{ 84 93 LoggedInUser: user, 85 94 Registrations: registrations, 86 95 Tabs: knotsTabs, 87 96 Tab: "knots", 97 + DefaultKnot: defaultKnot, 88 98 }) 89 99 } 90 100
+1
appview/models/profile.go
··· 20 20 Stats [2]VanityStat 21 21 PinnedRepos [6]syntax.ATURI 22 22 Pronouns string 23 + DefaultKnot string 23 24 } 24 25 25 26 func (p Profile) IsLinksEmpty() bool {
+3
appview/pages/pages.go
··· 409 409 Registrations []models.Registration 410 410 Tabs []map[string]any 411 411 Tab string 412 + DefaultKnot string 412 413 } 413 414 414 415 func (p *Pages) Knots(w io.Writer, params KnotsParams) error { ··· 474 475 type NewRepoParams struct { 475 476 LoggedInUser *oauth.User 476 477 Knots []string 478 + DefaultKnot string 477 479 } 478 480 479 481 func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { ··· 484 486 LoggedInUser *oauth.User 485 487 Knots []string 486 488 RepoInfo repoinfo.RepoInfo 489 + DefaultKnot string 487 490 } 488 491 489 492 func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
+24
appview/pages/templates/knots/index.html
··· 31 31 <div class="flex flex-col gap-6"> 32 32 {{ block "list" . }} {{ end }} 33 33 {{ block "register" . }} {{ end }} 34 + {{ block "default-knot" . }} {{ end }} 34 35 </div> 35 36 </section> 36 37 {{ end }} ··· 62 63 </section> 63 64 {{ end }} 64 65 66 + {{ define "default-knot" }} 67 + <section class="rounded w-full flex flex-col gap-2"> 68 + <h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">default knot</h2> 69 + <select id="default-knot" name="default-knot" 70 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700" 71 + hx-post="/profile/default-knot" 72 + hx-swap="none" 73 + name="default-knot"> 74 + <option value="" > 75 + Choose a default Knot 76 + </option> 77 + <option value="knot1.tangled.sh" {{if eq $.DefaultKnot "knot1.tangled.sh"}}selected{{end}} > 78 + knot1.tangled.sh 79 + </option> 80 + {{ range $registration := .Registrations }} 81 + <option value="{{ .Domain }}" class="py-1" {{if eq $.DefaultKnot .Domain}}selected{{end}}> 82 + {{ .Domain }} 83 + </option> 84 + {{ end }} 85 + </select> 86 + </section> 87 + {{ end }} 88 + 65 89 {{ define "register" }} 66 90 <section class="rounded w-full lg:w-fit flex flex-col gap-2"> 67 91 <h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">register a knot</h2>
+3 -1
appview/pages/templates/repo/fork.html
··· 25 25 value="{{ . }}" 26 26 class="mr-2" 27 27 id="domain-{{ . }}" 28 - {{if eq (len $.Knots) 1}}checked{{end}} 28 + {{if eq (len $.Knots) 1}}checked 29 + {{else if eq $.DefaultKnot . }}checked 30 + {{end}} 29 31 /> 30 32 <label for="domain-{{ . }}" class="dark:text-white">{{ . }}</label> 31 33 </div>
+3 -1
appview/pages/templates/repo/new.html
··· 155 155 class="mr-2" 156 156 id="domain-{{ . }}" 157 157 required 158 - {{if eq (len $.Knots) 1}}checked{{end}} 158 + {{if eq (len $.Knots) 1}}checked 159 + {{else if eq $.DefaultKnot . }}checked 160 + {{end}} 159 161 /> 160 162 <label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label> 161 163 </div>
+10
appview/repo/repo.go
··· 1003 1003 return 1004 1004 } 1005 1005 1006 + defaultKnot := "" 1007 + profile, err := db.GetProfile(rp.db, user.Did) 1008 + if err != nil { 1009 + rp.logger.Warn("gettings user profile to get default knot", "error", err) 1010 + } 1011 + if profile != nil { 1012 + defaultKnot = profile.DefaultKnot 1013 + } 1014 + 1006 1015 rp.pages.ForkRepo(w, pages.ForkRepoParams{ 1007 1016 LoggedInUser: user, 1008 1017 Knots: knots, 1009 1018 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1019 + DefaultKnot: defaultKnot, 1010 1020 }) 1011 1021 1012 1022 case http.MethodPost:
+28
appview/state/profile.go
··· 613 613 s.updateProfile(profile, w, r) 614 614 } 615 615 616 + func (s *State) UpdateProfileDefaultKnot(w http.ResponseWriter, r *http.Request) { 617 + err := r.ParseForm() 618 + if err != nil { 619 + log.Println("invalid profile update form", err) 620 + return 621 + } 622 + user := s.oauth.GetUser(r) 623 + 624 + profile, err := db.GetProfile(s.db, user.Did) 625 + if err != nil { 626 + log.Printf("getting profile data for %s: %s", user.Did, err) 627 + } 628 + 629 + profile.DefaultKnot = r.Form.Get("default-knot") 630 + 631 + tx, err := s.db.BeginTx(r.Context(), nil) 632 + if err != nil { 633 + log.Println("failed to start transaction", err) 634 + return 635 + } 636 + 637 + err = db.UpsertProfile(tx, profile) 638 + if err != nil { 639 + log.Println("failed to update profile", err) 640 + return 641 + } 642 + } 643 + 616 644 func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) { 617 645 user := s.oauth.GetUser(r) 618 646 tx, err := s.db.BeginTx(r.Context(), nil)
+1
appview/state/router.go
··· 162 162 r.Get("/edit-pins", s.EditPinsFragment) 163 163 r.Post("/bio", s.UpdateProfileBio) 164 164 r.Post("/pins", s.UpdateProfilePins) 165 + r.Post("/default-knot", s.UpdateProfileDefaultKnot) 165 166 }) 166 167 167 168 r.Mount("/settings", s.SettingsRouter())
+10
appview/state/state.go
··· 453 453 return 454 454 } 455 455 456 + defaultKnot := "" 457 + profile, err := db.GetProfile(s.db, user.Did) 458 + if err != nil { 459 + s.logger.Warn("gettings user profile to get default knot", "error", err) 460 + } 461 + if profile != nil { 462 + defaultKnot = profile.DefaultKnot 463 + } 464 + 456 465 s.pages.NewRepo(w, pages.NewRepoParams{ 457 466 LoggedInUser: user, 458 467 Knots: knots, 468 + DefaultKnot: defaultKnot, 459 469 }) 460 470 461 471 case http.MethodPost:
+2 -2
flake.lock
··· 99 99 "lastModified": 1731402384, 100 100 "narHash": "sha256-OwUmrPfEehLDz0fl2ChYLK8FQM2p0G1+EMrGsYEq+6g=", 101 101 "type": "tarball", 102 - "url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip" 102 + "url": "https://github.com/IBM/plex/releases/download/@ibm%2Fplex-mono@1.1.0/ibm-plex-mono.zip" 103 103 }, 104 104 "original": { 105 105 "type": "tarball", 106 - "url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip" 106 + "url": "https://github.com/IBM/plex/releases/download/@ibm%2Fplex-mono@1.1.0/ibm-plex-mono.zip" 107 107 } 108 108 }, 109 109 "indigo": {