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

# After

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								14f6fcf448
							
						
					
				
			
			
				commit
				
					
						1df06e3f39
					
				
			
		
					 2 changed files with 13 additions and 6 deletions
				
			
		| 
						 | 
					@ -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)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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}}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue