forked from mirror/gitea
Compare commits
14 Commits
83ba882bab
...
70e077036f
Author | SHA1 | Date | |
---|---|---|---|
Yarden Shoham | 70e077036f | ||
silverwind | 35def319fd | ||
Yarden Shoham | 0679e60c77 | ||
yp05327 | ce085b26fc | ||
Rafael Heard | 03753cbc0f | ||
Jason Song | 487ac9bf6c | ||
Lunny Xiao | 8d979f1692 | ||
sillyguodong | 2033eb7c11 | ||
silverwind | eb8c34fc36 | ||
Denys Konovalov | 7a90e5954f | ||
coldWater | e79a807a84 | ||
silverwind | bbef5fc5c3 | ||
yp05327 | 2da13675c0 | ||
wxiaoguang | 43de021ac1 |
2
Makefile
2
Makefile
|
@ -31,7 +31,7 @@ GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0
|
|||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.56.1
|
||||
GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11
|
||||
MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.4.1
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5
|
||||
SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@db51e79a0e37c572d8b59ae0c58bf2bbbbe53285
|
||||
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
|
||||
GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.3
|
||||
|
|
|
@ -2608,7 +2608,7 @@ LEVEL = Info
|
|||
;ENDLESS_TASK_TIMEOUT = 3h
|
||||
;; Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
;ABANDONED_JOB_TIMEOUT = 24h
|
||||
;; Strings committers can place inside a commit message to skip executing the corresponding actions workflow
|
||||
;; Strings committers can place inside a commit message or PR title to skip executing the corresponding actions workflow
|
||||
;SKIP_WORKFLOW_STRINGS = [skip ci],[ci skip],[no ci],[skip actions],[actions skip]
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -1406,7 +1406,7 @@ PROXY_HOSTS = *.github.com
|
|||
- `ZOMBIE_TASK_TIMEOUT`: **10m**: Timeout to stop the task which have running status, but haven't been updated for a long time
|
||||
- `ENDLESS_TASK_TIMEOUT`: **3h**: Timeout to stop the tasks which have running status and continuous updates, but don't end for a long time
|
||||
- `ABANDONED_JOB_TIMEOUT`: **24h**: Timeout to cancel the jobs which have waiting status, but haven't been picked by a runner for a long time
|
||||
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message to skip executing the corresponding actions workflow
|
||||
- `SKIP_WORKFLOW_STRINGS`: **[skip ci],[ci skip],[no ci],[skip actions],[actions skip]**: Strings committers can place inside a commit message or PR title to skip executing the corresponding actions workflow
|
||||
|
||||
`DEFAULT_ACTIONS_URL` indicates where the Gitea Actions runners should find the actions with relative path.
|
||||
For example, `uses: actions/checkout@v4` means `https://github.com/actions/checkout@v4` since the value of `DEFAULT_ACTIONS_URL` is `github`.
|
||||
|
|
|
@ -77,29 +77,62 @@ func writeField(w io.Writer, element, class, field string) error {
|
|||
}
|
||||
|
||||
// Render implements markup.Renderer
|
||||
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||
tmpBlock := bufio.NewWriter(output)
|
||||
maxSize := setting.UI.CSV.MaxFileSize
|
||||
|
||||
// FIXME: don't read all to memory
|
||||
rawBytes, err := io.ReadAll(input)
|
||||
if maxSize == 0 {
|
||||
return r.tableRender(ctx, input, tmpBlock)
|
||||
}
|
||||
|
||||
rawBytes, err := io.ReadAll(io.LimitReader(input, maxSize+1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < int64(len(rawBytes)) {
|
||||
if _, err := tmpBlock.WriteString("<pre>"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tmpBlock.WriteString("</pre>"); err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpBlock.Flush()
|
||||
if int64(len(rawBytes)) <= maxSize {
|
||||
return r.tableRender(ctx, bytes.NewReader(rawBytes), tmpBlock)
|
||||
}
|
||||
return r.fallbackRender(io.MultiReader(bytes.NewReader(rawBytes), input), tmpBlock)
|
||||
}
|
||||
|
||||
func (Renderer) fallbackRender(input io.Reader, tmpBlock *bufio.Writer) error {
|
||||
_, err := tmpBlock.WriteString("<pre>")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes))
|
||||
scan := bufio.NewScanner(input)
|
||||
scan.Split(bufio.ScanRunes)
|
||||
for scan.Scan() {
|
||||
switch scan.Text() {
|
||||
case `&`:
|
||||
_, err = tmpBlock.WriteString("&")
|
||||
case `'`:
|
||||
_, err = tmpBlock.WriteString("'") // "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
case `<`:
|
||||
_, err = tmpBlock.WriteString("<")
|
||||
case `>`:
|
||||
_, err = tmpBlock.WriteString(">")
|
||||
case `"`:
|
||||
_, err = tmpBlock.WriteString(""") // """ is shorter than """.
|
||||
default:
|
||||
_, err = tmpBlock.Write(scan.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tmpBlock.WriteString("</pre>")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpBlock.Flush()
|
||||
}
|
||||
|
||||
func (Renderer) tableRender(ctx *markup.RenderContext, input io.Reader, tmpBlock *bufio.Writer) error {
|
||||
rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
package markup
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -29,4 +31,12 @@ func TestRenderCSV(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, v, buf.String())
|
||||
}
|
||||
|
||||
t.Run("fallbackRender", func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
err := render.fallbackRender(strings.NewReader("1,<a>\n2,<b>"), bufio.NewWriter(&buf))
|
||||
assert.NoError(t, err)
|
||||
want := "<pre>1,<a>\n2,<b></pre>"
|
||||
assert.Equal(t, want, buf.String())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// settings
|
||||
|
@ -158,9 +159,11 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
|||
func loadRunModeFrom(rootCfg ConfigProvider) {
|
||||
rootSec := rootCfg.Section("")
|
||||
RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername())
|
||||
|
||||
// The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches.
|
||||
// Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly.
|
||||
unsafeAllowRunAsRoot := ConfigSectionKeyBool(rootSec, "I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")
|
||||
unsafeAllowRunAsRoot = unsafeAllowRunAsRoot || util.OptionalBoolParse(os.Getenv("GITEA_I_AM_BEING_UNSAFE_RUNNING_AS_ROOT")).Value()
|
||||
RunMode = os.Getenv("GITEA_RUN_MODE")
|
||||
if RunMode == "" {
|
||||
RunMode = rootSec.Key("RUN_MODE").MustString("prod")
|
||||
|
|
|
@ -132,10 +132,3 @@ type UserBadgeOption struct {
|
|||
// example: ["badge1","badge2"]
|
||||
BadgeSlugs []string `json:"badge_slugs" binding:"Required"`
|
||||
}
|
||||
|
||||
// BadgeList
|
||||
// swagger:response BadgeList
|
||||
type BadgeList struct {
|
||||
// in:body
|
||||
Body []Badge `json:"body"`
|
||||
}
|
||||
|
|
|
@ -212,3 +212,12 @@ func ToFloat64(number any) (float64, error) {
|
|||
func ToPointer[T any](val T) *T {
|
||||
return &val
|
||||
}
|
||||
|
||||
// IfZero returns "def" if "v" is a zero value, otherwise "v"
|
||||
func IfZero[T comparable](v, def T) T {
|
||||
var zero T
|
||||
if v == zero {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "12.1.0",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.11.8",
|
||||
|
@ -528,11 +529,31 @@
|
|||
"@csstools/css-tokenizer": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/selector-resolve-nested": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-1.1.0.tgz",
|
||||
"integrity": "sha512-uWvSaeRcHyeNenKg8tp17EVDRkpflmdyvbE0DHo6D/GdBb6PDnCYYU6gRpXhtICMGMcahQmj2zGxwFM/WC8hCg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss-selector-parser": "^6.0.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/selector-specificity": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-3.0.2.tgz",
|
||||
"integrity": "sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
@ -9816,6 +9837,32 @@
|
|||
"postcss": "^8.2.14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nesting": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.1.0.tgz",
|
||||
"integrity": "sha512-QOYnosaZ+mlP6plQrAxFw09UUp2Sgtxj1BVHN+rSVbtV0Yx48zRt9/9F/ZOoxOKBBEsaJk2MYhhVRjeRRw5yuw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/csstools"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@csstools/selector-resolve-nested": "^1.1.0",
|
||||
"@csstools/selector-specificity": "^3.0.2",
|
||||
"postcss-selector-parser": "^6.0.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-resolve-nested-selector": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"pdfobject": "2.3.0",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-loader": "8.1.1",
|
||||
"postcss-nesting": "12.1.0",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.11.8",
|
||||
|
|
|
@ -193,7 +193,4 @@ type swaggerParameterBodies struct {
|
|||
|
||||
// in:body
|
||||
UserBadgeOption api.UserBadgeOption
|
||||
|
||||
// in:body
|
||||
UserBadgeList api.BadgeList
|
||||
}
|
||||
|
|
|
@ -48,3 +48,10 @@ type swaggerResponseUserSettings struct {
|
|||
// in:body
|
||||
Body []api.UserSettings `json:"body"`
|
||||
}
|
||||
|
||||
// BadgeList
|
||||
// swagger:response BadgeList
|
||||
type swaggerResponseBadgeList struct {
|
||||
// in:body
|
||||
Body []api.Badge `json:"body"`
|
||||
}
|
||||
|
|
|
@ -588,6 +588,7 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
|||
return nil, nil
|
||||
}
|
||||
ctx.Data["BaseLink"] = orCtx.Link
|
||||
ctx.Data["BaseLinkNew"] = orCtx.LinkNew
|
||||
|
||||
var w *webhook.Webhook
|
||||
if orCtx.RepoID > 0 {
|
||||
|
|
|
@ -157,7 +157,7 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||
return fmt.Errorf("gitRepo.GetCommit: %w", err)
|
||||
}
|
||||
|
||||
if skipWorkflowsForCommit(input, commit) {
|
||||
if skipWorkflows(input, commit) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -223,8 +223,8 @@ func notify(ctx context.Context, input *notifyInput) error {
|
|||
return handleWorkflows(ctx, detectedWorkflows, commit, input, ref)
|
||||
}
|
||||
|
||||
func skipWorkflowsForCommit(input *notifyInput, commit *git.Commit) bool {
|
||||
// skip workflow runs with a configured skip-ci string in commit message if the event is push or pull_request(_sync)
|
||||
func skipWorkflows(input *notifyInput, commit *git.Commit) bool {
|
||||
// skip workflow runs with a configured skip-ci string in commit message or pr title if the event is push or pull_request(_sync)
|
||||
// https://docs.github.com/en/actions/managing-workflow-runs/skipping-workflow-runs
|
||||
skipWorkflowEvents := []webhook_module.HookEventType{
|
||||
webhook_module.HookEventPush,
|
||||
|
@ -233,6 +233,10 @@ func skipWorkflowsForCommit(input *notifyInput, commit *git.Commit) bool {
|
|||
}
|
||||
if slices.Contains(skipWorkflowEvents, input.Event) {
|
||||
for _, s := range setting.Actions.SkipWorkflowStrings {
|
||||
if input.PullRequest != nil && strings.Contains(input.PullRequest.Issue.Title, s) {
|
||||
log.Debug("repo %s: skipped run for pr %v because of %s string", input.Repo.RepoPath(), input.PullRequest.Issue.ID, s)
|
||||
return true
|
||||
}
|
||||
if strings.Contains(commit.CommitMessage, s) {
|
||||
log.Debug("repo %s with commit %s: skipped run because of %s string", input.Repo.RepoPath(), commit.ID, s)
|
||||
return true
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
webhook_model "code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/hostmatcher"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -226,49 +227,29 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) {
|
|||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
type hookCase struct {
|
||||
gotBody chan []byte
|
||||
gotBody chan []byte
|
||||
httpMethod string // default to POST
|
||||
}
|
||||
|
||||
cases := map[string]hookCase{
|
||||
webhook_module.SLACK: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.DISCORD: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.DINGTALK: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.TELEGRAM: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.MSTEAMS: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.FEISHU: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.MATRIX: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.WECHATWORK: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
webhook_module.PACKAGIST: {
|
||||
gotBody: make(chan []byte, 1),
|
||||
},
|
||||
cases := map[string]*hookCase{
|
||||
webhook_module.SLACK: {},
|
||||
webhook_module.DISCORD: {},
|
||||
webhook_module.DINGTALK: {},
|
||||
webhook_module.TELEGRAM: {},
|
||||
webhook_module.MSTEAMS: {},
|
||||
webhook_module.FEISHU: {},
|
||||
webhook_module.MATRIX: {httpMethod: "PUT"},
|
||||
webhook_module.WECHATWORK: {},
|
||||
webhook_module.PACKAGIST: {},
|
||||
}
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
typ := strings.Split(r.URL.Path, "/")[1] // URL: "/{webhook_type}/other-path"
|
||||
assert.Equal(t, "application/json", r.Header.Get("Content-Type"), r.URL.Path)
|
||||
|
||||
typ := strings.Split(r.URL.Path, "/")[1] // take first segment (after skipping leading slash)
|
||||
hc := cases[typ]
|
||||
require.NotNil(t, hc.gotBody, r.URL.Path)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
w.WriteHeader(200)
|
||||
hc.gotBody <- body
|
||||
assert.Equal(t, util.IfZero(cases[typ].httpMethod, "POST"), r.Method, "webhook test request %q", r.URL.Path)
|
||||
body, _ := io.ReadAll(r.Body) // read request and send it back to the test by testcase's chan
|
||||
cases[typ].gotBody <- body
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
t.Cleanup(s.Close)
|
||||
|
||||
|
@ -276,19 +257,17 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) {
|
|||
data, err := p.JSONPayload()
|
||||
assert.NoError(t, err)
|
||||
|
||||
for typ, hc := range cases {
|
||||
typ := typ
|
||||
hc := hc
|
||||
for typ := range cases {
|
||||
cases[typ].gotBody = make(chan []byte, 1)
|
||||
typ := typ // TODO: remove this workaround when Go >= 1.22
|
||||
t.Run(typ, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
hook := &webhook_model.Webhook{
|
||||
RepoID: 3,
|
||||
IsActive: true,
|
||||
Type: typ,
|
||||
URL: s.URL + "/" + typ,
|
||||
HTTPMethod: "POST",
|
||||
ContentType: 0, // set to 0 so that falling back to default request fails with "invalid content type"
|
||||
Meta: "{}",
|
||||
RepoID: 3,
|
||||
IsActive: true,
|
||||
Type: typ,
|
||||
URL: s.URL + "/" + typ,
|
||||
Meta: "{}",
|
||||
}
|
||||
assert.NoError(t, webhook_model.CreateWebhook(db.DefaultContext, hook))
|
||||
|
||||
|
@ -304,10 +283,11 @@ func TestWebhookDeliverSpecificTypes(t *testing.T) {
|
|||
assert.NotNil(t, hookTask)
|
||||
|
||||
assert.NoError(t, Deliver(context.Background(), hookTask))
|
||||
|
||||
select {
|
||||
case gotBody := <-hc.gotBody:
|
||||
case gotBody := <-cases[typ].gotBody:
|
||||
assert.NotEqual(t, string(data), string(gotBody), "request body must be different from the event payload")
|
||||
assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "request body was not saved")
|
||||
assert.Equal(t, hookTask.RequestInfo.Body, string(gotBody), "delivered webhook payload doesn't match saved request")
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("waited to long for request to happen")
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
</span>
|
||||
{{if eq .RefAction 3}}</del>{{end}}
|
||||
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
<span class="text grey muted-links"><a href="{{.RefIssueLink ctx}}"><b>{{.RefIssueTitle ctx}}</b> {{.RefIssueIdent ctx}}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -160,7 +160,7 @@
|
|||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{ctx.Locale.Tr "repo.issues.commit_ref_at" .EventTag $createdStr}}
|
||||
</span>
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-git-commit"}}
|
||||
<span class="text grey muted-links">{{.Content | SanitizeHTML}}</span>
|
||||
</div>
|
||||
|
@ -252,7 +252,7 @@
|
|||
{{ctx.Locale.Tr "repo.issues.stop_tracking_history" $createdStr}}
|
||||
</span>
|
||||
{{template "repo/issue/view_content/comments_delete_time" dict "ctxData" $ "comment" .}}
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-clock"}}
|
||||
{{if .RenderedContent}}
|
||||
{{/* compatibility with time comments made before v1.21 */}}
|
||||
|
@ -271,7 +271,7 @@
|
|||
{{ctx.Locale.Tr "repo.issues.add_time_history" $createdStr}}
|
||||
</span>
|
||||
{{template "repo/issue/view_content/comments_delete_time" dict "ctxData" $ "comment" .}}
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-clock"}}
|
||||
{{if .RenderedContent}}
|
||||
{{/* compatibility with time comments made before v1.21 */}}
|
||||
|
@ -331,7 +331,7 @@
|
|||
{{ctx.Locale.Tr "repo.issues.dependency.added_dependency" $createdStr}}
|
||||
</span>
|
||||
{{if .DependentIssue}}
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-plus"}}
|
||||
<span class="text grey muted-links">
|
||||
<a href="{{.DependentIssue.Link}}">
|
||||
|
@ -354,8 +354,8 @@
|
|||
{{ctx.Locale.Tr "repo.issues.dependency.removed_dependency" $createdStr}}
|
||||
</span>
|
||||
{{if .DependentIssue}}
|
||||
<div class="detail">
|
||||
<span class="text grey muted-links">{{svg "octicon-trash"}}</span>
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-trash"}}
|
||||
<span class="text grey muted-links">
|
||||
<a href="{{.DependentIssue.Link}}">
|
||||
{{if eq .DependentIssue.RepoID .Issue.RepoID}}
|
||||
|
@ -506,7 +506,7 @@
|
|||
|
||||
{{ctx.Locale.Tr "repo.issues.del_time_history" $createdStr}}
|
||||
</span>
|
||||
<div class="detail">
|
||||
<div class="detail flex-text-block">
|
||||
{{svg "octicon-clock"}}
|
||||
{{if .RenderedContent}}
|
||||
{{/* compatibility with time comments made before v1.21 */}}
|
||||
|
|
|
@ -17413,21 +17413,6 @@
|
|||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"BadgeList": {
|
||||
"description": "BadgeList",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "in:body",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Badge"
|
||||
},
|
||||
"x-go-name": "Body"
|
||||
}
|
||||
},
|
||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||
},
|
||||
"Branch": {
|
||||
"description": "Branch represents a repository branch",
|
||||
"type": "object",
|
||||
|
@ -24722,7 +24707,7 @@
|
|||
"parameterBodies": {
|
||||
"description": "parameterBodies",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/BadgeList"
|
||||
"$ref": "#/definitions/UserBadgeOption"
|
||||
}
|
||||
},
|
||||
"redirect": {
|
||||
|
|
|
@ -62,8 +62,8 @@
|
|||
</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=closestduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.closest_due_date"}}</a>
|
||||
<a class="{{if eq .SortType "furthestduedate"}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=furthestduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.furthest_due_date"}}</a>
|
||||
<a class="{{if or (eq .SortType "closestduedate") (not .SortType)}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=closestduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.earliest_due_data"}}</a>
|
||||
<a class="{{if eq .SortType "furthestduedate"}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=furthestduedate&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.latest_due_date"}}</a>
|
||||
<a class="{{if eq .SortType "leastcomplete"}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=leastcomplete&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.least_complete"}}</a>
|
||||
<a class="{{if eq .SortType "mostcomplete"}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostcomplete&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.most_complete"}}</a>
|
||||
<a class="{{if eq .SortType "mostissues"}}active {{end}}item" href="{{$.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort=mostissues&state={{$.State}}&q={{$.Keyword}}">{{ctx.Locale.Tr "repo.milestones.filter_sort.most_issues"}}</a>
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
actions_module "code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
|
@ -199,6 +200,7 @@ func TestPullRequestTargetEvent(t *testing.T) {
|
|||
|
||||
func TestSkipCI(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
session := loginUser(t, "user2")
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
// create the repo
|
||||
|
@ -209,7 +211,7 @@ func TestSkipCI(t *testing.T) {
|
|||
Gitignores: "Go",
|
||||
License: "MIT",
|
||||
Readme: "Default",
|
||||
DefaultBranch: "main",
|
||||
DefaultBranch: "master",
|
||||
IsPrivate: false,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
@ -228,12 +230,12 @@ func TestSkipCI(t *testing.T) {
|
|||
{
|
||||
Operation: "create",
|
||||
TreePath: ".gitea/workflows/pr.yml",
|
||||
ContentReader: strings.NewReader("name: test\non:\n push:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"),
|
||||
ContentReader: strings.NewReader("name: test\non:\n push:\n branches: [master]\n pull_request:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - run: echo helloworld\n"),
|
||||
},
|
||||
},
|
||||
Message: "add workflow",
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
OldBranch: "master",
|
||||
NewBranch: "master",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
|
@ -263,8 +265,8 @@ func TestSkipCI(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Message: fmt.Sprintf("%s add bar", setting.Actions.SkipWorkflowStrings[0]),
|
||||
OldBranch: "main",
|
||||
NewBranch: "main",
|
||||
OldBranch: "master",
|
||||
NewBranch: "master",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
|
@ -283,5 +285,42 @@ func TestSkipCI(t *testing.T) {
|
|||
|
||||
// the commit message contains a configured skip-ci string, so there is still only 1 record
|
||||
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
|
||||
|
||||
// add file to new branch
|
||||
addFileToBranchResp, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{
|
||||
Files: []*files_service.ChangeRepoFile{
|
||||
{
|
||||
Operation: "create",
|
||||
TreePath: "test-skip-ci",
|
||||
ContentReader: strings.NewReader("test-skip-ci"),
|
||||
},
|
||||
},
|
||||
Message: "add test file",
|
||||
OldBranch: "master",
|
||||
NewBranch: "test-skip-ci",
|
||||
Author: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
},
|
||||
Committer: &files_service.IdentityOptions{
|
||||
Name: user2.Name,
|
||||
Email: user2.Email,
|
||||
},
|
||||
Dates: &files_service.CommitDateOptions{
|
||||
Author: time.Now(),
|
||||
Committer: time.Now(),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, addFileToBranchResp)
|
||||
|
||||
resp := testPullCreate(t, session, "user2", "skip-ci", true, "master", "test-skip-ci", "[skip ci] test-skip-ci")
|
||||
|
||||
// check the redirected URL
|
||||
url := test.RedirectURL(resp)
|
||||
assert.Regexp(t, "^/user2/skip-ci/pulls/[0-9]*$", url)
|
||||
|
||||
// the pr title contains a configured skip-ci string, so there is still only 1 record
|
||||
assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: repo.ID}))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewWebHookLink(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
baseurl := "/user2/repo1/settings/hooks"
|
||||
tests := []string{
|
||||
// webhook list page
|
||||
baseurl,
|
||||
// new webhook page
|
||||
baseurl + "/gitea/new",
|
||||
// edit webhook page
|
||||
baseurl + "/1",
|
||||
}
|
||||
|
||||
for _, url := range tests {
|
||||
resp := session.MakeRequest(t, NewRequest(t, "GET", url), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
menus := htmlDoc.doc.Find(".ui.top.attached.header .ui.dropdown .menu a")
|
||||
menus.Each(func(i int, menu *goquery.Selection) {
|
||||
url, exist := menu.Attr("href")
|
||||
assert.True(t, exist)
|
||||
assert.True(t, strings.HasPrefix(url, baseurl))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
left: 50%;
|
||||
top: 50%;
|
||||
height: min(4em, 66.6%);
|
||||
width: fit-content; /* compat: safari - https://bugs.webkit.org/show_bug.cgi?id=267625 */
|
||||
aspect-ratio: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: isloadingspin 1000ms infinite linear;
|
||||
|
|
|
@ -1065,11 +1065,7 @@
|
|||
|
||||
.repository.view.issue .comment-list .event .detail {
|
||||
margin-top: 4px;
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.repository.view.issue .comment-list .event .detail .svg {
|
||||
margin-right: 2px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.repository.view.issue .comment-list .event .segments {
|
||||
|
|
|
@ -524,7 +524,7 @@ export function initRepositoryActionView() {
|
|||
width: 30%;
|
||||
max-width: 400px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: 12px;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndC
|
|||
import {initImageDiff} from './imagediff.js';
|
||||
import {showErrorToast} from '../modules/toast.js';
|
||||
import {submitEventSubmitter} from '../utils/dom.js';
|
||||
import {POST, GET} from '../modules/fetch.js';
|
||||
|
||||
const {csrfToken, pageData, i18n} = window.config;
|
||||
const {pageData, i18n} = window.config;
|
||||
|
||||
function initRepoDiffReviewButton() {
|
||||
const $reviewBox = $('#review-box');
|
||||
|
@ -63,8 +64,9 @@ function initRepoDiffConversationForm() {
|
|||
if (isSubmittedByButton && submitter.name) {
|
||||
formData.append(submitter.name, submitter.value);
|
||||
}
|
||||
const formDataString = String(new URLSearchParams(formData));
|
||||
const $newConversationHolder = $(await $.post($form.attr('action'), formDataString));
|
||||
|
||||
const response = await POST($form.attr('action'), {data: formData});
|
||||
const $newConversationHolder = $(await response.text());
|
||||
const {path, side, idx} = $newConversationHolder.data();
|
||||
|
||||
$form.closest('.conversation-holder').replaceWith($newConversationHolder);
|
||||
|
@ -75,7 +77,8 @@ function initRepoDiffConversationForm() {
|
|||
}
|
||||
$newConversationHolder.find('.dropdown').dropdown();
|
||||
initCompReactionSelector($newConversationHolder);
|
||||
} catch { // here the caught error might be a jQuery AJAX error (thrown by await $.post), which is not good to use for error message handling
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showErrorToast(i18n.network_error);
|
||||
} finally {
|
||||
$form.removeClass('is-loading');
|
||||
|
@ -89,15 +92,20 @@ function initRepoDiffConversationForm() {
|
|||
const action = $(this).data('action');
|
||||
const url = $(this).data('update-url');
|
||||
|
||||
const data = await $.post(url, {_csrf: csrfToken, origin, action, comment_id});
|
||||
try {
|
||||
const response = await POST(url, {data: new URLSearchParams({origin, action, comment_id})});
|
||||
const data = await response.text();
|
||||
|
||||
if ($(this).closest('.conversation-holder').length) {
|
||||
const conversation = $(data);
|
||||
$(this).closest('.conversation-holder').replaceWith(conversation);
|
||||
conversation.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(conversation);
|
||||
} else {
|
||||
window.location.reload();
|
||||
if ($(this).closest('.conversation-holder').length) {
|
||||
const conversation = $(data);
|
||||
$(this).closest('.conversation-holder').replaceWith(conversation);
|
||||
conversation.find('.dropdown').dropdown();
|
||||
initCompReactionSelector(conversation);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -132,7 +140,7 @@ function onShowMoreFiles() {
|
|||
initImageDiff();
|
||||
}
|
||||
|
||||
export function loadMoreFiles(url) {
|
||||
export async function loadMoreFiles(url) {
|
||||
const $target = $('a#diff-show-more-files');
|
||||
if ($target.hasClass('disabled') || pageData.diffFileInfo.isLoadingNewData) {
|
||||
return;
|
||||
|
@ -140,10 +148,10 @@ export function loadMoreFiles(url) {
|
|||
|
||||
pageData.diffFileInfo.isLoadingNewData = true;
|
||||
$target.addClass('disabled');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url,
|
||||
}).done((resp) => {
|
||||
|
||||
try {
|
||||
const response = await GET(url);
|
||||
const resp = await response.text();
|
||||
const $resp = $(resp);
|
||||
// the response is a full HTML page, we need to extract the relevant contents:
|
||||
// 1. append the newly loaded file list items to the existing list
|
||||
|
@ -152,10 +160,13 @@ export function loadMoreFiles(url) {
|
|||
$('body').append($resp.find('script#diff-data-script'));
|
||||
|
||||
onShowMoreFiles();
|
||||
}).always(() => {
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showErrorToast('An error occurred while loading more files.');
|
||||
} finally {
|
||||
$target.removeClass('disabled');
|
||||
pageData.diffFileInfo.isLoadingNewData = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initRepoDiffShowMore() {
|
||||
|
@ -167,7 +178,7 @@ function initRepoDiffShowMore() {
|
|||
loadMoreFiles(linkLoadMore);
|
||||
});
|
||||
|
||||
$(document).on('click', 'a.diff-load-button', (e) => {
|
||||
$(document).on('click', 'a.diff-load-button', async (e) => {
|
||||
e.preventDefault();
|
||||
const $target = $(e.target);
|
||||
|
||||
|
@ -178,19 +189,21 @@ function initRepoDiffShowMore() {
|
|||
$target.addClass('disabled');
|
||||
|
||||
const url = $target.data('href');
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url,
|
||||
}).done((resp) => {
|
||||
|
||||
try {
|
||||
const response = await GET(url);
|
||||
const resp = await response.text();
|
||||
|
||||
if (!resp) {
|
||||
$target.removeClass('disabled');
|
||||
return;
|
||||
}
|
||||
$target.parent().replaceWith($(resp).find('#diff-file-boxes .diff-file-body .file-body').children());
|
||||
onShowMoreFiles();
|
||||
}).fail(() => {
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
$target.removeClass('disabled');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import {setFileFolding} from './file-fold.js';
|
|||
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
|
||||
import {toAbsoluteUrl} from '../utils.js';
|
||||
import {initDropzone} from './common-global.js';
|
||||
import {POST, GET} from '../modules/fetch.js';
|
||||
|
||||
const {appSubUrl, csrfToken} = window.config;
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
export function initRepoIssueTimeTracking() {
|
||||
$(document).on('click', '.issue-add-time', () => {
|
||||
|
@ -40,7 +41,7 @@ export function initRepoIssueTimeTracking() {
|
|||
});
|
||||
}
|
||||
|
||||
function updateDeadline(deadlineString) {
|
||||
async function updateDeadline(deadlineString) {
|
||||
hideElem($('#deadline-err-invalid-date'));
|
||||
$('#deadline-loader').addClass('loading');
|
||||
|
||||
|
@ -56,23 +57,21 @@ function updateDeadline(deadlineString) {
|
|||
realDeadline = new Date(newDate);
|
||||
}
|
||||
|
||||
$.ajax(`${$('#update-issue-deadline-form').attr('action')}`, {
|
||||
data: JSON.stringify({
|
||||
due_date: realDeadline,
|
||||
}),
|
||||
headers: {
|
||||
'X-Csrf-Token': csrfToken,
|
||||
},
|
||||
contentType: 'application/json',
|
||||
type: 'POST',
|
||||
success() {
|
||||
try {
|
||||
const response = await POST($('#update-issue-deadline-form').attr('action'), {
|
||||
data: {due_date: realDeadline}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
window.location.reload();
|
||||
},
|
||||
error() {
|
||||
$('#deadline-loader').removeClass('loading');
|
||||
showElem($('#deadline-err-invalid-date'));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new Error('Invalid response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
$('#deadline-loader').removeClass('loading');
|
||||
showElem($('#deadline-err-invalid-date'));
|
||||
}
|
||||
}
|
||||
|
||||
export function initRepoIssueDue() {
|
||||
|
@ -156,12 +155,12 @@ export function initRepoIssueSidebarList() {
|
|||
|
||||
export function initRepoIssueCommentDelete() {
|
||||
// Delete comment
|
||||
$(document).on('click', '.delete-comment', function () {
|
||||
$(document).on('click', '.delete-comment', async function () {
|
||||
const $this = $(this);
|
||||
if (window.confirm($this.data('locale'))) {
|
||||
$.post($this.data('url'), {
|
||||
_csrf: csrfToken,
|
||||
}).done(() => {
|
||||
try {
|
||||
const response = await POST($this.data('url'));
|
||||
if (!response.ok) throw new Error('Failed to delete comment');
|
||||
const $conversationHolder = $this.closest('.conversation-holder');
|
||||
|
||||
// Check if this was a pending comment.
|
||||
|
@ -186,7 +185,9 @@ export function initRepoIssueCommentDelete() {
|
|||
}
|
||||
$conversationHolder.remove();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
@ -226,22 +227,32 @@ export function initRepoIssueCodeCommentCancel() {
|
|||
export function initRepoPullRequestUpdate() {
|
||||
// Pull Request update button
|
||||
const $pullUpdateButton = $('.update-button > button');
|
||||
$pullUpdateButton.on('click', function (e) {
|
||||
$pullUpdateButton.on('click', async function (e) {
|
||||
e.preventDefault();
|
||||
const $this = $(this);
|
||||
const redirect = $this.data('redirect');
|
||||
$this.addClass('loading');
|
||||
$.post($this.data('do'), {
|
||||
_csrf: csrfToken
|
||||
}).done((data) => {
|
||||
if (data.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
} else if (redirect) {
|
||||
window.location.href = redirect;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
let response;
|
||||
try {
|
||||
response = await POST($this.data('do'));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
$this.removeClass('loading');
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = await response?.json(); // the response is probably not a JSON
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (data?.redirect) {
|
||||
window.location.href = data.redirect;
|
||||
} else if (redirect) {
|
||||
window.location.href = redirect;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
$('.update-button > .dropdown').dropdown({
|
||||
|
@ -267,20 +278,24 @@ export function initRepoPullRequestAllowMaintainerEdit() {
|
|||
|
||||
const promptError = $checkbox.attr('data-prompt-error');
|
||||
$checkbox.checkbox({
|
||||
'onChange': () => {
|
||||
'onChange': async () => {
|
||||
const checked = $checkbox.checkbox('is checked');
|
||||
let url = $checkbox.attr('data-url');
|
||||
url += '/set_allow_maintainer_edit';
|
||||
$checkbox.checkbox('set disabled');
|
||||
$.ajax({url, type: 'POST',
|
||||
data: {_csrf: csrfToken, allow_maintainer_edit: checked},
|
||||
error: () => {
|
||||
showTemporaryTooltip($checkbox[0], promptError);
|
||||
},
|
||||
complete: () => {
|
||||
$checkbox.checkbox('set enabled');
|
||||
},
|
||||
});
|
||||
try {
|
||||
const response = await POST(url, {
|
||||
data: {allow_maintainer_edit: checked},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update maintainer edit permission');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
showTemporaryTooltip($checkbox[0], promptError);
|
||||
} finally {
|
||||
$checkbox.checkbox('set enabled');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -329,17 +344,15 @@ export function initRepoIssueWipTitle() {
|
|||
});
|
||||
}
|
||||
|
||||
export async function updateIssuesMeta(url, action, issueIds, elementId) {
|
||||
return $.ajax({
|
||||
type: 'POST',
|
||||
url,
|
||||
data: {
|
||||
_csrf: csrfToken,
|
||||
action,
|
||||
issue_ids: issueIds,
|
||||
id: elementId,
|
||||
},
|
||||
});
|
||||
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() {
|
||||
|
@ -511,15 +524,20 @@ export function initRepoPullRequestReview() {
|
|||
const td = ntr.find(`.add-comment-${side}`);
|
||||
const commentCloud = td.find('.comment-code-cloud');
|
||||
if (commentCloud.length === 0 && !ntr.find('button[name="pending_review"]').length) {
|
||||
const html = await $.get($(this).closest('[data-new-comment-url]').attr('data-new-comment-url'));
|
||||
td.html(html);
|
||||
td.find("input[name='line']").val(idx);
|
||||
td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
|
||||
td.find("input[name='path']").val(path);
|
||||
try {
|
||||
const response = await GET($(this).closest('[data-new-comment-url]').attr('data-new-comment-url'));
|
||||
const html = await response.text();
|
||||
td.html(html);
|
||||
td.find("input[name='line']").val(idx);
|
||||
td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
|
||||
td.find("input[name='path']").val(path);
|
||||
|
||||
initDropzone(td.find('.dropzone')[0]);
|
||||
const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
|
||||
editor.focus();
|
||||
initDropzone(td.find('.dropzone')[0]);
|
||||
const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
|
||||
editor.focus();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -547,11 +565,19 @@ export function initRepoIssueWipToggle() {
|
|||
const title = toggleWip.getAttribute('data-title');
|
||||
const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
|
||||
const updateUrl = toggleWip.getAttribute('data-update-url');
|
||||
await $.post(updateUrl, {
|
||||
_csrf: csrfToken,
|
||||
title: title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`,
|
||||
});
|
||||
window.location.reload();
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.append('title', title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`);
|
||||
|
||||
const response = await POST(updateUrl, {data: params});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to toggle WIP status');
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -576,39 +602,43 @@ export function initRepoIssueTitleEdit() {
|
|||
|
||||
$('#edit-title').on('click', editTitleToggle);
|
||||
$('#cancel-edit-title').on('click', editTitleToggle);
|
||||
$('#save-edit-title').on('click', editTitleToggle).on('click', function () {
|
||||
const pullrequest_targetbranch_change = function (update_url) {
|
||||
$('#save-edit-title').on('click', editTitleToggle).on('click', async function () {
|
||||
const pullrequest_targetbranch_change = async function (update_url) {
|
||||
const targetBranch = $('#pull-target-branch').data('branch');
|
||||
const $branchTarget = $('#branch_target');
|
||||
if (targetBranch === $branchTarget.text()) {
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
$.post(update_url, {
|
||||
_csrf: csrfToken,
|
||||
target_branch: targetBranch
|
||||
}).always(() => {
|
||||
try {
|
||||
await POST(update_url, {data: new URLSearchParams({target_branch: targetBranch})});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const pullrequest_target_update_url = $(this).attr('data-target-update-url');
|
||||
if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
|
||||
$editInput.val($issueTitle.text());
|
||||
pullrequest_targetbranch_change(pullrequest_target_update_url);
|
||||
await pullrequest_targetbranch_change(pullrequest_target_update_url);
|
||||
} else {
|
||||
$.post($(this).attr('data-update-url'), {
|
||||
_csrf: csrfToken,
|
||||
title: $editInput.val()
|
||||
}, (data) => {
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
params.append('title', $editInput.val());
|
||||
const response = await POST($(this).attr('data-update-url'), {data: params});
|
||||
const data = await response.json();
|
||||
$editInput.val(data.title);
|
||||
$issueTitle.text(data.title);
|
||||
if (pullrequest_target_update_url) {
|
||||
pullrequest_targetbranch_change(pullrequest_target_update_url); // it will reload the window
|
||||
await pullrequest_targetbranch_change(pullrequest_target_update_url); // it will reload the window
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ import {readFileSync} from 'node:fs';
|
|||
import {env} from 'node:process';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import tailwindConfig from './tailwind.config.js';
|
||||
import tailwindcssNesting from 'tailwindcss/nesting/index.js';
|
||||
import postcssNesting from 'postcss-nesting';
|
||||
|
||||
const {EsbuildPlugin} = EsBuildLoader;
|
||||
const {SourceMapDevToolPlugin, DefinePlugin} = webpack;
|
||||
|
@ -145,6 +147,7 @@ export default {
|
|||
sourceMap: sourceMaps === 'true',
|
||||
url: {filter: filterCssImport},
|
||||
import: {filter: filterCssImport},
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -152,7 +155,10 @@ export default {
|
|||
options: {
|
||||
postcssOptions: {
|
||||
map: false, // https://github.com/postcss/postcss/issues/1914
|
||||
plugins: [tailwindcss(tailwindConfig)],
|
||||
plugins: [
|
||||
tailwindcssNesting(postcssNesting({edition: '2024-02'})),
|
||||
tailwindcss(tailwindConfig),
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue