diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c32c5da82c..1b0255d198 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,8 @@ "ghcr.io/devcontainers-contrib/features/poetry:2": {}, "ghcr.io/devcontainers/features/python:1": { "version": "3.12" - } + }, + "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { "vscode": { @@ -25,7 +26,7 @@ "Vue.volar", "ms-azuretools.vscode-docker", "vitest.explorer", - "qwtel.sqlite-viewer", + "cweijan.vscode-database-client2", "GitHub.vscode-pull-request-github", "Azurite.azurite" ] diff --git a/.gitpod.yml b/.gitpod.yml index f573d55a76..8671edc47c 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -43,7 +43,7 @@ vscode: - Vue.volar - ms-azuretools.vscode-docker - vitest.explorer - - qwtel.sqlite-viewer + - cweijan.vscode-database-client2 - GitHub.vscode-pull-request-github ports: diff --git a/Makefile b/Makefile index 80efcbe46d..e9dc945206 100644 --- a/Makefile +++ b/Makefile @@ -25,10 +25,10 @@ COMMA := , XGO_VERSION := go-1.22.x -AIR_PACKAGE ?= github.com/cosmtrek/air@v1 +AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.6.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.5.1 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@db51e79a0e37c572d8b59ae0c58bf2bbbbe53285 diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index be5d632f54..7677168d83 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1687,6 +1687,16 @@ LEVEL = Info ;; convert \r\n to \n for Sendmail ;SENDMAIL_CONVERT_CRLF = true +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;[mailer.override_header] +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; This is empty by default, use it only if you know what you need it for. +;Reply-To = test@example.com, test2@example.com +;Content-Type = text/html; charset=utf-8 +;In-Reply-To = + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[email.incoming] diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index aabf1b20d8..0c15a866b6 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -724,11 +724,13 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type ## Mailer (`mailer`) -⚠️ This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older, +:::warning +This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older, please refer to [Gitea 1.17 app.ini example](https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini) and [Gitea 1.17 configuration document](https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md) +::: - `ENABLED`: **false**: Enable to use a mail service. - `PROTOCOL`: **_empty_**: Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._ @@ -761,6 +763,21 @@ and - `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]` - `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative. +## Override Email Headers (`mailer.override_header`) + +:::warning +This is empty by default, use it only if you know what you need it for. +::: + +examples would be: + +```ini +[mailer.override_header] +Reply-To = test@example.com, test2@example.com +Content-Type = text/html; charset=utf-8 +In-Reply-To = +``` + ## Incoming Email (`email.incoming`) - `ENABLED`: **false**: Enable handling of incoming emails. diff --git a/go.mod b/go.mod index 87f2b00e6a..6f739ed6e9 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/ProtonMail/go-crypto v1.0.0 github.com/PuerkitoBio/goquery v1.9.1 - github.com/alecthomas/chroma/v2 v2.13.0 + github.com/alecthomas/chroma/v2 v2.14.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/blevesearch/bleve/v2 v2.3.10 github.com/buildkite/terminal-to-html/v3 v3.11.0 diff --git a/go.sum b/go.sum index 84f7121908..543bd70866 100644 --- a/go.sum +++ b/go.sum @@ -82,11 +82,11 @@ github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06 github.com/RoaringBitmap/roaring v0.7.1/go.mod h1:jdT9ykXwHFNdJbEtxePexlFYH9LXucApeS0/+/g+p1I= github.com/RoaringBitmap/roaring v1.9.0 h1:lwKhr90/j0jVXJyh5X+vQN1VVn77rQFfYnh6RDRGCcE= github.com/RoaringBitmap/roaring v1.9.0/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= +github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI= -github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk= +github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= +github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= diff --git a/modules/setting/mailer.go b/modules/setting/mailer.go index a2bc2df444..58bfd67bfb 100644 --- a/modules/setting/mailer.go +++ b/modules/setting/mailer.go @@ -18,14 +18,15 @@ import ( // Mailer represents mail service. type Mailer struct { // Mailer - Name string `ini:"NAME"` - From string `ini:"FROM"` - EnvelopeFrom string `ini:"ENVELOPE_FROM"` - OverrideEnvelopeFrom bool `ini:"-"` - FromName string `ini:"-"` - FromEmail string `ini:"-"` - SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` - SubjectPrefix string `ini:"SUBJECT_PREFIX"` + Name string `ini:"NAME"` + From string `ini:"FROM"` + EnvelopeFrom string `ini:"ENVELOPE_FROM"` + OverrideEnvelopeFrom bool `ini:"-"` + FromName string `ini:"-"` + FromEmail string `ini:"-"` + SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"` + SubjectPrefix string `ini:"SUBJECT_PREFIX"` + OverrideHeader map[string][]string `ini:"-"` // SMTP sender Protocol string `ini:"PROTOCOL"` @@ -151,6 +152,12 @@ func loadMailerFrom(rootCfg ConfigProvider) { log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err) } + overrideHeader := rootCfg.Section("mailer.override_header").Keys() + MailService.OverrideHeader = make(map[string][]string) + for _, key := range overrideHeader { + MailService.OverrideHeader[key.Name()] = key.Strings(",") + } + // Infer SMTPPort if not set if MailService.SMTPPort == "" { switch MailService.Protocol { diff --git a/modules/structs/activity.go b/modules/structs/activity.go index 6d2ee56b08..ea27fbfd77 100644 --- a/modules/structs/activity.go +++ b/modules/structs/activity.go @@ -6,8 +6,11 @@ package structs import "time" type Activity struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` // Receiver user + ID int64 `json:"id"` + UserID int64 `json:"user_id"` // Receiver user + // the type of action + // + // enum: create_repo,rename_repo,star_repo,watch_repo,commit_repo,create_issue,create_pull_request,transfer_repo,push_tag,comment_issue,merge_pull_request,close_issue,reopen_issue,close_pull_request,reopen_pull_request,delete_tag,delete_branch,mirror_sync_push,mirror_sync_create,mirror_sync_delete,approve_pull_request,reject_pull_request,comment_pull,publish_release,pull_review_dismissed,pull_request_ready_for_review,auto_merge_pull_request OpType string `json:"op_type"` ActUserID int64 `json:"act_user_id"` ActUser *User `json:"act_user"` diff --git a/package-lock.json b/package-lock.json index 90cedd63d5..8b1ba766d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.4.0", + "@github/relative-time-element": "4.4.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.9.0", @@ -1028,9 +1028,9 @@ "integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A==" }, "node_modules/@github/relative-time-element": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.0.tgz", - "integrity": "sha512-CrI6oAecoahG7PF5dsgjdvlF5kCtusVMjg810EULD81TvnDsP+k/FRi/ClFubWLgBo4EGpr2EfvmumtqQFo7ow==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.1.tgz", + "integrity": "sha512-E2vRcIgDj8AHv/iHpQMLJ/RqKOJ704OXkKw6+Zdhk3X+kVQhOf3Wj8KVz4DfCQ1eOJR8XxY6XVv73yd+pjMfXA==" }, "node_modules/@github/text-expander-element": { "version": "2.6.1", diff --git a/package.json b/package.json index d7588e093f..5add488bb6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@citation-js/plugin-csl": "0.7.11", "@citation-js/plugin-software-formats": "0.6.1", "@github/markdown-toolbar-element": "2.2.3", - "@github/relative-time-element": "4.4.0", + "@github/relative-time-element": "4.4.1", "@github/text-expander-element": "2.6.1", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.9.0", diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 26b0ae226e..3633d0d007 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) { xmlResponse(ctx, http.StatusOK, Metadata) } -var searchTermExtract = regexp.MustCompile(`'([^']+)'`) +var ( + searchTermExtract = regexp.MustCompile(`'([^']+)'`) + searchTermExact = regexp.MustCompile(`\s+eq\s+'`) +) -func getSearchTerm(ctx *context.Context) string { +func getSearchTerm(ctx *context.Context) packages_model.SearchValue { searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'") - if searchTerm == "" { - // $filter contains a query like: - // (((Id ne null) and substringof('microsoft',tolower(Id))) - // We don't support these queries, just extract the search term. - match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter")) - if len(match) == 2 { - searchTerm = strings.TrimSpace(match[1]) + if searchTerm != "" { + return packages_model.SearchValue{ + Value: searchTerm, + ExactMatch: false, } } - return searchTerm + + // $filter contains a query like: + // (((Id ne null) and substringof('microsoft',tolower(Id))) + // https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5 + // We don't support these queries, just extract the search term. + filter := ctx.FormTrim("$filter") + match := searchTermExtract.FindStringSubmatch(filter) + if len(match) == 2 { + return packages_model.SearchValue{ + Value: strings.TrimSpace(match[1]), + ExactMatch: searchTermExact.MatchString(filter), + } + } + + return packages_model.SearchValue{} } // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs @@ -118,11 +132,9 @@ func SearchServiceV2(ctx *context.Context) { paginator := db.NewAbsoluteListOptions(skip, take) pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Type: packages_model.TypeNuGet, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNuGet, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), Paginator: paginator, }) @@ -169,10 +181,8 @@ func SearchServiceV2(ctx *context.Context) { // http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351 func SearchServiceV2Count(ctx *context.Context) { count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Name: packages_model.SearchValue{ - Value: getSearchTerm(ctx), - }, + OwnerID: ctx.Package.Owner.ID, + Name: getSearchTerm(ctx), IsInternal: optional.Some(false), }) if err != nil { diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index bf3571c835..a7362113e3 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -156,7 +156,7 @@ func GetContentHistoryDetail(ctx *context.Context) { // use chroma to render the diff html diffHTMLBuf := bytes.Buffer{} - diffHTMLBuf.WriteString("
")
+	diffHTMLBuf.WriteString("
")
 	for _, it := range diff {
 		if it.Type == diffmatchpatch.DiffInsert {
 			diffHTMLBuf.WriteString("")
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 063c995d52..0ddd5a48e2 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -1061,7 +1061,7 @@ func readFileName(rd *strings.Reader) (string, bool) {
 	char, _ := rd.ReadByte()
 	_ = rd.UnreadByte()
 	if char == '"' {
-		fmt.Fscanf(rd, "%q ", &name)
+		_, _ = fmt.Fscanf(rd, "%q ", &name)
 		if len(name) == 0 {
 			log.Error("Reader has no file name: reader=%+v", rd)
 			return "", true
@@ -1073,12 +1073,12 @@ func readFileName(rd *strings.Reader) (string, bool) {
 	} else {
 		// This technique is potentially ambiguous it may not be possible to uniquely identify the filenames from the diff line alone
 		ambiguity = true
-		fmt.Fscanf(rd, "%s ", &name)
+		_, _ = fmt.Fscanf(rd, "%s ", &name)
 		char, _ := rd.ReadByte()
 		_ = rd.UnreadByte()
 		for !(char == 0 || char == '"' || char == 'b') {
 			var suffix string
-			fmt.Fscanf(rd, "%s ", &suffix)
+			_, _ = fmt.Fscanf(rd, "%s ", &suffix)
 			name += " " + suffix
 			char, _ = rd.ReadByte()
 			_ = rd.UnreadByte()
diff --git a/services/mailer/mailer.go b/services/mailer/mailer.go
index 5e8e3dbb38..c5846e6104 100644
--- a/services/mailer/mailer.go
+++ b/services/mailer/mailer.go
@@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
 		msg.SetHeader(header, m.Headers[header]...)
 	}
 
-	if len(setting.MailService.SubjectPrefix) > 0 {
+	if setting.MailService.SubjectPrefix != "" {
 		msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
 	} else {
 		msg.SetHeader("Subject", m.Subject)
@@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
 	if len(msg.GetHeader("Message-ID")) == 0 {
 		msg.SetHeader("Message-ID", m.generateAutoMessageID())
 	}
+
+	for k, v := range setting.MailService.OverrideHeader {
+		if len(msg.GetHeader(k)) != 0 {
+			log.Debug("Mailer override header '%s' as per config", k)
+		}
+		msg.SetHeader(k, v...)
+	}
+
 	return msg
 }
 
diff --git a/services/mailer/mailer_test.go b/services/mailer/mailer_test.go
index 375ca35daa..6d7c44f40c 100644
--- a/services/mailer/mailer_test.go
+++ b/services/mailer/mailer_test.go
@@ -4,6 +4,7 @@
 package mailer
 
 import (
+	"strings"
 	"testing"
 	"time"
 
@@ -36,3 +37,78 @@ func TestGenerateMessageID(t *testing.T) {
 	gm = m.ToMessage()
 	assert.Equal(t, "", gm.GetHeader("Message-ID")[0])
 }
+
+func TestToMessage(t *testing.T) {
+	oldConf := *setting.MailService
+	defer func() {
+		setting.MailService = &oldConf
+	}()
+	setting.MailService.From = "test@gitea.com"
+
+	m1 := Message{
+		Info:            "info",
+		FromAddress:     "test@gitea.com",
+		FromDisplayName: "Test Gitea",
+		To:              "a@b.com",
+		Subject:         "Issue X Closed",
+		Body:            "Some Issue got closed by Y-Man",
+	}
+
+	buf := &strings.Builder{}
+	_, err := m1.ToMessage().WriteTo(buf)
+	assert.NoError(t, err)
+	header, _ := extractMailHeaderAndContent(t, buf.String())
+	assert.EqualValues(t, map[string]string{
+		"Content-Type":             "multipart/alternative;",
+		"Date":                     "Mon, 01 Jan 0001 00:00:00 +0000",
+		"From":                     "\"Test Gitea\" ",
+		"Message-ID":               "",
+		"Mime-Version":             "1.0",
+		"Subject":                  "Issue X Closed",
+		"To":                       "a@b.com",
+		"X-Auto-Response-Suppress": "All",
+	}, header)
+
+	setting.MailService.OverrideHeader = map[string][]string{
+		"Message-ID":     {""},               // delete message id
+		"Auto-Submitted": {"auto-generated"}, // suppress auto replay
+	}
+
+	buf = &strings.Builder{}
+	_, err = m1.ToMessage().WriteTo(buf)
+	assert.NoError(t, err)
+	header, _ = extractMailHeaderAndContent(t, buf.String())
+	assert.EqualValues(t, map[string]string{
+		"Content-Type":             "multipart/alternative;",
+		"Date":                     "Mon, 01 Jan 0001 00:00:00 +0000",
+		"From":                     "\"Test Gitea\" ",
+		"Message-ID":               "",
+		"Mime-Version":             "1.0",
+		"Subject":                  "Issue X Closed",
+		"To":                       "a@b.com",
+		"X-Auto-Response-Suppress": "All",
+		"Auto-Submitted":           "auto-generated",
+	}, header)
+}
+
+func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, string) {
+	header := make(map[string]string)
+
+	parts := strings.SplitN(mail, "boundary=", 2)
+	if !assert.Len(t, parts, 2) {
+		return nil, ""
+	}
+	content := strings.TrimSpace("boundary=" + parts[1])
+
+	hParts := strings.Split(parts[0], "\n")
+
+	for _, hPart := range hParts {
+		parts := strings.SplitN(hPart, ":", 2)
+		hk := strings.TrimSpace(parts[0])
+		if hk != "" {
+			header[hk] = strings.TrimSpace(parts[1])
+		}
+	}
+
+	return header, content
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 94dfdbced4..8f3e8c8251 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,6 +1,7 @@
 import {readFileSync} from 'node:fs';
 import {env} from 'node:process';
 import {parse} from 'postcss';
+import plugin from 'tailwindcss/plugin.js';
 
 const isProduction = env.NODE_ENV !== 'development';
 
@@ -98,4 +99,26 @@ export default {
       })),
     },
   },
+  plugins: [
+    plugin(({addUtilities}) => {
+      addUtilities({
+        // tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
+        // do not use:
+        // * "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex"
+        // * ".hidden" class: it has been polluted by Fomantic UI in many cases
+        // * inline style="display: none": it's difficult to tweak
+        // * jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important"
+        // only use:
+        // * this ".tw-hidden" class
+        // * showElem/hideElem/toggleElem functions in "utils/dom.js"
+        '.hidden.hidden': {
+          'display': 'none',
+        },
+        // proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128
+        '.break-anywhere': {
+          'overflow-wrap': 'anywhere',
+        },
+      });
+    }),
+  ],
 };
diff --git a/templates/repo/code/recently_pushed_new_branches.tmpl b/templates/repo/code/recently_pushed_new_branches.tmpl
index 7f613fcba7..025cc1a403 100644
--- a/templates/repo/code/recently_pushed_new_branches.tmpl
+++ b/templates/repo/code/recently_pushed_new_branches.tmpl
@@ -1,6 +1,6 @@
 {{range .RecentlyPushedNewBranches}}
-	
-
+
+
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}} {{$branchLink := HTMLFormat `%s` .BranchLink .BranchDisplayName}} {{ctx.Locale.Tr "repo.pulls.recently_pushed_new_branches" $branchLink $timeSince}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c552e48346..34f09f0587 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -18178,7 +18178,37 @@ "x-go-name": "IsPrivate" }, "op_type": { + "description": "the type of action", "type": "string", + "enum": [ + "create_repo", + "rename_repo", + "star_repo", + "watch_repo", + "commit_repo", + "create_issue", + "create_pull_request", + "transfer_repo", + "push_tag", + "comment_issue", + "merge_pull_request", + "close_issue", + "reopen_issue", + "close_pull_request", + "reopen_pull_request", + "delete_tag", + "delete_branch", + "mirror_sync_push", + "mirror_sync_create", + "mirror_sync_delete", + "approve_pull_request", + "reject_pull_request", + "comment_pull", + "publish_release", + "pull_review_dismissed", + "pull_request_ready_for_review", + "auto_merge_pull_request" + ], "x-go-name": "OpType" }, "ref_name": { diff --git a/templates/user/notification/notification_div.tmpl b/templates/user/notification/notification_div.tmpl index bf3b51ee3b..9790a7087a 100644 --- a/templates/user/notification/notification_div.tmpl +++ b/templates/user/notification/notification_div.tmpl @@ -44,14 +44,14 @@ {{end}}
-
+
{{.Repository.FullName}} {{if .Issue}}#{{.Issue.Index}}{{end}} {{if eq .Status 3}} {{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}} {{end}}
- + {{if .Issue}} {{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}} {{else}} diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 83947ff967..630b4de3f9 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -429,22 +429,33 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) t.Run("SearchService", func(t *testing.T) { cases := []struct { - Query string - Skip int - Take int - ExpectedTotal int64 - ExpectedResults int + Query string + Skip int + Take int + ExpectedTotal int64 + ExpectedResults int + ExpectedExactMatch bool }{ - {"", 0, 0, 1, 1}, - {"", 0, 10, 1, 1}, - {"gitea", 0, 10, 0, 0}, - {"test", 0, 10, 1, 1}, - {"test", 1, 10, 1, 0}, + {"", 0, 0, 4, 4, false}, + {"", 0, 10, 4, 4, false}, + {"gitea", 0, 10, 0, 0, false}, + {"test", 0, 10, 1, 1, false}, + {"test", 1, 10, 1, 0, false}, + {"almost.similar", 0, 0, 3, 3, true}, } - req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusCreated) + fakePackages := []string{ + packageName, + "almost.similar.dependency", + "almost.similar", + "almost.similar.dependant", + } + + for _, fakePackageName := range fakePackages { + req := NewRequestWithBody(t, "PUT", url, createPackage(fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusCreated) + } t.Run("v2", func(t *testing.T) { t.Run("Search()", func(t *testing.T) { @@ -491,6 +502,63 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) } }) + t.Run("Packages()", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Run("substringof", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + + t.Run("IdEq", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + for i, c := range cases { + if c.Query == "" { + // Ignore the `tolower(Id) eq ''` as it's unlikely to happen + continue + } + req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + expectedCount := 0 + if c.ExpectedExactMatch { + expectedCount = 1 + } + + assert.Equal(t, int64(expectedCount), result.Count, "case %d: unexpected total hits", i) + assert.Len(t, result.Entries, expectedCount, "case %d: unexpected result count", i) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)). + AddBasicAuth(user.Name) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, strconv.FormatInt(int64(expectedCount), 10), resp.Body.String(), "case %d: unexpected total hits", i) + } + }) + }) + t.Run("Next", func(t *testing.T) { req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). AddBasicAuth(user.Name) @@ -548,9 +616,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) }) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")). - AddBasicAuth(user.Name) - MakeRequest(t, req, http.StatusNoContent) + for _, fakePackageName := range fakePackages { + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")). + AddBasicAuth(user.Name) + MakeRequest(t, req, http.StatusNoContent) + } }) t.Run("RegistrationService", func(t *testing.T) { diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 60ecd7db72..15df9f3a45 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -35,22 +35,6 @@ Gitea's private styles use `g-` prefix. .interact-bg:hover { background: var(--color-hover) !important; } .interact-bg:active { background: var(--color-active) !important; } -/* -tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element. -do not use: -* "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex" -* ".hidden" class: it has been polluted by Fomantic UI in many cases -* inline style="display: none": it's difficult to tweak -* jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important" -only use: -* this ".tw-hidden" class -* showElem/hideElem/toggleElem functions in "utils/dom.js" -*/ -.tw-hidden.tw-hidden { display: none !important; } - -/* proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128 */ -.tw-break-anywhere { overflow-wrap: anywhere !important; } - @media (max-width: 767.98px) { /* double selector so it wins over .tw-flex (old .gt-df) etc */ .not-mobile.not-mobile { diff --git a/web_src/css/repo.css b/web_src/css/repo.css index d3036744fe..e44bc9811b 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2322,6 +2322,7 @@ tbody.commit-list { min-height: 12em; max-height: calc(100vh - 10.5rem); overflow-y: auto; + tab-size: 4; } .comment-diff-data pre {