From de6768ed546fbdd6237798c1fdb0715e7950ecfb Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Mon, 4 Mar 2024 09:02:51 +0800
Subject: [PATCH] Add an trailing slash to dashboard links (#29555)

Fix #29533, and add some tests for "base/paginate.tmpl"

(cherry picked from commit 8553b4600e3035b6f6ad6907c37cebd013fa4d64)

Conflicts:
	services/contexttest/context_tests.go
	trivial conflict because
	"Improve user experience for outdated comments" was skipped
---
 modules/translation/mock.go           |  4 +++-
 modules/translation/translation.go    |  2 +-
 routers/web/user/home_test.go         | 17 +++++++++++++++++
 services/contexttest/context_tests.go |  6 ++++--
 templates/base/paginate.tmpl          | 14 ++++++++------
 5 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/modules/translation/mock.go b/modules/translation/mock.go
index 1f0559f38d..18fbc1044a 100644
--- a/modules/translation/mock.go
+++ b/modules/translation/mock.go
@@ -9,7 +9,9 @@ import (
 )
 
 // MockLocale provides a mocked locale without any translations
-type MockLocale struct{}
+type MockLocale struct {
+	Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
+}
 
 var _ Locale = (*MockLocale)(nil)
 
diff --git a/modules/translation/translation.go b/modules/translation/translation.go
index b7c18f610a..36ae58a9f1 100644
--- a/modules/translation/translation.go
+++ b/modules/translation/translation.go
@@ -144,7 +144,7 @@ func Match(tags ...language.Tag) language.Tag {
 // locale represents the information of localization.
 type locale struct {
 	i18n.Locale
-	Lang, LangName string // these fields are used directly in templates: .i18n.Lang
+	Lang, LangName string // these fields are used directly in templates: ctx.Locale.Lang
 	msgPrinter     *message.Printer
 }
 
diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go
index 3f5fd26689..1cc9886308 100644
--- a/routers/web/user/home_test.go
+++ b/routers/web/user/home_test.go
@@ -11,6 +11,8 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
+	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/contexttest"
 
 	"github.com/stretchr/testify/assert"
@@ -113,3 +115,18 @@ func TestMilestonesForSpecificRepo(t *testing.T) {
 	assert.Len(t, ctx.Data["Milestones"], 1)
 	assert.Len(t, ctx.Data["Repos"], 2) // both repo 42 and 1 have milestones and both are owned by user 2
 }
+
+func TestDashboardPagination(t *testing.T) {
+	ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
+	page := context.NewPagination(10, 3, 1, 3)
+
+	setting.AppSubURL = "/SubPath"
+	out, err := ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
+	assert.NoError(t, err)
+	assert.Contains(t, out, `<a class=" item navigation" href="/SubPath/?page=2">`)
+
+	setting.AppSubURL = ""
+	out, err = ctx.RenderToHTML("base/paginate", map[string]any{"Link": setting.AppSubURL, "Page": page})
+	assert.NoError(t, err)
+	assert.Contains(t, out, `<a class=" item navigation" href="/?page=2">`)
+}
diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go
index dbce80bfea..431017a30d 100644
--- a/services/contexttest/context_tests.go
+++ b/services/contexttest/context_tests.go
@@ -12,6 +12,7 @@ import (
 	"net/url"
 	"strings"
 	"testing"
+	"time"
 
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -60,8 +61,9 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
 	base.Data = middleware.GetContextData(req.Context())
 	base.Locale = &translation.MockLocale{}
 
-	ctx := context.NewWebContext(base, &MockRender{}, nil)
-
+	ctx := context.NewWebContext(base, opt.Render, nil)
+	ctx.PageData = map[string]any{}
+	ctx.Data["PageStartTime"] = time.Now()
 	chiCtx := chi.NewRouteContext()
 	ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
 	return ctx, resp
diff --git a/templates/base/paginate.tmpl b/templates/base/paginate.tmpl
index 503817c339..ef7d0b341b 100644
--- a/templates/base/paginate.tmpl
+++ b/templates/base/paginate.tmpl
@@ -1,13 +1,15 @@
-{{$paginationLink := .Page.GetParams}}
+{{$paginationParams := .Page.GetParams}}
+{{$paginationLink := $.Link}}
+{{if eq $paginationLink AppSubUrl}}{{$paginationLink = print $paginationLink "/"}}{{end}}
 {{with .Page.Paginater}}
 	{{if gt .TotalPages 1}}
 		<div class="center page buttons">
 			<div class="ui borderless pagination menu">
-				<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$.Link}}{{if $paginationLink}}?{{$paginationLink}}{{end}}"{{end}}>
+				<a class="{{if .IsFirst}}disabled{{end}} item navigation" {{if not .IsFirst}}href="{{$paginationLink}}{{if $paginationParams}}?{{$paginationParams}}{{end}}"{{end}}>
 					{{svg "gitea-double-chevron-left" 16 "gt-mr-2"}}
 					<span class="navigation_label">{{ctx.Locale.Tr "admin.first_page"}}</span>
 				</a>
-				<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$.Link}}?page={{.Previous}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
+				<a class="{{if not .HasPrevious}}disabled{{end}} item navigation" {{if .HasPrevious}}href="{{$paginationLink}}?page={{.Previous}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
 					{{svg "octicon-chevron-left" 16 "gt-mr-2"}}
 					<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.previous"}}</span>
 				</a>
@@ -15,14 +17,14 @@
 					{{if eq .Num -1}}
 						<a class="disabled item">...</a>
 					{{else}}
-						<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$.Link}}?page={{.Num}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>{{.Num}}</a>
+						<a class="{{if .IsCurrent}}active {{end}}item gt-content-center" {{if not .IsCurrent}}href="{{$paginationLink}}?page={{.Num}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>{{.Num}}</a>
 					{{end}}
 				{{end}}
-				<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$.Link}}?page={{.Next}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
+				<a class="{{if not .HasNext}}disabled{{end}} item navigation" {{if .HasNext}}href="{{$paginationLink}}?page={{.Next}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
 					<span class="navigation_label">{{ctx.Locale.Tr "repo.issues.next"}}</span>
 					{{svg "octicon-chevron-right" 16 "gt-ml-2"}}
 				</a>
-				<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$.Link}}?page={{.TotalPages}}{{if $paginationLink}}&{{$paginationLink}}{{end}}"{{end}}>
+				<a class="{{if .IsLast}}disabled{{end}} item navigation" {{if not .IsLast}}href="{{$paginationLink}}?page={{.TotalPages}}{{if $paginationParams}}&{{$paginationParams}}{{end}}"{{end}}>
 					<span class="navigation_label">{{ctx.Locale.Tr "admin.last_page"}}</span>
 					{{svg "gitea-double-chevron-right" 16 "gt-ml-2"}}
 				</a>