Monorepo for Tangled

appview: allows a default knot to be configured

Signed-off-by: Will Andrews <will7989@hotmail.com>

willdot.net 080125f9 34d8a513

verified
+156 -9
+8
appview/db/db.go
··· 596 596 foreign key (webhook_id) references webhooks(id) on delete cascade 597 597 ); 598 598 599 + create table if not exists knot_preferences ( 600 + id integer primary key autoincrement, 601 + user_did text not null unique, 602 + default_knot text, 603 + 604 + foreign key (user_did, default_knot) references registrations(did, domain) on delete cascade 605 + ); 606 + 599 607 create table if not exists migrations ( 600 608 id integer primary key autoincrement, 601 609 name text unique
+35
appview/db/preferences.go
··· 50 50 51 51 return nil 52 52 } 53 + 54 + func GetKnotPreference(e Execer, did string) (*models.KnotPreference, error) { 55 + var knotPreference models.KnotPreference 56 + 57 + err := e.QueryRow( 58 + `select id, user_did, default_knot from knot_preferences where user_did = ?`, 59 + did, 60 + ).Scan(&knotPreference.ID, &knotPreference.Did, &knotPreference.DefaultKnot) 61 + if err == sql.ErrNoRows { 62 + return nil, nil 63 + } 64 + 65 + if err != nil { 66 + return nil, err 67 + } 68 + 69 + return &knotPreference, nil 70 + } 71 + 72 + func UpsertKnotPreference(e Execer, did, defaultKnot string) error { 73 + _, err := e.Exec( 74 + `insert or replace into knot_preferences ( 75 + user_did, 76 + default_knot 77 + ) 78 + values (?, ?)`, 79 + did, 80 + defaultKnot, 81 + ) 82 + if err != nil { 83 + return err 84 + } 85 + 86 + return nil 87 + }
+20 -2
appview/knots/knots.go
··· 68 68 return 69 69 } 70 70 71 + availableKnots, err := k.Enforcer.GetKnotsForUser(user.Did()) 72 + if err != nil { 73 + k.Logger.Error("failed to fetch available knots for user", "err", err) 74 + w.WriteHeader(http.StatusInternalServerError) 75 + return 76 + } 77 + 78 + defaultKnot := "" 79 + knotPrefence, err := db.GetKnotPreference(k.Db, user.Did()) 80 + if err != nil { 81 + k.Logger.Warn("gettings users knot preferences", "error", err) 82 + } 83 + if knotPrefence != nil { 84 + defaultKnot = knotPrefence.DefaultKnot 85 + } 86 + 71 87 k.Pages.Knots(w, pages.KnotsParams{ 72 - LoggedInUser: user, 73 - Registrations: registrations, 88 + LoggedInUser: user, 89 + Registrations: registrations, 90 + AvailableKnots: availableKnots, 91 + DefaultKnot: defaultKnot, 74 92 }) 75 93 } 76 94
+6
appview/models/preferences.go
··· 6 6 HideMine bool 7 7 HideOthers bool 8 8 } 9 + 10 + type KnotPreference struct { 11 + ID int 12 + Did string 13 + DefaultKnot string 14 + }
+7 -3
appview/pages/pages.go
··· 440 440 } 441 441 442 442 type KnotsParams struct { 443 - LoggedInUser *oauth.MultiAccountUser 444 - Registrations []models.Registration 445 - Tab string 443 + LoggedInUser *oauth.MultiAccountUser 444 + Registrations []models.Registration 445 + Tab string 446 + AvailableKnots []string 447 + DefaultKnot string 446 448 } 447 449 448 450 func (p *Pages) Knots(w io.Writer, params KnotsParams) error { ··· 506 508 type NewRepoParams struct { 507 509 LoggedInUser *oauth.MultiAccountUser 508 510 Knots []string 511 + DefaultKnot string 509 512 } 510 513 511 514 func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { ··· 516 519 LoggedInUser *oauth.MultiAccountUser 517 520 Knots []string 518 521 RepoInfo repoinfo.RepoInfo 522 + DefaultKnot string 519 523 } 520 524 521 525 func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
+28
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 }} ··· 60 61 </div> 61 62 <div id="operation-error" class="text-red-500 dark:text-red-400"></div> 62 63 </section> 64 + {{ end }} 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 + <form hx-post="/profile/default-knot" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch"> 70 + <select 71 + id="default-knot" 72 + name="default-knot" 73 + required 74 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"> 75 + {{/* 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? */}} 76 + <option value="[[none]]" class="py-1" {{ if not $.DefaultKnot }}selected{{ end }}> 77 + Choose a default knot 78 + </option> 79 + {{ range $.AvailableKnots }} 80 + <option value="{{ . }}" class="py-1" {{ if eq . $.DefaultKnot }}selected{{ end }}> 81 + {{ . }} 82 + </option> 83 + {{ end }} 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 + </section> 63 91 {{ end }} 64 92 65 93 {{ define "register" }}
+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
··· 156 156 class="mr-2" 157 157 id="domain-{{ . }}" 158 158 required 159 - {{if eq (len $.Knots) 1}}checked{{end}} 159 + {{if eq (len $.Knots) 1}}checked 160 + {{else if eq $.DefaultKnot . }}checked 161 + {{end}} 160 162 /> 161 163 <label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label> 162 164 </div>
+10
appview/repo/repo.go
··· 1004 1004 return 1005 1005 } 1006 1006 1007 + defaultKnot := "" 1008 + knotPrefence, err := db.GetKnotPreference(rp.db, user.Did()) 1009 + if err != nil { 1010 + rp.logger.Warn("gettings users knot preferences", "error", err) 1011 + } 1012 + if knotPrefence != nil { 1013 + defaultKnot = knotPrefence.DefaultKnot 1014 + } 1015 + 1007 1016 rp.pages.ForkRepo(w, pages.ForkRepoParams{ 1008 1017 LoggedInUser: user, 1009 1018 Knots: knots, 1010 1019 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1020 + DefaultKnot: defaultKnot, 1011 1021 }) 1012 1022 1013 1023 case http.MethodPost:
+23
appview/state/profile.go
··· 649 649 s.updateProfile(profile, w, r) 650 650 } 651 651 652 + func (s *State) UpdateDefaultKnotPreference(w http.ResponseWriter, r *http.Request) { 653 + err := r.ParseForm() 654 + if err != nil { 655 + log.Println("invalid preference update form", err) 656 + return 657 + } 658 + user := s.oauth.GetMultiAccountUser(r) 659 + 660 + defaultKnot := r.Form.Get("default-knot") 661 + 662 + if defaultKnot == "[[none]]" { // see pages/templates/knots/index.html for more info on why we use this value 663 + defaultKnot = "" 664 + } 665 + 666 + err = db.UpsertKnotPreference(s.db, user.Did(), defaultKnot) 667 + if err != nil { 668 + log.Println("failed to update default knot preference", err) 669 + return 670 + } 671 + 672 + s.pages.HxRefresh(w) 673 + } 674 + 652 675 func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) { 653 676 user := s.oauth.GetMultiAccountUser(r) 654 677 tx, err := s.db.BeginTx(r.Context(), nil)
+1
appview/state/router.go
··· 168 168 r.Post("/avatar", s.UploadProfileAvatar) 169 169 r.Delete("/avatar", s.RemoveProfileAvatar) 170 170 r.Post("/punchcard", s.UpdateProfilePunchcardSetting) 171 + r.Post("/default-knot", s.UpdateDefaultKnotPreference) 171 172 }) 172 173 173 174 r.Mount("/settings", s.SettingsRouter())
+10
appview/state/state.go
··· 430 430 return 431 431 } 432 432 433 + defaultKnot := "" 434 + knotPrefence, err := db.GetKnotPreference(s.db, user.Did()) 435 + if err != nil { 436 + s.logger.Warn("gettings users knot preferences", "error", err) 437 + } 438 + if knotPrefence != nil { 439 + defaultKnot = knotPrefence.DefaultKnot 440 + } 441 + 433 442 s.pages.NewRepo(w, pages.NewRepoParams{ 434 443 LoggedInUser: user, 435 444 Knots: knots, 445 + DefaultKnot: defaultKnot, 436 446 }) 437 447 438 448 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": {