Compare commits

..

13 Commits

Author SHA1 Message Date
Jonathan Miller e998c494b2 fix: resolve tech-debt batch 7 — dead routes, stale FIXMEs, feed revision
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 20s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
- chore: remove stale mustNotBeArchived FIXME (CanEnableEditor no longer exists)
- fix(routes): remove dead /cherry-pick/{sha} route — replaced by /_cherrypick/
- fix(feed): use full ref name instead of ShortName for file feed revision

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 13:46:06 -05:00
Jonathan Miller eafd5320e3 fix(build): remove unused imports causing compile errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 13:41:35 -05:00
Jonathan Miller 1ed8e463df chore: remove stale TODO comments (#328, #332)
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 9s
Universal: PR Check / Validate PR (pull_request) Failing after 17s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 34s
- Remove TODO from OAuth2 regenerate secret template — the
  functionality was already fully implemented
- Remove stub TODO test comments for TestMerge, TestNewPullRequest,
  TestAddTestPullRequestTask — stale for years
- Remove stale GetProjectsMode TODO — method is needed by templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 13:38:18 -05:00
Jonathan Miller cd0a803341 fix(issues): deprecate Issue.Ref branch selector UI (#307)
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 24s
Remove the branch/tag selector from the issue sidebar and new issue
form. The Issue.Ref field was added 8 years ago and provides minimal
value — it only saves a branch name and optionally restricts which
branch's commits can auto-close the issue.

Removed:
- Branch selector template (branch_selector_field.tmpl)
- Sidebar and new-form includes
- Ref badge from issue lists
- POST /{index}/ref web route and UpdateIssueRef handler
- GetRefEndNamesAndURLs calls from list renderers
- JS handler for branch selector dropdown

Preserved:
- DB column (Issue.Ref) — still used by commit-close logic
- API response still includes ref for backward compatibility

Closes #307

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 13:31:11 -05:00
Jonathan Miller 278645113b fix(cron): add missing translation for cleanup_expired_license_keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 13:14:45 -05:00
Jonathan Miller 7e312077e7 fix(docker): disable openssh s6 service in Dockerfile
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 8s
Universal: PR Check / Validate PR (pull_request) Failing after 18s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 43s
Bake the noop script directly into the image layer so openssh never
starts. We use external SSH (port 2222) via host networking, not the
container's sshd.

Fixes: #372

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 12:13:21 -05:00
Jonathan Miller 40130d1793 docs: update CHANGELOG for v05.15.00 dev cycle
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Successful in 20s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 11:55:21 -05:00
Jonathan Miller 79724b5bc9 feat(ui): add generic combo-multiselect component
Add a reusable multiselect dropdown component inspired by the issue
sidebar label picker, but decoupled from issue-specific logic.

Components:
- templates/shared/combolist.tmpl — generic template accepting Items,
  Name, Title, SelectedValues parameters
- web_src/js/features/combo-multiselect.ts — lightweight JS init that
  handles check/uncheck, search, and hidden input updates
- web_src/css/modules/combo-multiselect.css — check-mark visibility
  and selected-items list styling

Usage in any template:
  {{template "shared/combolist" dict
    "Name" "channels"
    "Title" "Update Channels"
    "Items" .AvailableChannels
    "SelectedValues" .SelectedChannelIDs
  }}

Items must have .Value and .Label fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 11:41:08 -05:00
Jonathan Miller 88e210bffb fix(build): use slices.Collect for maps.Values (Go 1.23+ compat)
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
Universal: PR Check / Validate PR (pull_request) Failing after 6s
PR RC Release / Build RC Release (pull_request) Failing after 25s
In Go 1.23+, maps.Values returns iter.Seq not a slice.
Use slices.Collect() to materialize the iterator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 11:38:00 -05:00
jmiller f73f628403 Merge pull request 'fix: tech-debt batch 5 — CSS cleanup' (#361) from fix/tech-debt-batch-5 into dev
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 6s
Branch Cleanup / Delete merged branch (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 21s
2026-05-31 16:19:35 +00:00
Jonathan Miller c07443aa87 fix: resolve tech-debt batch 5 — CSS cleanup
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Branch Policy Check / Verify merge target (pull_request) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 5s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
- fix(css): use calc(infinity * 1px) for --border-radius-full instead
  of magic 99999px value (supported in all modern browsers)
- fix(css): remove legacy .center class from 2015, replace 6 usages
  with tw-text-center utility class

Refs #318

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 11:19:01 -05:00
jmiller c1c7556916 Merge pull request 'fix: tech-debt batch 4 — parseIssueHref, job limit, stale TODOs' (#360) from fix/tech-debt-batch-4 into dev 2026-05-31 16:13:34 +00:00
Jonathan Miller 889f64009b fix: resolve tech-debt batch 4 — parseIssueHref, job limit, stale TODOs
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Branch Policy Check / Verify merge target (pull_request) Successful in 2s
PR RC Release / Build RC Release (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 8s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
- fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl
  for correct issue link parsing with sub-path deployments
- fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs,
  rejecting workflows that exceed the GitHub-compatible limit
- chore: remove stale TODO comment on OAuth redirect route

Refs #325, #334

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-31 11:12:44 -05:00
35 changed files with 241 additions and 173 deletions
+45
View File
@@ -3,6 +3,51 @@
This changelog goes through the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com).
## [v1.26.1-moko.05.15.00] - 2026-05-31
* BREAKING CHANGES
* Deprecated Issue.Ref branch selector UI (#307)
* Removed branch/tag selector from issue sidebar and new issue form
* Removed ref badge from issue lists
* Removed POST /ref web route and UpdateIssueRef handler
* DB column and commit-close logic preserved for backward compatibility
* API create/edit still accept `ref` field (no-op) for backward compat
* FEATURES
* feat(ui): add generic combo-multiselect component (#361)
* Reusable dropdown with search, checkable items, and selected-items display
* Template: `shared/combolist.tmpl` — accepts Items, Name, Title, SelectedValues
* Decoupled from issue sidebar — works in any form context
* feat(updates): extension metadata settings for update feed generation
* feat(licenses): platform enforcement, key deletion, expired key cleanup
* feat(licenses): store keys in plaintext, show full key with copy button
* TECH DEBT
* chore: full namespace migration from git.mokoconsulting.tech to code.mokoconsulting.tech (#336, #337, #344)
* Go module path, all imports, template URLs, workflow configs (2,276 files)
* fix(blame): set HasSourceRenderedToggle for renderable files (#344)
* fix(settings): translate team permission strings via data-locale attributes (#344)
* fix(dropzone): use relative path for non-image attachment markdown links (#344)
* fix(templates): add required validation to issue dropdown fields (#350)
* refactor(ts): remove redundant `handled` field from MarkdownHandleIndentionResult (#350)
* refactor(go): rename HasOrgOrUserVisible to IsOwnerVisibleToDoer (#350)
* refactor(go): replace ValuesRepository with maps.Values (Go 1.21+) (#357)
* refactor(go): remove CanEnableEditor wrapper, use CanContentChange directly (#357)
* fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl (#360)
* fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs (#360)
* fix(css): use calc(infinity * 1px) for --border-radius-full (#361)
* fix(css): remove legacy .center class from 2015, replace with tw-text-center (#361)
* chore: remove stale TODO from OAuth2 regenerate secret (already implemented) (#332)
* chore: remove stale pull request test stub TODOs (#328)
* chore: remove stale GetProjectsMode TODO
* chore: remove stale mustNotBeArchived/mustEnableEditor FIXME from API
* fix(routes): remove dead legacy /cherry-pick/{sha} route (replaced by /_cherrypick/)
* fix(feed): use full ref name instead of ShortName for file feed revision
* BUGFIXES
* fix(build): use slices.Collect for maps.Values (Go 1.23+ compat)
* fix(licenses): remove duplicate DeleteLicenseKey declaration
* fix(licenses): only show licenses tab when licensing is enabled
* fix(licenses): show feed URLs based on repo update platform setting
* fix(updates): correct dlid prefix and align XML with Joomla standard
## [v1.26.1-moko.05.06.00] - 2026-05-30
* FEATURES
+3
View File
@@ -77,6 +77,9 @@ RUN addgroup \
COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.mokoconsulting.tech/MokoConsulting/MokoGitea/gitea /app/gitea/gitea
# Disable openssh s6 service — we use external SSH (port 2222 via host).
RUN printf '#!/bin/sh\nexec sleep infinity\n' > /etc/s6/openssh/run && chmod 755 /etc/s6/openssh/run
ENV USER=git
ENV GITEA_CUSTOM=/data/gitea
-1
View File
@@ -20,7 +20,6 @@ import (
// MaxJobNumPerRun is the maximum number of jobs in a single run.
// https://docs.github.com/en/actions/reference/limits#existing-system-limits
// TODO: check this limit when creating jobs
const MaxJobNumPerRun = 256
// ActionRunJob represents a job of a run
-6
View File
@@ -80,10 +80,6 @@ func testPullRequestLoadHeadRepo(t *testing.T) {
assert.Equal(t, pr.HeadRepoID, pr.HeadRepo.ID)
}
// TODO TestMerge
// TODO TestNewPullRequest
func testPullRequestsNewest(t *testing.T) {
prs, count, err := issues_model.PullRequests(t.Context(), 1, &issues_model.PullRequestsOptions{
ListOptions: db.ListOptions{
@@ -274,8 +270,6 @@ func testPullRequestUpdateCols(t *testing.T) {
unittest.CheckConsistencyFor(t, pr)
}
// TODO TestAddTestPullRequestTask
func testPullRequestIsWorkInProgress(t *testing.T) {
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2})
pr.LoadIssue(t.Context())
+3 -2
View File
@@ -13,6 +13,7 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"maps"
"slices"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/optional"
@@ -47,12 +48,12 @@ func (repos RepositoryList) Swap(i, j int) {
// ValuesRepository converts a repository map to a list
func ValuesRepository(m map[int64]*Repository) []*Repository {
return maps.Values(m)
return slices.Collect(maps.Values(m))
}
// RepositoryListOfMap make list from values of map
func RepositoryListOfMap(repoMap map[int64]*Repository) RepositoryList {
return RepositoryList(maps.Values(repoMap))
return RepositoryList(slices.Collect(maps.Values(repoMap)))
}
func (repos RepositoryList) LoadUnits(ctx context.Context) error {
+1 -2
View File
@@ -195,7 +195,6 @@ type ProjectsConfig struct {
// FromDB fills up a ProjectsConfig from serialized format.
func (cfg *ProjectsConfig) FromDB(bs []byte) error {
// TODO: remove GetProjectsMode, only use ProjectsMode
cfg.ProjectsMode = ProjectsModeAll
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
}
@@ -205,11 +204,11 @@ func (cfg *ProjectsConfig) ToDB() ([]byte, error) {
return json.Marshal(cfg)
}
// GetProjectsMode returns the configured projects mode, defaulting to "all".
func (cfg *ProjectsConfig) GetProjectsMode() ProjectsMode {
if cfg.ProjectsMode != "" {
return cfg.ProjectsMode
}
return ProjectsModeAll
}
+1
View File
@@ -3075,6 +3075,7 @@
"admin.dashboard.reinit_missing_repos": "Reinitialize all missing Git repositories for which records exist",
"admin.dashboard.sync_external_users": "Synchronize external user data",
"admin.dashboard.cleanup_hook_task_table": "Clean up hook_task table",
"admin.dashboard.cleanup_expired_license_keys": "Clean up expired license keys (older than 1 year)",
"admin.dashboard.cleanup_packages": "Clean up expired packages",
"admin.dashboard.cleanup_actions": "Clean up expired actions' resources",
"admin.dashboard.server_uptime": "Server Uptime",
-1
View File
@@ -731,7 +731,6 @@ func mustEnableWiki(ctx *context.APIContext) {
}
}
// FIXME: for consistency, maybe most mustNotBeArchived checks should be replaced with mustEnableEditor
func mustNotBeArchived(ctx *context.APIContext) {
if ctx.Repo.Repository.IsArchived {
ctx.APIError(http.StatusLocked, fmt.Errorf("%s is archived", ctx.Repo.Repository.FullName()))
+1 -1
View File
@@ -22,7 +22,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
}
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
Revision: ctx.Repo.RefFullName.String()
File: fileName,
Page: 1,
})
-22
View File
@@ -306,29 +306,7 @@ func UpdateIssueTitle(ctx *context.Context) {
})
}
// UpdateIssueRef change issue's ref (branch)
func UpdateIssueRef(ctx *context.Context) {
issue := GetActionIssue(ctx)
if ctx.Written() {
return
}
if !ctx.IsSigned || (!issue.IsPoster(ctx.Doer.ID) && !ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)) || issue.IsPull {
ctx.HTTPError(http.StatusForbidden)
return
}
ref := ctx.FormTrim("ref")
if err := issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, ref); err != nil {
ctx.ServerError("ChangeRef", err)
return
}
ctx.JSON(http.StatusOK, map[string]any{
"ref": ref,
})
}
// UpdateIssueContent change issue's content
func UpdateIssueContent(ctx *context.Context) {
-2
View File
@@ -672,8 +672,6 @@ func prepareIssueFilterAndList(ctx *context.Context, milestoneID int64, projectI
}
ctx.Data["Assignees"] = shared_user.MakeSelfOnTop(ctx.Doer, assigneeUsers)
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, ctx.Repo.RepoLink)
ctx.Data["ApprovalCounts"] = func(issueID int64, typ string) int64 {
counts, ok := approvalCounts[issueID]
if !ok || len(counts) == 0 {
-7
View File
@@ -22,7 +22,6 @@ import (
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/base"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
issue_template "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/issue/template"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
@@ -85,12 +84,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles
}
metaData.AssigneesData.SelectedAssigneeIDs = strings.Join(selectedAssigneeIDStrings, ",")
if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/<ref>
template.Ref = git.BranchPrefix + template.Ref
}
ctx.Data["Reference"] = template.Ref
ctx.Data["RefEndName"] = git.RefName(template.Ref).ShortName()
return true, templateErrs
}
return false, templateErrs
-3
View File
@@ -23,7 +23,6 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/emoji"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/markup/markdown"
@@ -397,14 +396,12 @@ func ViewIssue(ctx *context.Context) {
}
}
ctx.Data["Reference"] = issue.Ref
ctx.Data["SignInLink"] = middleware.RedirectLinkUserLogin(ctx.Req)
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.Permission.IsAdmin() || ctx.Doer.IsAdmin)
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
ctx.Data["RefEndName"] = git.RefName(issue.Ref).ShortName()
tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
if err != nil {
-3
View File
@@ -39,7 +39,6 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/routers/web/shared/user"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
feed_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/feed"
issue_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/issue"
pull_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/pull"
"github.com/ProtonMail/go-crypto/openpgp"
@@ -587,8 +586,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
ctx.Data["IsShowClosed"] = isShowClosed
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, ctx.FormString("RepoLink"))
if err := issues.LoadAttributes(ctx); err != nil {
ctx.ServerError("issues.LoadAttributes", err)
return
-2
View File
@@ -23,7 +23,6 @@ import (
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
issue_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/issue"
pull_service "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/pull"
)
@@ -255,7 +254,6 @@ func NotificationSubscriptions(ctx *context.Context) {
ctx.Data["CommitLastStatus"] = lastStatus
ctx.Data["CommitStatuses"] = commitStatuses
ctx.Data["Issues"] = issues
ctx.Data["IssueRefEndNames"], ctx.Data["IssueRefURLs"] = issue_service.GetRefEndNamesAndURLs(issues, "")
approvalCounts, err := issues.GetApprovalCounts(ctx)
if err != nil {
-4
View File
@@ -595,7 +595,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Group("", func() {
m.Get("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth)
// TODO manage redirection
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
}, reqSignIn)
@@ -1353,7 +1352,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Post("/content", repo.UpdateIssueContent)
m.Post("/deadline", repo.UpdateIssueDeadline)
m.Post("/watch", repo.IssueWatch)
m.Post("/ref", repo.UpdateIssueRef)
m.Post("/pin", reqRepoAdmin, repo.IssuePinOrUnpin)
m.Post("/viewed-files", repo.UpdateViewedFiles)
m.Group("/dependency", func() {
@@ -1750,8 +1748,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
m.Get("/commit/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:([a-f0-9]{7,64})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
// FIXME: this route `/cherry-pick/{sha}` doesn't seem useful or right, the new code always uses `/_cherrypick/` which could handle branch name correctly
m.Get("/cherry-pick/{sha:([a-f0-9]{7,64})$}", repo.SetEditorconfigIfExists, context.RepoRefByDefaultBranch(), repo.CherryPick)
}, repo.MustBeNotEmpty)
m.Get("/rss/branch/*", context.RepoRefByType(git.RefTypeBranch), webAuth.AllowBasic, feedEnabled, feed.RenderBranchFeedRSS)
+4
View File
@@ -82,6 +82,10 @@ func InsertRun(ctx context.Context, run *actions_model.ActionRun, content []byte
return fmt.Errorf("parse workflow: %w", err)
}
if len(jobs) > actions_model.MaxJobNumPerRun {
return fmt.Errorf("workflow has too many jobs (%d), maximum is %d", len(jobs), actions_model.MaxJobNumPerRun)
}
titleChanged := len(jobs) > 0 && jobs[0].RunName != ""
if titleChanged {
run.Title = util.EllipsisDisplayString(jobs[0].RunName, 255)
+1 -1
View File
@@ -81,7 +81,7 @@
{{ctx.Locale.Tr "admin.emails.change_email_header"}}
</div>
<form class="content ui form" action="{{AppSubUrl}}/-/admin/emails/activate" method="post">
<p class="center">{{ctx.Locale.Tr "admin.emails.change_email_text"}}</p>
<p class="tw-text-center">{{ctx.Locale.Tr "admin.emails.change_email_text"}}</p>
<input type="hidden" name="sort" value="{{.SortType}}">
<input type="hidden" name="q" value="{{.Keyword}}">
+1 -1
View File
@@ -1,7 +1,7 @@
{{template "base/head" .}}
<div role="main" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home_title"}}{{end}}" class="page-content home">
<div class="tw-mb-8 tw-px-8">
<div class="center">
<div class="tw-text-center">
<img class="logo" width="220" height="220" src="{{AssetUrlPrefix}}/img/login-logo.png" alt="{{ctx.Locale.Tr "logo"}}" onerror="this.style.display='none'">
<div class="hero">
<h1 class="ui icon header title tw-text-balance">
+1 -1
View File
@@ -275,7 +275,7 @@
<summary class="right-content tw-py-2{{if .Err_Admin}} tw-text-red{{end}}">
{{ctx.Locale.Tr "install.admin_title"}}
</summary>
<p class="center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p>
<p class="tw-text-center">{{ctx.Locale.Tr "install.admin_setting_desc"}}</p>
<div class="inline field {{if .Err_AdminName}}error{{end}}">
<label for="admin_name">{{ctx.Locale.Tr "install.admin_name"}}</label>
<input id="admin_name" name="admin_name" value="{{.admin_name}}">
@@ -1,63 +1 @@
{{/* TODO: RemoveIssueRef: the Issue.Ref will be removed in 1.24 or 1.25 if no end user really needs it or there could be better alternative then.
PR: https://github.com/go-gitea/gitea/pull/32744
The Issue.Ref was added by Add possibility to record branch or tag information in an issue (#780)
After 8 years, this "branch selector" does nothing more than saving the branch/tag name into database and displays it,
or sometimes auto-close a ref-matched issue by a commit message when CloseIssuesViaCommitInAnyBranch=false.
There are still users using it:
* @didim99: it is a really useful feature to specify a branch in which issue found.
Still needs to figure out:
* Could the "recording branch/tag name" be replaced by other approaches?
* Write the branch name in the issue title/body then it will still be displayed, eg: `[bug] (fix/ui-broken-bug) there is a bug ....`
* Is "GitHub-like development sidebar (`#31899`)" good enough (or better) for your usage?
*/}}
{{if and (not .Issue.IsPull) (not .PageIsComparePull)}}
<input id="ref_selector" name="ref" type="hidden" value="{{.Reference}}">
<div class="ui dropdown select-branch branch-selector-dropdown ellipsis-text-items {{if not .HasIssuesOrPullsWritePermission}}disabled{{end}}"
data-no-results="{{ctx.Locale.Tr "no_results_found"}}"
{{if and .Issue (or .IsIssueWriter .HasIssuesOrPullsWritePermission)}}data-url-update-issueref="{{$.RepoLink}}/issues/{{.Issue.Index}}/ref"{{end}}
>
<div class="ui button branch-dropdown-button">
<span class="text-branch-name gt-ellipsis">{{if .Reference}}{{$.RefEndName}}{{else}}{{ctx.Locale.Tr "repo.issues.no_ref"}}{{end}}</span>
{{if .HasIssuesOrPullsWritePermission}}{{svg "octicon-triangle-down" 14 "dropdown icon"}}{{end}}
</div>
<div class="menu">
<div class="ui icon search input">
<i class="icon">{{svg "octicon-filter" 16}}</i>
<input name="search" placeholder="{{ctx.Locale.Tr "repo.filter_branch_and_tag"}}...">
</div>
<div class="branch-tag-tab">
<a class="branch-tag-item reference column muted active" href="#" data-target="#branch-list">
{{svg "octicon-git-branch" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.branches"}}
</a>
<a class="branch-tag-item reference column muted" href="#" data-target="#tag-list">
{{svg "octicon-tag" 16 "tw-mr-1"}} {{ctx.Locale.Tr "repo.tags"}}
</a>
</div>
<div class="branch-tag-divider"></div>
<div id="branch-list" class="scrolling menu reference-list-menu">
{{if or .Reference (not .Issue)}}
<div class="item tw-text-xs" data-id="" data-name="{{ctx.Locale.Tr "repo.issues.no_ref"}}" data-id-selector="#ref_selector"><strong><a href="#">{{ctx.Locale.Tr "repo.clear_ref"}}</a></strong></div>
{{end}}
{{range .Branches}}
<div class="item" data-id="refs/heads/{{.}}" data-name="{{.}}" data-id-selector="#ref_selector" title="{{.}}">{{.}}</div>
{{else}}
<div class="item disabled">{{ctx.Locale.Tr "no_results_found"}}</div>
{{end}}
</div>
<div id="tag-list" class="scrolling menu reference-list-menu tw-hidden">
{{if or .Reference (not .Issue)}}
<div class="item tw-text-xs" data-id="" data-name="{{ctx.Locale.Tr "repo.issues.no_ref"}}" data-id-selector="#ref_selector"><strong><a href="#">{{ctx.Locale.Tr "repo.clear_ref"}}</a></strong></div>
{{end}}
{{range .Tags}}
<div class="item" data-id="refs/tags/{{.}}" data-name="tags/{{.}}" data-id-selector="#ref_selector">{{.}}</div>
{{else}}
<div class="item disabled">{{ctx.Locale.Tr "no_results_found"}}</div>
{{end}}
</div>
</div>
</div>
<div class="divider"></div>
{{end}}
{{/* Issue.Ref branch selector removed the field is deprecated. The DB column is kept for commit-close logic. */}}
-2
View File
@@ -47,8 +47,6 @@
</div>
<div class="issue-content-right ui segment" data-global-init="initRepoIssueSidebar">
{{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}}
{{if .PageIsComparePull}}
{{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}}
<div class="divider"></div>
@@ -1,6 +1,4 @@
<div class="issue-content-right ui segment" data-global-init="initRepoIssueSidebar">
{{template "repo/issue/branch_selector_field" $}}{{/* TODO: RemoveIssueRef: template "repo/issue/branch_selector_field" $*/}}
{{if .Issue.IsPull}}
{{template "repo/issue/sidebar/reviewer_list" $.IssuePageMetaData}}
{{template "repo/issue/sidebar/wip_switch" $}}
+46
View File
@@ -0,0 +1,46 @@
{{/*
Generic multiselect combo list component.
Provides a dropdown with search, checkable items, and a selected-items display list.
Parameters:
Name - form input name (required)
Title - display label (required)
Items - slice of items, each must have .Value and .Label fields
SelectedValues - comma-separated string of selected values
Placeholder - search input placeholder (optional, defaults to "Filter...")
EmptyText - text when nothing is selected (optional)
Disabled - whether the control is disabled (optional)
Icon - gear icon shown next to title (optional, defaults to "octicon-gear")
*/}}
<div class="combo-multiselect" data-field-name="{{.Name}}">
<input class="combo-value" name="{{.Name}}" type="hidden" value="{{.SelectedValues}}">
<div class="ui dropdown full-width {{if .Disabled}}disabled{{end}}">
<a class="fixed-text muted">
<strong>{{.Title}}</strong> {{if not .Disabled}}{{svg (or .Icon "octicon-gear")}}{{end}}
</a>
<div class="menu">
{{if .Items}}
<div class="ui icon search input">
<i class="icon">{{svg "octicon-search" 16}}</i>
<input type="text" placeholder="{{or .Placeholder (ctx.Locale.Tr "search.filter")}}">
</div>
<div class="scrolling menu">
<a class="item clear-selection" href="#">{{ctx.Locale.Tr "repo.issues.new.clear_labels"}}</a>
<div class="divider"></div>
{{range .Items}}
<a class="item" data-value="{{.Value}}">
<span class="item-check-mark">{{svg "octicon-check" 16}}</span>
<span class="item-label">{{.Label}}</span>
</a>
{{end}}
</div>
{{else}}
<div class="item disabled">{{or .EmptyText (ctx.Locale.Tr "repo.issues.new.no_items")}}</div>
{{end}}
</div>
</div>
<div class="ui list combo-multiselect-list">
<span class="item empty-list">{{or .EmptyText "None"}}</span>
</div>
</div>
+1 -7
View File
@@ -82,13 +82,7 @@
<span class="gt-ellipsis">{{$project.Title}}</span>
</a>
{{end}}
{{if .Ref}}{{/* TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl" */}}
<a class="ref flex-text-inline tw-max-w-[300px]" {{if $.RepoLink}}href="{{index $.IssueRefURLs .ID}}"{{else}}href="{{.Repo.Link}}{{index $.IssueRefURLs .ID}}"{{end}}>
{{svg "octicon-git-branch" 14}}
<span class="gt-ellipsis">{{index $.IssueRefEndNames .ID}}</span>
</a>
{{end}}
{{$tasks := .GetTasks}}
{{$tasks := .GetTasks}}
{{if gt $tasks 0}}
{{$tasksDone := .GetTasksDone}}
<span class="checklist flex-text-inline">
+2 -2
View File
@@ -20,7 +20,7 @@
<button class="ui primary button">{{ctx.Locale.Tr "auth.send_reset_mail"}}</button>
</div>
{{else if .IsResetDisable}}
<p class="center">
<p class="tw-text-center">
{{if $.IsAdmin}}
{{ctx.Locale.Tr "auth.disable_forgot_password_mail_admin"}}
{{else}}
@@ -28,7 +28,7 @@
{{end}}
</p>
{{else if .ResendLimited}}
<p class="center">{{ctx.Locale.Tr "auth.resent_limit_prompt"}}</p>
<p class="tw-text-center">{{ctx.Locale.Tr "auth.resent_limit_prompt"}}</p>
{{end}}
</div>
</form>
+1 -1
View File
@@ -54,7 +54,7 @@
{{end}}
</div>
{{else}}
<p class="center">{{ctx.Locale.Tr "auth.invalid_code_forgot_password" (printf "%s/user/forgot_password" AppSubUrl)}}</p>
<p class="tw-text-center">{{ctx.Locale.Tr "auth.invalid_code_forgot_password" (printf "%s/user/forgot_password" AppSubUrl)}}</p>
{{end}}
</div>
</form>
@@ -21,7 +21,6 @@
</div>
{{end}}
<div class="item">
<!-- TODO add regenerate secret functionality */ -->
<form class="ui form ignore-dirty" action="{{.FormActionPath}}/regenerate_secret" method="post">
{{ctx.Locale.Tr "settings.oauth2_regenerate_secret_hint"}}
<button class="ui mini button tw-ml-2" type="submit">{{ctx.Locale.Tr "settings.oauth2_regenerate_secret"}}</button>
+1 -5
View File
@@ -24,7 +24,7 @@
/* other variables */
--border-radius: 4px;
--border-radius-medium: 6px;
--border-radius-full: 99999px; /* TODO: use calc(infinity * 1px) */
--border-radius-full: calc(infinity * 1px);
--opacity-disabled: 0.55;
--height-loading: 16rem;
--min-height-textarea: 132px; /* padding + 6 lines + border = calc(1.57142em + 6lh + 2px), but lh is not fully supported */
@@ -535,10 +535,6 @@ strong.attention-caution, svg.attention-caution {
color: var(--color-error-text);
}
/* FIXME: this is a longstanding dirty patch since 2015, it only makes the pages more messy and shouldn't be used */
.center {
text-align: center;
}
overflow-menu {
border-bottom: 1px solid var(--color-secondary) !important;
+1
View File
@@ -34,6 +34,7 @@
@import "./modules/codeeditor.css";
@import "./modules/chroma.css";
@import "./modules/charescape.css";
@import "./modules/combo-multiselect.css";
@import "./shared/flex-list.css";
@import "./shared/milestone.css";
+27
View File
@@ -0,0 +1,27 @@
/* Styles for the generic combo-multiselect component (shared/combolist.tmpl) */
.combo-multiselect > .ui.dropdown .item:not(.checked) .item-check-mark {
visibility: hidden;
}
.combo-multiselect > .ui.dropdown .item .item-check-mark {
margin-right: 0.5em;
}
.combo-multiselect > .combo-multiselect-list {
margin-top: 0.25em;
}
.combo-multiselect > .combo-multiselect-list > .item {
display: inline-block;
padding: 2px 6px;
margin: 2px;
border-radius: var(--border-radius);
background: var(--color-label-bg);
font-size: 0.9em;
}
.combo-multiselect > .combo-multiselect-list > .item.empty-list {
background: none;
color: var(--color-text-light-2);
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright 2026 Moko Consulting. All rights reserved.
// SPDX-License-Identifier: MIT
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
/**
* Generic multiselect combo list component.
* Works with the "shared/combolist" template to provide a reusable
* dropdown with search, checkable items, and a selected-items display list.
*
* Usage: add class="combo-multiselect" to the container element.
* The component is self-contained — no backend calls, just updates the hidden input.
*/
class ComboMultiselect {
container: HTMLElement;
elDropdown: HTMLElement;
elList: HTMLElement;
elComboValue: HTMLInputElement;
constructor(container: HTMLElement) {
this.container = container;
this.elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown')!;
this.elList = container.querySelector<HTMLElement>(':scope > .combo-multiselect-list')!;
this.elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value')!;
}
collectCheckedValues(): string[] {
return Array.from(
this.elDropdown.querySelectorAll('.menu > .item.checked'),
(el) => el.getAttribute('data-value')!,
);
}
updateUiList() {
const checkedValues = this.collectCheckedValues();
const elEmptyTip = this.elList.querySelector(':scope > .item.empty-list')!;
queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove());
for (const value of checkedValues) {
const el = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
if (!el) continue;
const labelText = el.querySelector('.item-label')?.textContent || value;
const listItem = document.createElement('span');
listItem.classList.add('item');
listItem.textContent = labelText;
this.elList.append(listItem);
}
toggleElem(elEmptyTip, checkedValues.length === 0);
this.elComboValue.value = checkedValues.join(',');
this.elComboValue.dispatchEvent(new Event('change', {bubbles: true}));
}
onItemClick(elItem: HTMLElement, e: Event) {
e.preventDefault();
if (elItem.matches('.clear-selection')) {
queryElems(this.elDropdown, '.menu > .item', (el) => el.classList.remove('checked'));
this.updateUiList();
return;
}
elItem.classList.toggle('checked');
this.updateUiList();
}
init() {
// Restore checked state from initial value
const initialValues = this.elComboValue.value ? this.elComboValue.value.split(',') : [];
for (const value of initialValues) {
const elItem = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
elItem?.classList.add('checked');
}
this.updateUiList();
addDelegatedEventListener(this.elDropdown, 'click', '.item', (el, e) => this.onItemClick(el, e));
fomanticQuery(this.elDropdown).dropdown('setting', {
action: 'nothing',
fullTextSearch: 'exact',
hideDividers: 'empty',
});
}
}
export function initComboMultiselect() {
queryElems(document, '.combo-multiselect', (el) => {
if (el.hasAttribute('data-combo-inited')) return;
el.setAttribute('data-combo-inited', 'true');
new ComboMultiselect(el).init();
});
}
-28
View File
@@ -9,33 +9,6 @@ import {showTemporaryTooltip} from '../modules/tippy.ts';
const {appSubUrl} = window.config;
function initRepoIssueBranchSelector(elSidebar: HTMLElement) {
// TODO: RemoveIssueRef: see "repo/issue/branch_selector_field.tmpl"
const elSelectBranch = elSidebar.querySelector('.ui.dropdown.select-branch.branch-selector-dropdown');
if (!elSelectBranch) return;
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
const elBranchMenu = elSelectBranch.querySelector('.reference-list-menu')!;
queryElems(elBranchMenu, '.item:not(.no-select)', (el) => el.addEventListener('click', async function (e) {
e.preventDefault();
const selectedValue = this.getAttribute('data-id')!; // eg: "refs/heads/my-branch"
const selectedText = this.getAttribute('data-name'); // eg: "my-branch"
if (urlUpdateIssueRef) {
// for existing issue, send request to update issue ref, and reload page
try {
await POST(urlUpdateIssueRef, {data: new URLSearchParams({ref: selectedValue})});
window.location.reload();
} catch (error) {
console.error(error);
}
} else {
// for new issue, only update UI&form, do not send request/reload
const selectedHiddenSelector = this.getAttribute('data-id-selector')!;
document.querySelector<HTMLInputElement>(selectedHiddenSelector)!.value = selectedValue;
elSelectBranch.querySelector('.text-branch-name')!.textContent = selectedText;
}
}));
}
function initRepoIssueDue(elSidebar: HTMLElement) {
const form = elSidebar.querySelector<HTMLFormElement>('.issue-due-form');
if (!form) return;
@@ -109,7 +82,6 @@ export function initRepoPullRequestAllowMaintainerEdit(elSidebar: HTMLElement) {
export function initRepoIssueSidebar() {
registerGlobalInitFunc('initRepoIssueSidebar', (elSidebar) => {
initRepoIssueBranchSelector(elSidebar);
initRepoIssueDue(elSidebar);
initRepoIssueSidebarDependency(elSidebar);
initRepoPullRequestAllowMaintainerEdit(elSidebar);
+2
View File
@@ -22,6 +22,7 @@ import {initUserExternalLogins, initUserCheckAppUrl} from './features/user-auth.
import {initRepoPullRequestReview, initRepoIssueFilterItemLabel} from './features/repo-issue.ts';
import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts';
import {initRepoTopicBar} from './features/repo-home.ts';
import {initComboMultiselect} from './features/combo-multiselect.ts';
import {initAdminCommon} from './features/admin/common.ts';
import {initRepoCodeView} from './features/repo-code.ts';
import {initSshKeyFormParser} from './features/sshkey-helper.ts';
@@ -105,6 +106,7 @@ const initPerformanceTracer = callInitFunctions([
initTableSort,
initRepoFileSearch,
initCopyContent,
initComboMultiselect,
initAdminCommon,
initAdminUserListSearchForm,
+5 -3
View File
@@ -52,9 +52,11 @@ export function stripTags(text: string): string {
}
export function parseIssueHref(href: string): IssuePathInfo {
// FIXME: it should use pathname and trim the appSubUrl ahead
const path = (href || '').replace(/[#?].*$/, '');
const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(path) || [];
let pathname = href || '';
try { pathname = new URL(pathname, window.location.origin).pathname } catch {}
const appSubUrl = window.config.appSubUrl;
if (appSubUrl && pathname.startsWith(appSubUrl)) pathname = pathname.substring(appSubUrl.length);
const [_, ownerName, repoName, pathType, indexString] = /([^/]+)\/([^/]+)\/(issues|pulls)\/([0-9]+)/.exec(pathname) || [];
return {ownerName, repoName, pathType, indexString};
}