From 3d95c2e4f94d79ca9a37654f16c6b80985ce3250 Mon Sep 17 00:00:00 2001
From: Akshay
- {{- range .TextFragments -}}
- {{ .Header }}
- {{- range .Lines -}}
- {{- if eq .Op.String "+" -}}
- {{ .Op.String }}{{ .Line }}
- {{- end -}}
-
- {{- if eq .Op.String "-" -}}
- {{ .Op.String }}{{ .Line }}
- {{- end -}}
-
- {{- if eq .Op.String " " -}}
- {{ .Op.String }}{{ .Line }}
- {{- end -}}
-
- {{- end -}}
- {{- end -}}
+
+ {{- range .TextFragments -}}
+ {{- .Header -}}
+ {{- range .Lines -}}
+ {{- if eq .Op.String "+" -}}
+
+ {{ .Op.String }}
+ {{ .Line }}
+
+ {{- end -}}
+ {{- if eq .Op.String "-" -}}
+
+ {{ .Op.String }}
+ {{ .Line }}
+
+ {{- end -}}
+ {{- if eq .Op.String " " -}}
+
+ {{ .Op.String }}
+ {{ .Line }}
+
+ {{- end -}}
+ {{- end -}}{{- end -}}
{{- end -}}
{{ end }}
diff --git a/appview/pages/templates/fragments/editIssueComment.html b/appview/pages/templates/fragments/editIssueComment.html
new file mode 100644
index 00000000..6d9d860a
--- /dev/null
+++ b/appview/pages/templates/fragments/editIssueComment.html
@@ -0,0 +1,52 @@
+{{ define "fragments/editIssueComment" }}
+ {{ with .Comment }}
+
+
+ {{ $owner := didOrHandle $.LoggedInUser.Did $.LoggedInUser.Handle }}
+ {{ $owner }}
+
+
+ {{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
+ {{ if $isIssueAuthor }}
+
+
+ author
+
+ {{ end }}
+
+
+
+ {{ .Created | timeFmt }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ end }}
+{{ end }}
+
diff --git a/appview/pages/templates/fragments/issueComment.html b/appview/pages/templates/fragments/issueComment.html
new file mode 100644
index 00000000..ed05504d
--- /dev/null
+++ b/appview/pages/templates/fragments/issueComment.html
@@ -0,0 +1,52 @@
+{{ define "fragments/issueComment" }}
+ {{ with .Comment }}
+
+
+ {{ $owner := index $.DidHandleMap .OwnerDid }}
+ {{ $owner }}
+
+
+ {{ $isIssueAuthor := eq .OwnerDid $.Issue.OwnerDid }}
+ {{ if $isIssueAuthor }}
+
+
+ author
+
+ {{ end }}
+
+
+
+ {{ .Created | timeFmt }}
+
+
+ {{ $isCommentOwner := eq $.LoggedInUser.Did .OwnerDid }}
+ {{ if and $isCommentOwner (not .Deleted) }}
+
+
+ {{ end }}
+
+ {{ if .Deleted }}
+ deleted {{ .Deleted | timeFmt }}
+ {{ end }}
+
+
+ {{ if not .Deleted }}
+
+ {{ .Body | markdown }}
+
+ {{ end }}
+
+ {{ end }}
+{{ end }}
diff --git a/appview/pages/templates/repo/issues/issue.html b/appview/pages/templates/repo/issues/issue.html
index 455a2faf..901b8fa6 100644
--- a/appview/pages/templates/repo/issues/issue.html
+++ b/appview/pages/templates/repo/issues/issue.html
@@ -1,4 +1,4 @@
-{{ define "title" }}{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} ·{{ .RepoInfo.FullName }}{{ end }}
+{{ define "title" }}{{ .Issue.Title }} · issue #{{ .Issue.IssueId }} · {{ .RepoInfo.FullName }}{{ end }}
{{ define "repoContent" }}
@@ -49,36 +49,14 @@
{{ range $index, $comment := .Comments }}
+ class="rounded bg-white px-6 py-4 relative dark:bg-gray-800">
{{ if eq $index 0 }}
{{ else }}
{{ end }}
-
- {{ $owner := index $.DidHandleMap .OwnerDid }}
-
- {{ $owner }}
-
-
-
- {{ .Created | timeFmt }}
-
-
-
- {{ .Body | markdown }}
-
+ {{ template "fragments/issueComment" (dict "RepoInfo" $.RepoInfo "LoggedInUser" $.LoggedInUser "DidHandleMap" $.DidHandleMap "Issue" $.Issue "Comment" .)}}
{{ end }}
diff --git a/appview/state/repo.go b/appview/state/repo.go
index d515e39e..8cc10c8e 100644
--- a/appview/state/repo.go
+++ b/appview/state/repo.go
@@ -14,6 +14,7 @@ import (
"strings"
"time"
+ "github.com/bluesky-social/indigo/atproto/data"
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
securejoin "github.com/cyphar/filepath-securejoin"
@@ -907,7 +908,7 @@ func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) {
}
}
-func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
+func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) {
user := s.auth.GetUser(r)
f, err := fullyResolvedRepo(r)
if err != nil {
@@ -984,6 +985,240 @@ func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
}
}
+func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) {
+ user := s.auth.GetUser(r)
+ f, err := fullyResolvedRepo(r)
+ if err != nil {
+ log.Println("failed to get repo and knot", err)
+ return
+ }
+
+ issueId := chi.URLParam(r, "issue")
+ issueIdInt, err := strconv.Atoi(issueId)
+ if err != nil {
+ http.Error(w, "bad issue id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ commentId := chi.URLParam(r, "comment_id")
+ commentIdInt, err := strconv.Atoi(commentId)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
+ if err != nil {
+ log.Println("failed to get issue", err)
+ s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
+ return
+ }
+
+ comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ return
+ }
+
+ identity, err := s.resolver.ResolveIdent(r.Context(), comment.OwnerDid)
+ if err != nil {
+ log.Println("failed to resolve did")
+ return
+ }
+
+ didHandleMap := make(map[string]string)
+ if !identity.Handle.IsInvalidHandle() {
+ didHandleMap[identity.DID.String()] = fmt.Sprintf("@%s", identity.Handle.String())
+ } else {
+ didHandleMap[identity.DID.String()] = identity.DID.String()
+ }
+
+ s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
+ LoggedInUser: user,
+ RepoInfo: f.RepoInfo(s, user),
+ DidHandleMap: didHandleMap,
+ Issue: issue,
+ Comment: comment,
+ })
+}
+
+func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) {
+ user := s.auth.GetUser(r)
+ f, err := fullyResolvedRepo(r)
+ if err != nil {
+ log.Println("failed to get repo and knot", err)
+ return
+ }
+
+ issueId := chi.URLParam(r, "issue")
+ issueIdInt, err := strconv.Atoi(issueId)
+ if err != nil {
+ http.Error(w, "bad issue id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ commentId := chi.URLParam(r, "comment_id")
+ commentIdInt, err := strconv.Atoi(commentId)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt)
+ if err != nil {
+ log.Println("failed to get issue", err)
+ s.pages.Notice(w, "issues", "Failed to load issue. Try again later.")
+ return
+ }
+
+ comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ return
+ }
+
+ if comment.OwnerDid != user.Did {
+ http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
+ return
+ }
+
+ switch r.Method {
+ case http.MethodGet:
+ s.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{
+ LoggedInUser: user,
+ RepoInfo: f.RepoInfo(s, user),
+ Issue: issue,
+ Comment: comment,
+ })
+ case http.MethodPost:
+ // extract form value
+ newBody := r.FormValue("body")
+ client, _ := s.auth.AuthorizedClient(r)
+ log.Println("comment at", comment.CommentAt)
+ rkey := comment.CommentAt.RecordKey()
+
+ // optimistic update
+ err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody)
+ if err != nil {
+ log.Println("failed to perferom update-description query", err)
+ s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.")
+ return
+ }
+
+ // update the record on pds
+ ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey.String())
+ if err != nil {
+ // failed to get record
+ log.Println(err, rkey.String())
+ s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")
+ return
+ }
+ value, _ := ex.Value.MarshalJSON() // we just did get record; it is valid json
+ record, _ := data.UnmarshalJSON(value)
+
+ repoAt := record["repo"].(string)
+ issueAt := record["issue"].(string)
+ createdAt := record["createdAt"].(string)
+ commentIdInt64 := int64(commentIdInt)
+
+ _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
+ Collection: tangled.RepoNSID,
+ Repo: user.Did,
+ Rkey: rkey.String(),
+ SwapRecord: ex.Cid,
+ Record: &lexutil.LexiconTypeDecoder{
+ Val: &tangled.RepoIssueComment{
+ Repo: &repoAt,
+ Issue: issueAt,
+ CommentId: &commentIdInt64,
+ Owner: &comment.OwnerDid,
+ Body: &newBody,
+ CreatedAt: &createdAt,
+ },
+ },
+ })
+ if err != nil {
+ log.Println(err)
+ }
+
+ // optimistic update for htmx
+ didHandleMap := map[string]string{
+ user.Did: user.Handle,
+ }
+ comment.Body = newBody
+
+ // return new comment body with htmx
+ s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{
+ LoggedInUser: user,
+ RepoInfo: f.RepoInfo(s, user),
+ DidHandleMap: didHandleMap,
+ Issue: issue,
+ Comment: comment,
+ })
+ return
+
+ }
+
+}
+
+func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) {
+ user := s.auth.GetUser(r)
+ f, err := fullyResolvedRepo(r)
+ if err != nil {
+ log.Println("failed to get repo and knot", err)
+ return
+ }
+
+ issueId := chi.URLParam(r, "issue")
+ issueIdInt, err := strconv.Atoi(issueId)
+ if err != nil {
+ http.Error(w, "bad issue id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ commentId := chi.URLParam(r, "comment_id")
+ commentIdInt, err := strconv.Atoi(commentId)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ log.Println("failed to parse issue id", err)
+ return
+ }
+
+ comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
+ if err != nil {
+ http.Error(w, "bad comment id", http.StatusBadRequest)
+ return
+ }
+
+ if comment.OwnerDid != user.Did {
+ http.Error(w, "you are not the author of this comment", http.StatusUnauthorized)
+ return
+ }
+
+ if comment.Deleted != nil {
+ http.Error(w, "comment already deleted", http.StatusBadRequest)
+ return
+ }
+
+ // optimistic deletion
+ err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt)
+ if err != nil {
+ log.Println("failed to delete comment")
+ s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
+ return
+ }
+
+ // delete from pds
+
+ // htmx fragment of comment after deletion
+ return
+}
+
func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) {
params := r.URL.Query()
state := params.Get("state")
diff --git a/appview/state/router.go b/appview/state/router.go
index 6297350d..fe1dfcf0 100644
--- a/appview/state/router.go
+++ b/appview/state/router.go
@@ -73,7 +73,13 @@ func (s *State) UserRouter() http.Handler {
r.Use(AuthMiddleware(s))
r.Get("/new", s.NewIssue)
r.Post("/new", s.NewIssue)
- r.Post("/{issue}/comment", s.IssueComment)
+ r.Post("/{issue}/comment", s.NewIssueComment)
+ r.Route("/{issue}/comment/{comment_id}/", func(r chi.Router) {
+ r.Get("/", s.IssueComment)
+ r.Delete("/", s.DeleteIssueComment)
+ r.Get("/edit", s.EditIssueComment)
+ r.Post("/edit", s.EditIssueComment)
+ })
r.Post("/{issue}/close", s.CloseIssue)
r.Post("/{issue}/reopen", s.ReopenIssue)
})
--
2.51.2