mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-02 15:09:33 -05:00
Merge branch 'main' into CalK16-pr-reviewer
This commit is contained in:
commit
418a13c27b
@ -4,28 +4,14 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrExecTimeout error when exec timed out
|
|
||||||
type ErrExecTimeout struct {
|
|
||||||
Duration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrExecTimeout if some error is ErrExecTimeout
|
|
||||||
func IsErrExecTimeout(err error) bool {
|
|
||||||
_, ok := err.(ErrExecTimeout)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrExecTimeout) Error() string {
|
|
||||||
return fmt.Sprintf("execution is timeout [duration: %v]", err.Duration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNotExist commit not exist error
|
// ErrNotExist commit not exist error
|
||||||
type ErrNotExist struct {
|
type ErrNotExist struct {
|
||||||
ID string
|
ID string
|
||||||
@ -62,21 +48,6 @@ func IsErrBadLink(err error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUnsupportedVersion error when required git version not matched
|
|
||||||
type ErrUnsupportedVersion struct {
|
|
||||||
Required string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrUnsupportedVersion if some error is ErrUnsupportedVersion
|
|
||||||
func IsErrUnsupportedVersion(err error) bool {
|
|
||||||
_, ok := err.(ErrUnsupportedVersion)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrUnsupportedVersion) Error() string {
|
|
||||||
return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
|
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
|
||||||
type ErrBranchNotExist struct {
|
type ErrBranchNotExist struct {
|
||||||
Name string
|
Name string
|
||||||
@ -185,3 +156,10 @@ func IsErrMoreThanOne(err error) bool {
|
|||||||
func (err *ErrMoreThanOne) Error() string {
|
func (err *ErrMoreThanOne) Error() string {
|
||||||
return fmt.Sprintf("ErrMoreThanOne Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
return fmt.Sprintf("ErrMoreThanOne Error: %v: %s\n%s", err.Err, err.StdErr, err.StdOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsErrCanceledOrKilled(err error) bool {
|
||||||
|
// When "cancel()" a git command's context, the returned error of "Run()" could be one of them:
|
||||||
|
// - context.Canceled
|
||||||
|
// - *exec.ExitError: "signal: killed"
|
||||||
|
return err != nil && (errors.Is(err, context.Canceled) || err.Error() == "signal: killed")
|
||||||
|
}
|
||||||
|
@ -166,9 +166,7 @@ func (c *CheckAttributeReader) Run() error {
|
|||||||
Stdout: c.stdOut,
|
Stdout: c.stdOut,
|
||||||
Stderr: stdErr,
|
Stderr: stdErr,
|
||||||
})
|
})
|
||||||
if err != nil && // If there is an error we need to return but:
|
if err != nil && !IsErrCanceledOrKilled(err) {
|
||||||
c.ctx.Err() != err && // 1. Ignore the context error if the context is cancelled or exceeds the deadline (RunWithContext could return c.ctx.Err() which is Canceled or DeadlineExceeded)
|
|
||||||
err.Error() != "signal: killed" { // 2. We should not pass up errors due to the program being killed
|
|
||||||
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
|
return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -54,7 +54,7 @@ func NewFuncMap() template.FuncMap {
|
|||||||
"StringUtils": NewStringUtils,
|
"StringUtils": NewStringUtils,
|
||||||
"SliceUtils": NewSliceUtils,
|
"SliceUtils": NewSliceUtils,
|
||||||
"JsonUtils": NewJsonUtils,
|
"JsonUtils": NewJsonUtils,
|
||||||
"DateUtils": NewDateUtils, // TODO: to be replaced by DateUtils
|
"DateUtils": NewDateUtils,
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// svg / avatar / icon / color
|
// svg / avatar / icon / color
|
||||||
@ -71,7 +71,7 @@ func NewFuncMap() template.FuncMap {
|
|||||||
"CountFmt": base.FormatNumberSI,
|
"CountFmt": base.FormatNumberSI,
|
||||||
"TimeSince": timeutil.TimeSince,
|
"TimeSince": timeutil.TimeSince,
|
||||||
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
"TimeSinceUnix": timeutil.TimeSinceUnix,
|
||||||
"DateTime": timeutil.DateTime,
|
"DateTime": dateTimeLegacy, // for backward compatibility only, do not use it anymore
|
||||||
"Sec2Time": util.SecToTime,
|
"Sec2Time": util.SecToTime,
|
||||||
"LoadTimes": func(startTime time.Time) string {
|
"LoadTimes": func(startTime time.Time) string {
|
||||||
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
||||||
|
@ -6,7 +6,9 @@ package templates
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,3 +34,27 @@ func (du *DateUtils) AbsoluteLong(time any) template.HTML {
|
|||||||
func (du *DateUtils) FullTime(time any) template.HTML {
|
func (du *DateUtils) FullTime(time any) template.HTML {
|
||||||
return timeutil.DateTime("full", time)
|
return timeutil.DateTime("full", time)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
|
||||||
|
// It shouldn't be used in new code. New code should use Time or TimeStamp as much as possible.
|
||||||
|
func (du *DateUtils) ParseLegacy(datetime string) time.Time {
|
||||||
|
return parseLegacy(datetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLegacy(datetime string) time.Time {
|
||||||
|
t, err := time.Parse(time.RFC3339, datetime)
|
||||||
|
if err != nil {
|
||||||
|
t, _ = time.ParseInLocation(time.DateOnly, datetime, setting.DefaultUILocation)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
|
||||||
|
if !setting.IsProd || setting.IsInTesting {
|
||||||
|
panic("dateTimeLegacy is for backward compatibility only, do not use it in new code")
|
||||||
|
}
|
||||||
|
if s, ok := datetime.(string); ok {
|
||||||
|
datetime = parseLegacy(s)
|
||||||
|
}
|
||||||
|
return timeutil.DateTime(format, datetime)
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package timeutil
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -16,32 +17,35 @@ import (
|
|||||||
func TestDateTime(t *testing.T) {
|
func TestDateTime(t *testing.T) {
|
||||||
testTz, _ := time.LoadLocation("America/New_York")
|
testTz, _ := time.LoadLocation("America/New_York")
|
||||||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||||
|
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
||||||
|
|
||||||
|
du := NewDateUtils(nil)
|
||||||
|
|
||||||
refTimeStr := "2018-01-01T00:00:00Z"
|
refTimeStr := "2018-01-01T00:00:00Z"
|
||||||
refDateStr := "2018-01-01"
|
refDateStr := "2018-01-01"
|
||||||
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
||||||
refTimeStamp := TimeStamp(refTime.Unix())
|
refTimeStamp := timeutil.TimeStamp(refTime.Unix())
|
||||||
|
|
||||||
assert.EqualValues(t, "-", DateTime("short", nil))
|
assert.EqualValues(t, "-", du.AbsoluteShort(nil))
|
||||||
assert.EqualValues(t, "-", DateTime("short", 0))
|
assert.EqualValues(t, "-", du.AbsoluteShort(0))
|
||||||
assert.EqualValues(t, "-", DateTime("short", time.Time{}))
|
assert.EqualValues(t, "-", du.AbsoluteShort(time.Time{}))
|
||||||
assert.EqualValues(t, "-", DateTime("short", TimeStamp(0)))
|
assert.EqualValues(t, "-", du.AbsoluteShort(timeutil.TimeStamp(0)))
|
||||||
|
|
||||||
actual := DateTime("short", "invalid")
|
actual := dateTimeLegacy("short", "invalid")
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="invalid">invalid</absolute-date>`, actual)
|
assert.EqualValues(t, `-`, actual)
|
||||||
|
|
||||||
actual = DateTime("short", refTimeStr)
|
actual = dateTimeLegacy("short", refTimeStr)
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01T00:00:00Z</absolute-date>`, actual)
|
|
||||||
|
|
||||||
actual = DateTime("short", refTime)
|
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
||||||
|
|
||||||
actual = DateTime("short", refDateStr)
|
actual = du.AbsoluteShort(refTime)
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01">2018-01-01</absolute-date>`, actual)
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
||||||
|
|
||||||
actual = DateTime("short", refTimeStamp)
|
actual = dateTimeLegacy("short", refDateStr)
|
||||||
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00-05:00">2018-01-01</absolute-date>`, actual)
|
||||||
|
|
||||||
|
actual = du.AbsoluteShort(refTimeStamp)
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
|
||||||
|
|
||||||
actual = DateTime("full", refTimeStamp)
|
actual = du.FullTime(refTimeStamp)
|
||||||
assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
assert.EqualValues(t, `<relative-time weekday="" year="numeric" format="datetime" month="short" day="numeric" hour="numeric" minute="numeric" second="numeric" data-tooltip-content data-tooltip-interactive="true" datetime="2017-12-31T19:00:00-05:00">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||||
}
|
}
|
@ -12,9 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// DateTime renders an absolute time HTML element by datetime.
|
// DateTime renders an absolute time HTML element by datetime.
|
||||||
func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
func DateTime(format string, datetime any) template.HTML {
|
||||||
// TODO: remove the extraAttrs argument, it's not used in any call to DateTime
|
|
||||||
|
|
||||||
if p, ok := datetime.(*time.Time); ok {
|
if p, ok := datetime.(*time.Time); ok {
|
||||||
datetime = *p
|
datetime = *p
|
||||||
}
|
}
|
||||||
@ -34,9 +32,6 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
|||||||
switch v := datetime.(type) {
|
switch v := datetime.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return "-"
|
return "-"
|
||||||
case string:
|
|
||||||
datetimeEscaped = html.EscapeString(v)
|
|
||||||
textEscaped = datetimeEscaped
|
|
||||||
case time.Time:
|
case time.Time:
|
||||||
if v.IsZero() || v.Unix() == 0 {
|
if v.IsZero() || v.Unix() == 0 {
|
||||||
return "-"
|
return "-"
|
||||||
@ -51,10 +46,7 @@ func DateTime(format string, datetime any, extraAttrs ...string) template.HTML {
|
|||||||
panic(fmt.Sprintf("Unsupported time type %T", datetime))
|
panic(fmt.Sprintf("Unsupported time type %T", datetime))
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs := make([]string, 0, 10+len(extraAttrs))
|
attrs := []string{`weekday=""`, `year="numeric"`}
|
||||||
attrs = append(attrs, extraAttrs...)
|
|
||||||
attrs = append(attrs, `weekday=""`, `year="numeric"`)
|
|
||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case "short", "long": // date only
|
case "short", "long": // date only
|
||||||
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
|
attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
unit_model "code.gitea.io/gitea/models/unit"
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/label"
|
"code.gitea.io/gitea/modules/label"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -739,11 +738,9 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
|||||||
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
|
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
|
||||||
if !repo.IsEmpty {
|
if !repo.IsEmpty {
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
|
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
|
||||||
if !git.IsErrUnsupportedVersion(err) {
|
|
||||||
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
|
ctx.Error(http.StatusInternalServerError, "SetDefaultBranch", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
updateRepoLicense = true
|
updateRepoLicense = true
|
||||||
}
|
}
|
||||||
repo.DefaultBranch = *opts.DefaultBranch
|
repo.DefaultBranch = *opts.DefaultBranch
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/private"
|
"code.gitea.io/gitea/modules/private"
|
||||||
gitea_context "code.gitea.io/gitea/services/context"
|
gitea_context "code.gitea.io/gitea/services/context"
|
||||||
@ -23,13 +22,11 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
|
|||||||
|
|
||||||
ctx.Repo.Repository.DefaultBranch = branch
|
ctx.Repo.Repository.DefaultBranch = branch
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
|
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
|
||||||
if !git.IsErrUnsupportedVersion(err) {
|
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
|
Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo_model.UpdateDefaultBranch(ctx, ctx.Repo.Repository); err != nil {
|
if err := repo_model.UpdateDefaultBranch(ctx, ctx.Repo.Repository); err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||||
|
@ -467,7 +467,7 @@ func serviceRPC(ctx *context.Context, h *serviceHandler, service string) {
|
|||||||
Stderr: &stderr,
|
Stderr: &stderr,
|
||||||
UseContextTimeout: true,
|
UseContextTimeout: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
if err.Error() != "signal: killed" {
|
if !git.IsErrCanceledOrKilled(err) {
|
||||||
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.getRepoDir(), err, stderr.String())
|
log.Error("Fail to serve RPC(%s) in %s: %v - %s", service, h.getRepoDir(), err, stderr.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1162,7 +1162,7 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi
|
|||||||
Dir: repoPath,
|
Dir: repoPath,
|
||||||
Stdout: writer,
|
Stdout: writer,
|
||||||
Stderr: stderr,
|
Stderr: stderr,
|
||||||
}); err != nil && err.Error() != "signal: killed" {
|
}); err != nil && !git.IsErrCanceledOrKilled(err) {
|
||||||
log.Error("error during GetDiff(git diff dir: %s): %v, stderr: %s", repoPath, err, stderr.String())
|
log.Error("error during GetDiff(git diff dir: %s): %v, stderr: %s", repoPath, err, stderr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,15 +613,9 @@ func checkAndUpdateEmptyRepository(ctx context.Context, m *repo_model.Mirror, re
|
|||||||
}
|
}
|
||||||
// Update the git repository default branch
|
// Update the git repository default branch
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, m.Repo, m.Repo.DefaultBranch); err != nil {
|
if err := gitrepo.SetDefaultBranch(ctx, m.Repo, m.Repo.DefaultBranch); err != nil {
|
||||||
if !git.IsErrUnsupportedVersion(err) {
|
|
||||||
log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err)
|
log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err)
|
||||||
desc := fmt.Sprintf("Failed to update default branch of underlying git repository '%s': %v", m.Repo.RepoPath(), err)
|
|
||||||
if err = system_model.CreateRepositoryNotice(desc); err != nil {
|
|
||||||
log.Error("CreateRepositoryNotice: %v", err)
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
m.Repo.IsEmpty = false
|
m.Repo.IsEmpty = false
|
||||||
// Update the is empty and default_branch columns
|
// Update the is empty and default_branch columns
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
if err := repo_model.UpdateRepositoryCols(ctx, m.Repo, "default_branch", "is_empty"); err != nil {
|
||||||
|
@ -602,12 +602,7 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
|
|||||||
log.Error("CancelPreviousJobs: %v", err)
|
log.Error("CancelPreviousJobs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, repo, newBranchName); err != nil {
|
return gitrepo.SetDefaultBranch(ctx, repo, newBranchName)
|
||||||
if !git.IsErrUnsupportedVersion(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -339,8 +339,7 @@ func (t *TemporaryUploadRepository) Push(doer *user_model.User, commitHash, bran
|
|||||||
func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
|
func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
|
||||||
stdoutReader, stdoutWriter, err := os.Pipe()
|
stdoutReader, stdoutWriter, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to open stdout pipe: %v", err)
|
return nil, fmt.Errorf("unable to open stdout pipe: %w", err)
|
||||||
return nil, fmt.Errorf("Unable to open stdout pipe: %w", err)
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = stdoutReader.Close()
|
_ = stdoutReader.Close()
|
||||||
@ -348,9 +347,7 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
|
|||||||
}()
|
}()
|
||||||
stderr := new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
var diff *gitdiff.Diff
|
var diff *gitdiff.Diff
|
||||||
var finalErr error
|
err = git.NewCommand(t.ctx, "diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
|
||||||
|
|
||||||
if err := git.NewCommand(t.ctx, "diff-index", "--src-prefix=\\a/", "--dst-prefix=\\b/", "--cached", "-p", "HEAD").
|
|
||||||
Run(&git.RunOpts{
|
Run(&git.RunOpts{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
Dir: t.basePath,
|
Dir: t.basePath,
|
||||||
@ -359,27 +356,19 @@ func (t *TemporaryUploadRepository) DiffIndex() (*gitdiff.Diff, error) {
|
|||||||
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
|
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
|
||||||
_ = stdoutWriter.Close()
|
_ = stdoutWriter.Close()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
diff, finalErr = gitdiff.ParsePatch(t.ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
|
var diffErr error
|
||||||
if finalErr != nil {
|
diff, diffErr = gitdiff.ParsePatch(t.ctx, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, stdoutReader, "")
|
||||||
log.Error("ParsePatch: %v", finalErr)
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
_ = stdoutReader.Close()
|
_ = stdoutReader.Close()
|
||||||
return finalErr
|
if diffErr != nil {
|
||||||
|
// if the diffErr is not nil, it will be returned as the error of "Run()"
|
||||||
|
return fmt.Errorf("ParsePatch: %w", diffErr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}); err != nil {
|
})
|
||||||
if finalErr != nil {
|
if err != nil && !git.IsErrCanceledOrKilled(err) {
|
||||||
log.Error("Unable to ParsePatch in temporary repo %s (%s). Error: %v", t.repo.FullName(), t.basePath, finalErr)
|
log.Error("Unable to diff-index in temporary repo %s (%s). Error: %v\nStderr: %s", t.repo.FullName(), t.basePath, err, stderr)
|
||||||
return nil, finalErr
|
return nil, fmt.Errorf("unable to run diff-index pipeline in temporary repo: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// If the process exited early, don't error
|
|
||||||
if err != context.Canceled {
|
|
||||||
log.Error("Unable to run diff-index pipeline in temporary repo %s (%s). Error: %v\nStderr: %s",
|
|
||||||
t.repo.FullName(), t.basePath, err, stderr)
|
|
||||||
return nil, fmt.Errorf("Unable to run diff-index pipeline in temporary repo %s. Error: %w\nStderr: %s",
|
|
||||||
t.repo.FullName(), err, stderr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, git.TrustedCmdArgs{"--cached"}, "HEAD")
|
diff.NumFiles, diff.TotalAddition, diff.TotalDeletion, err = git.GetDiffShortStat(t.ctx, t.basePath, git.TrustedCmdArgs{"--cached"}, "HEAD")
|
||||||
|
@ -183,11 +183,9 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
|||||||
repo.IsEmpty = false
|
repo.IsEmpty = false
|
||||||
if repo.DefaultBranch != setting.Repository.DefaultBranch {
|
if repo.DefaultBranch != setting.Repository.DefaultBranch {
|
||||||
if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
|
if err := gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil {
|
||||||
if !git.IsErrUnsupportedVersion(err) {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Update the is empty and default_branch columns
|
// Update the is empty and default_branch columns
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
|
if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil {
|
||||||
return fmt.Errorf("UpdateRepositoryCols: %w", err)
|
return fmt.Errorf("UpdateRepositoryCols: %w", err)
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
{{if .Milestone.DeadlineString}}
|
{{if .Milestone.DeadlineString}}
|
||||||
<span{{if .IsOverdue}} class="text red"{{end}}>
|
<span{{if .IsOverdue}} class="text red"{{end}}>
|
||||||
{{svg "octicon-calendar"}}
|
{{svg "octicon-calendar"}}
|
||||||
{{DateTime "short" .Milestone.DeadlineString}}
|
{{ctx.DateUtils.AbsoluteShort (.Milestone.DeadlineString|ctx.DateUtils.ParseLegacy)}}
|
||||||
</span>
|
</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{svg "octicon-calendar"}}
|
{{svg "octicon-calendar"}}
|
||||||
|
@ -59,7 +59,7 @@
|
|||||||
{{if .DeadlineString}}
|
{{if .DeadlineString}}
|
||||||
<span class="flex-text-inline {{if .IsOverdue}}text red{{end}}">
|
<span class="flex-text-inline {{if .IsOverdue}}text red{{end}}">
|
||||||
{{svg "octicon-calendar" 14}}
|
{{svg "octicon-calendar" 14}}
|
||||||
{{DateTime "short" .DeadlineString}}
|
{{ctx.DateUtils.AbsoluteShort (.DeadlineString|ctx.DateUtils.ParseLegacy)}}
|
||||||
</span>
|
</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{svg "octicon-calendar" 14}}
|
{{svg "octicon-calendar" 14}}
|
||||||
|
@ -296,7 +296,8 @@
|
|||||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||||
<span class="text grey muted-links">
|
<span class="text grey muted-links">
|
||||||
{{template "shared/user/authorlink" .Poster}}
|
{{template "shared/user/authorlink" .Poster}}
|
||||||
{{ctx.Locale.Tr "repo.issues.due_date_added" (DateTime "long" .Content) $createdStr}}
|
{{$dueDate := ctx.DateUtils.AbsoluteLong (.Content|ctx.DateUtils.ParseLegacy)}}
|
||||||
|
{{ctx.Locale.Tr "repo.issues.due_date_added" $dueDate $createdStr}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{else if eq .Type 17}}
|
{{else if eq .Type 17}}
|
||||||
@ -307,8 +308,8 @@
|
|||||||
{{template "shared/user/authorlink" .Poster}}
|
{{template "shared/user/authorlink" .Poster}}
|
||||||
{{$parsedDeadline := StringUtils.Split .Content "|"}}
|
{{$parsedDeadline := StringUtils.Split .Content "|"}}
|
||||||
{{if eq (len $parsedDeadline) 2}}
|
{{if eq (len $parsedDeadline) 2}}
|
||||||
{{$from := DateTime "long" (index $parsedDeadline 1)}}
|
{{$to := ctx.DateUtils.AbsoluteLong ((index $parsedDeadline 0)|ctx.DateUtils.ParseLegacy)}}
|
||||||
{{$to := DateTime "long" (index $parsedDeadline 0)}}
|
{{$from := ctx.DateUtils.AbsoluteLong ((index $parsedDeadline 1)|ctx.DateUtils.ParseLegacy)}}
|
||||||
{{ctx.Locale.Tr "repo.issues.due_date_modified" $to $from $createdStr}}
|
{{ctx.Locale.Tr "repo.issues.due_date_modified" $to $from $createdStr}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</span>
|
||||||
@ -319,7 +320,8 @@
|
|||||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||||
<span class="text grey muted-links">
|
<span class="text grey muted-links">
|
||||||
{{template "shared/user/authorlink" .Poster}}
|
{{template "shared/user/authorlink" .Poster}}
|
||||||
{{ctx.Locale.Tr "repo.issues.due_date_remove" (DateTime "long" .Content) $createdStr}}
|
{{$dueDate := ctx.DateUtils.AbsoluteLong (.Content|ctx.DateUtils.ParseLegacy)}}
|
||||||
|
{{ctx.Locale.Tr "repo.issues.due_date_remove" $dueDate $createdStr}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{else if eq .Type 19}}
|
{{else if eq .Type 19}}
|
||||||
|
@ -116,7 +116,7 @@
|
|||||||
{{if .DeadlineString}}
|
{{if .DeadlineString}}
|
||||||
<span{{if .IsOverdue}} class="text red"{{end}}>
|
<span{{if .IsOverdue}} class="text red"{{end}}>
|
||||||
{{svg "octicon-calendar" 14}}
|
{{svg "octicon-calendar" 14}}
|
||||||
{{DateTime "short" .DeadlineString}}
|
{{ctx.DateUtils.AbsoluteShort (.DeadlineString|ctx.DateUtils.ParseLegacy)}}
|
||||||
</span>
|
</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{svg "octicon-calendar" 14}}
|
{{svg "octicon-calendar" 14}}
|
||||||
|
@ -90,3 +90,14 @@ export function initRepoCommonFilterSearchDropdown(selector) {
|
|||||||
message: {noResults: $dropdown[0].getAttribute('data-no-results')},
|
message: {noResults: $dropdown[0].getAttribute('data-no-results')},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateIssuesMeta(url, action, issue_ids, id) {
|
||||||
|
try {
|
||||||
|
const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to update issues meta');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {updateIssuesMeta} from './repo-issue.ts';
|
import {updateIssuesMeta} from './repo-common.ts';
|
||||||
import {toggleElem, hideElem, isElemHidden} from '../utils/dom.ts';
|
import {toggleElem, hideElem, isElemHidden} from '../utils/dom.ts';
|
||||||
import {htmlEscape} from 'escape-goat';
|
import {htmlEscape} from 'escape-goat';
|
||||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||||
|
274
web_src/js/features/repo-issue-sidebar.ts
Normal file
274
web_src/js/features/repo-issue-sidebar.ts
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
import {POST} from '../modules/fetch.ts';
|
||||||
|
import {updateIssuesMeta} from './repo-common.ts';
|
||||||
|
import {svg} from '../svg.ts';
|
||||||
|
import {htmlEscape} from 'escape-goat';
|
||||||
|
|
||||||
|
// if there are draft comments, confirm before reloading, to avoid losing comments
|
||||||
|
function reloadConfirmDraftComment() {
|
||||||
|
const commentTextareas = [
|
||||||
|
document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
|
||||||
|
document.querySelector('#comment-form textarea'),
|
||||||
|
];
|
||||||
|
for (const textarea of commentTextareas) {
|
||||||
|
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
|
||||||
|
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
|
||||||
|
if (textarea && textarea.value.trim().length > 10) {
|
||||||
|
textarea.parentElement.scrollIntoView();
|
||||||
|
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function initBranchSelector() {
|
||||||
|
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||||
|
if (!elSelectBranch) return;
|
||||||
|
|
||||||
|
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
|
||||||
|
const $selectBranch = $(elSelectBranch);
|
||||||
|
const $branchMenu = $selectBranch.find('.reference-list-menu');
|
||||||
|
$branchMenu.find('.item:not(.no-select)').on('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(selectedHiddenSelector).value = selectedValue;
|
||||||
|
elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// List submits
|
||||||
|
function initListSubmits(selector, outerSelector) {
|
||||||
|
const $list = $(`.ui.${outerSelector}.list`);
|
||||||
|
const $noSelect = $list.find('.no-select');
|
||||||
|
const $listMenu = $(`.${selector} .menu`);
|
||||||
|
let hasUpdateAction = $listMenu.data('action') === 'update';
|
||||||
|
const items = {};
|
||||||
|
|
||||||
|
$(`.${selector}`).dropdown({
|
||||||
|
'action': 'nothing', // do not hide the menu if user presses Enter
|
||||||
|
fullTextSearch: 'exact',
|
||||||
|
async onHide() {
|
||||||
|
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
// TODO: Add batch functionality and make this 1 network request.
|
||||||
|
const itemEntries = Object.entries(items);
|
||||||
|
for (const [elementId, item] of itemEntries) {
|
||||||
|
await updateIssuesMeta(
|
||||||
|
item['update-url'],
|
||||||
|
item.action,
|
||||||
|
item['issue-id'],
|
||||||
|
elementId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (itemEntries.length) {
|
||||||
|
reloadConfirmDraftComment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
$listMenu.find('.item:not(.no-select)').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.classList.contains('ban-change')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
|
||||||
|
|
||||||
|
const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment
|
||||||
|
const scope = this.getAttribute('data-scope');
|
||||||
|
|
||||||
|
$(this).parent().find('.item').each(function () {
|
||||||
|
if (scope) {
|
||||||
|
// Enable only clicked item for scoped labels
|
||||||
|
if (this.getAttribute('data-scope') !== scope) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this !== clickedItem && !this.classList.contains('checked')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (this !== clickedItem) {
|
||||||
|
// Toggle for other labels
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.classList.contains('checked')) {
|
||||||
|
$(this).removeClass('checked');
|
||||||
|
$(this).find('.octicon-check').addClass('tw-invisible');
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
if (!($(this).data('id') in items)) {
|
||||||
|
items[$(this).data('id')] = {
|
||||||
|
'update-url': $listMenu.data('update-url'),
|
||||||
|
action: 'detach',
|
||||||
|
'issue-id': $listMenu.data('issue-id'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete items[$(this).data('id')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$(this).addClass('checked');
|
||||||
|
$(this).find('.octicon-check').removeClass('tw-invisible');
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
if (!($(this).data('id') in items)) {
|
||||||
|
items[$(this).data('id')] = {
|
||||||
|
'update-url': $listMenu.data('update-url'),
|
||||||
|
action: 'attach',
|
||||||
|
'issue-id': $listMenu.data('issue-id'),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
delete items[$(this).data('id')];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Which thing should be done for choosing review requests
|
||||||
|
// to make chosen items be shown on time here?
|
||||||
|
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listIds = [];
|
||||||
|
$(this).parent().find('.item').each(function () {
|
||||||
|
if (this.classList.contains('checked')) {
|
||||||
|
listIds.push($(this).data('id'));
|
||||||
|
$($(this).data('id-selector')).removeClass('tw-hidden');
|
||||||
|
} else {
|
||||||
|
$($(this).data('id-selector')).addClass('tw-hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!listIds.length) {
|
||||||
|
$noSelect.removeClass('tw-hidden');
|
||||||
|
} else {
|
||||||
|
$noSelect.addClass('tw-hidden');
|
||||||
|
}
|
||||||
|
$($(this).parent().data('id')).val(listIds.join(','));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$listMenu.find('.no-select.item').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
(async () => {
|
||||||
|
await updateIssuesMeta(
|
||||||
|
$listMenu.data('update-url'),
|
||||||
|
'clear',
|
||||||
|
$listMenu.data('issue-id'),
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
reloadConfirmDraftComment();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(this).parent().find('.item').each(function () {
|
||||||
|
$(this).removeClass('checked');
|
||||||
|
$(this).find('.octicon-check').addClass('tw-invisible');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list.find('.item').each(function () {
|
||||||
|
$(this).addClass('tw-hidden');
|
||||||
|
});
|
||||||
|
$noSelect.removeClass('tw-hidden');
|
||||||
|
$($(this).parent().data('id')).val('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectItem(select_id, input_id) {
|
||||||
|
const $menu = $(`${select_id} .menu`);
|
||||||
|
const $list = $(`.ui${select_id}.list`);
|
||||||
|
const hasUpdateAction = $menu.data('action') === 'update';
|
||||||
|
|
||||||
|
$menu.find('.item:not(.no-select)').on('click', function () {
|
||||||
|
$(this).parent().find('.item').each(function () {
|
||||||
|
$(this).removeClass('selected active');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(this).addClass('selected active');
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
(async () => {
|
||||||
|
await updateIssuesMeta(
|
||||||
|
$menu.data('update-url'),
|
||||||
|
'',
|
||||||
|
$menu.data('issue-id'),
|
||||||
|
$(this).data('id'),
|
||||||
|
);
|
||||||
|
reloadConfirmDraftComment();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = '';
|
||||||
|
if (input_id === '#milestone_id') {
|
||||||
|
icon = svg('octicon-milestone', 18, 'tw-mr-2');
|
||||||
|
} else if (input_id === '#project_id') {
|
||||||
|
icon = svg('octicon-project', 18, 'tw-mr-2');
|
||||||
|
} else if (input_id === '#assignee_id') {
|
||||||
|
icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list.find('.selected').html(`
|
||||||
|
<a class="item muted sidebar-item-link" href="${htmlEscape(this.getAttribute('data-href'))}">
|
||||||
|
${icon}
|
||||||
|
${htmlEscape(this.textContent)}
|
||||||
|
</a>
|
||||||
|
`);
|
||||||
|
|
||||||
|
$(`.ui${select_id}.list .no-select`).addClass('tw-hidden');
|
||||||
|
$(input_id).val($(this).data('id'));
|
||||||
|
});
|
||||||
|
$menu.find('.no-select.item').on('click', function () {
|
||||||
|
$(this).parent().find('.item:not(.no-select)').each(function () {
|
||||||
|
$(this).removeClass('selected active');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasUpdateAction) {
|
||||||
|
(async () => {
|
||||||
|
await updateIssuesMeta(
|
||||||
|
$menu.data('update-url'),
|
||||||
|
'',
|
||||||
|
$menu.data('issue-id'),
|
||||||
|
$(this).data('id'),
|
||||||
|
);
|
||||||
|
reloadConfirmDraftComment();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list.find('.selected').html('');
|
||||||
|
$list.find('.no-select').removeClass('tw-hidden');
|
||||||
|
$(input_id).val('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initRepoIssueSidebar() {
|
||||||
|
initBranchSelector();
|
||||||
|
|
||||||
|
// Init labels and assignees
|
||||||
|
initListSubmits('select-label', 'labels');
|
||||||
|
initListSubmits('select-assignees', 'assignees');
|
||||||
|
initListSubmits('select-assignees-modify', 'assignees');
|
||||||
|
initListSubmits('select-reviewers-modify', 'assignees');
|
||||||
|
|
||||||
|
// Milestone, Assignee, Project
|
||||||
|
selectItem('.select-project', '#project_id');
|
||||||
|
selectItem('.select-milestone', '#milestone_id');
|
||||||
|
selectItem('.select-assignee', '#assignee_id');
|
||||||
|
}
|
@ -7,6 +7,8 @@ import {ComboMarkdownEditor, getComboMarkdownEditor, initComboMarkdownEditor} fr
|
|||||||
import {toAbsoluteUrl} from '../utils.ts';
|
import {toAbsoluteUrl} from '../utils.ts';
|
||||||
import {GET, POST} from '../modules/fetch.ts';
|
import {GET, POST} from '../modules/fetch.ts';
|
||||||
import {showErrorToast} from '../modules/toast.ts';
|
import {showErrorToast} from '../modules/toast.ts';
|
||||||
|
import {initRepoIssueSidebar} from './repo-issue-sidebar.ts';
|
||||||
|
import {updateIssuesMeta} from './repo-common.ts';
|
||||||
|
|
||||||
const {appSubUrl} = window.config;
|
const {appSubUrl} = window.config;
|
||||||
|
|
||||||
@ -369,17 +371,6 @@ export function initRepoIssueWipTitle() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateIssuesMeta(url, action, issue_ids, id) {
|
|
||||||
try {
|
|
||||||
const response = await POST(url, {data: new URLSearchParams({action, issue_ids, id})});
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to update issues meta');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initRepoIssueComments() {
|
export function initRepoIssueComments() {
|
||||||
if (!$('.repository.view.issue .timeline').length) return;
|
if (!$('.repository.view.issue .timeline').length) return;
|
||||||
|
|
||||||
@ -665,7 +656,7 @@ export function initRepoIssueBranchSelect() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initSingleCommentEditor($commentForm) {
|
async function initSingleCommentEditor($commentForm) {
|
||||||
// pages:
|
// pages:
|
||||||
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
|
// * normal new issue/pr page: no status-button, no comment-button (there is only a normal submit button which can submit empty content)
|
||||||
// * issue/pr view page: with comment form, has status-button and comment-button
|
// * issue/pr view page: with comment form, has status-button and comment-button
|
||||||
@ -687,7 +678,7 @@ export async function initSingleCommentEditor($commentForm) {
|
|||||||
syncUiState();
|
syncUiState();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initIssueTemplateCommentEditors($commentForm) {
|
function initIssueTemplateCommentEditors($commentForm) {
|
||||||
// pages:
|
// pages:
|
||||||
// * new issue with issue template
|
// * new issue with issue template
|
||||||
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
const $comboFields = $commentForm.find('.combo-editor-dropzone');
|
||||||
@ -733,3 +724,18 @@ export function initArchivedLabelHandler() {
|
|||||||
toggleElem(label, label.classList.contains('checked'));
|
toggleElem(label, label.classList.contains('checked'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initRepoCommentFormAndSidebar() {
|
||||||
|
const $commentForm = $('.comment.form');
|
||||||
|
if (!$commentForm.length) return;
|
||||||
|
|
||||||
|
if ($commentForm.find('.field.combo-editor-dropzone').length) {
|
||||||
|
// at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form
|
||||||
|
initIssueTemplateCommentEditors($commentForm);
|
||||||
|
} else if ($commentForm.find('.combo-markdown-editor').length) {
|
||||||
|
// it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message
|
||||||
|
initSingleCommentEditor($commentForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
initRepoIssueSidebar();
|
||||||
|
}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import {
|
import {
|
||||||
|
initRepoCommentFormAndSidebar,
|
||||||
initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete,
|
initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete,
|
||||||
initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue,
|
initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue,
|
||||||
initRepoIssueTitleEdit, initRepoIssueWipToggle,
|
initRepoIssueTitleEdit, initRepoIssueWipToggle,
|
||||||
initRepoPullRequestUpdate, updateIssuesMeta, initIssueTemplateCommentEditors, initSingleCommentEditor,
|
initRepoPullRequestUpdate,
|
||||||
} from './repo-issue.ts';
|
} from './repo-issue.ts';
|
||||||
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
|
import {initUnicodeEscapeButton} from './repo-unicode-escape.ts';
|
||||||
import {svg} from '../svg.ts';
|
|
||||||
import {htmlEscape} from 'escape-goat';
|
|
||||||
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
|
import {initRepoBranchTagSelector} from '../components/RepoBranchTagSelector.vue';
|
||||||
import {
|
import {
|
||||||
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
|
initRepoCloneLink, initRepoCommonBranchOrTagDropdown, initRepoCommonFilterSearchDropdown,
|
||||||
@ -16,32 +15,13 @@ import {initCitationFileCopyContent} from './citation.ts';
|
|||||||
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
||||||
import {initRepoDiffConversationNav} from './repo-diff.ts';
|
import {initRepoDiffConversationNav} from './repo-diff.ts';
|
||||||
import {initCompReactionSelector} from './comp/ReactionSelector.ts';
|
import {initCompReactionSelector} from './comp/ReactionSelector.ts';
|
||||||
import {initRepoSettingBranches} from './repo-settings.ts';
|
import {initRepoSettings} from './repo-settings.ts';
|
||||||
import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.ts';
|
import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.ts';
|
||||||
import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.ts';
|
import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.ts';
|
||||||
import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
|
import {hideElem, queryElemChildren, showElem} from '../utils/dom.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
|
||||||
import {initRepoIssueCommentEdit} from './repo-issue-edit.ts';
|
import {initRepoIssueCommentEdit} from './repo-issue-edit.ts';
|
||||||
|
import {initRepoMilestone} from './repo-milestone.ts';
|
||||||
// if there are draft comments, confirm before reloading, to avoid losing comments
|
import {initRepoNew} from './repo-new.ts';
|
||||||
function reloadConfirmDraftComment() {
|
|
||||||
const commentTextareas = [
|
|
||||||
document.querySelector('.edit-content-zone:not(.tw-hidden) textarea'),
|
|
||||||
document.querySelector('#comment-form textarea'),
|
|
||||||
];
|
|
||||||
for (const textarea of commentTextareas) {
|
|
||||||
// Most users won't feel too sad if they lose a comment with 10 chars, they can re-type these in seconds.
|
|
||||||
// But if they have typed more (like 50) chars and the comment is lost, they will be very unhappy.
|
|
||||||
if (textarea && textarea.value.trim().length > 10) {
|
|
||||||
textarea.parentElement.scrollIntoView();
|
|
||||||
if (!window.confirm('Page will be reloaded, but there are draft comments. Continuing to reload will discard the comments. Continue?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initBranchSelectorTabs() {
|
export function initBranchSelectorTabs() {
|
||||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
||||||
@ -56,324 +36,16 @@ export function initBranchSelectorTabs() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoCommentForm() {
|
|
||||||
const $commentForm = $('.comment.form');
|
|
||||||
if (!$commentForm.length) return;
|
|
||||||
|
|
||||||
if ($commentForm.find('.field.combo-editor-dropzone').length) {
|
|
||||||
// at the moment, if a form has multiple combo-markdown-editors, it must be an issue template form
|
|
||||||
initIssueTemplateCommentEditors($commentForm);
|
|
||||||
} else if ($commentForm.find('.combo-markdown-editor').length) {
|
|
||||||
// it's quite unclear about the "comment form" elements, sometimes it's for issue comment, sometimes it's for file editor/uploader message
|
|
||||||
initSingleCommentEditor($commentForm);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initBranchSelector() {
|
|
||||||
const elSelectBranch = document.querySelector('.ui.dropdown.select-branch');
|
|
||||||
if (!elSelectBranch) return;
|
|
||||||
|
|
||||||
const urlUpdateIssueRef = elSelectBranch.getAttribute('data-url-update-issueref');
|
|
||||||
const $selectBranch = $(elSelectBranch);
|
|
||||||
const $branchMenu = $selectBranch.find('.reference-list-menu');
|
|
||||||
$branchMenu.find('.item:not(.no-select)').on('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(selectedHiddenSelector).value = selectedValue;
|
|
||||||
elSelectBranch.querySelector('.text-branch-name').textContent = selectedText;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initBranchSelector();
|
|
||||||
|
|
||||||
// List submits
|
|
||||||
function initListSubmits(selector, outerSelector) {
|
|
||||||
const $list = $(`.ui.${outerSelector}.list`);
|
|
||||||
const $noSelect = $list.find('.no-select');
|
|
||||||
const $listMenu = $(`.${selector} .menu`);
|
|
||||||
let hasUpdateAction = $listMenu.data('action') === 'update';
|
|
||||||
const items = {};
|
|
||||||
|
|
||||||
$(`.${selector}`).dropdown({
|
|
||||||
'action': 'nothing', // do not hide the menu if user presses Enter
|
|
||||||
fullTextSearch: 'exact',
|
|
||||||
async onHide() {
|
|
||||||
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
// TODO: Add batch functionality and make this 1 network request.
|
|
||||||
const itemEntries = Object.entries(items);
|
|
||||||
for (const [elementId, item] of itemEntries) {
|
|
||||||
await updateIssuesMeta(
|
|
||||||
item['update-url'],
|
|
||||||
item.action,
|
|
||||||
item['issue-id'],
|
|
||||||
elementId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (itemEntries.length) {
|
|
||||||
reloadConfirmDraftComment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$listMenu.find('.item:not(.no-select)').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this.classList.contains('ban-change')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasUpdateAction = $listMenu.data('action') === 'update'; // Update the var
|
|
||||||
|
|
||||||
const clickedItem = this; // eslint-disable-line unicorn/no-this-assignment
|
|
||||||
const scope = this.getAttribute('data-scope');
|
|
||||||
|
|
||||||
$(this).parent().find('.item').each(function () {
|
|
||||||
if (scope) {
|
|
||||||
// Enable only clicked item for scoped labels
|
|
||||||
if (this.getAttribute('data-scope') !== scope) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this !== clickedItem && !this.classList.contains('checked')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (this !== clickedItem) {
|
|
||||||
// Toggle for other labels
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.classList.contains('checked')) {
|
|
||||||
$(this).removeClass('checked');
|
|
||||||
$(this).find('.octicon-check').addClass('tw-invisible');
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
if (!($(this).data('id') in items)) {
|
|
||||||
items[$(this).data('id')] = {
|
|
||||||
'update-url': $listMenu.data('update-url'),
|
|
||||||
action: 'detach',
|
|
||||||
'issue-id': $listMenu.data('issue-id'),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
delete items[$(this).data('id')];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$(this).addClass('checked');
|
|
||||||
$(this).find('.octicon-check').removeClass('tw-invisible');
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
if (!($(this).data('id') in items)) {
|
|
||||||
items[$(this).data('id')] = {
|
|
||||||
'update-url': $listMenu.data('update-url'),
|
|
||||||
action: 'attach',
|
|
||||||
'issue-id': $listMenu.data('issue-id'),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
delete items[$(this).data('id')];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Which thing should be done for choosing review requests
|
|
||||||
// to make chosen items be shown on time here?
|
|
||||||
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const listIds = [];
|
|
||||||
$(this).parent().find('.item').each(function () {
|
|
||||||
if (this.classList.contains('checked')) {
|
|
||||||
listIds.push($(this).data('id'));
|
|
||||||
$($(this).data('id-selector')).removeClass('tw-hidden');
|
|
||||||
} else {
|
|
||||||
$($(this).data('id-selector')).addClass('tw-hidden');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!listIds.length) {
|
|
||||||
$noSelect.removeClass('tw-hidden');
|
|
||||||
} else {
|
|
||||||
$noSelect.addClass('tw-hidden');
|
|
||||||
}
|
|
||||||
$($(this).parent().data('id')).val(listIds.join(','));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
$listMenu.find('.no-select.item').on('click', function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
(async () => {
|
|
||||||
await updateIssuesMeta(
|
|
||||||
$listMenu.data('update-url'),
|
|
||||||
'clear',
|
|
||||||
$listMenu.data('issue-id'),
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
reloadConfirmDraftComment();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(this).parent().find('.item').each(function () {
|
|
||||||
$(this).removeClass('checked');
|
|
||||||
$(this).find('.octicon-check').addClass('tw-invisible');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$list.find('.item').each(function () {
|
|
||||||
$(this).addClass('tw-hidden');
|
|
||||||
});
|
|
||||||
$noSelect.removeClass('tw-hidden');
|
|
||||||
$($(this).parent().data('id')).val('');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init labels and assignees
|
|
||||||
initListSubmits('select-label', 'labels');
|
|
||||||
initListSubmits('select-assignees', 'assignees');
|
|
||||||
initListSubmits('select-assignees-modify', 'assignees');
|
|
||||||
initListSubmits('select-reviewers', 'reviewers');
|
|
||||||
initListSubmits('select-reviewers-modify', 'reviewers');
|
|
||||||
|
|
||||||
function selectItem(select_id, input_id) {
|
|
||||||
const $menu = $(`${select_id} .menu`);
|
|
||||||
const $list = $(`.ui${select_id}.list`);
|
|
||||||
const hasUpdateAction = $menu.data('action') === 'update';
|
|
||||||
|
|
||||||
$menu.find('.item:not(.no-select)').on('click', function () {
|
|
||||||
$(this).parent().find('.item').each(function () {
|
|
||||||
$(this).removeClass('selected active');
|
|
||||||
});
|
|
||||||
|
|
||||||
$(this).addClass('selected active');
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
(async () => {
|
|
||||||
await updateIssuesMeta(
|
|
||||||
$menu.data('update-url'),
|
|
||||||
'',
|
|
||||||
$menu.data('issue-id'),
|
|
||||||
$(this).data('id'),
|
|
||||||
);
|
|
||||||
reloadConfirmDraftComment();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon = '';
|
|
||||||
if (input_id === '#milestone_id') {
|
|
||||||
icon = svg('octicon-milestone', 18, 'tw-mr-2');
|
|
||||||
} else if (input_id === '#project_id') {
|
|
||||||
icon = svg('octicon-project', 18, 'tw-mr-2');
|
|
||||||
} else if (input_id === '#assignee_id') {
|
|
||||||
icon = `<img class="ui avatar image tw-mr-2" alt="avatar" src=${$(this).data('avatar')}>`;
|
|
||||||
} else if (input_id === '#reviewer_id') {
|
|
||||||
icon = `<img class="ui avatar image gt-mr-3" alt="avatar" src=${$(this).data('avatar')}>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$list.find('.selected').html(`
|
|
||||||
<a class="item muted sidebar-item-link" href="${htmlEscape(this.getAttribute('data-href'))}">
|
|
||||||
${icon}
|
|
||||||
${htmlEscape(this.textContent)}
|
|
||||||
</a>
|
|
||||||
`);
|
|
||||||
|
|
||||||
$(`.ui${select_id}.list .no-select`).addClass('tw-hidden');
|
|
||||||
$(input_id).val($(this).data('id'));
|
|
||||||
});
|
|
||||||
$menu.find('.no-select.item').on('click', function () {
|
|
||||||
$(this).parent().find('.item:not(.no-select)').each(function () {
|
|
||||||
$(this).removeClass('selected active');
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasUpdateAction) {
|
|
||||||
(async () => {
|
|
||||||
await updateIssuesMeta(
|
|
||||||
$menu.data('update-url'),
|
|
||||||
'',
|
|
||||||
$menu.data('issue-id'),
|
|
||||||
$(this).data('id'),
|
|
||||||
);
|
|
||||||
reloadConfirmDraftComment();
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
$list.find('.selected').html('');
|
|
||||||
$list.find('.no-select').removeClass('tw-hidden');
|
|
||||||
$(input_id).val('');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Milestone, Assignee, Project
|
|
||||||
selectItem('.select-project', '#project_id');
|
|
||||||
selectItem('.select-milestone', '#milestone_id');
|
|
||||||
selectItem('.select-assignee', '#assignee_id');
|
|
||||||
selectItem('.select-reviewer', '#reviewer_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initRepository() {
|
export function initRepository() {
|
||||||
if (!$('.page-content.repository').length) return;
|
if (!$('.page-content.repository').length) return;
|
||||||
|
|
||||||
initRepoBranchTagSelector('.js-branch-tag-selector');
|
initRepoBranchTagSelector('.js-branch-tag-selector');
|
||||||
|
initRepoCommentFormAndSidebar();
|
||||||
// Options
|
|
||||||
if ($('.repository.settings.options').length > 0) {
|
|
||||||
// Enable or select internal/external wiki system and issue tracker.
|
|
||||||
$('.enable-system').on('change', function () {
|
|
||||||
if (this.checked) {
|
|
||||||
$($(this).data('target')).removeClass('disabled');
|
|
||||||
if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
|
|
||||||
} else {
|
|
||||||
$($(this).data('target')).addClass('disabled');
|
|
||||||
if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('.enable-system-radio').on('change', function () {
|
|
||||||
if (this.value === 'false') {
|
|
||||||
$($(this).data('target')).addClass('disabled');
|
|
||||||
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
|
|
||||||
} else if (this.value === 'true') {
|
|
||||||
$($(this).data('target')).removeClass('disabled');
|
|
||||||
if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const $trackerIssueStyleRadios = $('.js-tracker-issue-style');
|
|
||||||
$trackerIssueStyleRadios.on('change input', () => {
|
|
||||||
const checkedVal = $trackerIssueStyleRadios.filter(':checked').val();
|
|
||||||
$('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
initCompLabelEdit('.repository.labels');
|
initCompLabelEdit('.repository.labels');
|
||||||
|
initRepoMilestone();
|
||||||
// Milestones
|
initRepoNew();
|
||||||
if ($('.repository.new.milestone').length > 0) {
|
|
||||||
$('#clear-date').on('click', () => {
|
|
||||||
$('#deadline').val('');
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Repo Creation
|
|
||||||
if ($('.repository.new.repo').length > 0) {
|
|
||||||
$('input[name="gitignores"], input[name="license"]').on('change', () => {
|
|
||||||
const gitignores = $('input[name="gitignores"]').val();
|
|
||||||
const license = $('input[name="license"]').val();
|
|
||||||
if (gitignores || license) {
|
|
||||||
document.querySelector('input[name="auto_init"]').checked = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare or pull request
|
// Compare or pull request
|
||||||
const $repoDiff = $('.repository.diff');
|
const $repoDiff = $('.repository.diff');
|
||||||
@ -384,7 +56,7 @@ export function initRepository() {
|
|||||||
|
|
||||||
initRepoCloneLink();
|
initRepoCloneLink();
|
||||||
initCitationFileCopyContent();
|
initCitationFileCopyContent();
|
||||||
initRepoSettingBranches();
|
initRepoSettings();
|
||||||
|
|
||||||
// Issues
|
// Issues
|
||||||
if ($('.repository.view.issue').length > 0) {
|
if ($('.repository.view.issue').length > 0) {
|
||||||
|
11
web_src/js/features/repo-milestone.ts
Normal file
11
web_src/js/features/repo-milestone.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
export function initRepoMilestone() {
|
||||||
|
// Milestones
|
||||||
|
if ($('.repository.new.milestone').length > 0) {
|
||||||
|
$('#clear-date').on('click', () => {
|
||||||
|
$('#deadline').val('');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
14
web_src/js/features/repo-new.ts
Normal file
14
web_src/js/features/repo-new.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
export function initRepoNew() {
|
||||||
|
// Repo Creation
|
||||||
|
if ($('.repository.new.repo').length > 0) {
|
||||||
|
$('input[name="gitignores"], input[name="license"]').on('change', () => {
|
||||||
|
const gitignores = $('input[name="gitignores"]').val();
|
||||||
|
const license = $('input[name="license"]').val();
|
||||||
|
if (gitignores || license) {
|
||||||
|
document.querySelector('input[name="auto_init"]').checked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import {POST} from '../modules/fetch.ts';
|
|||||||
|
|
||||||
const {appSubUrl, csrfToken} = window.config;
|
const {appSubUrl, csrfToken} = window.config;
|
||||||
|
|
||||||
export function initRepoSettingsCollaboration() {
|
function initRepoSettingsCollaboration() {
|
||||||
// Change collaborator access mode
|
// Change collaborator access mode
|
||||||
for (const dropdownEl of queryElems('.page-content.repository .ui.dropdown.access-mode')) {
|
for (const dropdownEl of queryElems('.page-content.repository .ui.dropdown.access-mode')) {
|
||||||
const textEl = dropdownEl.querySelector(':scope > .text');
|
const textEl = dropdownEl.querySelector(':scope > .text');
|
||||||
@ -43,7 +43,7 @@ export function initRepoSettingsCollaboration() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoSettingSearchTeamBox() {
|
function initRepoSettingsSearchTeamBox() {
|
||||||
const searchTeamBox = document.querySelector('#search-team-box');
|
const searchTeamBox = document.querySelector('#search-team-box');
|
||||||
if (!searchTeamBox) return;
|
if (!searchTeamBox) return;
|
||||||
|
|
||||||
@ -69,13 +69,13 @@ export function initRepoSettingSearchTeamBox() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoSettingGitHook() {
|
function initRepoSettingsGitHook() {
|
||||||
if (!$('.edit.githook').length) return;
|
if (!$('.edit.githook').length) return;
|
||||||
const filename = document.querySelector('.hook-filename').textContent;
|
const filename = document.querySelector('.hook-filename').textContent;
|
||||||
const _promise = createMonaco($('#content')[0], filename, {language: 'shell'});
|
createMonaco($('#content')[0], filename, {language: 'shell'});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoSettingBranches() {
|
function initRepoSettingsBranches() {
|
||||||
if (!document.querySelector('.repository.settings.branches')) return;
|
if (!document.querySelector('.repository.settings.branches')) return;
|
||||||
|
|
||||||
for (const el of document.querySelectorAll('.toggle-target-enabled')) {
|
for (const el of document.querySelectorAll('.toggle-target-enabled')) {
|
||||||
@ -117,3 +117,41 @@ export function initRepoSettingBranches() {
|
|||||||
markMatchedStatusChecks();
|
markMatchedStatusChecks();
|
||||||
document.querySelector('#status_check_contexts').addEventListener('input', onInputDebounce(markMatchedStatusChecks));
|
document.querySelector('#status_check_contexts').addEventListener('input', onInputDebounce(markMatchedStatusChecks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initRepoSettingsOptions() {
|
||||||
|
if ($('.repository.settings.options').length > 0) {
|
||||||
|
// Enable or select internal/external wiki system and issue tracker.
|
||||||
|
$('.enable-system').on('change', function () {
|
||||||
|
if (this.checked) {
|
||||||
|
$($(this).data('target')).removeClass('disabled');
|
||||||
|
if (!$(this).data('context')) $($(this).data('context')).addClass('disabled');
|
||||||
|
} else {
|
||||||
|
$($(this).data('target')).addClass('disabled');
|
||||||
|
if (!$(this).data('context')) $($(this).data('context')).removeClass('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('.enable-system-radio').on('change', function () {
|
||||||
|
if (this.value === 'false') {
|
||||||
|
$($(this).data('target')).addClass('disabled');
|
||||||
|
if ($(this).data('context') !== undefined) $($(this).data('context')).removeClass('disabled');
|
||||||
|
} else if (this.value === 'true') {
|
||||||
|
$($(this).data('target')).removeClass('disabled');
|
||||||
|
if ($(this).data('context') !== undefined) $($(this).data('context')).addClass('disabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const $trackerIssueStyleRadios = $('.js-tracker-issue-style');
|
||||||
|
$trackerIssueStyleRadios.on('change input', () => {
|
||||||
|
const checkedVal = $trackerIssueStyleRadios.filter(':checked').val();
|
||||||
|
$('#tracker-issue-style-regex-box').toggleClass('disabled', checkedVal !== 'regexp');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initRepoSettings() {
|
||||||
|
if (!document.querySelector('.page-content.repository.settings')) return;
|
||||||
|
initRepoSettingsOptions();
|
||||||
|
initRepoSettingsBranches();
|
||||||
|
initRepoSettingsCollaboration();
|
||||||
|
initRepoSettingsSearchTeamBox();
|
||||||
|
initRepoSettingsGitHook();
|
||||||
|
}
|
||||||
|
@ -43,11 +43,6 @@ import {initSshKeyFormParser} from './features/sshkey-helper.ts';
|
|||||||
import {initUserSettings} from './features/user-settings.ts';
|
import {initUserSettings} from './features/user-settings.ts';
|
||||||
import {initRepoActivityTopAuthorsChart, initRepoArchiveLinks} from './features/repo-common.ts';
|
import {initRepoActivityTopAuthorsChart, initRepoArchiveLinks} from './features/repo-common.ts';
|
||||||
import {initRepoMigrationStatusChecker} from './features/repo-migrate.ts';
|
import {initRepoMigrationStatusChecker} from './features/repo-migrate.ts';
|
||||||
import {
|
|
||||||
initRepoSettingGitHook,
|
|
||||||
initRepoSettingsCollaboration,
|
|
||||||
initRepoSettingSearchTeamBox,
|
|
||||||
} from './features/repo-settings.ts';
|
|
||||||
import {initRepoDiffView} from './features/repo-diff.ts';
|
import {initRepoDiffView} from './features/repo-diff.ts';
|
||||||
import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.ts';
|
import {initOrgTeamSearchRepoBox, initOrgTeamSettings} from './features/org-team.ts';
|
||||||
import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts';
|
import {initUserAuthWebAuthn, initUserAuthWebAuthnRegister} from './features/user-auth-webauthn.ts';
|
||||||
@ -59,7 +54,7 @@ import {initCompWebHookEditor} from './features/comp/WebHookEditor.ts';
|
|||||||
import {initRepoBranchButton} from './features/repo-branch.ts';
|
import {initRepoBranchButton} from './features/repo-branch.ts';
|
||||||
import {initCommonOrganization} from './features/common-organization.ts';
|
import {initCommonOrganization} from './features/common-organization.ts';
|
||||||
import {initRepoWikiForm} from './features/repo-wiki.ts';
|
import {initRepoWikiForm} from './features/repo-wiki.ts';
|
||||||
import {initRepoCommentForm, initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts';
|
import {initRepository, initBranchSelectorTabs} from './features/repo-legacy.ts';
|
||||||
import {initCopyContent} from './features/copycontent.ts';
|
import {initCopyContent} from './features/copycontent.ts';
|
||||||
import {initCaptcha} from './features/captcha.ts';
|
import {initCaptcha} from './features/captcha.ts';
|
||||||
import {initRepositoryActionView} from './components/RepoActionView.vue';
|
import {initRepositoryActionView} from './components/RepoActionView.vue';
|
||||||
@ -180,7 +175,6 @@ onDomReady(() => {
|
|||||||
initRepoArchiveLinks,
|
initRepoArchiveLinks,
|
||||||
initRepoBranchButton,
|
initRepoBranchButton,
|
||||||
initRepoCodeView,
|
initRepoCodeView,
|
||||||
initRepoCommentForm,
|
|
||||||
initBranchSelectorTabs,
|
initBranchSelectorTabs,
|
||||||
initRepoEllipsisButton,
|
initRepoEllipsisButton,
|
||||||
initRepoDiffCommitBranchesAndTags,
|
initRepoDiffCommitBranchesAndTags,
|
||||||
@ -202,9 +196,6 @@ onDomReady(() => {
|
|||||||
initRepoPullRequestReview,
|
initRepoPullRequestReview,
|
||||||
initRepoRelease,
|
initRepoRelease,
|
||||||
initRepoReleaseNew,
|
initRepoReleaseNew,
|
||||||
initRepoSettingGitHook,
|
|
||||||
initRepoSettingSearchTeamBox,
|
|
||||||
initRepoSettingsCollaboration,
|
|
||||||
initRepoTemplateSearch,
|
initRepoTemplateSearch,
|
||||||
initRepoTopicBar,
|
initRepoTopicBar,
|
||||||
initRepoWikiForm,
|
initRepoWikiForm,
|
||||||
|
Loading…
Reference in New Issue
Block a user