From 99d6916a1387fa8e161d6ead6abdeb69f4e2860b Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Wed, 1 Apr 2026 19:45:14 +0900 Subject: [PATCH] wip: appview: unified comment fragments/handlers Signed-off-by: Seongmin Lee --- appview/db/comments.go | 11 + appview/issues/issues.go | 22 +- appview/pages/pages.go | 35 +- .../fragments/comment/commentBody.html | 9 + .../fragments/comment/commentHeader.html | 60 +++ .../templates/fragments/comment/edit.html | 44 +++ .../templates/fragments/comment/reply.html | 66 ++++ .../fragments/comment/replyPlaceholder.html | 14 + .../repo/issues/fragments/commentList.html | 34 +- appview/state/comment.go | 373 ++++++++++++++++++ appview/state/router.go | 10 + 11 files changed, 644 insertions(+), 34 deletions(-) create mode 100644 appview/pages/templates/fragments/comment/commentBody.html create mode 100644 appview/pages/templates/fragments/comment/commentHeader.html create mode 100644 appview/pages/templates/fragments/comment/edit.html create mode 100644 appview/pages/templates/fragments/comment/reply.html create mode 100644 appview/pages/templates/fragments/comment/replyPlaceholder.html create mode 100644 appview/state/comment.go diff --git a/appview/db/comments.go b/appview/db/comments.go index 1a9a1504..9b065de3 100644 --- a/appview/db/comments.go +++ b/appview/db/comments.go @@ -136,6 +136,17 @@ func DeleteComments(e Execer, filters ...orm.Filter) error { return err } +func GetComment(e Execer, filters ...orm.Filter) (models.Comment, error) { + comments, err := GetComments(e, filters...) + if err != nil { + return models.Comment{}, err + } + if len(comments) != 1 { + return models.Comment{}, fmt.Errorf("expected 1 comment, got %d", len(comments)) + } + return comments[0], nil +} + func GetComments(e Execer, filters ...orm.Filter) ([]models.Comment, error) { var comments []models.Comment diff --git a/appview/issues/issues.go b/appview/issues/issues.go index 9e9757d4..3a2094f9 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -133,7 +133,7 @@ func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { defs[l.AtUri().String()] = &l } - rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ + err = rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ LoggedInUser: user, RepoInfo: rp.repoResolver.GetRepoInfo(r, user), Issue: issue, @@ -143,6 +143,9 @@ func (rp *Issues) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { UserReacted: userReactions, LabelDefs: defs, }) + if err != nil { + l.Error("failed to render", "err", err) + } } func (rp *Issues) EditIssue(w http.ResponseWriter, r *http.Request) { @@ -547,13 +550,6 @@ func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "IssueComment") user := rp.oauth.GetMultiAccountUser(r) - issue, ok := r.Context().Value("issue").(*models.Issue) - if !ok { - l.Error("failed to get issue") - rp.pages.Error404(w) - return - } - commentId := chi.URLParam(r, "commentId") comments, err := db.GetComments( rp.db, @@ -574,7 +570,6 @@ func (rp *Issues) IssueComment(w http.ResponseWriter, r *http.Request) { rp.pages.IssueCommentBodyFragment(w, pages.IssueCommentBodyParams{ LoggedInUser: user, RepoInfo: rp.repoResolver.GetRepoInfo(r, user), - Issue: issue, Comment: &comment, }) } @@ -693,7 +688,6 @@ func (rp *Issues) EditIssueComment(w http.ResponseWriter, r *http.Request) { rp.pages.IssueCommentBodyFragment(w, pages.IssueCommentBodyParams{ LoggedInUser: user, RepoInfo: rp.repoResolver.GetRepoInfo(r, user), - Issue: issue, Comment: &newComment, }) } @@ -775,13 +769,6 @@ func (rp *Issues) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { l := rp.logger.With("handler", "DeleteIssueComment") user := rp.oauth.GetMultiAccountUser(r) - issue, ok := r.Context().Value("issue").(*models.Issue) - if !ok { - l.Error("failed to get issue") - rp.pages.Error404(w) - return - } - commentId := chi.URLParam(r, "commentId") comments, err := db.GetComments( rp.db, @@ -845,7 +832,6 @@ func (rp *Issues) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { rp.pages.IssueCommentBodyFragment(w, pages.IssueCommentBodyParams{ LoggedInUser: user, RepoInfo: rp.repoResolver.GetRepoInfo(r, user), - Issue: issue, Comment: &comment, }) } diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 02ef4008..4b428d08 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1208,7 +1208,6 @@ func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentP type IssueCommentBodyParams struct { LoggedInUser *oauth.MultiAccountUser RepoInfo repoinfo.RepoInfo - Issue *models.Issue Comment *models.Comment } @@ -1593,6 +1592,40 @@ func (p *Pages) Home(w io.Writer, params TimelineParams) error { return p.execute("timeline/home", w, params) } +type CommentBodyFragmentParams struct { + Comment models.Comment +} + +func (p *Pages) CommentBodyFragment(w io.Writer, params CommentBodyFragmentParams) error { + return p.executePlain("fragments/comment/commentBody", w, params) +} + +type EditCommentFragmentParams struct { + Comment models.Comment +} + +func (p *Pages) EditCommentFragment(w io.Writer, params EditCommentFragmentParams) error { + return p.executePlain("fragments/comment/edit", w, params) +} + +type ReplyCommentFragmentParams struct { + LoggedInUser *oauth.MultiAccountUser + Comment models.Comment +} + +func (p *Pages) ReplyCommentFragment(w io.Writer, params ReplyCommentFragmentParams) error { + return p.executePlain("fragments/comment/reply", w, params) +} + +type ReplyPlaceholderFragmentParams struct { + LoggedInUser *oauth.MultiAccountUser + Comment struct{ AtUri string } +} + +func (p *Pages) ReplyPlaceholderFragment(w io.Writer, params ReplyPlaceholderFragmentParams) error { + return p.executePlain("fragments/comment/replyPlaceholder", w, params) +} + func (p *Pages) Static() http.Handler { if p.dev { return http.StripPrefix("/static/", http.FileServer(http.Dir("appview/pages/static"))) diff --git a/appview/pages/templates/fragments/comment/commentBody.html b/appview/pages/templates/fragments/comment/commentBody.html new file mode 100644 index 00000000..9bef885e --- /dev/null +++ b/appview/pages/templates/fragments/comment/commentBody.html @@ -0,0 +1,9 @@ +{{ define "fragments/comment/commentBody" }} +
+ {{ if not .Comment.Deleted }} +
{{ .Comment.Body.Text | markdown }}
+ {{ else }} +
[deleted by author]
+ {{ end }} +
+{{ end }} diff --git a/appview/pages/templates/fragments/comment/commentHeader.html b/appview/pages/templates/fragments/comment/commentHeader.html new file mode 100644 index 00000000..d3a29312 --- /dev/null +++ b/appview/pages/templates/fragments/comment/commentHeader.html @@ -0,0 +1,60 @@ +{{ define "fragments/comment/commentHeader" }} +
+ {{ $handle := resolve .Comment.Did.String }} + {{ $handle }} + {{ template "hats" $ }} + + {{ template "timestamp" . }} + {{ $isCommentOwner := and .LoggedInUser (eq .LoggedInUser.Did .Comment.Did.String) }} + {{ if and $isCommentOwner (not .Comment.Deleted) }} + {{ if not .Comment.IsLegacy }} + {{ template "editCommentBtn" . }} + {{ end }} + {{ template "deleteCommentBtn" . }} + {{ end }} +
+{{ end }} + +{{ define "hats" }} + {{ $isIssueAuthor := eq .Comment.Did .Issue.Did }} + {{ if $isIssueAuthor }} + (author) + {{ end }} +{{ end }} + +{{ define "timestamp" }} + + {{ if .Comment.Deleted }} + {{ template "repo/fragments/shortTimeAgo" .Comment.Deleted }} + {{ else if .Comment.Edited }} + edited {{ template "repo/fragments/shortTimeAgo" .Comment.Edited }} + {{ else }} + {{ template "repo/fragments/shortTimeAgo" .Comment.Created }} + {{ end }} + +{{ end }} + +{{ define "editCommentBtn" }} + + {{ i "pencil" "size-3" }} + +{{ end }} + +{{ define "deleteCommentBtn" }} + + {{ i "trash-2" "size-3" }} + {{ i "loader-circle" "size-3 animate-spin hidden group-[.htmx-request]:inline" }} + +{{ end }} diff --git a/appview/pages/templates/fragments/comment/edit.html b/appview/pages/templates/fragments/comment/edit.html new file mode 100644 index 00000000..8b39dfee --- /dev/null +++ b/appview/pages/templates/fragments/comment/edit.html @@ -0,0 +1,44 @@ +{{ define "fragments/comment/edit" }} +
+ + + {{ template "editActions" $ }} +
+{{ end }} + +{{ define "editActions" }} +
+ {{ template "cancel" . }} + {{ template "save" . }} +
+{{ end }} + +{{ define "save" }} + +{{ end }} + +{{ define "cancel" }} + +{{ end }} diff --git a/appview/pages/templates/fragments/comment/reply.html b/appview/pages/templates/fragments/comment/reply.html new file mode 100644 index 00000000..c7929861 --- /dev/null +++ b/appview/pages/templates/fragments/comment/reply.html @@ -0,0 +1,66 @@ +{{ define "fragments/comment/reply" }} +
+ {{ template "user/fragments/picHandleLink" .LoggedInUser.Did }} + + + + + {{ template "replyActions" . }} +
+{{ end }} + +{{ define "replyActions" }} +
+ {{ template "cancel" . }} + {{ template "reply" . }} +
+{{ end }} + +{{ define "cancel" }} + +{{ end }} + +{{ define "reply" }} + +{{ end }} diff --git a/appview/pages/templates/fragments/comment/replyPlaceholder.html b/appview/pages/templates/fragments/comment/replyPlaceholder.html new file mode 100644 index 00000000..2c9ecf0a --- /dev/null +++ b/appview/pages/templates/fragments/comment/replyPlaceholder.html @@ -0,0 +1,14 @@ +{{ define "fragments/comment/replyPlaceholder" }} + {{ if .LoggedInUser }} + {{ template "user/fragments/pic" (list .LoggedInUser.Did "size-8 mr-1") }} + {{ end }} + + +{{ end }} diff --git a/appview/pages/templates/repo/issues/fragments/commentList.html b/appview/pages/templates/repo/issues/fragments/commentList.html index 4fd6cd91..4c6a7a78 100644 --- a/appview/pages/templates/repo/issues/fragments/commentList.html +++ b/appview/pages/templates/repo/issues/fragments/commentList.html @@ -1,40 +1,44 @@ {{ define "repo/issues/fragments/commentList" }}
{{ range $item := .CommentList }} - {{ template "commentListing" (list $ .) }} + {{ template "commentListItem" (list $ .) }} {{ end }}
{{ end }} -{{ define "commentListing" }} +{{ define "commentListItem" }} {{ $root := index . 0 }} - {{ $comment := index . 1 }} + {{ $item := index . 1 }} {{ $params := (dict - "RepoInfo" $root.RepoInfo "LoggedInUser" $root.LoggedInUser - "Issue" $root.Issue - "Comment" $comment.Self) }} + "Comment" $item.Self) }}
{{ template "topLevelComment" $params }}
- {{ range $index, $reply := $comment.Replies }} + {{ range $index, $reply := $item.Replies }}
{{ template "replyComment" (dict - "RepoInfo" $root.RepoInfo "LoggedInUser" $root.LoggedInUser - "Issue" $root.Issue - "Comment" $reply) + "Comment" $reply) }}
{{ end }}
- {{ template "repo/issues/fragments/replyIssueCommentPlaceholder" $params }} +
+ {{ if $item.Self.IsLegacy }} + {{ if $root.LoggedInUser }} + Can't reply to legacy comment. + {{ end }} + {{ else }} + {{ template "fragments/comment/replyPlaceholder" (dict "LoggedInUser" $root.LoggedInUser) }} + {{ end }} +
{{ end }} @@ -44,8 +48,8 @@ {{ template "user/fragments/picLink" (list .Comment.Did.String "size-8 mr-1") }}
- {{ template "repo/issues/fragments/issueCommentHeader" . }} - {{ template "repo/issues/fragments/issueCommentBody" . }} + {{ template "fragments/comment/commentHeader" . }} + {{ template "fragments/comment/commentBody" . }}
{{ end }} @@ -56,8 +60,8 @@ {{ template "user/fragments/picLink" (list .Comment.Did.String "size-8 mr-1") }}
- {{ template "repo/issues/fragments/issueCommentHeader" . }} - {{ template "repo/issues/fragments/issueCommentBody" . }} + {{ template "fragments/comment/commentHeader" . }} + {{ template "fragments/comment/commentBody" . }}
{{ end }} diff --git a/appview/state/comment.go b/appview/state/comment.go new file mode 100644 index 00000000..f3a68256 --- /dev/null +++ b/appview/state/comment.go @@ -0,0 +1,373 @@ +package state + +import ( + "fmt" + "net/http" + "time" + + comatproto "github.com/bluesky-social/indigo/api/atproto" + "github.com/bluesky-social/indigo/atproto/syntax" + lexutil "github.com/bluesky-social/indigo/lex/util" + indigoxrpc "github.com/bluesky-social/indigo/xrpc" + + "tangled.org/core/api/tangled" + "tangled.org/core/appview/db" + "tangled.org/core/appview/models" + "tangled.org/core/appview/pages" + "tangled.org/core/orm" + "tangled.org/core/tid" +) + +func (s *State) CommentBodyFragment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "CommentBodyFragment") + + commentAt := r.URL.Query().Get("aturi") + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", commentAt)) + if err != nil { + l.Error("failed to fetch comment", "aturi", commentAt) + http.Error(w, "Failed to fetch comment", http.StatusInternalServerError) + return + } + + s.pages.CommentBodyFragment(w, pages.CommentBodyFragmentParams{ + Comment: comment, + }) +} + +func (s *State) EditCommentFragment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "EditCommentFragment") + + commentAt := r.URL.Query().Get("aturi") + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", commentAt)) + if err != nil { + l.Error("failed to fetch comment", "aturi", commentAt) + http.Error(w, "Failed to fetch comment", http.StatusInternalServerError) + return + } + + s.pages.EditCommentFragment(w, pages.EditCommentFragmentParams{ + Comment: comment, + }) +} + +func (s *State) NewReplyCommentFragment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "NewReplyCommentFragment") + + parentAt := r.URL.Query().Get("parent") + parent, err := db.GetComment(s.db, orm.FilterEq("at_uri", parentAt)) + if err != nil { + l.Error("failed to fetch comment", "aturi", parentAt) + http.Error(w, "Failed to fetch comment", http.StatusInternalServerError) + return + } + + s.pages.ReplyCommentFragment(w, pages.ReplyCommentFragmentParams{ + LoggedInUser: s.oauth.GetMultiAccountUser(r), + Comment: parent, + }) +} + +func (s *State) ReplyPlaceholderFragment(w http.ResponseWriter, r *http.Request) { + s.pages.ReplyPlaceholderFragment(w, pages.ReplyPlaceholderFragmentParams{ + LoggedInUser: s.oauth.GetMultiAccountUser(r), + Comment: struct{ AtUri string }{ + AtUri: r.URL.Query().Get("parent"), + }, + }) +} + +func (s *State) NewComment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "NewComment") + user := s.oauth.GetMultiAccountUser(r) + + noticeId := "comment" + ctx := r.Context() + + body := r.FormValue("body") + if body == "" { + s.pages.Notice(w, noticeId, "Body is required") + return + } + + // TODO(boltless): normalize markdown body + normalizedBody := body + mentions, references := s.mentionsResolver.Resolve(ctx, body) + + markdownBody := tangled.Markup_Markdown{ + Text: normalizedBody, + Original: &body, + Blobs: nil, + } + + subjectUri, err := syntax.ParseATURI(r.FormValue("subject-uri")) + if err != nil { + s.pages.Notice(w, noticeId, "Subject URI should be valid AT-URI") + return + } + + // ingest CID of subject record on-demand. + // TODO(boltless): appview should ingest CID of all atproto records + subjectCid, err := func(uri syntax.ATURI) (syntax.CID, error) { + ident, err := s.idResolver.ResolveIdent(ctx, uri.Authority().String()) + if err != nil { + return "", err + } + + xrpcc := indigoxrpc.Client{Host: ident.PDSEndpoint()} + out, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", uri.Collection().String(), ident.DID.String(), uri.RecordKey().String()) + if err != nil { + return "", err + } + if out.Cid == nil { + return "", fmt.Errorf("record CID is empty") + } + + cid, err := syntax.ParseCID(*out.Cid) + if err != nil { + return "", err + } + + return cid, nil + }(subjectUri) + if err != nil { + l.Error("failed to backfill subject record", "subject.uri", subjectUri, "err", err) + s.pages.Notice(w, noticeId, "failed to backfill subject record") + return + } + + subject := comatproto.RepoStrongRef{ + Uri: subjectUri.String(), + Cid: subjectCid.String(), + } + + var replyTo *comatproto.RepoStrongRef + replyToUriRaw := r.FormValue("reply-to-uri") + replyToCidRaw := r.FormValue("reply-to-cid") + if replyToUriRaw != "" && replyToCidRaw != "" { + uri, err := syntax.ParseATURI(replyToUriRaw) + if err != nil { + s.pages.Notice(w, noticeId, "reply-to-uri should be valid AT-URI") + return + } + cid, err := syntax.ParseCID(replyToCidRaw) + if err != nil { + s.pages.Notice(w, noticeId, "reply-to-cid should be valid CID") + return + } + replyTo = &comatproto.RepoStrongRef{ + Uri: uri.String(), + Cid: cid.String(), + } + } + + comment := models.Comment{ + Did: syntax.DID(user.Active.Did), + Collection: tangled.FeedCommentNSID, + Rkey: syntax.RecordKey(tid.TID()), + + Subject: subject, + Body: markdownBody, + Created: time.Now(), + ReplyTo: replyTo, + } + if err = comment.Validate(); err != nil { + l.Error("failed to validate comment", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment.") + return + } + + client, err := s.oauth.AuthorizedClient(r) + if err != nil { + l.Error("failed to get authorized client", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment.") + return + } + + // create a record first + out, err := comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ + Collection: comment.Collection.String(), + Repo: comment.Did.String(), + Rkey: comment.Rkey.String(), + Record: &lexutil.LexiconTypeDecoder{Val: comment.AsRecord()}, + }) + if err != nil { + l.Error("failed to create comment", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment.") + return + } + + comment.Cid = syntax.CID(out.Cid) + + tx, err := s.db.Begin() + if err != nil { + l.Error("failed to start transaction", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment, try again later.") + return + } + defer tx.Rollback() + + err = db.PutComment(tx, &comment, references) + if err != nil { + l.Error("failed to create comment", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment.") + return + } + + err = tx.Commit() + if err != nil { + l.Error("failed to commit transaction", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment, try again later.") + return + } + + s.notifier.NewComment(ctx, &comment, mentions) + + // TODO: return comment or reply-comment fragment + // onattach, htmx-callback to focus on comment. + panic("unimplemented") +} + +func (s *State) EditComment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "EditComment") + user := s.oauth.GetMultiAccountUser(r) + + noticeId := "comment" + ctx := r.Context() + + commentAt := r.URL.Query().Get("aturi") + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", commentAt)) + if err != nil { + l.Error("failed to fetch comment", "aturi", commentAt) + s.pages.Notice(w, noticeId, "Failed to fetch comment") + return + } + + if comment.Did.String() != user.Active.Did { + l.Error("unauthorized comment edit", "expectedDid", comment.Did, "gotDid", user.Active.Did) + s.pages.Notice(w, noticeId, "You are not the author of this comment") + return + } + + body := r.FormValue("body") + if body == "" { + s.pages.Notice(w, noticeId, "Body is required") + return + } + + // TODO(boltless): normalize markdown body + normalizedBody := body + _, references := s.mentionsResolver.Resolve(ctx, body) + + now := time.Now() + newComment := comment + newComment.Body = tangled.Markup_Markdown{ + Text: normalizedBody, + Original: &body, + Blobs: nil, + } + newComment.Edited = &now + + client, err := s.oauth.AuthorizedClient(r) + if err != nil { + l.Error("failed to get authorized client", "err", err) + s.pages.Notice(w, noticeId, "Failed to create comment. try again later.") + return + } + + // update the record first + exCid := comment.Cid.String() + out, err := comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ + Collection: newComment.Collection.String(), + Repo: newComment.Did.String(), + Rkey: newComment.Rkey.String(), + SwapRecord: &exCid, + Record: &lexutil.LexiconTypeDecoder{ + Val: newComment.AsRecord(), + }, + }) + if err != nil { + l.Error("failed to update comment", "err", err) + s.pages.Notice(w, noticeId, "Failed to update comment, try again later.") + return + } + + newComment.Cid = syntax.CID(out.Cid) + + tx, err := s.db.Begin() + if err != nil { + l.Error("failed to start transaction", "err", err) + s.pages.Notice(w, noticeId, "Failed to update comment, try again later.") + return + } + defer tx.Rollback() + + err = db.PutComment(tx, &newComment, references) + if err != nil { + l.Error("failed to perferom update-description query", "err", err) + s.pages.Notice(w, noticeId, "Failed to update comment, try again later.") + return + } + err = tx.Commit() + if err != nil { + l.Error("failed to commit transaction", "err", err) + s.pages.Notice(w, noticeId, "Failed to update comment, try again later.") + return + } + + s.pages.CommentBodyFragment(w, pages.CommentBodyFragmentParams{ + Comment: comment, + }) +} + +func (s *State) DeleteComment(w http.ResponseWriter, r *http.Request) { + l := s.logger.With("handler", "DeleteComment") + user := s.oauth.GetMultiAccountUser(r) + + noticeId := "comment" + ctx := r.Context() + + commentAt := r.URL.Query().Get("aturi") + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", commentAt)) + if err != nil { + l.Error("failed to fetch comment", "aturi", commentAt) + s.pages.Notice(w, noticeId, "Failed to fetch comment.") + return + } + + if comment.Did.String() != user.Active.Did { + l.Error("unauthorized action", "expectedDid", comment.Did, "gotDid", user.Active.Did) + s.pages.Notice(w, noticeId, "you are not the author of this comment") + return + } + + if comment.Deleted != nil { + s.pages.Notice(w, noticeId, "Comment already deleted") + return + } + + client, err := s.oauth.AuthorizedClient(r) + if err != nil { + l.Error("failed to get authorized client", "err", err) + s.pages.Notice(w, "comment", "Failed to delete comment.") + return + } + _, err = comatproto.RepoDeleteRecord(ctx, client, &comatproto.RepoDeleteRecord_Input{ + Collection: comment.Collection.String(), + Repo: comment.Did.String(), + Rkey: comment.Rkey.String(), + }) + if err != nil { + l.Error("failed to delete from PDS", "err", err) + s.pages.Notice(w, noticeId, "Failed to delete comment, try again later.") + return + } + + // optimistic update for htmx response + now := time.Now() + comment.Body = tangled.Markup_Markdown{} + comment.Deleted = &now + + s.pages.CommentBodyFragment(w, pages.CommentBodyFragmentParams{ + Comment: comment, + }) +} diff --git a/appview/state/router.go b/appview/state/router.go index a76271ac..541542dc 100644 --- a/appview/state/router.go +++ b/appview/state/router.go @@ -161,6 +161,16 @@ func (s *State) StandardRouter(mw *middleware.Middleware) http.Handler { r.Delete("/", s.React) }) + r.With(middleware.AuthMiddleware(s.oauth)).Route("/comment", func(r chi.Router) { + r.Get("/", s.CommentBodyFragment) + r.Get("/edit", s.EditCommentFragment) + r.Get("/reply", s.NewReplyCommentFragment) + r.Get("/reply/placeholder", s.ReplyPlaceholderFragment) + r.Post("/", s.NewComment) + r.Put("/", s.EditComment) + r.Delete("/", s.DeleteComment) + }) + r.Route("/profile", func(r chi.Router) { r.Use(middleware.AuthMiddleware(s.oauth)) r.Get("/edit-bio", s.EditBioFragment) -- 2.51.2