Refactor "htmx" to "fetch action" (#37208)

The only remaining (hard) part is "templates/repo/editor/edit.tmpl", see the FIXME

By the way:

* Make "user unfollow" use basic color but not red color, indeed it is not dangerous
* Fix "org folllow" layout (use block gap instead of inline gap)
This commit is contained in:
wxiaoguang
2026-04-15 02:38:07 +08:00
committed by GitHub
parent 893df6b265
commit 17f62bfec5
10 changed files with 30 additions and 36 deletions

View File

@@ -5,7 +5,6 @@ package repo
import ( import (
"net/http" "net/http"
"strconv"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@@ -46,12 +45,7 @@ func IssueWatch(ctx *context.Context) {
return return
} }
watch, err := strconv.ParseBool(ctx.Req.PostFormValue("watch")) watch := ctx.FormBool("watch")
if err != nil {
ctx.ServerError("watch is not bool", err)
return
}
if err := issues_model.CreateOrUpdateIssueWatch(ctx, ctx.Doer.ID, issue.ID, watch); err != nil { if err := issues_model.CreateOrUpdateIssueWatch(ctx, ctx.Doer.ID, issue.ID, watch); err != nil {
ctx.ServerError("CreateOrUpdateIssueWatch", err) ctx.ServerError("CreateOrUpdateIssueWatch", err)
return return

View File

@@ -228,7 +228,7 @@ func (d *DiffLine) RenderBlobExcerptButtons(fileNameHash string, data *DiffBlobE
link += fmt.Sprintf("&pull_issue_index=%d", data.PullIssueIndex) link += fmt.Sprintf("&pull_issue_index=%d", data.PullIssueIndex)
} }
return htmlutil.HTMLFormat( return htmlutil.HTMLFormat(
`<button class="code-expander-button" hx-target="closest tr" hx-get="%s" data-hidden-comment-ids=",%s,">%s</button>`, `<button class="code-expander-button" data-fetch-sync="$closest(tr)" data-fetch-url="%s" data-hidden-comment-ids=",%s,">%s</button>`,
link, dataHiddenCommentIDs, svg.RenderHTML(svgName), link, dataHiddenCommentIDs, svg.RenderHTML(svgName),
) )
} }

View File

@@ -23,7 +23,7 @@
{{template "base/head_script" .}} {{template "base/head_script" .}}
{{template "custom/header" .}} {{template "custom/header" .}}
</head> </head>
<body hx-swap="outerHTML" hx-push-url="false"> <body>
{{template "custom/body_outer_pre" .}} {{template "custom/body_outer_pre" .}}
<div class="full height"> <div class="full height">

View File

@@ -1,4 +1,4 @@
<button class="ui basic button tw-mr-0" hx-post="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}"> <button class="ui basic button" data-fetch-method="post" data-fetch-url="{{.Org.HomeLink}}?action={{if $.IsFollowing}}unfollow{{else}}follow{{end}}">
{{if $.IsFollowing}} {{if $.IsFollowing}}
{{ctx.Locale.Tr "user.unfollow"}} {{ctx.Locale.Tr "user.unfollow"}}
{{else}} {{else}}

View File

@@ -7,9 +7,9 @@
{{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}} {{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}}
{{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}} {{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}}
</span> </span>
<span class="flex-text-inline tw-ml-auto tw-text-16 tw-whitespace-nowrap"> <span class="flex-text-block tw-ml-auto tw-text-16 tw-whitespace-nowrap">
{{if .EnableFeed}} {{if .EnableFeed}}
<a class="ui basic label button tw-mr-0" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}"> <a class="ui basic label button" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
{{svg "octicon-rss" 24}} {{svg "octicon-rss" 24}}
</a> </a>
{{end}} {{end}}

View File

@@ -23,6 +23,8 @@
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a> <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
<a class="item{{if not .CodeEditorConfig.Previewable}} tw-hidden{{end}}" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a> <a class="item{{if not .CodeEditorConfig.Previewable}} tw-hidden{{end}}" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
{{if not .IsNewFile}} {{if not .IsNewFile}}
{{/*FIXME: the related logic is totally a mess, need to completely rewrite, that's also the root reason for
why the "migrate to CodeMirror" PR took very long time on the legacy code and introduced "#file-name (filenameInput)" regressions many times*/}}
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a> <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
{{end}} {{end}}
</div> </div>

View File

