From 6992ef98fc227a60cf06e0a06b9ae2492b3d61be Mon Sep 17 00:00:00 2001
From: Yarden Shoham <git@yardenshoham.com>
Date: Mon, 5 Feb 2024 11:56:20 +0200
Subject: [PATCH] Don't do a full page load when clicking `Watch` or `Star`
 (#29001)

- The watch/unwatch button and star/unstar get their own template
- The backend returns HTML instead of redirect

---------

Signed-off-by: Yarden Shoham <git@yardenshoham.com>
Co-authored-by: John Olheiser <john.olheiser@gmail.com>
---
 routers/web/repo/repo.go          | 31 ++++++++++++++++++++++++++++++
 templates/repo/header.tmpl        | 32 ++-----------------------------
 templates/repo/star_unstar.tmpl   | 14 ++++++++++++++
 templates/repo/watch_unwatch.tmpl | 14 ++++++++++++++
 4 files changed, 61 insertions(+), 30 deletions(-)
 create mode 100644 templates/repo/star_unstar.tmpl
 create mode 100644 templates/repo/watch_unwatch.tmpl

diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 6880d64614..bede21be17 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -302,6 +302,11 @@ func CreatePost(ctx *context.Context) {
 	handleCreateError(ctx, ctxUser, err, "CreatePost", tplCreate, &form)
 }
 
+const (
+	tplWatchUnwatch base.TplName = "repo/watch_unwatch"
+	tplStarUnstar   base.TplName = "repo/star_unstar"
+)
+
 // Action response for actions to a repository
 func Action(ctx *context.Context) {
 	var err error
@@ -334,6 +339,32 @@ func Action(ctx *context.Context) {
 		return
 	}
 
+	switch ctx.Params(":action") {
+	case "watch", "unwatch":
+		ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
+	case "star", "unstar":
+		ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
+	}
+
+	switch ctx.Params(":action") {
+	case "watch", "unwatch", "star", "unstar":
+		// we have to reload the repository because NumStars or NumWatching (used in the templates) has just changed
+		ctx.Data["Repository"], err = repo_model.GetRepositoryByName(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.Name)
+		if err != nil {
+			ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
+			return
+		}
+	}
+
+	switch ctx.Params(":action") {
+	case "watch", "unwatch":
+		ctx.HTML(http.StatusOK, tplWatchUnwatch)
+		return
+	case "star", "unstar":
+		ctx.HTML(http.StatusOK, tplStarUnstar)
+		return
+	}
+
 	ctx.RedirectToFirst(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
 }
 
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index dac30af600..93102467cc 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -58,37 +58,9 @@
 						{{svg "octicon-rss" 16}}
 					</a>
 					{{end}}
-					<form method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch?redirect_to={{$.Link}}">
-						{{$.CsrfTokenHtml}}
-						<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
-							<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
-								{{if $.IsWatchingRepo}}
-									{{svg "octicon-eye-closed" 16}}<span class="text">{{ctx.Locale.Tr "repo.unwatch"}}</span>
-								{{else}}
-									{{svg "octicon-eye"}}<span class="text">{{ctx.Locale.Tr "repo.watch"}}</span>
-								{{end}}
-							</button>
-							<a class="ui basic label" href="{{.Link}}/watchers">
-								{{CountFmt .NumWatches}}
-							</a>
-						</div>
-					</form>
+					{{template "repo/watch_unwatch" $}}
 					{{if not $.DisableStars}}
-						<form method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
-							{{$.CsrfTokenHtml}}
-							<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
-								<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
-									{{if $.IsStaringRepo}}
-										{{svg "octicon-star-fill"}}<span class="text">{{ctx.Locale.Tr "repo.unstar"}}</span>
-									{{else}}
-										{{svg "octicon-star"}}<span class="text">{{ctx.Locale.Tr "repo.star"}}</span>
-									{{end}}
-								</button>
-								<a class="ui basic label" href="{{.Link}}/stars">
-									{{CountFmt .NumStars}}
-								</a>
-							</div>
-						</form>
+					{{template "repo/star_unstar" $}}
 					{{end}}
 					{{if and (not .IsEmpty) ($.Permission.CanRead $.UnitTypeCode)}}
 						<div class="ui labeled button
diff --git a/templates/repo/star_unstar.tmpl b/templates/repo/star_unstar.tmpl
new file mode 100644
index 0000000000..9c342f4065
--- /dev/null
+++ b/templates/repo/star_unstar.tmpl
@@ -0,0 +1,14 @@
+<form hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}un{{end}}star">
+	<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
+		<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
+			{{if $.IsStaringRepo}}
+				{{svg "octicon-star-fill"}}<span class="text">{{ctx.Locale.Tr "repo.unstar"}}</span>
+			{{else}}
+				{{svg "octicon-star"}}<span class="text">{{ctx.Locale.Tr "repo.star"}}</span>
+			{{end}}
+		</button>
+		<a hx-boost="false" class="ui basic label" href="{{$.RepoLink}}/stars">
+			{{CountFmt .Repository.NumStars}}
+		</a>
+	</div>
+</form>
diff --git a/templates/repo/watch_unwatch.tmpl b/templates/repo/watch_unwatch.tmpl
new file mode 100644
index 0000000000..c42bc5a9e7
--- /dev/null
+++ b/templates/repo/watch_unwatch.tmpl
@@ -0,0 +1,14 @@
+<form hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}un{{end}}watch">
+	<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
+		<button type="submit" class="ui compact small basic button"{{if not $.IsSigned}} disabled{{end}}>
+			{{if $.IsWatchingRepo}}
+				{{svg "octicon-eye-closed" 16}}<span class="text">{{ctx.Locale.Tr "repo.unwatch"}}</span>
+			{{else}}
+				{{svg "octicon-eye"}}<span class="text">{{ctx.Locale.Tr "repo.watch"}}</span>
+			{{end}}
+		</button>
+		<a hx-boost="false" class="ui basic label" href="{{.RepoLink}}/watchers">
+			{{CountFmt .Repository.NumWatches}}
+		</a>
+	</div>
+</form>