diff --git a/Makefile b/Makefile index eb48766194f..59cc27ee8a1 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ IMPORT := code.gitea.io/gitea GO ?= go SHASUM ?= shasum -a 256 -HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) +HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) COMMA := , XGO_VERSION := go-1.20.x @@ -41,7 +41,7 @@ DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG) -ifeq ($(HAS_GO), GO) +ifeq ($(HAS_GO), yes) GOPATH ?= $(shell $(GO) env GOPATH) export PATH := $(GOPATH)/bin:$(PATH) diff --git a/cmd/actions.go b/cmd/actions.go new file mode 100644 index 00000000000..66ad336da50 --- /dev/null +++ b/cmd/actions.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +var ( + // CmdActions represents the available actions sub-commands. + CmdActions = cli.Command{ + Name: "actions", + Usage: "", + Description: "Commands for managing Gitea Actions", + Subcommands: []cli.Command{ + subcmdActionsGenRunnerToken, + }, + } + + subcmdActionsGenRunnerToken = cli.Command{ + Name: "generate-runner-token", + Usage: "Generate a new token for a runner to use to register with the server", + Action: runGenerateActionsRunnerToken, + Aliases: []string{"grt"}, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "scope, s", + Value: "", + Usage: "{owner}[/{repo}] - leave empty for a global runner", + }, + }, + } +) + +func runGenerateActionsRunnerToken(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setting.InitProviderFromExistingFile() + setting.LoadCommonSettings() + + scope := c.String("scope") + + respText, extra := private.GenerateActionsRunnerToken(ctx, scope) + if extra.HasError() { + return handleCliResponseExtra(extra) + } + _, _ = fmt.Printf("%s\n", respText) + return nil +} diff --git a/cmd/convert.go b/cmd/convert.go index d9b89495c1b..8c7746fdf29 100644 --- a/cmd/convert.go +++ b/cmd/convert.go @@ -17,7 +17,7 @@ import ( var CmdConvert = cli.Command{ Name: "convert", Usage: "Convert the database", - Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4 or MSSQL database from varchar to nvarchar", Action: runConvert, } @@ -35,17 +35,22 @@ func runConvert(ctx *cli.Context) error { log.Info("Log path: %s", setting.Log.RootPath) log.Info("Configuration file: %s", setting.CustomConf) - if !setting.Database.Type.IsMySQL() { - fmt.Println("This command can only be used with a MySQL database") - return nil + switch { + case setting.Database.Type.IsMySQL(): + if err := db.ConvertUtf8ToUtf8mb4(); err != nil { + log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") + case setting.Database.Type.IsMSSQL(): + if err := db.ConvertVarcharToNVarchar(); err != nil { + log.Fatal("Failed to convert database from varchar to nvarchar: %v", err) + return err + } + fmt.Println("Converted successfully, please confirm your database's all columns character is NVARCHAR now") + default: + fmt.Println("This command can only be used with a MySQL or MSSQL database") } - if err := db.ConvertUtf8ToUtf8mb4(); err != nil { - log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) - return err - } - - fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") - return nil } diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index d3362e57313..4d01d6e640e 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -551,3 +551,28 @@ Restore-repo restore repository data from disk dir: - `--owner_name lunny`: Restore destination owner name - `--repo_name tango`: Restore destination repository name - `--units `: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units. + +### actions generate-runner-token + +Generate a new token for a runner to use to register with the server + +- Options: + - `--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner + +To register a global runner: + +``` +gitea actions generate-runner-token +``` + +To register a runner for a specific organization, in this case `org`: + +``` +gitea actions generate-runner-token -s org +``` + +To register a runner for a specific repo, in this case `username/test-repo`: + +``` +gitea actions generate-runner-token -s username/test-repo +``` diff --git a/docs/content/doc/administration/https-support.zh-cn.md b/docs/content/doc/administration/https-support.zh-cn.md index 75e9284b59c..3f55d022ec3 100644 --- a/docs/content/doc/administration/https-support.zh-cn.md +++ b/docs/content/doc/administration/https-support.zh-cn.md @@ -58,7 +58,7 @@ PORT_TO_REDIRECT = 3080 [ACME](https://tools.ietf.org/html/rfc8555) 是一种证书颁发机构标准协议,允许您自动请求和续订 SSL/TLS 证书。[Let`s Encrypt](https://letsencrypt.org/) 是使用此标准的免费公开信任的证书颁发机构服务器。仅实施“HTTP-01”和“TLS-ALPN-01”挑战。为了使 ACME 质询通过并验证您的域所有权,“80”端口(“HTTP-01”)或“443”端口(“TLS-ALPN-01”)上 gitea 域的外部流量必须由 gitea 实例提供服务。可能需要设置 [HTTP 重定向](#设置http重定向) 和端口转发才能正确路由外部流量。否则,到端口“80”的正常流量将自动重定向到 HTTPS。**您必须同意**ACME提供商的服务条款(默认为Let's Encrypt的 [服务条款](https://letsencrypt.org/documents/LE-SA-v1.2-2017年11月15日.pdf)。 -实用默认 Let's Encrypt 的最小配置如下: +使用默认 Let's Encrypt 的最小配置如下: ```ini [server] @@ -92,7 +92,7 @@ ACME_EMAIL=email@example.com 按照 [reverse proxy guide](../reverse-proxies) 的规则设置你的反向代理服务器 -然后,按照下面的想到启用 HTTPS: +然后,按照下面的向导启用 HTTPS: - [nginx](https://nginx.org/en/docs/http/configuring_https_servers.html) - [apache2/httpd](https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html) diff --git a/docs/content/doc/installation/upgrade-from-gitea.zh-cn.md b/docs/content/doc/installation/upgrade-from-gitea.zh-cn.md new file mode 100644 index 00000000000..8429ca492e7 --- /dev/null +++ b/docs/content/doc/installation/upgrade-from-gitea.zh-cn.md @@ -0,0 +1,91 @@ +--- +date: "2021-09-02T16:00:00+08:00" +title: "从旧版 Gitea 升级" +slug: "upgrade-from-gitea" +weight: 100 +toc: false +draft: false +menu: + sidebar: + parent: "installation" + name: "从旧版 Gitea 升级" + weight: 100 + identifier: "upgrade-from-gitea" +--- + +# 从旧版 Gitea 升级 + +**目录** + +{{< toc >}} + +想要升级 Gitea,只需要下载新版,停止运行旧版,进行数据备份,然后运行新版就好。 +每次 Gitea 实例启动时,它都会检查是否要进行数据库迁移。 +如果需要进行数据库迁移,Gitea 会花一些时间完成升级然后继续服务。 + +## 为重大变更检查更新日志 + +为了让 Gitea 变得更好,进行重大变更是不可避免的,尤其是一些里程碑更新的发布。 +在更新前,请 [在 Gitea 博客上阅读更新日志](https://blog.gitea.io/) +并检查重大变更是否会影响你的 Gitea 实例。 + +## 降级前的备份 + +Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y`), +这些版本拥有相同的数据库结构,可以自由升级或降级。 +其他情况 (`a.b.?` -> `a.c.?`)下, +新版 Gitea 可能将会将数据库升级到与旧版数据库不同的结构。 + +举个例子: + +| 当前 | 目标 | 结果 | +| --- | --- | --- | +| 1.4.0 | 1.4.1 | ✅ | +| 1.4.1 | 1.4.0 | ⚠️ 不建议,后果自负!尽管数据库结构可能不会变更,让它可以正常工作。我们强烈建议降级前进行完全的备份。 | +| 1.4.x | 1.5.y | ✅ 数据库会被自动升级。你可以直接从 1.4.x 升级到最新的 1.5.y。 | +| 1.5.y | 1.4.x | ❌ 数据库已被升级且不可被旧版 Gitea 利用,使用备份来进行降级。 | + +**因为你不能基于升级后的数据库运行旧版 Gitea,所以你应该在数据库升级前完成数据备份。** + +如果你在生产环境下使用 Gitea,你应该在升级前做好备份,哪怕只是小版本的补丁更新。 + +备份步骤: + +* 停止 Gitea 实例 +* 备份数据库 +* 备份 Gitea 配置文件 +* 备份 Gitea 在 `APP_DATA_PATH` 中的数据文件 +* 备份 Gitea 的外部存储 (例如: S3/MinIO 或被使用的其他存储) + +如果你在使用云服务或拥有快照功能的文件系统, +最好对 Gitea 的数据盘及相关资料存储进行一次快照。 + +## 从 Docker 升级 + +* `docker pull` 拉取 Gitea 的最新发布版。 +* 停止运行中的实例,备份数据。 +* 使用 `docker` 或 `docker-compose` 启动更新的 Gitea Docker 容器. + +## 从包升级 + +* 停止运行中的实例,备份数据。 +* 使用你的包管理器更新 Gitea 到最新版本。 +* 启动 Gitea 实例。 + +## 从二进制升级 + +* 下载最新的 Gitea 二进制文件到临时文件夹中。 +* 停止运行中的实例,备份数据。 +* 将旧的 Gitea 二进制文件覆盖成新的。 +* 启动 Gitea 实例。 + +在 Linux 系统上自动执行以上步骤的脚本可在 [Gitea 的 source tree 中找到 `contrib/upgrade.sh` 来获取](https://github.com/go-gitea/gitea/blob/main/contrib/upgrade.sh). + +## 小心你的自定义模板 + +Gitea 的模板结构与变量可能会随着各个版本的发布发生变化,如果你使用了自定义模板, +你得注意你的模板与你使用的 Gitea 版本的兼容性。 + +如果自定义模板与 Gitea 版本不兼容,你可能会得到: +`50x` 服务器错误,页面元素丢失或故障,莫名其妙的页面布局,等等… +移除或更新不兼容的模板,Gitea Web 才可以正常工作。 diff --git a/main.go b/main.go index eeedf54c274..1589fa97db4 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, + cmd.CmdActions, } // Now adjust these commands to add our global configuration options diff --git a/models/activities/user_heatmap.go b/models/activities/user_heatmap.go index d3f0f0db73b..33207995267 100644 --- a/models/activities/user_heatmap.go +++ b/models/activities/user_heatmap.go @@ -69,3 +69,12 @@ func getUserHeatmapData(user *user_model.User, team *organization.Team, doer *us OrderBy("timestamp"). Find(&hdata) } + +// GetTotalContributionsInHeatmap returns the total number of contributions in a heatmap +func GetTotalContributionsInHeatmap(hdata []*UserHeatmapData) int64 { + var total int64 + for _, v := range hdata { + total += v.Contributions + } + return total +} diff --git a/models/db/convert.go b/models/db/convert.go index b17e68c87e5..112c8575ca2 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -42,6 +42,33 @@ func ConvertUtf8ToUtf8mb4() error { return nil } +// ConvertVarcharToNVarchar converts database and tables from varchar to nvarchar if it's mssql +func ConvertVarcharToNVarchar() error { + if x.Dialect().URI().DBType != schemas.MSSQL { + return nil + } + + sess := x.NewSession() + defer sess.Close() + res, err := sess.QuerySliceString(`SELECT 'ALTER TABLE ' + OBJECT_NAME(SC.object_id) + ' MODIFY SC.name NVARCHAR(' + CONVERT(VARCHAR(5),SC.max_length) + ')' +FROM SYS.columns SC +JOIN SYS.types ST +ON SC.system_type_id = ST.system_type_id +AND SC.user_type_id = ST.user_type_id +WHERE ST.name ='varchar'`) + if err != nil { + return err + } + for _, row := range res { + if len(row) == 1 { + if _, err = sess.Exec(row[0]); err != nil { + return err + } + } + } + return err +} + // Cell2Int64 converts a xorm.Cell type to int64, // and handles possible irregular cases. func Cell2Int64(val xorm.Cell) int64 { diff --git a/models/user/user.go b/models/user/user.go index 5709ed7ff27..5f152780bff 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -41,6 +41,18 @@ const ( // UserTypeOrganization defines an organization UserTypeOrganization + + // UserTypeReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on + UserTypeUserReserved + + // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved + UserTypeOrganizationReserved + + // UserTypeBot defines a bot user + UserTypeBot + + // UserTypeRemoteUser defines a remote user for federated users + UserTypeRemoteUser ) const ( @@ -312,6 +324,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.user_id"). Where("follow.follow_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -333,6 +346,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO Select("`user`.*"). Join("LEFT", "follow", "`user`.id=follow.follow_id"). Where("follow.user_id=?", u.ID). + And("`user`.type=?", UserTypeIndividual). And(isUserVisibleToViewerCond(viewer)) if listOptions.Page != 0 { @@ -959,7 +973,7 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { if len(name) == 0 { return nil, ErrUserNotExist{0, name, 0} } - u := &User{LowerName: strings.ToLower(name)} + u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} has, err := db.GetEngine(ctx).Get(u) if err != nil { return nil, err diff --git a/modules/actions/github.go b/modules/actions/github.go index 1148554139c..f3cb335da98 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -21,6 +21,7 @@ const ( githubEventIssueComment = "issue_comment" githubEventRelease = "release" githubEventPullRequestComment = "pull_request_comment" + githubEventGollum = "gollum" ) // canGithubEventMatch check if the input Github event can match any Gitea event. @@ -29,6 +30,10 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent case githubEventRegistryPackage: return triggedEvent == webhook_module.HookEventPackage + // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum + case githubEventGollum: + return triggedEvent == webhook_module.HookEventWiki + case githubEventIssues: switch triggedEvent { case webhook_module.HookEventIssues, diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index d21dc1d809c..f37f4f2878a 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -119,8 +119,6 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - // FIXME: `wiki` event should match `gollum` event - // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#gollum webhook_module.HookEventWiki: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 6724abafd85..6ef5d599422 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -92,6 +92,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on:\n registry_package:\n types: [updated]", expected: false, }, + { + desc: "HookEventWiki(wiki) matches githubEventGollum(gollum)", + triggedEvent: webhook_module.HookEventWiki, + payload: nil, + yamlOn: "on: gollum", + expected: true, + }, } for _, tc := range testCases { diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go index 6e90847e06a..1675b68be2a 100644 --- a/modules/markup/markdown/convertyaml.go +++ b/modules/markup/markdown/convertyaml.go @@ -34,7 +34,7 @@ func nodeToTable(meta *yaml.Node) ast.Node { func mappingNodeToTable(meta *yaml.Node) ast.Node { table := east.NewTable() - alignments := []east.Alignment{} + alignments := make([]east.Alignment, 0, len(meta.Content)/2) for i := 0; i < len(meta.Content); i += 2 { alignments = append(alignments, east.AlignNone) } diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 50b438219b8..816e93b700f 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -34,16 +34,17 @@ type ASTTransformer struct{} // Transform transforms the given AST tree. func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { firstChild := node.FirstChild() - createTOC := false + tocMode := "" ctx := pc.Get(renderContextKey).(*markup.RenderContext) rc := pc.Get(renderConfigKey).(*RenderConfig) + + tocList := make([]markup.Header, 0, 20) if rc.yamlNode != nil { metaNode := rc.toMetaNode() if metaNode != nil { node.InsertBefore(node, firstChild, metaNode) } - createTOC = rc.TOC - ctx.TableOfContents = make([]markup.Header, 0, 100) + tocMode = rc.TOC } attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote]) @@ -59,15 +60,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) } } - text := n.Text(reader.Source()) + txt := n.Text(reader.Source()) header := markup.Header{ - Text: util.BytesToReadOnlyString(text), + Text: util.BytesToReadOnlyString(txt), Level: v.Level, } if id, found := v.AttributeString("id"); found { header.ID = util.BytesToReadOnlyString(id.([]byte)) } - ctx.TableOfContents = append(ctx.TableOfContents, header) + tocList = append(tocList, header) case *ast.Image: // Images need two things: // @@ -201,14 +202,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa return ast.WalkContinue, nil }) - if createTOC && len(ctx.TableOfContents) > 0 { - lang := rc.Lang - if len(lang) == 0 { - lang = setting.Langs[0] - } - tocNode := createTOCNode(ctx.TableOfContents, lang) - if tocNode != nil { + showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main" + showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar + if len(tocList) > 0 && (showTocInMain || showTocInSidebar) { + if showTocInMain { + tocNode := createTOCNode(tocList, rc.Lang, nil) node.InsertBefore(node, firstChild, tocNode) + } else { + tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"}) + ctx.SidebarTocNode = tocNode } } @@ -373,7 +375,11 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast. func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { var err error if entering { - _, err = w.WriteString("
") + if _, err = w.WriteString("") } else { _, err = w.WriteString("
") } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index f1ffea88724..d4a7195dc50 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -29,8 +29,8 @@ import ( ) var ( - converter goldmark.Markdown - once = sync.Once{} + specMarkdown goldmark.Markdown + specMarkdownOnce sync.Once ) var ( @@ -56,7 +56,7 @@ func (l *limitWriter) Write(data []byte) (int, error) { if err != nil { return n, err } - return n, fmt.Errorf("Rendered content too large - truncating render") + return n, fmt.Errorf("rendered content too large - truncating render") } n, err := l.w.Write(data) l.sum += int64(n) @@ -73,10 +73,10 @@ func newParserContext(ctx *markup.RenderContext) parser.Context { return pc } -// actualRender renders Markdown to HTML without handling special links. -func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { - once.Do(func() { - converter = goldmark.New( +// SpecializedMarkdown sets up the Gitea specific markdown extensions +func SpecializedMarkdown() goldmark.Markdown { + specMarkdownOnce.Do(func() { + specMarkdown = goldmark.New( goldmark.WithExtensions( extension.NewTable( extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)), @@ -139,13 +139,18 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) ) // Override the original Tasklist renderer! - converter.Renderer().AddOptions( + specMarkdown.Renderer().AddOptions( renderer.WithNodeRenderers( util.Prioritized(NewHTMLRenderer(), 10), ), ) }) + return specMarkdown +} +// actualRender renders Markdown to HTML without handling special links. +func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { + converter := SpecializedMarkdown() lw := &limitWriter{ w: output, limit: setting.UI.MaxDisplayFileSize * 3, @@ -174,7 +179,7 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer) buf = giteautil.NormalizeEOL(buf) rc := &RenderConfig{ - Meta: "table", + Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)), Icon: "table", Lang: "", } diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go index f9c9cbc5f42..691df743120 100644 --- a/modules/markup/markdown/renderconfig.go +++ b/modules/markup/markdown/renderconfig.go @@ -7,32 +7,42 @@ import ( "fmt" "strings" + "code.gitea.io/gitea/modules/markup" + "github.com/yuin/goldmark/ast" "gopkg.in/yaml.v3" ) // RenderConfig represents rendering configuration for this file type RenderConfig struct { - Meta string + Meta markup.RenderMetaMode Icon string - TOC bool + TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view Lang string yamlNode *yaml.Node } +func renderMetaModeFromString(s string) markup.RenderMetaMode { + switch strings.TrimSpace(strings.ToLower(s)) { + case "none": + return markup.RenderMetaAsNone + case "table": + return markup.RenderMetaAsTable + default: // "details" + return markup.RenderMetaAsDetails + } +} + // UnmarshalYAML implement yaml.v3 UnmarshalYAML func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { if rc == nil { - rc = &RenderConfig{ - Meta: "table", - Icon: "table", - Lang: "", - } + return nil } + rc.yamlNode = value type commonRenderConfig struct { - TOC bool `yaml:"include_toc"` + TOC string `yaml:"include_toc"` Lang string `yaml:"lang"` } var basic commonRenderConfig @@ -54,58 +64,45 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { if err := value.Decode(&stringBasic); err == nil { if stringBasic.Gitea != "" { - switch strings.TrimSpace(strings.ToLower(stringBasic.Gitea)) { - case "none": - rc.Meta = "none" - case "table": - rc.Meta = "table" - default: // "details" - rc.Meta = "details" - } + rc.Meta = renderMetaModeFromString(stringBasic.Gitea) } return nil } - type giteaControl struct { + type yamlRenderConfig struct { Meta *string `yaml:"meta"` Icon *string `yaml:"details_icon"` - TOC *bool `yaml:"include_toc"` + TOC *string `yaml:"include_toc"` Lang *string `yaml:"lang"` } - type complexGiteaConfig struct { - Gitea *giteaControl `yaml:"gitea"` - } - var complex complexGiteaConfig - if err := value.Decode(&complex); err != nil { - return fmt.Errorf("unable to decode into complexRenderConfig %w", err) + type yamlRenderConfigWrapper struct { + Gitea *yamlRenderConfig `yaml:"gitea"` } - if complex.Gitea == nil { + var cfg yamlRenderConfigWrapper + if err := value.Decode(&cfg); err != nil { + return fmt.Errorf("unable to decode into yamlRenderConfigWrapper %w", err) + } + + if cfg.Gitea == nil { return nil } - if complex.Gitea.Meta != nil { - switch strings.TrimSpace(strings.ToLower(*complex.Gitea.Meta)) { - case "none": - rc.Meta = "none" - case "table": - rc.Meta = "table" - default: // "details" - rc.Meta = "details" - } + if cfg.Gitea.Meta != nil { + rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta) } - if complex.Gitea.Icon != nil { - rc.Icon = strings.TrimSpace(strings.ToLower(*complex.Gitea.Icon)) + if cfg.Gitea.Icon != nil { + rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon)) } - if complex.Gitea.Lang != nil && *complex.Gitea.Lang != "" { - rc.Lang = *complex.Gitea.Lang + if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" { + rc.Lang = *cfg.Gitea.Lang } - if complex.Gitea.TOC != nil { - rc.TOC = *complex.Gitea.TOC + if cfg.Gitea.TOC != nil { + rc.TOC = *cfg.Gitea.TOC } return nil @@ -116,9 +113,9 @@ func (rc *RenderConfig) toMetaNode() ast.Node { return nil } switch rc.Meta { - case "table": + case markup.RenderMetaAsTable: return nodeToTable(rc.yamlNode) - case "details": + case markup.RenderMetaAsDetails: return nodeToDetails(rc.yamlNode, rc.Icon) default: return nil diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go index f7f5e885a3b..c53acdc77a7 100644 --- a/modules/markup/markdown/renderconfig_test.go +++ b/modules/markup/markdown/renderconfig_test.go @@ -60,7 +60,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { }, { "toc", &RenderConfig{ - TOC: true, + TOC: "true", Meta: "table", Icon: "table", Lang: "", @@ -68,7 +68,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { }, { "tocfalse", &RenderConfig{ - TOC: false, + TOC: "false", Meta: "table", Icon: "table", Lang: "", @@ -78,7 +78,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "toclang", &RenderConfig{ Meta: "table", Icon: "table", - TOC: true, + TOC: "true", Lang: "testlang", }, ` include_toc: true @@ -120,7 +120,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "complex2", &RenderConfig{ Lang: "two", Meta: "table", - TOC: true, + TOC: "true", Icon: "smiley", }, ` lang: one @@ -155,7 +155,7 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang) } if got.TOC != tt.expected.TOC { - t.Errorf("TOC Expected %t Got %t", tt.expected.TOC, got.TOC) + t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC) } }) } diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index 3715f031af5..9602040931a 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -13,10 +13,14 @@ import ( "github.com/yuin/goldmark/ast" ) -func createTOCNode(toc []markup.Header, lang string) ast.Node { +func createTOCNode(toc []markup.Header, lang string, detailsAttrs map[string]string) ast.Node { details := NewDetails() summary := NewSummary() + for k, v := range detailsAttrs { + details.SetAttributeString(k, []byte(v)) + } + summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc")))) details.AppendChild(details, summary) ul := ast.NewList('-') @@ -40,7 +44,7 @@ func createTOCNode(toc []markup.Header, lang string) ast.Node { } li := ast.NewListItem(currentLevel * 2) a := ast.NewLink() - a.Destination = []byte(fmt.Sprintf("#%s", url.PathEscape(header.ID))) + a.Destination = []byte(fmt.Sprintf("#%s", url.QueryEscape(header.ID))) a.AppendChild(a, ast.NewString([]byte(header.Text))) li.AppendChild(li, a) ul.AppendChild(ul, li) diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index e934aed925e..f2477f1e9ee 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -16,6 +16,16 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + + "github.com/yuin/goldmark/ast" +) + +type RenderMetaMode string + +const ( + RenderMetaAsDetails RenderMetaMode = "details" // default + RenderMetaAsNone RenderMetaMode = "none" + RenderMetaAsTable RenderMetaMode = "table" ) type ProcessorHelper struct { @@ -63,7 +73,8 @@ type RenderContext struct { GitRepo *git.Repository ShaExistCache map[string]bool cancelFn func() - TableOfContents []Header + SidebarTocNode ast.Node + RenderMetaAs RenderMetaMode InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page } diff --git a/modules/private/actions.go b/modules/private/actions.go new file mode 100644 index 00000000000..be24e16d3ff --- /dev/null +++ b/modules/private/actions.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" +) + +// Email structure holds a data for sending general emails +type GenerateTokenRequest struct { + Scope string +} + +// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function +func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) { + reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token" + + req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{ + Scope: scope, + }) + + resp, extra := requestJSONResp(req, &responseText{}) + return resp.Text, extra +} diff --git a/modules/private/hook.go b/modules/private/hook.go index 0563e4d80a6..c0fe9ef1fb3 100644 --- a/modules/private/hook.go +++ b/modules/private/hook.go @@ -129,7 +129,8 @@ func SetDefaultBranch(ctx context.Context, ownerName, repoName, branch string) R url.PathEscape(branch), ) req := newInternalRequest(ctx, reqURL, "POST") - return requestJSONUserMsg(req, "") + _, extra := requestJSONResp(req, &responseText{}) + return extra } // SSHLog sends ssh error log response diff --git a/modules/structs/org.go b/modules/structs/org.go index b4c58623fd2..7c83dcdee7e 100644 --- a/modules/structs/org.go +++ b/modules/structs/org.go @@ -30,8 +30,8 @@ type OrganizationPermissions struct { // CreateOrgOption options for creating an organization type CreateOrgOption struct { // required: true - UserName string `json:"username" binding:"Required"` - FullName string `json:"full_name"` + UserName string `json:"username" binding:"Required;Username;MaxSize(40)"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` @@ -45,7 +45,7 @@ type CreateOrgOption struct { // EditOrgOption options for editing an organization type EditOrgOption struct { - FullName string `json:"full_name"` + FullName string `json:"full_name" binding:"MaxSize(100)"` Description string `json:"description" binding:"MaxSize(255)"` Website string `json:"website" binding:"ValidUrl;MaxSize(255)"` Location string `json:"location" binding:"MaxSize(50)"` diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 27d6000daf2..24687a46061 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -171,13 +171,6 @@ func NewFuncMap() []template.FuncMap { } return false }, - "Iterate": func(arg interface{}) (items []int64) { - count, _ := util.ToInt64(arg) - for i := int64(0); i < count; i++ { - items = append(items, i) - } - return items - }, // ----------------------------------------------------------------- // setting diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 5ba96510881..f3b7c8cf8b9 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -118,6 +118,12 @@ footer = Footer footer.software = About Software footer.links = Links +[heatmap] +number_of_contributions_in_the_last_12_months = %s contributions in the last 12 months +no_contributions = No contributions +less = Less +more = More + [editor] buttons.heading.tooltip = Add heading buttons.bold.tooltip = Add bold text @@ -246,7 +252,7 @@ install_btn_confirm = Install Gitea test_git_failed = Could not test 'git' command: %v sqlite3_not_available = This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version). invalid_db_setting = The database settings are invalid: %v -invalid_db_table = The database table '%s' is invalid: %v +invalid_db_table = The database table "%s" is invalid: %v invalid_repo_path = The repository root path is invalid: %v invalid_app_data_path = The app data path is invalid: %v run_user_not_match = The 'run as' username is not the current username: %s -> %s @@ -312,7 +318,7 @@ repo_no_results = No matching repositories found. user_no_results = No matching users found. org_no_results = No matching organizations found. code_no_results = No source code matching your search term found. -code_search_results = Search results for '%s' +code_search_results = Search results for "%s" code_last_indexed_at = Last indexed %s relevant_repositories_tooltip = Repositories that are forks or that have no topic, no icon, and no description are hidden. relevant_repositories = Only relevant repositories are being shown, show unfiltered results. @@ -489,8 +495,8 @@ size_error = ` must be size %s.` min_size_error = ` must contain at least %s characters.` max_size_error = ` must contain at most %s characters.` email_error = ` is not a valid email address.` -url_error = `'%s' is not a valid URL.` -include_error = ` must contain substring '%s'.` +url_error = `"%s" is not a valid URL.` +include_error = ` must contain substring "%s".` glob_pattern_error = ` glob pattern is invalid: %s.` regex_pattern_error = ` regex pattern is invalid: %s.` username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.` @@ -515,7 +521,7 @@ team_name_been_taken = The team name is already taken. team_no_units_error = Allow access to at least one repository section. email_been_used = The email address is already used. email_invalid = The email address is invalid. -openid_been_used = The OpenID address '%s' is already used. +openid_been_used = The OpenID address "%s" is already used. username_password_incorrect = Username or password is incorrect. password_complexity = Password does not pass complexity requirements: password_lowercase_one = At least one lowercase character @@ -567,9 +573,9 @@ disabled_public_activity = This user has disabled the public visibility of the a email_visibility.limited = Your email address is visible to all authenticated users email_visibility.private = Your email address is only visible to you and administrators -form.name_reserved = The username '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a username. -form.name_chars_not_allowed = User name '%s' contains invalid characters. +form.name_reserved = The username "%s" is reserved. +form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username. +form.name_chars_not_allowed = User name "%s" contains invalid characters. [settings] profile = Profile @@ -600,7 +606,7 @@ location = Location update_theme = Update Theme update_profile = Update Profile update_language = Update Language -update_language_not_found = Language '%s' is not available. +update_language_not_found = Language "%s" is not available. update_language_success = Language has been updated. update_profile_success = Your profile has been updated. change_username = Your username has been changed. @@ -677,7 +683,7 @@ add_new_email = Add New Email Address add_new_openid = Add New OpenID URI add_email = Add Email Address add_openid = Add OpenID URI -add_email_confirmation_sent = A confirmation email has been sent to '%s'. Please check your inbox within the next %s to confirm your email address. +add_email_confirmation_sent = A confirmation email has been sent to "%s". Please check your inbox within the next %s to confirm your email address. add_email_success = The new email address has been added. email_preference_set_success = Email preference has been set successfully. add_openid_success = The new OpenID address has been added. @@ -716,7 +722,7 @@ gpg_token_help = You can generate a signature using: gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig gpg_token_signature = Armored GPG signature key_signature_gpg_placeholder = Begins with '-----BEGIN PGP SIGNATURE-----' -verify_gpg_key_success = GPG key '%s' has been verified. +verify_gpg_key_success = GPG key "%s" has been verified. ssh_key_verified=Verified Key ssh_key_verified_long=Key has been verified with a token and can be used to verify commits matching any activated email addresses for this user. ssh_key_verify=Verify @@ -726,15 +732,15 @@ ssh_token = Token ssh_token_help = You can generate a signature using: ssh_token_signature = Armored SSH signature key_signature_ssh_placeholder = Begins with '-----BEGIN SSH SIGNATURE-----' -verify_ssh_key_success = SSH key '%s' has been verified. +verify_ssh_key_success = SSH key "%s" has been verified. subkeys = Subkeys key_id = Key ID key_name = Key Name key_content = Content principal_content = Content -add_key_success = The SSH key '%s' has been added. -add_gpg_key_success = The GPG key '%s' has been added. -add_principal_success = The SSH certificate principal '%s' has been added. +add_key_success = The SSH key "%s" has been added. +add_gpg_key_success = The GPG key "%s" has been added. +add_principal_success = The SSH certificate principal "%s" has been added. delete_key = Remove ssh_key_deletion = Remove SSH Key gpg_key_deletion = Remove GPG Key @@ -990,8 +996,8 @@ archive.pull.nocomment = This repo is archived. You cannot comment on pull reque form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository. form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories. -form.name_reserved = The repository name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. +form.name_reserved = The repository name "%s" is reserved. +form.name_pattern_not_allowed = The pattern "%s" is not allowed in a repository name. form.repo_size_limit_negative = Repository size limitation cannot be negative. form.repo_size_limit_only_by_admins = Only administrators can change the repository size limitation. @@ -1145,7 +1151,7 @@ editor.must_be_on_a_branch = You must be on a branch to make or propose changes editor.fork_before_edit = You must fork this repository to make or propose changes to this file. editor.delete_this_file = Delete File editor.must_have_write_access = You must have write access to make or propose changes to this file. -editor.file_delete_success = File '%s' has been deleted. +editor.file_delete_success = File "%s" has been deleted. editor.name_your_file = Name your file… editor.filename_help = Add a directory by typing its name followed by a slash ('/'). Remove a directory by typing backspace at the beginning of the input field. editor.or = or @@ -1153,12 +1159,12 @@ editor.cancel_lower = Cancel editor.commit_signed_changes = Commit Signed Changes editor.commit_changes = Commit Changes editor.add_tmpl = Add '' -editor.add = Add '%s' -editor.update = Update '%s' -editor.delete = Delete '%s' +editor.add = Add %s +editor.update = Update %s +editor.delete = Delete %s editor.patch = Apply Patch editor.patching = Patching: -editor.fail_to_apply_patch = Unable to apply patch '%s' +editor.fail_to_apply_patch = Unable to apply patch "%s" editor.new_patch = New Patch editor.commit_message_desc = Add an optional extended description… editor.signoff_desc = Add a Signed-off-by trailer by the committer at the end of the commit log message. @@ -1170,29 +1176,29 @@ editor.new_branch_name = Name the new branch for this commit editor.new_branch_name_desc = New branch name… editor.cancel = Cancel editor.filename_cannot_be_empty = The filename cannot be empty. -editor.filename_is_invalid = The filename is invalid: '%s'. -editor.branch_does_not_exist = Branch '%s' does not exist in this repository. -editor.branch_already_exists = Branch '%s' already exists in this repository. -editor.directory_is_a_file = Directory name '%s' is already used as a filename in this repository. -editor.file_is_a_symlink = '%s' is a symbolic link. Symbolic links cannot be edited in the web editor -editor.filename_is_a_directory = Filename '%s' is already used as a directory name in this repository. -editor.file_editing_no_longer_exists = The file being edited, '%s', no longer exists in this repository. -editor.file_deleting_no_longer_exists = The file being deleted, '%s', no longer exists in this repository. +editor.filename_is_invalid = The filename is invalid: "%s". +editor.branch_does_not_exist = Branch "%s" does not exist in this repository. +editor.branch_already_exists = Branch "%s" already exists in this repository. +editor.directory_is_a_file = Directory name "%s" is already used as a filename in this repository. +editor.file_is_a_symlink = "%s" is a symbolic link. Symbolic links cannot be edited in the web editor +editor.filename_is_a_directory = Filename "%s" is already used as a directory name in this repository. +editor.file_editing_no_longer_exists = The file being edited, "%s", no longer exists in this repository. +editor.file_deleting_no_longer_exists = The file being deleted, "%s", no longer exists in this repository. editor.file_changed_while_editing = The file contents have changed since you started editing. Click here to see them or Commit Changes again to overwrite them. -editor.file_already_exists = A file named '%s' already exists in this repository. +editor.file_already_exists = A file named "%s" already exists in this repository. editor.commit_empty_file_header = Commit an empty file editor.commit_empty_file_text = The file you're about to commit is empty. Proceed? editor.no_changes_to_show = There are no changes to show. -editor.fail_to_update_file = Failed to update/create file '%s'. +editor.fail_to_update_file = Failed to update/create file "%s". editor.fail_to_update_file_summary = Error Message: editor.push_rejected_no_message = The change was rejected by the server without a message. Please check Git Hooks. editor.push_rejected = The change was rejected by the server. Please check Git Hooks. editor.push_rejected_summary = Full Rejection Message: editor.add_subdir = Add a directory… -editor.unable_to_upload_files = Failed to upload files to '%s' with error: %v -editor.upload_file_is_locked = File '%s' is locked by %s. -editor.upload_files_to_dir = Upload files to '%s' -editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s'. +editor.unable_to_upload_files = Failed to upload files to "%s" with error: %v +editor.upload_file_is_locked = File "%s" is locked by %s. +editor.upload_files_to_dir = Upload files to "%s" +editor.cannot_commit_to_protected_branch = Cannot commit to protected branch "%s". editor.no_commit_to_branch = Unable to commit directly to branch because: editor.user_no_push_to_branch = User cannot push to branch editor.require_signed_commit = Branch requires a signed commit @@ -1201,7 +1207,7 @@ editor.revert = Revert %s onto: commits.desc = Browse source code change history. commits.commits = Commits -commits.no_commits = No commits in common. '%s' and '%s' have entirely different histories. +commits.no_commits = No commits in common. "%s" and "%s" have entirely different histories. commits.nothing_to_compare = These branches are equal. commits.search = Search commits… commits.search.tooltip = You can prefix keywords with "author:", "committer:", "after:", or "before:", e.g. "revert author:Alice before:2019-01-13". @@ -1237,14 +1243,14 @@ projects.create = Create Project projects.title = Title projects.new = New Project projects.new_subheader = Coordinate, track, and update your work in one place, so projects stay transparent and on schedule. -projects.create_success = The project '%s' has been created. +projects.create_success = The project "%s" has been created. projects.deletion = Delete Project projects.deletion_desc = Deleting a project removes it from all related issues. Continue? projects.deletion_success = The project has been deleted. projects.edit = Edit Project projects.edit_subheader = Projects organize issues and track progress. projects.modify = Edit Project -projects.edit_success = Project '%s' has been updated. +projects.edit_success = Project "%s" has been updated. projects.type.none = "None" projects.type.basic_kanban = "Basic Kanban" projects.type.bug_triage = "Bug Triage" @@ -1316,7 +1322,7 @@ issues.label_templates.title = Load a predefined set of labels issues.label_templates.info = No labels exist yet. Create a label with 'New Label' or use a predefined label set: issues.label_templates.helper = Select a label set issues.label_templates.use = Use Label Set -issues.label_templates.fail_to_load_file = Failed to load label template file '%s': %v +issues.label_templates.fail_to_load_file = Failed to load label template file "%s": %v issues.add_label = added the %s label %s issues.add_labels = added the %s labels %s issues.remove_label = removed the %s label %s @@ -1560,7 +1566,7 @@ issues.review.add_review_request = "requested review from %s %s" issues.review.remove_review_request = "removed review request for %s %s" issues.review.remove_review_request_self = "refused to review %s" issues.review.pending = Pending -issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select '%s' -> '%s/%s/%s' at the top of the page. +issues.review.pending.tooltip = This comment is not currently visible to other users. To submit your pending comments, select "%s" -> "%s/%s/%s" at the top of the page. issues.review.review = Review issues.review.reviewers = Reviewers issues.review.outdated = Outdated @@ -1728,12 +1734,12 @@ milestones.desc = Description milestones.due_date = Due Date (optional) milestones.clear = Clear milestones.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." -milestones.create_success = The milestone '%s' has been created. +milestones.create_success = The milestone "%s" has been created. milestones.edit = Edit Milestone milestones.edit_subheader = Milestones organize issues and track progress. milestones.cancel = Cancel milestones.modify = Update Milestone -milestones.edit_success = Milestone '%s' has been updated. +milestones.edit_success = Milestone "%s" has been updated. milestones.deletion = Delete Milestone milestones.deletion_desc = Deleting a milestone removes it from all related issues. Continue? milestones.deletion_success = The milestone has been deleted. @@ -1744,7 +1750,7 @@ milestones.filter_sort.most_complete = Most complete milestones.filter_sort.most_issues = Most issues milestones.filter_sort.least_issues = Least issues -signing.will_sign = This commit will be signed with key '%s' +signing.will_sign = This commit will be signed with key "%s" signing.wont_sign.error = There was an error whilst checking if the commit could be signed signing.wont_sign.nokey = There is no key available to sign this commit signing.wont_sign.never = Commits are never signed @@ -1780,9 +1786,9 @@ wiki.file_revision = Page Revision wiki.wiki_page_revisions = Wiki Page Revisions wiki.back_to_wiki = Back to wiki page wiki.delete_page_button = Delete Page -wiki.delete_page_notice_1 = Deleting the wiki page '%s' cannot be undone. Continue? +wiki.delete_page_notice_1 = Deleting the wiki page "%s" cannot be undone. Continue? wiki.page_already_exists = A wiki page with the same name already exists. -wiki.reserved_page = The wiki page name '%s' is reserved. +wiki.reserved_page = The wiki page name "%s" is reserved. wiki.pages = Pages wiki.last_updated = Last updated %s wiki.page_name_desc = Enter a name for this Wiki page. Some special names are: 'Home', '_Sidebar' and '_Footer'. @@ -2129,7 +2135,7 @@ settings.title = Title settings.deploy_key_content = Content settings.key_been_used = A deploy key with identical content is already in use. settings.key_name_used = A deploy key with the same name already exists. -settings.add_key_success = The deploy key '%s' has been added. +settings.add_key_success = The deploy key "%s" has been added. settings.deploy_key_deletion = Remove Deploy Key settings.deploy_key_deletion_desc = Removing a deploy key will revoke its access to this repository. Continue? settings.deploy_key_deletion_success = The deploy key has been removed. @@ -2178,9 +2184,9 @@ settings.protect_unprotected_file_patterns = "Unprotected file patterns (separat settings.protect_unprotected_file_patterns_desc = "Unprotected files that are allowed to be changed directly if user has write access, bypassing push restriction. Multiple patterns can be separated using semicolon (';'). See github.com/gobwas/glob documentation for pattern syntax. Examples: .drone.yml, /docs/**/*.txt." settings.add_protected_branch = Enable protection settings.delete_protected_branch = Disable protection -settings.update_protect_branch_success = Branch protection for rule '%s' has been updated. -settings.remove_protected_branch_success = Branch protection for rule '%s' has been removed. -settings.remove_protected_branch_failed = Removing branch protection rule '%s' failed. +settings.update_protect_branch_success = Branch protection for rule "%s" has been updated. +settings.remove_protected_branch_success = Branch protection for rule "%s" has been removed. +settings.remove_protected_branch_failed = Removing branch protection rule "%s" failed. settings.protected_branch_deletion = Delete Branch Protection settings.protected_branch_deletion_desc = Disabling branch protection allows users with write permission to push to the branch. Continue? settings.block_rejected_reviews = Block merge on rejected reviews @@ -2365,42 +2371,42 @@ release.tags_for = Tags for %s branch.name = Branch Name branch.search = Search branches -branch.already_exists = A branch named '%s' already exists. +branch.already_exists = A branch named "%s" already exists. branch.delete_head = Delete -branch.delete = Delete Branch '%s' +branch.delete = Delete Branch "%s" branch.delete_html = Delete Branch branch.delete_desc = Deleting a branch is permanent. It CANNOT be undone. Continue? -branch.deletion_success = Branch '%s' has been deleted. -branch.deletion_failed = Failed to delete branch '%s'. -branch.delete_branch_has_new_commits = Branch '%s' cannot be deleted because new commits have been added after merging. +branch.deletion_success = Branch "%s" has been deleted. +branch.deletion_failed = Failed to delete branch "%s". +branch.delete_branch_has_new_commits = Branch "%s" cannot be deleted because new commits have been added after merging. branch.create_branch = Create branch %s -branch.create_from = from '%s' -branch.create_success = Branch '%s' has been created. -branch.branch_already_exists = Branch '%s' already exists in this repository. -branch.branch_name_conflict = Branch name '%s' conflicts with the already existing branch '%s'. -branch.tag_collision = Branch '%s' cannot be created as a tag with same name already exists in the repository. +branch.create_from = from "%s" +branch.create_success = Branch "%s" has been created. +branch.branch_already_exists = Branch "%s" already exists in this repository. +branch.branch_name_conflict = Branch name "%s" conflicts with the already existing branch "%s". +branch.tag_collision = Branch "%s" cannot be created as a tag with same name already exists in the repository. branch.deleted_by = Deleted by %s -branch.restore_success = Branch '%s' has been restored. -branch.restore_failed = Failed to restore branch '%s'. -branch.protected_deletion_failed = Branch '%s' is protected. It cannot be deleted. -branch.default_deletion_failed = Branch '%s' is the default branch. It cannot be deleted. -branch.restore = Restore Branch '%s' -branch.download = Download Branch '%s' +branch.restore_success = Branch "%s" has been restored. +branch.restore_failed = Failed to restore branch "%s". +branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted. +branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted. +branch.restore = Restore Branch "%s" +branch.download = Download Branch "%s" branch.included_desc = This branch is part of the default branch branch.included = Included branch.create_new_branch = Create branch from branch: branch.confirm_create_branch = Create branch branch.create_branch_operation = Create branch branch.new_branch = Create new branch -branch.new_branch_from = Create new branch from '%s' +branch.new_branch_from = Create new branch from "%s" branch.renamed = Branch %s was renamed to %s. tag.create_tag = Create tag %s tag.create_tag_operation = Create tag tag.confirm_create_tag = Create tag -tag.create_tag_from = Create new tag from '%s' +tag.create_tag_from = Create new tag from "%s" -tag.create_success = Tag '%s' has been created. +tag.create_success = Tag "%s" has been created. topic.manage_topics = Manage Topics topic.done = Done @@ -2437,8 +2443,8 @@ team_permission_desc = Permission team_unit_desc = Allow Access to Repository Sections team_unit_disabled = (Disabled) -form.name_reserved = The organization name '%s' is reserved. -form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. +form.name_reserved = The organization name "%s" is reserved. +form.name_pattern_not_allowed = The pattern "%s" is not allowed in an organization name. form.create_org_not_allowed = You are not allowed to create an organization. settings = Settings @@ -2647,7 +2653,7 @@ users.created = Created users.last_login = Last Sign-In users.never_login = Never Signed-In users.send_register_notify = Send User Registration Notification -users.new_success = The user account '%s' has been created. +users.new_success = The user account "%s" has been created. users.edit = Edit users.auth_source = Authentication Source users.local = Local @@ -2847,7 +2853,7 @@ auths.tip.yandex = Create a new application at https://oauth.yandex.com/client/n auths.tip.mastodon = Input a custom instance URL for the mastodon instance you want to authenticate with (or use the default one) auths.edit = Edit Authentication Source auths.activated = This Authentication Source is Activated -auths.new_success = The authentication '%s' has been added. +auths.new_success = The authentication "%s" has been added. auths.update_success = The authentication source has been updated. auths.update = Update Authentication Source auths.delete = Delete Authentication Source @@ -2855,7 +2861,7 @@ auths.delete_auth_title = Delete Authentication Source auths.delete_auth_desc = Deleting an authentication source prevents users from using it to sign in. Continue? auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first. auths.deletion_success = The authentication source has been deleted. -auths.login_source_exist = The authentication source '%s' already exists. +auths.login_source_exist = The authentication source "%s" already exists. auths.login_source_of_type_exist = An authentication source of this type already exists. auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://) @@ -2948,8 +2954,8 @@ config.mailer_sendmail_timeout = Sendmail Timeout config.mailer_use_dummy = Dummy config.test_email_placeholder = Email (e.g. test@example.com) config.send_test_mail = Send Testing Email -config.test_mail_failed = Failed to send a testing email to '%s': %v -config.test_mail_sent = A testing email has been sent to '%s'. +config.test_mail_failed = Failed to send a testing email to "%s": %v +config.test_mail_sent = A testing email has been sent to "%s". config.oauth_config = OAuth Configuration config.oauth_enabled = Enabled @@ -3340,7 +3346,7 @@ name = Name creation = Add Secret creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_ creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted. -creation.success = The secret '%s' has been added. +creation.success = The secret "%s" has been added. creation.failed = Failed to add secret. deletion = Remove secret deletion.description = Removing a secret is permanent and cannot be undone. Continue? diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 3bd988d72f2..0141199138f 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -119,6 +119,20 @@ footer.software=О программе footer.links=Ссылки [editor] +buttons.heading.tooltip=Добавить заголовок +buttons.bold.tooltip=Добавить жирный текст +buttons.italic.tooltip=Добавить курсив +buttons.quote.tooltip=Цитировать текст +buttons.code.tooltip=Добавить код +buttons.link.tooltip=Добавить ссылку +buttons.list.unordered.tooltip=Добавить маркированный список +buttons.list.ordered.tooltip=Добавить нумерованный список +buttons.list.task.tooltip=Добавить список задач +buttons.mention.tooltip=Упомянуть пользователя или команду +buttons.ref.tooltip=Сослаться на задачу или запрос на слияние +buttons.switch_to_legacy.tooltip=Использовать старый редактор +buttons.enable_monospace_font=Включить моноширинный шрифт +buttons.disable_monospace_font=Выключить моноширинный шрифт [filter] string.asc=A - Я @@ -1267,9 +1281,9 @@ issues.remove_labels=удалил(а) метки %s %s issues.add_remove_labels=добавил(а) метки %s и удалил(а) %s %s issues.add_milestone_at=`добавил(а) к этапу %s %s` issues.add_project_at=`добавил(а) в %s проект %s` -issues.change_milestone_at=`поменял(а) целевой этап с %s на %s %s` +issues.change_milestone_at=`изменил(а) целевой этап с %s на %s %s` issues.change_project_at=`изменил(а) проект с %s на %s %s` -issues.remove_milestone_at=`удалил(а) из этапа %s %s` +issues.remove_milestone_at=`удалил(а) это из этапа %s %s` issues.remove_project_at=`удалил(а) это из проекта %s %s` issues.deleted_milestone=`(удалено)` issues.deleted_project=`(удалено)` @@ -1423,9 +1437,9 @@ issues.start_tracking_history=`начал(а) работать %s` issues.tracker_auto_close=Таймер будет остановлен автоматически, когда эта проблема будет закрыта issues.tracking_already_started=`Вы уже начали отслеживать время для другой задачи!` issues.stop_tracking=Остановить таймер -issues.stop_tracking_history=`перестал работать %s` +issues.stop_tracking_history=`перестал(а) работать %s` issues.cancel_tracking=Отмена -issues.cancel_tracking_history=`отменил отслеживание %s` +issues.cancel_tracking_history=`отменил(а) отслеживание %s` issues.add_time=Вручную добавить время issues.del_time=Удалить этот журнал времени issues.add_time_short=Добавить время @@ -1463,7 +1477,7 @@ issues.dependency.cancel=Отменить issues.dependency.remove=Удалить issues.dependency.remove_info=Удалить эту зависимость issues.dependency.added_dependency=`добавил(а) новую зависимость %s` -issues.dependency.removed_dependency=`убрал зависимость %s` +issues.dependency.removed_dependency=`убрал(а) зависимость %s` issues.dependency.pr_closing_blockedby=Закрытие этого запроса на слияние блокируется следующими задачами issues.dependency.issue_closing_blockedby=Закрытие этой задачи блокируется следующими задачами issues.dependency.issue_close_blocks=Эта задача блокирует закрытие следующих задач @@ -1543,7 +1557,7 @@ pulls.has_pull_request=`Запрос на слияние этих веток у pulls.create=Создать запрос на слияние pulls.title_desc=хочет влить %[1]d коммит(ов) из %[2]s в %[3]s pulls.merged_title_desc=слито %[1]d коммит(ов) из %[2]s в %[3]s %[4]s -pulls.change_target_branch_at=`изменил целевую ветку с %s на %s %s` +pulls.change_target_branch_at=`изменил(а) целевую ветку с %s на %s %s` pulls.tab_conversation=Обсуждение pulls.tab_commits=Коммиты pulls.tab_files=Изменённые файлы @@ -2606,6 +2620,7 @@ repos.issues=Задачи repos.size=Размер packages.package_manage_panel=Управление пакетами +packages.total_size=Общий размер: %s packages.owner=Владелец packages.creator=Автор packages.name=Наименование @@ -2975,6 +2990,7 @@ reopen_pull_request=`переоткрыл(а) запрос на слияние < comment_issue=`прокомментировал(а) задачу %[3]s#%[2]s` comment_pull=`прокомментировал(а) запрос на слияние %[3]s#%[2]s` merge_pull_request=`принял(а) запрос на слияние %[3]s#%[2]s` +auto_merge_pull_request=`автоматически принял(а) запрос на слияние %[3]s#%[2]s` transfer_repo=передал(а) репозиторий %s %s push_tag=создал(а) тег %[3]s в %[4]s delete_tag=удалил(а) тэг %[2]s из %[3]s @@ -3101,6 +3117,7 @@ container.multi_arch=ОС / архитектура container.labels=Метки container.labels.key=Ключ container.labels.value=Значение +generic.download=Скачать пакет из командной строки: generic.documentation=Для получения дополнительной информации об общем реестре смотрите документацию. helm.registry=Настроить реестр из командной строки: helm.install=Чтобы установить пакет, выполните следующую команду: diff --git a/routers/private/actions.go b/routers/private/actions.go new file mode 100644 index 00000000000..b7e416f56a3 --- /dev/null +++ b/routers/private/actions.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "errors" + "fmt" + "net/http" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/util" +) + +// GenerateActionsRunnerToken generates a new runner token for a given scope +func GenerateActionsRunnerToken(ctx *context.PrivateContext) { + var genRequest private.GenerateTokenRequest + rd := ctx.Req.Body + defer rd.Close() + + if err := json.NewDecoder(rd).Decode(&genRequest); err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + + owner, repo, err := parseScope(ctx, genRequest.Scope) + if err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + } + + token, err := actions_model.GetUnactivatedRunnerToken(ctx, owner, repo) + if errors.Is(err, util.ErrNotExist) { + token, err = actions_model.NewRunnerToken(ctx, owner, repo) + if err != nil { + err := fmt.Sprintf("error while creating runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + } else if err != nil { + err := fmt.Sprintf("could not get unactivated runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + + ctx.PlainText(http.StatusOK, token.Token) +} + +func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) { + ownerID = 0 + repoID = 0 + if scope == "" { + return ownerID, repoID, nil + } + + ownerName, repoName, found := strings.Cut(scope, "/") + + u, err := user_model.GetUserByName(ctx, ownerName) + if err != nil { + return ownerID, repoID, err + } + + if !found { + return u.ID, repoID, nil + } + + r, err := repo_model.GetRepositoryByName(u.ID, repoName) + if err != nil { + return ownerID, repoID, err + } + repoID = r.ID + return ownerID, repoID, nil +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 4acede33705..b4d32c37a64 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -77,6 +77,7 @@ func Routes() *web.Route { r.Get("/manager/processes", Processes) r.Post("/mail/send", SendEmail) r.Post("/restore_repo", RestoreRepo) + r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken) return r } diff --git a/routers/web/base.go b/routers/web/base.go index da18a75643e..79991d89db5 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -143,31 +143,29 @@ func Recovery(ctx goctx.Context) func(next http.Handler) http.Handler { "locale": lc, } - user := context.GetContextUser(req) + // TODO: this recovery handler is usually called without Gitea's web context, so we shouldn't touch that context too much + // Otherwise, the 500 page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic + user := context.GetContextUser(req) // almost always nil if user == nil { // Get user from session if logged in - do not attempt to sign-in user = auth.SessionUser(sessionStore) } - if user != nil { - store["IsSigned"] = true - store["SignedUser"] = user - store["SignedUserID"] = user.ID - store["SignedUserName"] = user.Name - store["IsAdmin"] = user.IsAdmin - } else { - store["SignedUserID"] = int64(0) - store["SignedUserName"] = "" - } httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform") w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) - if !setting.IsProd { + if !setting.IsProd || (user != nil && user.IsAdmin) { store["ErrorMsg"] = combinedErr } + + defer func() { + if err := recover(); err != nil { + log.Error("HTML render in Recovery handler panics again: %v", err) + } + }() err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store)) if err != nil { - log.Error("%v", err) + log.Error("HTML render in Recovery handler fails again: %v", err) } } }() diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 9cd5fc9e402..150050f76b9 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -1014,7 +1014,7 @@ func Stars(ctx *context.Context) { // Forks render repository's forked users func Forks(ctx *context.Context) { - ctx.Data["Title"] = ctx.Tr("repos.forks") + ctx.Data["Title"] = ctx.Tr("repo.forks") page := ctx.FormInt("page") if page <= 0 { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index fe2becb7bb4..0c5c5eed7de 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -298,7 +298,15 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Data["footerPresent"] = false } - ctx.Data["toc"] = rctx.TableOfContents + if rctx.SidebarTocNode != nil { + sb := &strings.Builder{} + err = markdown.SpecializedMarkdown().Renderer().Render(sb, nil, rctx.SidebarTocNode) + if err != nil { + log.Error("Failed to render wiki sidebar TOC: %v", err) + } else { + ctx.Data["sidebarTocContent"] = sb.String() + } + } // get commit count - wiki revisions commitsCount, _ := wikiRepo.FileCommitsCount(wiki_service.DefaultBranch, pageFilename) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index a0a5dc3c4b9..1f77379044a 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -107,6 +107,7 @@ func Dashboard(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } feeds, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index d690fa4d011..b39ba58f124 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -74,6 +74,7 @@ func Profile(ctx *context.Context) { return } ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) } if len(ctx.ContextUser.Description) != 0 { diff --git a/services/actions/notifier.go b/services/actions/notifier.go index 6956c25cee2..4ac77276ffe 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -526,3 +526,38 @@ func (n *actionsNotifier) NotifyPullRequestChangeTargetBranch(ctx context.Contex WithPullRequest(pr). Notify(ctx) } + +func (n *actionsNotifier) NotifyNewWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyNewWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiCreated, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyEditWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page, comment string) { + ctx = withMethod(ctx, "NotifyEditWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiEdited, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + Comment: comment, + }).Notify(ctx) +} + +func (n *actionsNotifier) NotifyDeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, page string) { + ctx = withMethod(ctx, "NotifyDeleteWikiPage") + + newNotifyInput(repo, doer, webhook_module.HookEventWiki).WithPayload(&api.WikiPayload{ + Action: api.HookWikiDeleted, + Repository: convert.ToRepo(ctx, repo, perm_model.AccessModeOwner), + Sender: convert.ToUser(ctx, doer, nil), + Page: page, + }).Notify(ctx) +} diff --git a/services/auth/source/db/authenticate.go b/services/auth/source/db/authenticate.go index ec899844998..76445e0d6d5 100644 --- a/services/auth/source/db/authenticate.go +++ b/services/auth/source/db/authenticate.go @@ -40,5 +40,13 @@ func Authenticate(user *user_model.User, login, password string) (*user_model.Us } } + // attempting to login as a non-user account + if user.Type != user_model.UserTypeIndividual { + return nil, user_model.ErrUserProhibitLogin{ + UID: user.ID, + Name: user.Name, + } + } + return user, nil } diff --git a/templates/org/create.tmpl b/templates/org/create.tmpl index f734a39a937..7e988ba0c79 100644 --- a/templates/org/create.tmpl +++ b/templates/org/create.tmpl @@ -11,7 +11,7 @@ {{template "base/alert" .}}
- + {{.locale.Tr "org.org_name_helper"}}
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 833b97e3472..1caa4210e62 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -14,26 +14,27 @@ {{.CsrfTokenHtml}}
- +
- +
- +
- +
- +
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index a1d1c047515..318006d96e2 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -65,28 +65,16 @@

{{.FormatWarning}}

{{end}} -
-
+
+
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}} {{.content | Safe}}
- {{if or .sidebarPresent .toc}} -
- {{if .toc}} + {{if or .sidebarPresent .sidebarTocContent}} +
+ {{if .sidebarTocContent}}
-
- -
{{.locale.Tr "toc"}}
-
- {{$level := 0}} - {{range .toc}} - {{if lt $level .Level}}{{range Iterate (Eval .Level "-" $level)}}
    {{end}}{{end}} - {{if gt $level .Level}}{{range Iterate (Eval $level "-" .Level)}}
{{end}}{{end}} - {{$level = .Level}} -
  • {{.Text}}
  • - {{end}} - {{range Iterate $level}}{{end}} -
    + {{.sidebarTocContent | Safe}}
    {{end}} {{if .sidebarPresent}} diff --git a/templates/user/heatmap.tmpl b/templates/user/heatmap.tmpl index 9d58bc8fc5e..5d42a5435bb 100644 --- a/templates/user/heatmap.tmpl +++ b/templates/user/heatmap.tmpl @@ -1,5 +1,11 @@ {{if .HeatmapData}} -
    +
    {{.locale.Tr "user.heatmap.loading"}}
    diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index b2098fdd032..a3a54221541 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -214,7 +214,7 @@ func TestAPICreateFile(t *testing.T) { req = NewRequestWithJSON(t, "POST", url, &createFileOptions) resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &fileResponse) - expectedMessage := "Add '" + treePath + "'\n" + expectedMessage := "Add " + treePath + "\n" assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) // Test trying to create a file that already exists, should fail diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 9b80dc150a6..ae28c97002d 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -99,7 +99,7 @@ func TestAPIDeleteFile(t *testing.T) { req = NewRequestWithJSON(t, "DELETE", url, &deleteFileOptions) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) - expectedMessage := "Delete '" + treePath + "'\n" + expectedMessage := "Delete " + treePath + "\n" assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) // Test deleting a file with the wrong SHA diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index 8e07511aafd..177d7282cab 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -199,7 +199,7 @@ func TestAPIUpdateFile(t *testing.T) { req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) - expectedMessage := "Update '" + treePath + "'\n" + expectedMessage := "Update " + treePath + "\n" assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) // Test updating a file with the wrong SHA diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index 495290ed569..de2a9d7d23b 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -55,7 +55,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { // Check if master branch has been locked successfully flashCookie := session.GetCookie(gitea_context.CookieNameFlash) assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527master%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522master%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) // Request editor page req = NewRequest(t, "GET", "/user2/repo1/_new/master/") @@ -76,7 +76,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) // Check body for error message - assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch 'master'.") + assert.Contains(t, resp.Body.String(), "Cannot commit to protected branch "master".") // remove the protected branch csrf = GetCSRF(t, session, "/user2/repo1/settings/branches") @@ -95,7 +95,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { // Check if master branch has been locked successfully flashCookie = session.GetCookie(gitea_context.CookieNameFlash) assert.NotNil(t, flashCookie) - assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25271%2527%2Bfailed.", flashCookie.Value) + assert.EqualValues(t, "error%3DRemoving%2Bbranch%2Bprotection%2Brule%2B%25221%2522%2Bfailed.", flashCookie.Value) }) } diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index 943a0051a16..c3386b8f3ad 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -483,7 +483,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelist, unprotectedFil // Check if master branch has been locked successfully flashCookie := ctx.Session.GetCookie(gitea_context.CookieNameFlash) assert.NotNil(t, flashCookie) - assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2527"+url.QueryEscape(branch)+"%2527%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) + assert.EqualValues(t, "success%3DBranch%2Bprotection%2Bfor%2Brule%2B%2522"+url.QueryEscape(branch)+"%2522%2Bhas%2Bbeen%2Bupdated.", flashCookie.Value) } } diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 35f7cb8ab76..3a13fb4fcb9 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -67,7 +67,7 @@ func TestPullCreate(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) assert.Regexp(t, `\+Hello, World \(Edited\)`, resp.Body) assert.Regexp(t, "diff", resp.Body) - assert.Regexp(t, `Subject: \[PATCH\] Update 'README.md'`, resp.Body) + assert.Regexp(t, `Subject: \[PATCH\] Update README.md`, resp.Body) assert.NotRegexp(t, "diff.*diff", resp.Body) // not two diffs, just one }) } diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 55cf295257a..ee185f22380 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -187,7 +187,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, "Branch 'user1/repo1:feature/test' has been deleted.", resultMsg) + assert.EqualValues(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg) }) } diff --git a/web_src/css/base.css b/web_src/css/base.css index c48a36c8547..bdf601951b7 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -9,7 +9,7 @@ /* non-color variables */ --border-radius: 0.28571429rem; --opacity-disabled: 0.55; - --height-loading: 12rem; + --height-loading: 16rem; /* base colors */ --color-primary: #4183c4; --color-primary-contrast: #ffffff; diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index 8d64bd751b8..beb93e1e86d 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -25,7 +25,8 @@ .gt-overflow-x-scroll { overflow-x: scroll !important; } .gt-cursor-default { cursor: default !important; } .gt-items-start { align-items: flex-start !important; } -.gt-whitespace-pre { white-space: pre !important } +.gt-whitespace-pre { white-space: pre !important; } +.gt-invisible { visibility: hidden !important; } .gt-mono { font-family: var(--fonts-monospace) !important; diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 90f8c7091e9..d0f11e8e765 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -542,7 +542,7 @@ .markup-block-error { display: block !important; /* override fomantic .ui.form .error.message {display: none} */ - border: 1px solid var(--color-error-border) !important; + border: none !important; margin-bottom: 0 !important; border-bottom-left-radius: 0 !important; border-bottom-right-radius: 0 !important; diff --git a/web_src/css/repository.css b/web_src/css/repository.css index 5c903856287..05e50548d9b 100644 --- a/web_src/css/repository.css +++ b/web_src/css/repository.css @@ -3261,14 +3261,15 @@ td.blob-excerpt { display: none; } -.wiki-content-toc > ul > li { - margin-bottom: 4px; -} - .wiki-content-toc ul { margin: 0; list-style: none; - padding-left: 1em; + padding: 5px 0 5px 1em; +} + +.wiki-content-toc ul ul { + border-left: 1px var(--color-secondary); + border-left-style: dashed; } /* fomantic's last-child selector does not work with hidden last child */ diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue index 98ffce44b55..7834ebe82cc 100644 --- a/web_src/js/components/ActivityHeatmap.vue +++ b/web_src/js/components/ActivityHeatmap.vue @@ -1,7 +1,7 @@