@@ -1,12 +1,7 @@
<form hx-boost="true" hx-sync="this:replace" hx-target="this" method="post" action="{{.Issue.Link}}/watch"> <button class="fluid ui button" type="button" data-fetch-method="post" data-fetch-url="{{.Issue.Link}}/watch?watch={{not $.IssueWatch.IsWatching}}">
<input type="hidden" name="watch" value="{{if $.IssueWatch.IsWatching}}0{{else}}1{{end}}"> {{if $.IssueWatch.IsWatching}}
<button class="fluid ui button"> {{svg "octicon-mute" 16}} {{ctx.Locale.Tr "repo.issues.unsubscribe"}}
{{if $.IssueWatch.IsWatching}} {{else}}
{{svg "octicon-mute" 16}} {{svg "octicon-unmute" 16}} {{ctx.Locale.Tr "repo.issues.subscribe"}}
{{ctx.Locale.Tr "repo.issues.unsubscribe"}} {{end}}
{{else}} </button>
{{svg "octicon-unmute" 16}}
{{ctx.Locale.Tr "repo.issues.subscribe"}}
{{end}}
</button>
</form>

View File

@@ -115,16 +115,19 @@
{{end}} {{end}}
{{if and .IsSigned (ne .SignedUserID .ContextUser.ID)}} {{if and .IsSigned (ne .SignedUserID .ContextUser.ID)}}
{{if not .UserBlocking}} {{if not .UserBlocking}}
<li class="follow" hx-target="#profile-avatar-card" hx-indicator="#profile-avatar-card"> <li class="follow">
{{if $.IsFollowing}} {{$buttonExtraClass := Iif $.IsFollowing "" "primary"}}
<button hx-post="{{.ContextUser.HomeLink}}?action=unfollow" class="ui basic red button"> {{$followAction := Iif $.IsFollowing "unfollow" "follow"}}
<button class="ui basic {{$buttonExtraClass}} button"
data-fetch-method="post" data-fetch-url="{{.ContextUser.HomeLink}}?action={{$followAction}}"
data-fetch-sync="$body #profile-avatar-card"
>
{{if $.IsFollowing}}
{{svg "octicon-person"}} {{ctx.Locale.Tr "user.unfollow"}} {{svg "octicon-person"}} {{ctx.Locale.Tr "user.unfollow"}}
</button> {{else}}
{{else}}
<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> {{end}}
{{end}} </button>
</li> </li>
{{end}} {{end}}
<li> <li>

View File

@@ -53,8 +53,8 @@
{{DateUtils.TimeSince $one.UpdatedUnix}} {{DateUtils.TimeSince $one.UpdatedUnix}}
{{end}} {{end}}
</div> </div>
<form class="notifications-buttons" action="{{AppSubUrl}}/notifications/status?type={{$.PageType}}&page={{$.Page.Paginater.Current}}&perPage={{$.Page.Paginater.PagingNum}}" method="post" <form class="notifications-buttons form-fetch-action" action="{{AppSubUrl}}/notifications/status?type={{$.PageType}}&page={{$.Page.Paginater.Current}}&perPage={{$.Page.Paginater.PagingNum}}" method="post"
hx-boost="true" hx-target="#notification_div" hx-swap="outerHTML" data-fetch-sync="$body #notification_div"
> >
<input type="hidden" name="notification_id" value="{{$one.ID}}"> <input type="hidden" name="notification_id" value="{{$one.ID}}">
{{if ne $one.Status $statusPinned}} {{if ne $one.Status $statusPinned}}

View File

@@ -147,12 +147,12 @@ func TestCompareCodeExpand(t *testing.T) {
req := NewRequest(t, "GET", "/user1/test_blob_excerpt/compare/main...user2/test_blob_excerpt-fork:forked-branch") req := NewRequest(t, "GET", "/user1/test_blob_excerpt/compare/main...user2/test_blob_excerpt-fork:forked-branch")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
els := htmlDoc.Find(`button.code-expander-button[hx-get]`) els := htmlDoc.Find(`button.code-expander-button[data-fetch-url]`)
// all the links in the comparison should be to the forked repo&branch // all the links in the comparison should be to the forked repo&branch
assert.NotZero(t, els.Length()) assert.NotZero(t, els.Length())
for i := 0; i < els.Length(); i++ { for i := 0; i < els.Length(); i++ {
link := els.Eq(i).AttrOr("hx-get", "") link := els.Eq(i).AttrOr("data-fetch-url", "")
assert.True(t, strings.HasPrefix(link, "/user2/test_blob_excerpt-fork/blob_excerpt/")) assert.True(t, strings.HasPrefix(link, "/user2/test_blob_excerpt-fork/blob_excerpt/"))
} }
}) })