Monorepo for Tangled tangled.org
2

Configure Feed

Select the types of activity you want to include in your feed.

appview/{pages,pulls}: support PR title/body edit

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
date (Jun 21, 2026, 8:28 PM +0900) commit 693d0644 parent 6b7b5868 change-id mtslnntr
+166 -7
+10
appview/pages/pages.go
··· 1383 1383 return p.executePlain("fragments/markdownPreview", w, body) 1384 1384 } 1385 1385 1386 + type EditPullParams struct { 1387 + LoggedInUser *oauth.MultiAccountUser 1388 + RepoInfo repoinfo.RepoInfo 1389 + Pull *models.Pull 1390 + } 1391 + 1392 + func (p *Pages) EditPullFragment(w io.Writer, params EditPullParams) error { 1393 + return p.executePlain("repo/pulls/fragments/pullEdit", w, params) 1394 + } 1395 + 1386 1396 type RepoPullsParams struct { 1387 1397 BaseParams 1388 1398 RepoInfo repoinfo.RepoInfo
+42
appview/pages/templates/repo/pulls/fragments/pullEdit.html
··· 1 + {{ define "repo/pulls/fragments/pullEdit" }} 2 + <form 3 + hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/edit" 4 + hx-trigger="submit, keydown[(ctrlKey || metaKey) && key=='Enter'] from:(find input[type=text],find textarea)" 5 + hx-swap="none" 6 + hx-indicator="find button[type=submit]" 7 + > 8 + <div class="flex flex-col gap-2"> 9 + <div> 10 + <label for="title">title</label> 11 + <input type="text" name="title" id="title" class="w-full" value="{{ .Pull.Title }}" /> 12 + </div> 13 + <div> 14 + <label for="body">body</label> 15 + {{ template "fragments/markdownEditor" 16 + (dict "Name" "body" 17 + "Value" .Pull.Body 18 + "BlobName" "blob" 19 + "Rows" 15 20 + "Placeholder" "Describe your pull request. Markdown is supported.") }} 21 + </div> 22 + <div class="flex justify-between"> 23 + <div id="pulls" class="error"></div> 24 + <div class="flex gap-2 items-stretch"> 25 + <a 26 + class="btn flex items-center gap-2 no-underline hover:no-underline" 27 + type="button" 28 + href="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}" 29 + > 30 + {{ i "x" "w-4 h-4" }} 31 + cancel 32 + </a> 33 + <button type="submit" class="btn-create group flex items-center gap-2"> 34 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 35 + {{ i "pencil" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 36 + edit 37 + </button> 38 + </div> 39 + </div> 40 + </div> 41 + </form> 42 + {{ end }}
+17 -7
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 1 1 {{ define "repo/pulls/fragments/pullHeader" }} 2 - <header class="pb-2"> 3 - <h1 class="text-2xl dark:text-white"> 4 - {{ .Pull.Title | description }} 5 - <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span> 6 - </h1> 7 - </header> 2 + <section id="pull-{{ .Pull.PullId }}"> 3 + <header class="pb-2"> 4 + <h1 class="text-2xl dark:text-white"> 5 + {{ .Pull.Title | description }} 6 + <span class="text-gray-500 dark:text-gray-400">#{{ .Pull.PullId }}</span> 7 + </h1> 8 + </header> 8 9 9 - <section> 10 10 <div class="flex items-center gap-2"> 11 11 {{ template "repo/pulls/fragments/pullState" .Pull.State }} 12 12 <span class="text-gray-500 dark:text-gray-400 text-sm flex flex-wrap items-center gap-1"> ··· 38 38 {{ .Pull.PullSource.Branch }} 39 39 {{ end }} 40 40 </span> 41 + {{ end }} 42 + 43 + {{ if and .LoggedInUser (eq .LoggedInUser.Did .Pull.OwnerDid) }} 44 + <a 45 + class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group cursor-pointer" 46 + hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/edit" 47 + hx-swap="innerHTML" 48 + hx-target="#pull-{{.Pull.PullId}}"> 49 + {{ i "pencil" "size-3" }} 50 + </a> 41 51 {{ end }} 42 52 </span> 43 53 </div>
+95
appview/pulls/edit.go
··· 1 + package pulls 2 + 3 + import ( 4 + "net/http" 5 + 6 + comatproto "github.com/bluesky-social/indigo/api/atproto" 7 + lexutil "github.com/bluesky-social/indigo/lex/util" 8 + 9 + "tangled.org/core/api/tangled" 10 + "tangled.org/core/appview/db" 11 + "tangled.org/core/appview/models" 12 + "tangled.org/core/appview/pages" 13 + ) 14 + 15 + func (s *Pulls) EditPull(w http.ResponseWriter, r *http.Request) { 16 + l := s.logger.With("handler", "EditPull") 17 + user := s.oauth.GetMultiAccountUser(r) 18 + ctx := r.Context() 19 + 20 + pull, ok := r.Context().Value("pull").(*models.Pull) 21 + if !ok { 22 + l.Error("failed to get pull") 23 + s.pages.Error404(w) 24 + return 25 + } 26 + 27 + switch r.Method { 28 + case http.MethodGet: 29 + s.pages.EditPullFragment(w, pages.EditPullParams{ 30 + LoggedInUser: user, 31 + RepoInfo: s.repoResolver.GetRepoInfo(r, user), 32 + Pull: pull, 33 + }) 34 + case http.MethodPost: 35 + noticeId := "pulls" 36 + newPull := *pull 37 + newPull.Title = r.FormValue("title") 38 + newPull.Body = r.FormValue("body") 39 + newPull.Mentions, newPull.References = s.mentionsResolver.Resolve(ctx, newPull.Body) 40 + 41 + // edit an atproto record 42 + client, err := s.oauth.AuthorizedClient(r) 43 + if err != nil { 44 + l.Error("failed to get authorized client", "err", err) 45 + s.pages.Notice(w, noticeId, "Failed to edit pull.") 46 + return 47 + } 48 + 49 + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, newPull.Rkey) 50 + if err != nil { 51 + l.Error("failed to get record", "err", err) 52 + s.pages.Notice(w, noticeId, "Failed to edit pull, no record found on PDS.") 53 + return 54 + } 55 + 56 + newRecord := newPull.AsRecord() 57 + _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 58 + Collection: tangled.RepoPullNSID, 59 + Repo: user.Did, 60 + Rkey: newPull.Rkey, 61 + SwapRecord: ex.Cid, 62 + Record: &lexutil.LexiconTypeDecoder{ 63 + Val: &newRecord, 64 + }, 65 + }) 66 + if err != nil { 67 + l.Error("failed to edit record on PDS", "err", err) 68 + s.pages.Notice(w, noticeId, "Failed to edit pull on PDS.") 69 + return 70 + } 71 + 72 + tx, err := s.db.BeginTx(r.Context(), nil) 73 + if err != nil { 74 + l.Error("failed to start tx", "err", err) 75 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 76 + return 77 + } 78 + defer tx.Rollback() 79 + 80 + err = db.PutPull(tx, &newPull) 81 + if err != nil { 82 + l.Error("failed to create pull request in database", "err", err) 83 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 84 + return 85 + } 86 + 87 + if err = tx.Commit(); err != nil { 88 + l.Error("failed to commit transaction for pull request", "err", err) 89 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 90 + return 91 + } 92 + 93 + s.pages.HxRefresh(w) 94 + } 95 + }
+2
appview/pulls/router.go
··· 35 35 36 36 r.Group(func(r chi.Router) { 37 37 r.Use(middleware.AuthMiddleware(s.oauth)) 38 + r.Get("/edit", s.EditPull) 39 + r.Post("/edit", s.EditPull) 38 40 r.Route("/resubmit", func(r chi.Router) { 39 41 r.Get("/", s.ResubmitPull) 40 42 r.Post("/", s.ResubmitPull)