feat(ui): commit view redesign for pull request page (#7948)
Co-authored-by: 0ko <0ko@noreply.codeberg.org> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7948 Reviewed-by: 0ko <0ko@noreply.codeberg.org>
This commit is contained in:
committed by
0ko
co-authored by
0ko
parent
1d12151086
commit
cce8152879
@@ -1125,7 +1125,7 @@ func fullHashPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if hash != "" {
|
||||
text += " (" + hash + ")"
|
||||
}
|
||||
replaceContent(node, start, end, createCodeLink(urlFull, text, "commit"))
|
||||
replaceContent(node, start, end, createCodeLink(urlFull, text, ""))
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
@@ -1409,7 +1409,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
}
|
||||
|
||||
link := util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], "commit", hash)
|
||||
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit"))
|
||||
replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), ""))
|
||||
start = 0
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
|
||||
@@ -340,52 +340,52 @@ func TestRender_AutoLink(t *testing.T) {
|
||||
t.Run("Commit", func(t *testing.T) {
|
||||
// render valid commit URLs
|
||||
tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24</code></a>", "/forgejo/forgejo")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">"+TestOrgRepo+"@d8a994ef24</code></a>", "/forgejo/forgejo")
|
||||
assert(
|
||||
tmp+"#diff-2",
|
||||
"<a href=\""+tmp+"#diff-2\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>",
|
||||
"<a href=\""+tmp+"#diff-2\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>",
|
||||
TestRepoURL,
|
||||
)
|
||||
assert(
|
||||
tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc",
|
||||
"<a href=\""+tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-953bb4f01b)</code></a>",
|
||||
"<a href=\""+tmp+"#diff-953bb4f01b7c77fa18f0cd54211255051e647dbc\"><code class=\"nohighlight\">d8a994ef24 (diff-953bb4f01b)</code></a>",
|
||||
TestRepoURLWithoutSlash,
|
||||
)
|
||||
|
||||
// render other commit URLs
|
||||
tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">external-link.gitea.io/go-gitea/gitea@d8a994ef24 (diff-2)</code></a>", TestOrgRepo)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">external-link.gitea.io/go-gitea/gitea@d8a994ef24 (diff-2)</code></a>", TestOrgRepo)
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://external-link.gitea.io/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
tmp = TestAppURL + "gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">localhost:3000/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/gogits/gogs")
|
||||
|
||||
tmp = "http://localhost:3000/sub/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", TestRepoURLWithoutSlash)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">localhost:3000/sub/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub/gogits/gogs")
|
||||
|
||||
tmp = "http://localhost:3000/sub1/sub2/sub3/gogits/gogs/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL+"sub1/sub2/sub3/")()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">190d949293</code></a>", "http://localhost:3000/sub1/sub2/sub3/gogits/gogs")
|
||||
defer test.MockVariableValue(&setting.AppURL, TestAppURL)()
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "http://localhost:3000/sub1/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "http://localhost:3000/sub1/gogits/gogs")
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">localhost:3000/sub1/sub2/sub3/gogits/gogs@190d949293</code></a>", "https://external-link.gitea.io/go-gitea/gitea")
|
||||
|
||||
// if the repository happens to be named like one of the known app routes (e.g. `src`),
|
||||
// we can parse the URL correctly, if there is no sub path
|
||||
tmp = "http://localhost:3000/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
tmp = "http://localhost:3000/gogits/src/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">gogits/src@190d949293</code></a>", TestRepoURL)
|
||||
// but if there is a sub path, we cannot reliably distinguish the repo name from the app route
|
||||
tmp = "http://localhost:3000/sub/gogits/src/commit/190d9492934af498c3f669d6a2431dc5459e5b20"
|
||||
assert(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">sub/gogits@190d949293</code></a>", TestRepoURL)
|
||||
assert(tmp, "<a href=\""+tmp+"\"><code class=\"nohighlight\">sub/gogits@190d949293</code></a>", TestRepoURL)
|
||||
})
|
||||
|
||||
t.Run("Compare", func(t *testing.T) {
|
||||
|
||||
@@ -26,6 +26,11 @@ func (du *DateUtils) AbsoluteShort(time any) template.HTML {
|
||||
return dateTimeFormat("short", time)
|
||||
}
|
||||
|
||||
// AbsoluteShort renders in "Sun, 01 Jan, 2006" format
|
||||
func (du *DateUtils) AbsoluteShortWithDay(time any) template.HTML {
|
||||
return dateTimeFormat("short-with-day", time)
|
||||
}
|
||||
|
||||
// AbsoluteLong renders in "January 01, 2006" format
|
||||
func (du *DateUtils) AbsoluteLong(time any) template.HTML {
|
||||
return dateTimeFormat("long", time)
|
||||
@@ -114,6 +119,10 @@ func dateTimeFormat(format string, datetime any) template.HTML {
|
||||
case "short", "long": // date only
|
||||
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
|
||||
return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
case "short-with-day":
|
||||
attrs = append(attrs, `month="short"`, `day="numeric"`)
|
||||
attrs[0] = `weekday="short"`
|
||||
return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
case "full": // full date including time
|
||||
attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
|
||||
return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testInput = ` space @mention-user
|
||||
const testInput = ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
@@ -113,7 +113,7 @@ func TestRenderCommitBody(t *testing.T) {
|
||||
[[remote link|<a href="https://example.com/image.jpg" class="link">https://example.com/image.jpg</a>]]
|
||||
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code class="nohighlight">88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code class="nohighlight">88fc37a3c0</code></a>
|
||||
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb"><code class="nohighlight">88fc37a3c0</code></a>
|
||||
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
<span class="emoji" aria-label="thumbs up" data-alias="+1">👍</span>
|
||||
<a href="mailto:mail@domain.com" class="mailto">mail@domain.com</a>
|
||||
@@ -125,7 +125,7 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
||||
}
|
||||
|
||||
func TestRenderCommitMessage(t *testing.T) {
|
||||
expected := `space <a href="/mention-user" class="mention">@mention-user</a> `
|
||||
expected := `space <a href="/mention-user" class="mention">@mention-user</a>`
|
||||
|
||||
assert.EqualValues(t, expected, RenderCommitMessage(t.Context(), testInput, testMetas))
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderIssueTitle(t *testing.T) {
|
||||
expected := ` space @mention-user
|
||||
expected := ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
@@ -163,7 +163,7 @@ mail@domain.com
|
||||
}
|
||||
|
||||
func TestRenderRefIssueTitle(t *testing.T) {
|
||||
expected := ` space @mention-user
|
||||
expected := ` space @mention-user
|
||||
/just/a/path.bin
|
||||
https://example.com/file.bin
|
||||
[local link](file.bin)
|
||||
@@ -189,7 +189,7 @@ mail@domain.com
|
||||
}
|
||||
|
||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||
expected := `<p>space <a href="/mention-user" class="mention" rel="nofollow">@mention-user</a><br/>
|
||||
expected := `<p>space <a href="/mention-user" class="mention" rel="nofollow">@mention-user</a>
|
||||
/just/a/path.bin
|
||||
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
||||
<a href="/file.bin" rel="nofollow">local link</a>
|
||||
|
||||
@@ -28,6 +28,21 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="sha">
|
||||
{{$class := "ui sha label"}}
|
||||
{{if .Signature}}
|
||||
{{$class = (print $class " isSigned")}}
|
||||
{{if .Verification.Verified}}
|
||||
{{if eq .Verification.TrustStatus "trusted"}}
|
||||
{{$class = (print $class " isVerified")}}
|
||||
{{else if eq .Verification.TrustStatus "untrusted"}}
|
||||
{{$class = (print $class " isVerifiedUntrusted")}}
|
||||
{{else}}
|
||||
{{$class = (print $class " isVerifiedUnmatched")}}
|
||||
{{end}}
|
||||
{{else if .Verification.Warning}}
|
||||
{{$class = (print $class " isWarning")}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{$commitShaLink := ""}}
|
||||
{{if $.PageIsWiki}}
|
||||
{{$commitShaLink = (printf "%s/wiki/commit/%s" $commitRepoLink (PathEscape .ID.String))}}
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
<h4 class="ui top attached header commits-table tw-flex tw-items-center tw-justify-between">
|
||||
<div class="commits-table-left tw-flex tw-items-center">
|
||||
{{if or .PageIsCommits (gt .CommitCount 0)}}
|
||||
{{ctx.Locale.TrPluralString .CommitCount "counters.n_commits" (ctx.Locale.PrettyNumber .CommitCount)}}
|
||||
{{else if .IsNothingToCompare}}
|
||||
{{ctx.Locale.Tr "repo.commits.nothing_to_compare"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .IsDiffCompare}}
|
||||
<div class="commits-table-right tw-whitespace-nowrap">
|
||||
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="primary sha label tw-mx-0">
|
||||
<span class="shortsha">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</span>
|
||||
</a>
|
||||
...
|
||||
<a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="primary sha label tw-mx-0">
|
||||
<span class="shortsha">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</span>
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</h4>
|
||||
|
||||
{{if .PageIsCommits}}
|
||||
<h4 class="ui top attached header commits-table tw-flex tw-items-center tw-justify-between">
|
||||
<div class="commits-table-left tw-flex tw-items-center">
|
||||
{{if or .PageIsCommits (gt .CommitCount 0)}}
|
||||
{{ctx.Locale.TrPluralString .CommitCount "counters.n_commits" (ctx.Locale.PrettyNumber .CommitCount)}}
|
||||
{{else if .IsNothingToCompare}}
|
||||
{{ctx.Locale.Tr "repo.commits.nothing_to_compare"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .IsDiffCompare}}
|
||||
<div class="commits-table-right tw-whitespace-nowrap">
|
||||
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
|
||||
...
|
||||
<a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="ui green sha label tw-mx-0">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search">
|
||||
<div class="ui small fluid action input">
|
||||
@@ -34,7 +29,11 @@
|
||||
{{end}}
|
||||
|
||||
{{if and .Commits (gt .CommitCount 0)}}
|
||||
{{template "repo/commits_list" .}}
|
||||
{{if .PageIsPullCommits}}
|
||||
{{template "repo/pulls/commits_list" .}}
|
||||
{{else}}
|
||||
{{template "repo/commits_list" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .OldFilename}}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
{{$commitRepoLink := $.RepoLink}}
|
||||
{{if $.CommitRepoLink}}{{$commitRepoLink = $.CommitRepoLink}}{{end}}
|
||||
{{$prevDate := ""}}
|
||||
{{range .Commits}}
|
||||
{{$commitTime := .Committer.When}}
|
||||
{{if not $commitTime}}{{$commitTime = .Author.When}}{{end}}
|
||||
{{$commitDate := $commitTime.Format "2006-01-02"}}
|
||||
{{if ne $commitDate $prevDate}}
|
||||
{{if ne $prevDate ""}}</div></article>{{end}} <!-- close previous date group -->
|
||||
{{$prevDate = $commitDate}}
|
||||
|
||||
<!-- Start a new commit group -->
|
||||
<article class="commit-group">
|
||||
<h4>{{DateUtils.AbsoluteShortWithDay $commitTime}}</h4>
|
||||
<div class="commits tw-divide-y tw-divide-[--color-secondary]">
|
||||
{{end}}
|
||||
|
||||
<div class="commit">
|
||||
<div class="author">
|
||||
{{$userName := .Author.Name}}
|
||||
{{if .User}}
|
||||
{{if and .User.FullName DefaultShowFullName}}
|
||||
{{$userName = .User.FullName}}
|
||||
{{end}}
|
||||
{{ctx.AvatarUtils.Avatar .User 28}}<a class="muted author-name" href="{{.User.HomeLink}}">{{$userName}}</a>
|
||||
{{else}}
|
||||
{{ctx.AvatarUtils.AvatarByEmail .Author.Email .Author.Name 28}}
|
||||
<span class="author-name">{{$userName}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="message">
|
||||
{{$commitLink := (printf "%s/pulls/%d/commits/%s" $commitRepoLink $.Issue.Index (PathEscape .ID.String))}}
|
||||
<div class="message-wrapper">
|
||||
{{RenderCommitMessageLinkSubject $.Context .Message $commitLink ($.Repository.ComposeMetas ctx)}}
|
||||
{{if IsMultilineCommitMessage .Message}}
|
||||
<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button>
|
||||
{{end}}
|
||||
<span class="ci-status">
|
||||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
|
||||
</span>
|
||||
{{if IsMultilineCommitMessage .Message}}
|
||||
<pre class="commit-body tw-hidden">{{RenderCommitBody $.Context .Message ($.Repository.ComposeMetas ctx)}}</pre>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<div class="button-sequence">
|
||||
{{$commitShaLink := (printf "%s/pulls/%d/commits/%s" $commitRepoLink $.Issue.Index (PathEscape .ID.String))}}
|
||||
{{template "repo/shabox" (dict
|
||||
"sha1" .ID.String
|
||||
"commitLink" $commitShaLink
|
||||
"signature" .Signature
|
||||
"verification" .Verification
|
||||
)}}
|
||||
<button class="btn interact-bg tw-p-2" data-clipboard-text="{{.ID}}">
|
||||
{{svg "octicon-copy"}}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<details class="dropdown dir-rtl">
|
||||
<summary class="border" aria-label="{{ctx.Locale.Tr "toggle_menu"}}">
|
||||
{{svg "octicon-kebab-horizontal"}}
|
||||
</summary>
|
||||
<div class="content">
|
||||
<ul>
|
||||
{{if $.FileName}}
|
||||
<li>
|
||||
<a
|
||||
class="interact item"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_single_diff"}}"
|
||||
href="{{printf "%s/commit/%s?files=%s" $commitRepoLink (PathEscape .ID.String) (PathEscapeSegments $.FileName)}}">
|
||||
{{svg "octicon-file-diff"}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
<li>
|
||||
<a
|
||||
class="interact item"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_path"}}"
|
||||
href="{{if $.FileName}}{{printf "%s/src/commit/%s/%s" $commitRepoLink (PathEscape .ID.String) (PathEscapeSegments $.FileName)}}{{else}}{{printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}}{{end}}">
|
||||
{{svg "octicon-file-code"}} {{ctx.Locale.Tr "repo.commits.view_path"}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="date">
|
||||
{{if .Committer}}{{DateUtils.TimeSince .Committer.When}}{{else}}{{DateUtils.TimeSince .Author.When}}{{end}}
|
||||
</div>
|
||||
|
||||
<div class="commit-buttons">
|
||||
<button class="btn interact-bg tw-p-2" data-tooltip-content="{{ctx.Locale.Tr "copy_hash"}}" data-clipboard-text="{{.ID}}">{{svg "octicon-copy"}}</button>
|
||||
{{if $.FileName}}
|
||||
<a
|
||||
class="btn interact-bg tw-p-2"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_single_diff"}}"
|
||||
href="{{printf "%s/commit/%s?files=%s" $commitRepoLink (PathEscape .ID.String) (PathEscapeSegments $.FileName)}}">
|
||||
{{svg "octicon-file-diff"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a
|
||||
class="btn interact-bg tw-p-2"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.commits.view_path"}}"
|
||||
href="{{if $.FileName}}{{printf "%s/src/commit/%s/%s" $commitRepoLink (PathEscape .ID.String) (PathEscapeSegments $.FileName)}}{{else}}{{printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}}{{end}}">
|
||||
{{svg "octicon-file-code"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div></div> <!-- Final group close -->
|
||||
@@ -1,7 +1,9 @@
|
||||
{{if .IsSigned}}
|
||||
<style>
|
||||
.comment,
|
||||
.commit-summary,
|
||||
.singular-commit,
|
||||
.commit-header,
|
||||
.message-wrapper,
|
||||
.commit-body {
|
||||
.mention[href="{{AppSubUrl}}/{{.SignedUser.Name}}" i] {
|
||||
background-color: var(--color-primary-alpha-30);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// @watch start
|
||||
// templates/repo/pulls/commits_list.tmpl
|
||||
// web_src/css/repo.css
|
||||
// web_src/css/repo/commit-list.css
|
||||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.describe(`PR commits`, () => {
|
||||
test.use({user: 'user2'});
|
||||
|
||||
test('Any layout', async ({page}) => {
|
||||
const response = await page.goto('/user2/repo1/pulls/3/commits');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
const commitGroup = page.locator('.commit-group:first-of-type');
|
||||
|
||||
// Date group visibility test
|
||||
await expect(commitGroup).toBeVisible();
|
||||
await expect(commitGroup.locator('h4')).toBeVisible();
|
||||
|
||||
const commit = commitGroup.locator('.commit:first-child');
|
||||
|
||||
await expect(commit).toHaveCSS('display', 'grid');
|
||||
});
|
||||
|
||||
test('Mobile responsive layout checks', async ({page, isMobile}) => {
|
||||
test.skip(!isMobile);
|
||||
|
||||
const response = await page.goto('/user2/repo1/pulls/3/commits');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
// Mobile-specific visibility test
|
||||
const commit = page.locator('.commit-group:first-of-type .commit:first-child');
|
||||
await expect(commit.locator('.commit-buttons')).toBeHidden();
|
||||
await expect(commit.locator('.button-sequence button[data-clipboard-text]')).toBeVisible();
|
||||
|
||||
// Mobile-specific grid positioning
|
||||
// toHaveCSS returns absolute values in px with decimals. This matcher only
|
||||
// checks if the string has two \S+px separated by one \s+
|
||||
await expect(commit).toHaveCSS('grid-template-columns', /^\S+px\s+\S+px$/);
|
||||
await expect(commit.locator('.author')).toHaveCSS('grid-column-start', '1');
|
||||
await expect(commit.locator('.date')).toHaveCSS('grid-column-start', '2');
|
||||
await expect(commit.locator('.message')).toHaveCSS('grid-column-end', 'span 2');
|
||||
|
||||
// Horizontal scrolling to check for overflow
|
||||
await expect(page.locator('.commits').first()).not.toHaveCSS('overflow-x', 'scroll');
|
||||
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Dropdown check in mobile viewport', async ({page, isMobile}) => {
|
||||
test.skip(!isMobile);
|
||||
|
||||
const response = await page.goto('/user2/repo1/pulls/3/commits');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
const commit = page.locator('.commit-group:first-of-type .commit:first-child');
|
||||
|
||||
// Click dropdown btn
|
||||
const dropdown = commit.locator('details.dropdown');
|
||||
await expect(dropdown).toBeVisible();
|
||||
await dropdown.locator('summary').click();
|
||||
|
||||
// List menu items of dropdown
|
||||
const menuItem = commit.locator('details.dropdown ul li a'); // repo_path; always visible
|
||||
await expect(menuItem).toHaveAttribute('href', '/user2/repo1/src/commit/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd');
|
||||
await menuItem.click();
|
||||
await page.waitForURL(/.*\/user2\/repo1\/src\/commit\/5f22f7d0d95d614d25a5b68592adb345a4b5c7f/);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Desktop responsive layout checks', async ({page, isMobile}) => {
|
||||
test.skip(isMobile);
|
||||
|
||||
const response = await page.goto('/user2/repo1/pulls/3/commits');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
// Desktop-specific visibility test
|
||||
const commit = page.locator('.commit-group:first-of-type .commit:first-child');
|
||||
await expect(commit.locator('.commit-buttons')).toBeVisible();
|
||||
await expect(commit.locator('.button-sequence button[data-clipboard-text]')).toBeHidden();
|
||||
await expect(commit.locator('details.dropdown')).toBeHidden();
|
||||
|
||||
// Desktop layout is has specific grid-template-columns
|
||||
// toHaveCSS returns absolute values in px with decimals. This matcher only
|
||||
// checks if the string has five \S+px separated by four \s+
|
||||
await expect(commit).toHaveCSS('grid-template-columns', /^\S+px\s+\S+px\s+\S+px\s+\S+px\s+\S+px$/);
|
||||
|
||||
await screenshot(page);
|
||||
});
|
||||
});
|
||||
@@ -115,7 +115,7 @@ test('PR: Navigate by single commit', async ({page}) => {
|
||||
const response = await page.goto('/user2/repo1/pulls/3/commits');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
await page.locator('tbody.commit-list td.message a').nth(1).click();
|
||||
await page.locator('.commit .message-wrapper a').nth(1).click();
|
||||
await page.waitForURL(/.*\/user2\/repo1\/pulls\/3\/commits\/4a357436d925b5c974181ff12a994538ddc5a269/);
|
||||
await screenshot(page);
|
||||
|
||||
|
||||
@@ -724,7 +724,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) {
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Get first commit URL
|
||||
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
|
||||
commitURL, exists := doc.doc.Find(".commits .commit .sha.label").Last().Attr("href")
|
||||
assert.True(t, exists)
|
||||
assert.NotEmpty(t, commitURL)
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ func TestPullCommitLinks(t *testing.T) {
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
commitSha := htmlDoc.Find(".commit-list td.sha a.sha.label").First()
|
||||
commitSha := htmlDoc.Find(".commits .commit a").First()
|
||||
commitShaHref, _ := commitSha.Attr("href")
|
||||
assert.Equal(t, "/user2/repo1/pulls/3/commits/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commitShaHref)
|
||||
|
||||
commitLink := htmlDoc.Find(".commit-list td.message a").First()
|
||||
commitLink := htmlDoc.Find(".commit .message-wrapper a").First()
|
||||
commitLinkHref, _ := commitLink.Attr("href")
|
||||
assert.Equal(t, "/user2/repo1/pulls/3/commits/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commitLinkHref)
|
||||
}
|
||||
@@ -75,12 +75,12 @@ func TestPullCommitLinksSHA256(t *testing.T) {
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
commitSha := htmlDoc.Find(".commit-list td.sha a.sha.label").First()
|
||||
commitSha := htmlDoc.Find(".commits .commit a").First()
|
||||
commitShaHref, commitShaOk := commitSha.Attr("href")
|
||||
assert.True(t, commitShaOk)
|
||||
assert.Equal(t, "/user2/repo256/pulls/1/commits/004581b3bb63754502364664021404490ee747ce58e98d27c046f2e46f5f2f55", commitShaHref)
|
||||
|
||||
commitLink := htmlDoc.Find(".commit-list td.message a").First()
|
||||
commitLink := htmlDoc.Find(".commit .message-wrapper a").First()
|
||||
commitLinkHref, commitLinkOk := commitLink.Attr("href")
|
||||
assert.True(t, commitLinkOk)
|
||||
assert.Equal(t, "/user2/repo256/pulls/1/commits/004581b3bb63754502364664021404490ee747ce58e98d27c046f2e46f5f2f55", commitLinkHref)
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestPullCreate_CommitStatus(t *testing.T) {
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
// Get first commit URL
|
||||
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
|
||||
commitURL, exists := doc.doc.Find(".commits .commit .sha.label").Last().Attr("href")
|
||||
assert.True(t, exists)
|
||||
assert.NotEmpty(t, commitURL)
|
||||
|
||||
@@ -84,12 +84,12 @@ func TestPullCreate_CommitStatus(t *testing.T) {
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
doc = NewHTMLParser(t, resp.Body)
|
||||
|
||||
commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href")
|
||||
commitURL, exists = doc.doc.Find(".commits .commit .sha.label").Last().Attr("href")
|
||||
assert.True(t, exists)
|
||||
assert.NotEmpty(t, commitURL)
|
||||
assert.Equal(t, commitID, path.Base(commitURL))
|
||||
|
||||
cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class")
|
||||
cls, ok := doc.doc.Find(".commits .commit .message-wrapper .commit-status").Last().Attr("class")
|
||||
assert.True(t, ok)
|
||||
assert.Contains(t, cls, statesIcons[status])
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
@import "./form.css";
|
||||
|
||||
@import "./repo.css";
|
||||
@import "./repo/commit-list.css";
|
||||
@import "./repo/file-view.css";
|
||||
@import "./repo/release-tag.css";
|
||||
@import "./repo/issue-card.css";
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
font-family: var(--fonts-monospace);
|
||||
font-size: 13px;
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.primary.sha.label {
|
||||
@@ -24,13 +23,13 @@
|
||||
}
|
||||
|
||||
.sha.label .shortsha {
|
||||
padding: 0.33rem 0.5rem;
|
||||
padding: 0.1rem 0.5rem;
|
||||
}
|
||||
|
||||
.sha.label .signature {
|
||||
color: var(--color-text);
|
||||
background: var(--color-light);
|
||||
padding: 0.25rem 0.33rem;
|
||||
padding: 0.15rem 0.33rem;
|
||||
border-inline-start: 1px solid var(--color-light-border);
|
||||
border-start-end-radius: inherit;
|
||||
border-end-end-radius: inherit;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/* Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
|
||||
/* Modern layout for templates/repo/pulls/commits_list.tmpl using responsive grid */
|
||||
|
||||
.commit-group {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
h4 {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.commits {
|
||||
background: var(--color-box-body);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius-medium);
|
||||
}
|
||||
|
||||
.commits .commit {
|
||||
display: grid;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem 1rem;
|
||||
grid-template-columns: minmax(8ch, 15%) 1fr auto minmax(12ch, 10%) auto;
|
||||
align-items: center;
|
||||
|
||||
.date {
|
||||
max-width: 16ch;
|
||||
white-space: nowrap;
|
||||
justify-self: end;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.commits .commit .author {
|
||||
display: flex;
|
||||
column-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.commits .commit .author .author-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
align-self: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.commits .commit .sha.label {
|
||||
/* Unwanted property of .sha.label, needs fix upstream in hashbox.css */
|
||||
margin-inline: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.commit-group .commits .commit .sha.label .signature {
|
||||
/* Hide signature badge (lock icon) on narrow layout */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.commit-group .commits .commit {
|
||||
details.dropdown,
|
||||
.button-sequence button[data-clipboard-text] {
|
||||
/* Hide additional copy button and dropdown opener that are exclusive to mobile layout */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.button-sequence {
|
||||
display: contents;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 639.9px) {
|
||||
.commit-group .commits .commit {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
.author {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.date {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.message {
|
||||
grid-row: 2;
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
.actions {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.commit-buttons {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user