Don't do a full page load when clicking the follow button (#28872)

- Use htmx to perform the button request
- `hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}'` to authenticate (we
should probably learn to reuse this)
- `hx-post="{{.ContextUser.HomeLink}}?action=follow"` to send a POST
request to follow the user
- `hx-target="#profile-avatar-card"` to target the card div for
replacement
- `hx-swap="outerHTML"` to replace the card (as opposed to its inner
content) with the new card that shows the new follower count and button
color
- Change the backend response to return a `<div>` tag (the card) instead
of a redirect to the user page

# Before

![before](https://github.com/go-gitea/gitea/assets/20454870/86899d15-41c9-42ed-bd85-253b9caac7f8)

# After

![after](https://github.com/go-gitea/gitea/assets/20454870/59455d96-548c-4a81-a5b0-fab1dc1e87ef)

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
This commit is contained in:
Yarden Shoham 2024-01-21 00:37:22 +02:00 committed by GitHub
parent 14f6fcf448
commit 1df06e3f39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 13 additions and 6 deletions

View file

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -26,6 +27,10 @@ import (
shared_user "code.gitea.io/gitea/routers/web/shared/user" shared_user "code.gitea.io/gitea/routers/web/shared/user"
) )
const (
tplProfileBigAvatar base.TplName = "shared/user/profile_big_avatar"
)
// OwnerProfile render profile page for a user or a organization (aka, repo owner) // OwnerProfile render profile page for a user or a organization (aka, repo owner)
func OwnerProfile(ctx *context.Context) { func OwnerProfile(ctx *context.Context) {
if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") { if strings.Contains(ctx.Req.Header.Get("Accept"), "application/rss+xml") {
@ -309,8 +314,10 @@ func Action(ctx *context.Context) {
if err != nil { if err != nil {
log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err) log.Error("Failed to apply action %q: %v", ctx.FormString("action"), err)
ctx.JSONError(fmt.Sprintf("Action %q failed", ctx.FormString("action"))) ctx.Error(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action")))
return return
} }
ctx.JSONOK()
shared_user.PrepareContextForProfileBigAvatar(ctx)
ctx.HTML(http.StatusOK, tplProfileBigAvatar)
} }

View file

@ -1,4 +1,4 @@
<div class="ui card"> <div id="profile-avatar-card" class="ui card">
<div id="profile-avatar" class="content gt-df"> <div id="profile-avatar" class="content gt-df">
{{if eq .SignedUserID .ContextUser.ID}} {{if eq .SignedUserID .ContextUser.ID}}
<a class="image" href="{{AppSubUrl}}/user/settings" data-tooltip-content="{{ctx.Locale.Tr "user.change_avatar"}}"> <a class="image" href="{{AppSubUrl}}/user/settings" data-tooltip-content="{{ctx.Locale.Tr "user.change_avatar"}}">
@ -110,13 +110,13 @@
</li> </li>
{{end}} {{end}}
{{if and .IsSigned (ne .SignedUserID .ContextUser.ID)}} {{if and .IsSigned (ne .SignedUserID .ContextUser.ID)}}
<li class="follow"> <li class="follow" hx-headers='{"x-csrf-token": "{{.CsrfToken}}"}' hx-target="#profile-avatar-card" hx-swap="outerHTML">
{{if $.IsFollowing}} {{if $.IsFollowing}}
<button class="ui basic red button link-action" data-url="{{.ContextUser.HomeLink}}?action=unfollow"> <button hx-post="{{.ContextUser.HomeLink}}?action=unfollow" class="ui basic red button">
{{svg "octicon-person"}} {{ctx.Locale.Tr "user.unfollow"}} {{svg "octicon-person"}} {{ctx.Locale.Tr "user.unfollow"}}
</button> </button>
{{else}} {{else}}
<button class="ui basic primary button link-action" data-url="{{.ContextUser.HomeLink}}?action=follow"> <button hx-post="{{.ContextUser.HomeLink}}?action=follow" class="ui basic primary button">
{{svg "octicon-person"}} {{ctx.Locale.Tr "user.follow"}} {{svg "octicon-person"}} {{ctx.Locale.Tr "user.follow"}}
</button> </button>
{{end}} {{end}}