mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-08 03:29:43 +02:00
Merge branch 'main' into main
This commit is contained in:
43 files changed
+441
-438
No files matched your search
@@ -34,7 +34,7 @@ func (r *RepoComment) ResolveLink(link, preferLinkType string) string {
|
|||||||
case markup.LinkTypeRoot:
|
case markup.LinkTypeRoot:
|
||||||
return r.ctx.ResolveLinkRoot(link)
|
return r.ctx.ResolveLinkRoot(link)
|
||||||
default:
|
default:
|
||||||
return r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link)
|
return r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefSubURL, link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ var _ markup.RenderHelper = (*RepoComment)(nil)
|
|||||||
type RepoCommentOptions struct {
|
type RepoCommentOptions struct {
|
||||||
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||||
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
CurrentRefPath string // eg: "branch/main" or "commit/11223344"
|
CurrentRefSubURL string // eg: "branch/main" or "commit/11223344"
|
||||||
FootnoteContextID string // the extra context ID for footnotes, used to avoid conflicts with other footnotes in the same page
|
FootnoteContextID string // the extra context ID for footnotes, used to avoid conflicts with other footnotes in the same page
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ func TestRepoComment(t *testing.T) {
|
|||||||
`, rendered)
|
`, rendered)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("WithCurrentRefPath", func(t *testing.T) {
|
t.Run("WithCurrentRefSubURL", func(t *testing.T) {
|
||||||
rctx := NewRenderContextRepoComment(t.Context(), repo1, RepoCommentOptions{CurrentRefPath: "/commit/1234"}).
|
rctx := NewRenderContextRepoComment(t.Context(), repo1, RepoCommentOptions{CurrentRefSubURL: "/commit/1234"}).
|
||||||
WithMarkupType(markdown.MarkupName)
|
WithMarkupType(markdown.MarkupName)
|
||||||
|
|
||||||
// the ref path is only used to render commit message: a commit message is rendered at the commit page with its commit ID path
|
// the ref path is only used to render commit message: a commit message is rendered at the commit page with its commit ID path
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ func (r *RepoFile) ResolveLink(link, preferLinkType string) (finalLink string) {
|
|||||||
case markup.LinkTypeRoot:
|
case markup.LinkTypeRoot:
|
||||||
finalLink = r.ctx.ResolveLinkRoot(link)
|
finalLink = r.ctx.ResolveLinkRoot(link)
|
||||||
case markup.LinkTypeRaw:
|
case markup.LinkTypeRaw:
|
||||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||||
case markup.LinkTypeMedia:
|
case markup.LinkTypeMedia:
|
||||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||||
default:
|
default:
|
||||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link)
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefSubURL), r.opts.CurrentTreePath, link)
|
||||||
}
|
}
|
||||||
return finalLink
|
return finalLink
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,8 @@ type RepoFileOptions struct {
|
|||||||
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
DeprecatedRepoName string // it is only a patch for the non-standard "markup" api
|
||||||
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
|
|
||||||
CurrentRefPath string // eg: "branch/main", it is a sub URL path escaped by callers, TODO: rename to CurrentRefSubURL
|
CurrentRefSubURL string // eg: "branch/main" or "branch/my%20branch", it is a sub URL path escaped by callers
|
||||||
CurrentTreePath string // eg: "path/to/file" in the repo, it is the tree path without URL path escaping
|
CurrentTreePath string // eg: "path/to/file" in the repo, it is the tree path without URL path escaping
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, opts ...RepoFileOptions) *markup.RenderContext {
|
func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, opts ...RepoFileOptions) *markup.RenderContext {
|
||||||
@@ -71,9 +71,8 @@ func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// External render's iframe needs this to generate correct links
|
// External render's iframe needs this to generate correct links
|
||||||
// TODO: maybe need to make it access "CurrentRefPath" directly (but impossible at the moment due to cycle-import)
|
// TODO: maybe need to make it access "CurrentRefSubURL" directly (but impossible at the moment due to cycle-import)
|
||||||
// CurrentRefPath is already path-escaped by callers
|
rctx.RenderOptions.Metas["RefTypeNameSubURL"] = helper.opts.CurrentRefSubURL
|
||||||
rctx.RenderOptions.Metas["RefTypeNameSubURL"] = helper.opts.CurrentRefPath
|
|
||||||
rctx = rctx.WithHelper(helper).WithEnableHeadingIDGeneration(true)
|
rctx = rctx.WithHelper(helper).WithEnableHeadingIDGeneration(true)
|
||||||
return rctx
|
return rctx
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ func TestRepoFile(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
t.Run("AbsoluteAndRelative", func(t *testing.T) {
|
||||||
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{CurrentRefPath: "branch/main"}).
|
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{CurrentRefSubURL: "branch/main"}).
|
||||||
WithMarkupType(markdown.MarkupName)
|
WithMarkupType(markdown.MarkupName)
|
||||||
rendered, err := markup.RenderString(rctx, `
|
rendered, err := markup.RenderString(rctx, `
|
||||||
[/test](/test)
|
[/test](/test)
|
||||||
@@ -53,8 +53,8 @@ func TestRepoFile(t *testing.T) {
|
|||||||
`, rendered)
|
`, rendered)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("WithCurrentRefPath", func(t *testing.T) {
|
t.Run("WithCurrentRefSubURL", func(t *testing.T) {
|
||||||
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{CurrentRefPath: "/commit/1234"}).
|
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{CurrentRefSubURL: "/commit/1234"}).
|
||||||
WithMarkupType(markdown.MarkupName)
|
WithMarkupType(markdown.MarkupName)
|
||||||
rendered, err := markup.RenderString(rctx, `
|
rendered, err := markup.RenderString(rctx, `
|
||||||
[/test](/test)
|
[/test](/test)
|
||||||
@@ -66,10 +66,10 @@ func TestRepoFile(t *testing.T) {
|
|||||||
`, rendered)
|
`, rendered)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("WithCurrentRefPathByTag", func(t *testing.T) {
|
t.Run("WithCurrentRefSubURLByTag", func(t *testing.T) {
|
||||||
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
|
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
|
||||||
CurrentRefPath: "/commit/1234",
|
CurrentRefSubURL: "/commit/1234",
|
||||||
CurrentTreePath: "my-dir",
|
CurrentTreePath: "my-dir",
|
||||||
}).
|
}).
|
||||||
WithMarkupType(markdown.MarkupName)
|
WithMarkupType(markdown.MarkupName)
|
||||||
rendered, err := markup.RenderString(rctx, `
|
rendered, err := markup.RenderString(rctx, `
|
||||||
@@ -89,8 +89,8 @@ func TestRepoFileOrgMode(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Links", func(t *testing.T) {
|
t.Run("Links", func(t *testing.T) {
|
||||||
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
|
rctx := NewRenderContextRepoFile(t.Context(), repo1, RepoFileOptions{
|
||||||
CurrentRefPath: "/commit/1234",
|
CurrentRefSubURL: "/commit/1234",
|
||||||
CurrentTreePath: "my-dir",
|
CurrentTreePath: "my-dir",
|
||||||
}).WithRelativePath("my-dir/a.org")
|
}).WithRelativePath("my-dir/a.org")
|
||||||
|
|
||||||
rendered, err := markup.RenderString(rctx, `
|
rendered, err := markup.RenderString(rctx, `
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ func (r *RepoWiki) ResolveLink(link, preferLinkType string) (finalLink string) {
|
|||||||
case markup.LinkTypeRoot:
|
case markup.LinkTypeRoot:
|
||||||
finalLink = r.ctx.ResolveLinkRoot(link)
|
finalLink = r.ctx.ResolveLinkRoot(link)
|
||||||
case markup.LinkTypeMedia, markup.LinkTypeRaw:
|
case markup.LinkTypeMedia, markup.LinkTypeRaw:
|
||||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki/raw", r.opts.currentRefPath), r.opts.currentTreePath, link)
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki/raw", r.opts.currentRefSubURL), r.opts.currentTreePath, link)
|
||||||
default:
|
default:
|
||||||
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki", r.opts.currentRefPath), r.opts.currentTreePath, link)
|
finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "wiki", r.opts.currentRefSubURL), r.opts.currentTreePath, link)
|
||||||
}
|
}
|
||||||
return finalLink
|
return finalLink
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,8 @@ type RepoWikiOptions struct {
|
|||||||
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
DeprecatedOwnerName string // it is only a patch for the non-standard "markup" api
|
||||||
|
|
||||||
// these options are not used at the moment because Wiki doesn't support sub-path, nor branch
|
// these options are not used at the moment because Wiki doesn't support sub-path, nor branch
|
||||||
currentRefPath string // eg: "branch/main"
|
currentRefSubURL string // eg: "branch/main"
|
||||||
currentTreePath string // eg: "path/to/file" in the repo
|
currentTreePath string // eg: "path/to/file" in the repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext {
|
func NewRenderContextRepoWiki(ctx context.Context, repo *repo_model.Repository, opts ...RepoWikiOptions) *markup.RenderContext {
|
||||||
|
|||||||
+37
-14
@@ -37,6 +37,42 @@ type ServeHeaderOptions struct {
|
|||||||
LastModified time.Time
|
LastModified time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Disable JS execution on the same origin, since we serve the file from the same origin as Gitea server.
|
||||||
|
// This rule can be relaxed in the future as long as it is properly sandboxed.
|
||||||
|
// "style-src" is for SVG inline styles (from Display SVG files as images instead of text #14101)
|
||||||
|
serveHeaderCspDefault = "default-src 'none'; style-src 'unsafe-inline'; sandbox"
|
||||||
|
|
||||||
|
// No sandbox attribute for PDF as it breaks rendering in at least Safari.
|
||||||
|
// This should generally be safe as scripts inside PDF can not escape the PDF document.
|
||||||
|
// See https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion.
|
||||||
|
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
|
||||||
|
serveHeaderCspPdf = "default-src 'none'; style-src 'unsafe-inline'"
|
||||||
|
|
||||||
|
// For audios and videos, actually it doesn't really need CSP (just like Gitea <= 1.25)
|
||||||
|
serveHeaderCspAudioVideo = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
func serveSetHeaderContentRelated(w http.ResponseWriter, contentType string) {
|
||||||
|
header := w.Header()
|
||||||
|
contentType = util.IfZero(contentType, typesniffer.MimeTypeApplicationOctetStream)
|
||||||
|
header.Set("Content-Type", contentType)
|
||||||
|
header.Set("X-Content-Type-Options", "nosniff")
|
||||||
|
|
||||||
|
csp := serveHeaderCspDefault
|
||||||
|
if strings.HasPrefix(contentType, "application/pdf") {
|
||||||
|
csp = serveHeaderCspPdf
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(contentType, "video/") || strings.HasPrefix(contentType, "audio/") {
|
||||||
|
csp = serveHeaderCspAudioVideo
|
||||||
|
}
|
||||||
|
if csp != "" {
|
||||||
|
header.Set("Content-Security-Policy", csp)
|
||||||
|
} else {
|
||||||
|
header.Del("Content-Security-Policy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ServeSetHeaders sets necessary content serve headers
|
// ServeSetHeaders sets necessary content serve headers
|
||||||
func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
|
func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
@@ -46,24 +82,11 @@ func ServeSetHeaders(w http.ResponseWriter, opts ServeHeaderOptions) {
|
|||||||
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
w.Header().Add(gzhttp.HeaderNoCompression, "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := util.IfZero(opts.ContentType, typesniffer.MimeTypeApplicationOctetStream)
|
serveSetHeaderContentRelated(w, opts.ContentType)
|
||||||
header.Set("Content-Type", contentType)
|
|
||||||
header.Set("X-Content-Type-Options", "nosniff")
|
|
||||||
|
|
||||||
if opts.ContentLength != nil {
|
if opts.ContentLength != nil {
|
||||||
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
|
header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable script execution of HTML/SVG files, since we serve the file from the same origin as Gitea server
|
|
||||||
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; sandbox")
|
|
||||||
if strings.Contains(contentType, "application/pdf") {
|
|
||||||
// no sandbox attribute for PDF as it breaks rendering in at least safari. this
|
|
||||||
// should generally be safe as scripts inside PDF can not escape the PDF document
|
|
||||||
// see https://bugs.chromium.org/p/chromium/issues/detail?id=413851 for more discussion
|
|
||||||
// HINT: PDF-RENDER-SANDBOX: PDF won't render in sandboxed context
|
|
||||||
header.Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Filename != "" && opts.ContentDisposition != "" {
|
if opts.Filename != "" && opts.ContentDisposition != "" {
|
||||||
header.Set("Content-Disposition", encodeContentDisposition(opts.ContentDisposition, path.Base(opts.Filename)))
|
header.Set("Content-Disposition", encodeContentDisposition(opts.ContentDisposition, path.Base(opts.Filename)))
|
||||||
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
header.Set("Access-Control-Expose-Headers", "Content-Disposition")
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -106,3 +108,28 @@ func TestServeUserContentByFile(t *testing.T) {
|
|||||||
test(t, http.StatusPartialContent, data[1:])
|
test(t, http.StatusPartialContent, data[1:])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServeSetHeaderContentRelated(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
contentType string
|
||||||
|
csp string
|
||||||
|
}{
|
||||||
|
{"", serveHeaderCspDefault},
|
||||||
|
{"any", serveHeaderCspDefault},
|
||||||
|
{"application/pdf", serveHeaderCspPdf},
|
||||||
|
{"application/pdf; other", serveHeaderCspPdf},
|
||||||
|
{"audio/mp4", serveHeaderCspAudioVideo},
|
||||||
|
{"video/ogg; other", serveHeaderCspAudioVideo},
|
||||||
|
{typesniffer.MimeTypeImageSvg, serveHeaderCspDefault},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
serveSetHeaderContentRelated(w, c.contentType)
|
||||||
|
csp := w.Header().Get("Content-Security-Policy")
|
||||||
|
assert.Equal(t, c.csp, csp, "content-type: %s", c.contentType)
|
||||||
|
assert.Equal(t, "nosniff", w.Header().Get("X-Content-Type-Options")) // it should always be there
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure sandboxed
|
||||||
|
require.Contains(t, serveHeaderCspDefault, "; sandbox")
|
||||||
|
}
|
||||||
@@ -2474,7 +2474,7 @@
|
|||||||
"repo.settings.tags.protection.allowed.noone": "无",
|
"repo.settings.tags.protection.allowed.noone": "无",
|
||||||
"repo.settings.tags.protection.create": "保护 Git 标签",
|
"repo.settings.tags.protection.create": "保护 Git 标签",
|
||||||
"repo.settings.tags.protection.none": "没有受保护的 Git 标签。",
|
"repo.settings.tags.protection.none": "没有受保护的 Git 标签。",
|
||||||
"repo.settings.tags.protection.pattern.description": "您可以使用单个名称或 glob 表达式匹配或正则表达式来匹配多个 Git 标签。了解详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">保护标签指南</a>。",
|
"repo.settings.tags.protection.pattern.description": "您可以使用单个名称或 glob 表达式匹配或正则表达式来匹配多个 Git 标签。欲了解详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">保护标签指南</a>。",
|
||||||
"repo.settings.bot_token": "Bot 令牌",
|
"repo.settings.bot_token": "Bot 令牌",
|
||||||
"repo.settings.chat_id": "聊天 ID",
|
"repo.settings.chat_id": "聊天 ID",
|
||||||
"repo.settings.thread_id": "线程 ID",
|
"repo.settings.thread_id": "线程 ID",
|
||||||
@@ -3099,11 +3099,11 @@
|
|||||||
"admin.packages.size": "大小",
|
"admin.packages.size": "大小",
|
||||||
"admin.packages.published": "已发布",
|
"admin.packages.published": "已发布",
|
||||||
"admin.defaulthooks": "默认 Web 钩子",
|
"admin.defaulthooks": "默认 Web 钩子",
|
||||||
"admin.defaulthooks.desc": "当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子是默认配置,将被复制到所有新的仓库中。详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">Web 钩子指南</a>。",
|
"admin.defaulthooks.desc": "当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子是默认配置,将被复制到所有新的仓库中。欲了解详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">Web 钩子指南</a>。",
|
||||||
"admin.defaulthooks.add_webhook": "添加默认 Web 钩子",
|
"admin.defaulthooks.add_webhook": "添加默认 Web 钩子",
|
||||||
"admin.defaulthooks.update_webhook": "更新默认 Web 钩子",
|
"admin.defaulthooks.update_webhook": "更新默认 Web 钩子",
|
||||||
"admin.systemhooks": "系统 Web 钩子",
|
"admin.systemhooks": "系统 Web 钩子",
|
||||||
"admin.systemhooks.desc": "当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子将作用于系统上的所有仓库,所以请考虑这可能带来的任何性能影响。了解详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">Web 钩子指南</a>。",
|
"admin.systemhooks.desc": "当某些 Gitea 事件触发时,Web 钩子自动向服务器发出 HTTP POST 请求。这里定义的 Web 钩子将作用于系统上的所有仓库,所以请考虑这可能带来的任何性能影响。欲了解详情请访问 <a target=\"_blank\" rel=\"noopener\" href=\"%s\">Web 钩子指南</a>。",
|
||||||
"admin.systemhooks.add_webhook": "添加系统 Web 钩子",
|
"admin.systemhooks.add_webhook": "添加系统 Web 钩子",
|
||||||
"admin.systemhooks.update_webhook": "更新系统 Web 钩子",
|
"admin.systemhooks.update_webhook": "更新系统 Web 钩子",
|
||||||
"admin.auths.auth_manage_panel": "认证源管理",
|
"admin.auths.auth_manage_panel": "认证源管理",
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
case "gfm": // legacy mode
|
case "gfm": // legacy mode
|
||||||
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
||||||
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
||||||
CurrentRefPath: refPath, CurrentTreePath: treePath,
|
CurrentRefSubURL: refPath, CurrentTreePath: treePath,
|
||||||
})
|
})
|
||||||
rctx = rctx.WithMarkupType(markdown.MarkupName)
|
rctx = rctx.WithMarkupType(markdown.MarkupName)
|
||||||
case "comment":
|
case "comment":
|
||||||
@@ -87,7 +87,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
case "file":
|
case "file":
|
||||||
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
rctx = renderhelper.NewRenderContextRepoFile(ctx, repoModel, renderhelper.RepoFileOptions{
|
||||||
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
DeprecatedOwnerName: repoOwnerName, DeprecatedRepoName: repoName,
|
||||||
CurrentRefPath: refPath, CurrentTreePath: treePath,
|
CurrentRefSubURL: refPath, CurrentTreePath: treePath,
|
||||||
})
|
})
|
||||||
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ func prepareOrgProfileReadme(ctx *context.Context, prepareResult *shared_user.Pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileRepo, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileRepo, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileRepo.DefaultBranch)),
|
CurrentRefSubURL: path.Join("branch", util.PathEscapeSegments(profileRepo.DefaultBranch)),
|
||||||
})
|
})
|
||||||
ctx.Data["ProfileReadmeContent"], err = markdown.RenderString(rctx, readmeBytes)
|
ctx.Data["ProfileReadmeContent"], err = markdown.RenderString(rctx, readmeBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -609,6 +609,8 @@ func DeleteTeam(ctx *context.Context) {
|
|||||||
// TeamInvite renders the team invite page
|
// TeamInvite renders the team invite page
|
||||||
func TeamInvite(ctx *context.Context) {
|
func TeamInvite(ctx *context.Context) {
|
||||||
invite, org, team, inviter, err := getTeamInviteFromContext(ctx)
|
invite, org, team, inviter, err := getTeamInviteFromContext(ctx)
|
||||||
|
// TODO: to quickly debug the UI, can uncomment this (don't worry, it won't pass CI lint)
|
||||||
|
// invite, org, team, inviter, err = &org_model.TeamInvite{}, &org_model.Organization{}, &org_model.Team{}, ctx.Doer, nil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if org_model.IsErrTeamInviteNotFound(err) {
|
if org_model.IsErrTeamInviteNotFound(err) {
|
||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
@@ -409,7 +408,7 @@ func Diff(ctx *context.Context) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Data["NoteCommit"] = note.Commit
|
ctx.Data["NoteCommit"] = note.Commit
|
||||||
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
||||||
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))})
|
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefSubURL: "commit/" + util.PathEscapeSegments(commitID)})
|
||||||
htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
||||||
ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, htmlMessage)
|
ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, htmlMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
pull_model "code.gitea.io/gitea/models/pull"
|
|
||||||
"code.gitea.io/gitea/models/renderhelper"
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
@@ -826,6 +825,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
panic("impossible, issue must be the same")
|
panic("impossible, issue must be the same")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pull := issue.PullRequest
|
||||||
data := &pullMergeBoxData{}
|
data := &pullMergeBoxData{}
|
||||||
prInfo.MergeBoxData = data
|
prInfo.MergeBoxData = data
|
||||||
|
|
||||||
@@ -834,14 +834,12 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
statusCheckData = &pullCommitStatusCheckData{} // make the following logic easier, no need to keep checking "nil"
|
statusCheckData = &pullCommitStatusCheckData{} // make the following logic easier, no need to keep checking "nil"
|
||||||
}
|
}
|
||||||
|
|
||||||
pull := issue.PullRequest
|
|
||||||
canDelete := false
|
canDelete := false
|
||||||
allowMerge := false
|
allowMerge := false
|
||||||
canWriteToHeadRepo := false
|
canWriteToHeadRepo := false
|
||||||
|
|
||||||
pull_service.StartPullRequestCheckOnView(ctx, pull)
|
pull_service.StartPullRequestCheckOnView(ctx, pull)
|
||||||
|
|
||||||
ctx.Data["GetCommitMessages"] = ""
|
|
||||||
if !prInfo.IsPullRequestBroken {
|
if !prInfo.IsPullRequestBroken {
|
||||||
var err error
|
var err error
|
||||||
ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer)
|
ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer)
|
||||||
@@ -849,7 +847,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
ctx.ServerError("IsUserAllowedToUpdate", err)
|
ctx.ServerError("IsUserAllowedToUpdate", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(ctx, pull)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if pull.IsFilesConflicted() {
|
if pull.IsFilesConflicted() {
|
||||||
@@ -903,59 +900,11 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.ReloadingInterval = util.Iif(pull != nil && pull.IsChecking(), 2000, 0)
|
data.ReloadingInterval = util.Iif(pull.IsChecking(), 2000, 0)
|
||||||
ctx.Data["CanWriteToHeadRepo"] = canWriteToHeadRepo
|
data.ShowMergeInstructions = canWriteToHeadRepo
|
||||||
ctx.Data["ShowMergeInstructions"] = canWriteToHeadRepo
|
data.ShowPullCommands = pull.HeadRepo != nil && !pull.HasMerged && !issue.IsClosed
|
||||||
ctx.Data["AllowMerge"] = allowMerge
|
ctx.Data["AllowMerge"] = allowMerge
|
||||||
|
|
||||||
prUnit, err := issue.Repo.GetUnit(ctx, unit.TypePullRequests)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUnit", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
prConfig := prUnit.PullRequestsConfig()
|
|
||||||
|
|
||||||
ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge
|
|
||||||
|
|
||||||
var mergeStyle repo_model.MergeStyle
|
|
||||||
// Check correct values and select default
|
|
||||||
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
|
|
||||||
!prConfig.IsMergeStyleAllowed(ms) {
|
|
||||||
if prConfig.IsMergeStyleAllowed(prConfig.DefaultMergeStyle) && !ok {
|
|
||||||
mergeStyle = prConfig.DefaultMergeStyle
|
|
||||||
} else if prConfig.AllowMerge {
|
|
||||||
mergeStyle = repo_model.MergeStyleMerge
|
|
||||||
} else if prConfig.AllowRebase {
|
|
||||||
mergeStyle = repo_model.MergeStyleRebase
|
|
||||||
} else if prConfig.AllowRebaseMerge {
|
|
||||||
mergeStyle = repo_model.MergeStyleRebaseMerge
|
|
||||||
} else if prConfig.AllowSquash {
|
|
||||||
mergeStyle = repo_model.MergeStyleSquash
|
|
||||||
} else if prConfig.AllowFastForwardOnly {
|
|
||||||
mergeStyle = repo_model.MergeStyleFastForwardOnly
|
|
||||||
} else if prConfig.AllowManualMerge {
|
|
||||||
mergeStyle = repo_model.MergeStyleManuallyMerged
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["MergeStyle"] = mergeStyle
|
|
||||||
|
|
||||||
defaultMergeMessage, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetDefaultMergeMessage", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["DefaultMergeMessage"] = defaultMergeMessage
|
|
||||||
ctx.Data["DefaultMergeBody"] = defaultMergeBody
|
|
||||||
|
|
||||||
defaultSquashMergeMessage, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
|
||||||
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
|
||||||
|
|
||||||
pb := prInfo.ProtectedBranchRule
|
pb := prInfo.ProtectedBranchRule
|
||||||
if pb != nil {
|
if pb != nil {
|
||||||
pb.Repo = pull.BaseRepo
|
pb.Repo = pull.BaseRepo
|
||||||
@@ -995,6 +944,9 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prConfig := issue.Repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||||
|
data.AutodetectManualMerge = prConfig.AutodetectManualMerge
|
||||||
|
|
||||||
stillCanManualMerge := func() bool {
|
stillCanManualMerge := func() bool {
|
||||||
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
|
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
|
||||||
return false
|
return false
|
||||||
@@ -1007,13 +959,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
|
|
||||||
ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
|
ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
|
||||||
|
|
||||||
// Check if there is a pending pr merge
|
|
||||||
ctx.Data["HasPendingPullRequestMerge"], ctx.Data["PendingPullRequestMerge"], err = pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetScheduledMergeByPullID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
enableStatusCheck := pb != nil && pb.EnableStatusCheck
|
enableStatusCheck := pb != nil && pb.EnableStatusCheck
|
||||||
ctx.Data["EnableStatusCheck"] = enableStatusCheck
|
ctx.Data["EnableStatusCheck"] = enableStatusCheck
|
||||||
|
|
||||||
@@ -1043,6 +988,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
|||||||
(!data.requireSigned || data.willSign) // signing requirement is satisfied
|
(!data.requireSigned || data.willSign) // signing requirement is satisfied
|
||||||
|
|
||||||
ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData
|
ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData
|
||||||
|
prInfo.prepareMergeBoxFormProps(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
|
func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
|
||||||
|
|||||||
@@ -164,12 +164,13 @@ func getPullInfo(ctx *context.Context) (issue *issues_model.Issue, ok bool) {
|
|||||||
func (prInfo *pullRequestViewInfo) setTemplateDataMergeTarget(ctx *context.Context) {
|
func (prInfo *pullRequestViewInfo) setTemplateDataMergeTarget(ctx *context.Context) {
|
||||||
pull := prInfo.issue.PullRequest
|
pull := prInfo.issue.PullRequest
|
||||||
if ctx.Repo.Owner.Name == pull.MustHeadUserName(ctx) {
|
if ctx.Repo.Owner.Name == pull.MustHeadUserName(ctx) {
|
||||||
ctx.Data["HeadTarget"] = pull.HeadBranch
|
prInfo.headTarget = pull.HeadBranch
|
||||||
} else if pull.HeadRepo == nil {
|
} else if pull.HeadRepo == nil {
|
||||||
ctx.Data["HeadTarget"] = ctx.Locale.Tr("repo.pull.deleted_branch", pull.HeadBranch)
|
prInfo.headTarget = ctx.Locale.TrString("repo.pull.deleted_branch", pull.HeadBranch)
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["HeadTarget"] = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
|
prInfo.headTarget = pull.MustHeadUserName(ctx) + "/" + pull.HeadRepo.Name + ":" + pull.HeadBranch
|
||||||
}
|
}
|
||||||
|
ctx.Data["HeadTarget"] = prInfo.headTarget
|
||||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||||
headBranchLink := ""
|
headBranchLink := ""
|
||||||
if pull.Flow == issues_model.PullRequestFlowGithub {
|
if pull.Flow == issues_model.PullRequestFlowGithub {
|
||||||
@@ -268,6 +269,11 @@ type pullMergeBoxData struct {
|
|||||||
HasOverridableBlockers bool
|
HasOverridableBlockers bool
|
||||||
CanMergeNow bool
|
CanMergeNow bool
|
||||||
|
|
||||||
|
MergeFormProps map[string]any
|
||||||
|
ShowPullCommands bool
|
||||||
|
ShowMergeInstructions bool
|
||||||
|
AutodetectManualMerge bool
|
||||||
|
|
||||||
// don't expose unneeded fields to templates, need more refactoring changes
|
// don't expose unneeded fields to templates, need more refactoring changes
|
||||||
hasStatusCheckBlocker bool
|
hasStatusCheckBlocker bool
|
||||||
isPullBranchDeletable bool
|
isPullBranchDeletable bool
|
||||||
@@ -289,6 +295,7 @@ type pullRequestViewInfo struct {
|
|||||||
|
|
||||||
IsPullRequestBroken bool
|
IsPullRequestBroken bool
|
||||||
HeadBranchCommitID string
|
HeadBranchCommitID string
|
||||||
|
headTarget string // for display purpose only
|
||||||
|
|
||||||
CompareInfo git_service.CompareInfo
|
CompareInfo git_service.CompareInfo
|
||||||
ProtectedBranchRule *git_model.ProtectedBranch
|
ProtectedBranchRule *git_model.ProtectedBranch
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
pull_model "code.gitea.io/gitea/models/pull"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
pull_service "code.gitea.io/gitea/services/pull"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context) {
|
||||||
|
pull := prInfo.issue.PullRequest
|
||||||
|
prConfig := ctx.Repo.Repository.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||||
|
|
||||||
|
// Check correct values and select default
|
||||||
|
var mergeStyle repo_model.MergeStyle
|
||||||
|
if prConfig.IsMergeStyleAllowed(prConfig.DefaultMergeStyle) {
|
||||||
|
mergeStyle = prConfig.DefaultMergeStyle
|
||||||
|
} else if prConfig.AllowMerge {
|
||||||
|
mergeStyle = repo_model.MergeStyleMerge
|
||||||
|
} else if prConfig.AllowRebase {
|
||||||
|
mergeStyle = repo_model.MergeStyleRebase
|
||||||
|
} else if prConfig.AllowRebaseMerge {
|
||||||
|
mergeStyle = repo_model.MergeStyleRebaseMerge
|
||||||
|
} else if prConfig.AllowSquash {
|
||||||
|
mergeStyle = repo_model.MergeStyleSquash
|
||||||
|
} else if prConfig.AllowFastForwardOnly {
|
||||||
|
mergeStyle = repo_model.MergeStyleFastForwardOnly
|
||||||
|
} else if prConfig.AllowManualMerge {
|
||||||
|
mergeStyle = repo_model.MergeStyleManuallyMerged
|
||||||
|
}
|
||||||
|
if mergeStyle == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is a pending pr merge
|
||||||
|
hasPendingPullRequestMerge, pendingPullRequestMerge, err := pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetScheduledMergeByPullID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPendingPullRequestMergeTip template.HTML
|
||||||
|
if hasPendingPullRequestMerge {
|
||||||
|
createdPRMergeStr := templates.TimeSince(pendingPullRequestMerge.CreatedUnix)
|
||||||
|
hasPendingPullRequestMergeTip = ctx.Locale.Tr("repo.pulls.auto_merge_has_pending_schedule", pendingPullRequestMerge.Doer.Name, createdPRMergeStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultMergeTitle, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetDefaultMergeMessage", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defaultSquashMergeTitle, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultSquashMergeCommitMessages string
|
||||||
|
if !prInfo.IsPullRequestBroken {
|
||||||
|
defaultSquashMergeCommitMessages = pull_service.GetSquashMergeCommitMessages(ctx, pull)
|
||||||
|
}
|
||||||
|
|
||||||
|
allOverridableChecksOk := !prInfo.MergeBoxData.HasOverridableBlockers
|
||||||
|
prInfo.MergeBoxData.MergeFormProps = map[string]any{
|
||||||
|
"baseLink": prInfo.issue.Link(),
|
||||||
|
"textCancel": ctx.Locale.Tr("cancel"),
|
||||||
|
"textDeleteBranch": ctx.Locale.Tr("repo.branch.delete", prInfo.headTarget),
|
||||||
|
"textAutoMergeButtonWhenSucceed": ctx.Locale.Tr("repo.pulls.auto_merge_button_when_succeed"),
|
||||||
|
"textAutoMergeWhenSucceed": ctx.Locale.Tr("repo.pulls.auto_merge_when_succeed"),
|
||||||
|
"textAutoMergeCancelSchedule": ctx.Locale.Tr("repo.pulls.auto_merge_cancel_schedule"),
|
||||||
|
"textClearMergeMessage": ctx.Locale.Tr("repo.pulls.clear_merge_message"),
|
||||||
|
"textClearMergeMessageHint": ctx.Locale.Tr("repo.pulls.clear_merge_message_hint"),
|
||||||
|
"textMergeCommitId": ctx.Locale.Tr("repo.pulls.merge_commit_id"),
|
||||||
|
|
||||||
|
"canMergeNow": prInfo.MergeBoxData.CanMergeNow,
|
||||||
|
"allOverridableChecksOk": allOverridableChecksOk,
|
||||||
|
"emptyCommit": pull.IsEmpty(),
|
||||||
|
"pullHeadCommitID": prInfo.CompareInfo.HeadCommitID,
|
||||||
|
"isPullBranchDeletable": prInfo.MergeBoxData.isPullBranchDeletable,
|
||||||
|
"defaultMergeStyle": mergeStyle,
|
||||||
|
"defaultDeleteBranchAfterMerge": prConfig.DefaultDeleteBranchAfterMerge,
|
||||||
|
"mergeMessageFieldPlaceHolder": ctx.Locale.Tr("repo.editor.commit_message_desc"),
|
||||||
|
"defaultMergeMessage": defaultMergeBody,
|
||||||
|
|
||||||
|
"hasPendingPullRequestMerge": hasPendingPullRequestMerge,
|
||||||
|
"hasPendingPullRequestMergeTip": hasPendingPullRequestMergeTip,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this pr can be merged now, then hide the auto merge
|
||||||
|
generalHideAutoMerge := prInfo.MergeBoxData.CanMergeNow && allOverridableChecksOk
|
||||||
|
|
||||||
|
prInfo.MergeBoxData.MergeFormProps["mergeStyles"] = []any{
|
||||||
|
map[string]any{
|
||||||
|
"name": "merge",
|
||||||
|
"allowed": prConfig.AllowMerge,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.merge_pull_request"),
|
||||||
|
"mergeTitleFieldText": defaultMergeTitle,
|
||||||
|
"mergeMessageFieldText": defaultMergeBody,
|
||||||
|
"hideAutoMerge": generalHideAutoMerge,
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"name": "rebase",
|
||||||
|
"allowed": prConfig.AllowRebase,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_pull_request"),
|
||||||
|
"hideMergeMessageTexts": true,
|
||||||
|
"hideAutoMerge": generalHideAutoMerge,
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"name": "rebase-merge",
|
||||||
|
"allowed": prConfig.AllowRebaseMerge,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.rebase_merge_commit_pull_request"),
|
||||||
|
"mergeTitleFieldText": defaultMergeTitle,
|
||||||
|
"mergeMessageFieldText": defaultMergeBody,
|
||||||
|
"hideAutoMerge": generalHideAutoMerge,
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"name": "squash",
|
||||||
|
"allowed": prConfig.AllowSquash,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.squash_merge_pull_request"),
|
||||||
|
"mergeTitleFieldText": defaultSquashMergeTitle,
|
||||||
|
"mergeMessageFieldText": defaultSquashMergeCommitMessages + defaultSquashMergeBody,
|
||||||
|
"hideAutoMerge": generalHideAutoMerge,
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"name": "fast-forward-only",
|
||||||
|
"allowed": prConfig.AllowFastForwardOnly && pull.CommitsBehind == 0,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.fast_forward_only_merge_pull_request"),
|
||||||
|
"hideMergeMessageTexts": true,
|
||||||
|
"hideAutoMerge": generalHideAutoMerge,
|
||||||
|
},
|
||||||
|
map[string]any{
|
||||||
|
"name": "manually-merged",
|
||||||
|
"allowed": prConfig.AllowManualMerge,
|
||||||
|
"textDoMerge": ctx.Locale.Tr("repo.pulls.merge_manually"),
|
||||||
|
"hideMergeMessageTexts": true,
|
||||||
|
"hideAutoMerge": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,8 +40,8 @@ func RenderFile(ctx *context.Context) {
|
|||||||
defer blobReader.Close()
|
defer blobReader.Close()
|
||||||
|
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
CurrentRefSubURL: ctx.Repo.RefTypeNameSubURL(),
|
||||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
}).WithRelativePath(ctx.Repo.TreePath).WithStandalonePage(markup.StandalonePageOptions{
|
}).WithRelativePath(ctx.Repo.TreePath).WithStandalonePage(markup.StandalonePageOptions{
|
||||||
CurrentWebTheme: ctx.TemplateContext.CurrentWebTheme(),
|
CurrentWebTheme: ctx.TemplateContext.CurrentWebTheme(),
|
||||||
RenderQueryString: ctx.Req.URL.RawQuery,
|
RenderQueryString: ctx.Req.URL.RawQuery,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tplStarUnstar templates.TplName = "repo/star_unstar"
|
const tplStarUnstar templates.TplName = "repo/header/star"
|
||||||
|
|
||||||
func ActionStar(ctx *context.Context) {
|
func ActionStar(ctx *context.Context) {
|
||||||
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, ctx.PathParam("action") == "star")
|
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, ctx.PathParam("action") == "star")
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func prepareFileViewLfsAttrs(ctx *context.Context) (*attribute.Attributes, bool)
|
|||||||
|
|
||||||
func handleFileViewRenderMarkup(ctx *context.Context, prefetchBuf []byte, utf8Reader io.Reader) bool {
|
func handleFileViewRenderMarkup(ctx *context.Context, prefetchBuf []byte, utf8Reader io.Reader) bool {
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
CurrentRefSubURL: ctx.Repo.RefTypeNameSubURL(),
|
||||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
}).WithRelativePath(ctx.Repo.TreePath)
|
}).WithRelativePath(ctx.Repo.TreePath)
|
||||||
|
|
||||||
renderer := rctx.DetectMarkupRenderer(prefetchBuf)
|
renderer := rctx.DetectMarkupRenderer(prefetchBuf)
|
||||||
|
|||||||
@@ -190,8 +190,8 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
|
|||||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||||
|
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
CurrentRefSubURL: ctx.Repo.RefTypeNameSubURL(),
|
||||||
CurrentTreePath: path.Dir(readmeFullPath),
|
CurrentTreePath: path.Dir(readmeFullPath),
|
||||||
}).WithRelativePath(readmeFullPath)
|
}).WithRelativePath(readmeFullPath)
|
||||||
renderer := rctx.DetectMarkupRenderer(buf)
|
renderer := rctx.DetectMarkupRenderer(buf)
|
||||||
if renderer != nil {
|
if renderer != nil {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tplWatchUnwatch templates.TplName = "repo/watch_unwatch"
|
const tplWatchUnwatch templates.TplName = "repo/header/watch"
|
||||||
|
|
||||||
func ActionWatch(ctx *context.Context) {
|
func ActionWatch(ctx *context.Context) {
|
||||||
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, ctx.PathParam("action") == "watch")
|
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, ctx.PathParam("action") == "watch")
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.R
|
|||||||
log.Error("failed to GetBlobContent: %v", err)
|
log.Error("failed to GetBlobContent: %v", err)
|
||||||
} else {
|
} else {
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, profileDbRepo, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
CurrentRefSubURL: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
|
||||||
})
|
})
|
||||||
if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
|
if profileContent, err := markdown.RenderString(rctx, bytes); err != nil {
|
||||||
log.Error("failed to RenderString: %v", err)
|
log.Error("failed to RenderString: %v", err)
|
||||||
|
|||||||
@@ -132,14 +132,22 @@ func CreateScheduleTask(ctx context.Context, spec *actions_model.ActionScheduleS
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withScheduleInEventPayload(eventPayload, schedule string) string {
|
func withScheduleInEventPayload(eventPayload, schedule string) string {
|
||||||
if schedule == "" || eventPayload == "" {
|
if schedule == "" {
|
||||||
return eventPayload
|
return eventPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
event := map[string]any{}
|
// eventPayload originates from json.Marshal(input.Payload) in handleSchedules,
|
||||||
if err := json.Unmarshal([]byte(eventPayload), &event); err != nil {
|
// so a nil payload is stored as the literal "null" and pre-existing rows may be
|
||||||
log.Error("withScheduleInEventPayload: unmarshal: %v", err)
|
// empty. Both cases start from a fresh map so the schedule field can still be set.
|
||||||
return eventPayload
|
var event map[string]any
|
||||||
|
if eventPayload != "" {
|
||||||
|
if err := json.Unmarshal([]byte(eventPayload), &event); err != nil {
|
||||||
|
log.Error("withScheduleInEventPayload: unmarshal: %v", err)
|
||||||
|
return eventPayload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event == nil {
|
||||||
|
event = map[string]any{}
|
||||||
}
|
}
|
||||||
|
|
||||||
event["schedule"] = schedule
|
event["schedule"] = schedule
|
||||||
|
|||||||
@@ -22,9 +22,20 @@ func TestWithScheduleInEventPayload(t *testing.T) {
|
|||||||
assert.Equal(t, "refs/heads/main", event["ref"])
|
assert.Equal(t, "refs/heads/main", event["ref"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("keeps empty payload", func(t *testing.T) {
|
t.Run("adds schedule to null payload", func(t *testing.T) {
|
||||||
|
updated := withScheduleInEventPayload("null", "37 12 5 1 2")
|
||||||
|
|
||||||
|
event := map[string]any{}
|
||||||
|
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
|
||||||
|
assert.Equal(t, "37 12 5 1 2", event["schedule"])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("adds schedule to empty payload", func(t *testing.T) {
|
||||||
updated := withScheduleInEventPayload("", "37 12 5 1 2")
|
updated := withScheduleInEventPayload("", "37 12 5 1 2")
|
||||||
assert.Empty(t, updated)
|
|
||||||
|
event := map[string]any{}
|
||||||
|
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
|
||||||
|
assert.Equal(t, "37 12 5 1 2", event["schedule"])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("keeps payload when schedule empty", func(t *testing.T) {
|
t.Run("keeps payload when schedule empty", func(t *testing.T) {
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
@@ -91,31 +89,14 @@ func (c TemplateContext) AppFullLink(link ...string) template.URL {
|
|||||||
return template.URL(s + "/" + strings.TrimPrefix(link[0], "/"))
|
return template.URL(s + "/" + strings.TrimPrefix(link[0], "/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue(func() (ret struct {
|
|
||||||
scriptImportRemainingPart string
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
// add onerror handler to alert users when the script fails to load:
|
|
||||||
// * for end users: there were many users reporting that "UI doesn't work", actually they made mistakes in their config
|
|
||||||
// * for developers: help them to remember to run "make watch-frontend" to build frontend assets
|
|
||||||
// the message will be directly put in the onerror JS code's string
|
|
||||||
onScriptErrorPrompt := `Please make sure the asset files can be accessed.`
|
|
||||||
if !setting.IsProd {
|
|
||||||
onScriptErrorPrompt += `\n\nFor development, run: make watch-frontend.`
|
|
||||||
}
|
|
||||||
onScriptErrorJS := fmt.Sprintf(`alert('Failed to load asset file from ' + this.src + '. %s')`, onScriptErrorPrompt)
|
|
||||||
ret.scriptImportRemainingPart = `onerror="` + html.EscapeString(onScriptErrorJS) + `"></script>`
|
|
||||||
return ret
|
|
||||||
})
|
|
||||||
|
|
||||||
func (c TemplateContext) ScriptImport(path string, typ ...string) template.HTML {
|
func (c TemplateContext) ScriptImport(path string, typ ...string) template.HTML {
|
||||||
if len(typ) > 0 {
|
if len(typ) > 0 {
|
||||||
if typ[0] == "module" {
|
if typ[0] == "module" {
|
||||||
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" type="module" src="` + html.EscapeString(public.AssetURI(path)) + `" ` + globalVars().scriptImportRemainingPart)
|
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" type="module" src="` + html.EscapeString(public.AssetURI(path)) + `"></script>`)
|
||||||
}
|
}
|
||||||
panic("unsupported script type: " + typ[0])
|
panic("unsupported script type: " + typ[0])
|
||||||
}
|
}
|
||||||
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" src="` + html.EscapeString(public.AssetURI(path)) + `" ` + globalVars().scriptImportRemainingPart)
|
return template.HTML(`<script nonce="` + c.CspScriptNonce() + `" src="` + html.EscapeString(public.AssetURI(path)) + `"></script>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c TemplateContext) CspScriptNonce() (ret string) {
|
func (c TemplateContext) CspScriptNonce() (ret string) {
|
||||||
|
|||||||
@@ -11,5 +11,8 @@
|
|||||||
{{template "base/footer_content" .}}
|
{{template "base/footer_content" .}}
|
||||||
{{ctx.ScriptImport "js/index.js" "module"}}
|
{{ctx.ScriptImport "js/index.js" "module"}}
|
||||||
{{template "custom/footer" .}}
|
{{template "custom/footer" .}}
|
||||||
|
<script nonce="{{ctx.CspScriptNonce}}" type="module">
|
||||||
|
if (!window.config?.frontendInited) alert("Frontend is not initialized, check console errors or asset files.")
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
+21
-15
@@ -1,13 +1,13 @@
|
|||||||
<div class="ui container tw-flex">
|
<div class="ui container tw-flex tw-gap-4">
|
||||||
{{ctx.AvatarUtils.Avatar .Org 100 "org-avatar"}}
|
<div>{{ctx.AvatarUtils.Avatar .Org 100}}</div>
|
||||||
<div id="org-info" class="tw-flex tw-flex-col tw-flex-1 tw-break-anywhere">
|
<div class="flex-relaxed-list">
|
||||||
<div class="ui header">
|
<div class="ui header flex-left-right tw-m-0">
|
||||||
{{.Org.DisplayName}}
|
<div class="flex-text-block">
|
||||||
<span class="org-visibility">
|
<span class="tw-text-2xl">{{.Org.DisplayName}}</span>
|
||||||
{{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}}
|
{{if .Org.Visibility.IsLimited}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}</span>{{end}}
|
||||||
{{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}}
|
{{if .Org.Visibility.IsPrivate}}<span class="ui large basic horizontal label">{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}</span>{{end}}
|
||||||
</span>
|
</div>
|
||||||
<span class="flex-text-block tw-ml-auto tw-text-16 tw-whitespace-nowrap">
|
<div class="flex-text-block">
|
||||||
{{if .EnableFeed}}
|
{{if .EnableFeed}}
|
||||||
<a class="ui basic label button" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
|
<a class="ui basic label button" href="{{.Org.HomeLink}}.rss" data-tooltip-content="{{ctx.Locale.Tr "rss_feed"}}">
|
||||||
{{svg "octicon-rss" 24}}
|
{{svg "octicon-rss" 24}}
|
||||||
@@ -16,14 +16,20 @@
|
|||||||
{{if .IsSigned}}
|
{{if .IsSigned}}
|
||||||
{{template "org/follow_unfollow" .}}
|
{{template "org/follow_unfollow" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .RenderedDescription}}<div class="render-content markup">{{.RenderedDescription}}</div>{{end}}
|
{{if .RenderedDescription}}
|
||||||
<div class="tw-text-text-light meta tw-mt-1">
|
<div class="render-content markup">{{.RenderedDescription}}</div>
|
||||||
{{if .Org.Location}}<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>{{end}}
|
{{end}}
|
||||||
{{if .Org.Website}}<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>{{end}}
|
<div>
|
||||||
{{if .IsSigned}}
|
{{if .Org.Location}}
|
||||||
{{if .Org.Email}}<div class="flex-text-block">{{svg "octicon-mail"}} <a class="muted" href="mailto:{{.Org.Email}}">{{.Org.Email}}</a></div>{{end}}
|
<div class="flex-text-block">{{svg "octicon-location"}} <span>{{.Org.Location}}</span></div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Org.Website}}
|
||||||
|
<div class="flex-text-block">{{svg "octicon-link"}} <a class="muted" target="_blank" rel="me" href="{{.Org.Website}}">{{.Org.Website}}</a></div>
|
||||||
|
{{end}}
|
||||||
|
{{if and .IsSigned .Org.Email}}
|
||||||
|
<div class="flex-text-block">{{svg "octicon-mail"}} <a class="muted" href="mailto:{{.Org.Email}}">{{.Org.Email}}</a></div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
-8
@@ -55,11 +55,12 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .NumMembers}}
|
{{if .NumMembers}}
|
||||||
<h4 class="ui top attached header tw-flex">
|
<h4 class="ui top attached header flex-left-right">
|
||||||
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.members"}}</strong>
|
<strong>{{ctx.Locale.Tr "org.members"}}</strong>
|
||||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/members"><span>{{.NumMembers}}</span> {{svg "octicon-chevron-right"}}</a>
|
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/members"><span>{{.NumMembers}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached segment members">
|
{{/* gap 8px below is specifically chosen to make sure a full line of avatars can exactly fit the segment width */}}
|
||||||
|
<div class="ui attached segment flex-text-block tw-flex-wrap tw-gap-[8px]">
|
||||||
{{range $memberUser := .OrgOverviewMembers}}
|
{{range $memberUser := .OrgOverviewMembers}}
|
||||||
{{if or $.IsOrganizationMember (call $.IsPublicMember $memberUser.ID)}}
|
{{if or $.IsOrganizationMember (call $.IsPublicMember $memberUser.ID)}}
|
||||||
{{template "shared/user/avatarlink" dict "user" $memberUser "size" 32 "tooltip" true}}
|
{{template "shared/user/avatarlink" dict "user" $memberUser "size" 32 "tooltip" true}}
|
||||||
@@ -68,20 +69,22 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .IsOrganizationMember}}
|
{{if .IsOrganizationMember}}
|
||||||
<div class="ui top attached header tw-flex">
|
<div class="ui top attached header flex-left-right">
|
||||||
<strong class="tw-flex-1">{{ctx.Locale.Tr "org.teams"}}</strong>
|
<strong>{{ctx.Locale.Tr "org.teams"}}</strong>
|
||||||
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right"}}</a>
|
<a class="tw-text-text-light flex-text-inline" href="{{.OrgLink}}/teams"><span>{{.Org.NumTeams}}</span> {{svg "octicon-chevron-right"}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui attached table segment teams">
|
<div class="ui attached segment">
|
||||||
|
<div class="flex-relaxed-list">
|
||||||
{{range .OrgOverviewTeams}}
|
{{range .OrgOverviewTeams}}
|
||||||
<div class="item">
|
<div>
|
||||||
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong class="team-name">{{.Name}}</strong></a>
|
<a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
|
||||||
<p class="tw-text-text-light">
|
<p class="tw-text-text-light">
|
||||||
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{ctx.Locale.Tr "org.lower_members"}}</a> ·
|
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{ctx.Locale.Tr "org.lower_members"}}</a> ·
|
||||||
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{ctx.Locale.Tr "org.lower_repositories"}}</a>
|
<a class="muted" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{ctx.Locale.Tr "org.lower_repositories"}}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsOrganizationOwner}}
|
{{if .IsOrganizationOwner}}
|
||||||
<div class="ui bottom attached segment">
|
<div class="ui bottom attached segment">
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
{{template "base/head" .}}
|
{{template "base/head" .}}
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content organization invite">
|
<div role="main" aria-label="{{.Title}}" class="page-content">
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
<div class="ui centered card">
|
<div class="tw-flex tw-justify-center">
|
||||||
<div class="image">
|
<div class="flex-relaxed-list">
|
||||||
{{ctx.AvatarUtils.Avatar .Organization 140}}
|
<div class="tw-flex tw-justify-center">{{ctx.AvatarUtils.Avatar .Organization 140}}</div>
|
||||||
</div>
|
<div>{{ctx.Locale.Tr "org.teams.invite.title" .Team.Name .Organization.Name}}</div>
|
||||||
<div class="content">
|
<div>{{ctx.Locale.Tr "org.teams.invite.by" .Inviter.Name}}</div>
|
||||||
<div class="header">{{ctx.Locale.Tr "org.teams.invite.title" .Team.Name .Organization.Name}}</div>
|
<div>{{ctx.Locale.Tr "org.teams.invite.description"}}</div>
|
||||||
<div class="meta">{{ctx.Locale.Tr "org.teams.invite.by" .Inviter.Name}}</div>
|
<form class="ui form tw-mt-4" action="" method="post">
|
||||||
<div class="description">{{ctx.Locale.Tr "org.teams.invite.description"}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="extra content">
|
|
||||||
<form class="ui form" action="" method="post">
|
|
||||||
<button class="fluid ui primary button">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
<button class="fluid ui primary button">{{ctx.Locale.Tr "org.teams.join"}}</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,21 +15,21 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</h4>
|
</h4>
|
||||||
<div class="ui attached table segment detail">
|
|
||||||
<div class="item">
|
<div class="ui attached segment">
|
||||||
{{if .Team.Description}}
|
{{if .Team.Description}}
|
||||||
{{.Team.Description}}
|
{{.Team.Description}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="tw-text-text-light tw-italic">{{ctx.Locale.Tr "org.teams.no_desc"}}</span>
|
<span class="tw-text-text-light tw-italic">{{ctx.Locale.Tr "org.teams.no_desc"}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{if eq .Team.LowerName "owners"}}
|
|
||||||
<div class="item">
|
<div class="ui attached segment">
|
||||||
|
{{/* TODO: old indent is kept to make diff changes minimal, can be reformatted in the future */}}
|
||||||
|
{{if eq .Team.LowerName "owners"}}
|
||||||
<p>{{ctx.Locale.Tr "org.teams.owners_permission_desc"}}</p>
|
<p>{{ctx.Locale.Tr "org.teams.owners_permission_desc"}}</p>
|
||||||
<p>{{ctx.Locale.Tr "org.teams.owners_permission_suggestion"}}</p>
|
<p>{{ctx.Locale.Tr "org.teams.owners_permission_suggestion"}}</p>
|
||||||
</div>
|
{{else}}
|
||||||
{{else}}
|
|
||||||
<div class="item">
|
|
||||||
<h3>{{ctx.Locale.Tr "org.team_access_desc"}}</h3>
|
<h3>{{ctx.Locale.Tr "org.team_access_desc"}}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{{if .Team.IncludesAllRepositories}}
|
{{if .Team.IncludesAllRepositories}}
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
{{end}}
|
||||||
{{end}}
|
</div>
|
||||||
</div>
|
|
||||||
{{if .IsOrganizationOwner}}
|
{{if .IsOrganizationOwner}}
|
||||||
<div class="ui bottom attached segment">
|
<div class="ui bottom attached segment">
|
||||||
<a class="ui small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{ctx.Locale.Tr "org.teams.settings"}}</a>
|
<a class="ui small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{ctx.Locale.Tr "org.teams.settings"}}</a>
|
||||||
|
|||||||
@@ -57,59 +57,12 @@
|
|||||||
{{svg "octicon-rss" 16}}
|
{{svg "octicon-rss" 16}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/watch_unwatch" $}}
|
{{template "repo/header/watch" $}}
|
||||||
{{if not $.DisableStars}}
|
{{if not $.DisableStars}}
|
||||||
{{template "repo/star_unstar" $}}
|
{{template "repo/header/star" $}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and (not .IsEmpty) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
|
{{if and (not .IsEmpty) ($.Permission.CanRead ctx.Consts.RepoUnitTypeCode)}}
|
||||||
<div class="ui labeled button
|
{{template "repo/header/fork" $}}
|
||||||
{{if or (not $.IsSigned) (and (not $.CanSignedUserFork) (not $.UserAndOrgForks))}}
|
|
||||||
disabled
|
|
||||||
{{end}}"
|
|
||||||
{{if not $.IsSigned}}
|
|
||||||
data-tooltip-content="{{ctx.Locale.Tr "repo.fork_guest_user"}}"
|
|
||||||
{{else if and (not $.CanSignedUserFork) (not $.UserAndOrgForks)}}
|
|
||||||
data-tooltip-content="{{ctx.Locale.Tr "repo.fork_from_self"}}"
|
|
||||||
{{end}}
|
|
||||||
>
|
|
||||||
<a class="ui compact{{if $.ShowForkModal}} show-modal{{end}} small basic button"
|
|
||||||
{{if not $.CanSignedUserFork}}
|
|
||||||
{{if gt (len $.UserAndOrgForks) 1}}
|
|
||||||
href="#" data-modal="#fork-repo-modal"
|
|
||||||
{{else if eq (len $.UserAndOrgForks) 1}}
|
|
||||||
href="{{AppSubUrl}}/{{(index $.UserAndOrgForks 0).FullName}}"
|
|
||||||
{{/*else is not required here, because the button shouldn't link to any site if you can't create a fork*/}}
|
|
||||||
{{end}}
|
|
||||||
{{else if not $.UserAndOrgForks}}
|
|
||||||
href="{{$.RepoLink}}/fork"
|
|
||||||
{{else}}
|
|
||||||
href="#" data-modal="#fork-repo-modal"
|
|
||||||
{{end}}
|
|
||||||
>
|
|
||||||
{{svg "octicon-repo-forked"}}<span class="text not-mobile">{{ctx.Locale.Tr "repo.fork"}}</span>
|
|
||||||
</a>
|
|
||||||
<a class="ui basic label" href="{{.Link}}/forks">
|
|
||||||
{{CountFmt .NumForks}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="ui small modal" id="fork-repo-modal">
|
|
||||||
<div class="header">
|
|
||||||
{{ctx.Locale.Tr "repo.already_forked" .Name}}
|
|
||||||
</div>
|
|
||||||
<div class="content tw-text-left">
|
|
||||||
<div class="ui list">
|
|
||||||
{{range $.UserAndOrgForks}}
|
|
||||||
<div class="ui item tw-py-2">
|
|
||||||
<a href="{{.Link}}">{{svg "octicon-repo-forked" 16 "tw-mr-2"}}{{.FullName}}</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{if $.CanSignedUserFork}}
|
|
||||||
<div class="divider"></div>
|
|
||||||
<a href="{{$.RepoLink}}/fork">{{ctx.Locale.Tr "repo.fork_to_different_account"}}</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{{$canNotForkOwn := and $.IsSigned (not $.CanSignedUserFork) (not $.UserAndOrgForks)}}
|
||||||
|
<div class="ui labeled button"
|
||||||
|
{{if not $.IsSigned}}
|
||||||
|
data-tooltip-content="{{ctx.Locale.Tr "repo.fork_guest_user"}}"
|
||||||
|
{{else if $canNotForkOwn}}
|
||||||
|
data-tooltip-content="{{ctx.Locale.Tr "repo.fork_from_self"}}"
|
||||||
|
{{end}}
|
||||||
|
>
|
||||||
|
<a role="button" class="ui compact small basic button {{if $.ShowForkModal}}show-modal{{end}}"
|
||||||
|
{{if not $.IsSigned}}
|
||||||
|
href="{{AppSubUrl}}/user/login"
|
||||||
|
{{else if $.ShowForkModal}}{{/* see backend comment for this logic */}}
|
||||||
|
href="#" data-modal="#fork-repo-modal"
|
||||||
|
{{else if eq (len $.UserAndOrgForks) 1}}
|
||||||
|
href="{{AppSubUrl}}/{{(index $.UserAndOrgForks 0).FullName}}"
|
||||||
|
{{else if $canNotForkOwn}}
|
||||||
|
href="#"
|
||||||
|
{{else}}
|
||||||
|
href="{{$.RepoLink}}/fork"
|
||||||
|
{{end}}
|
||||||
|
>
|
||||||
|
{{svg "octicon-repo-forked"}}<span class="text not-mobile">{{ctx.Locale.Tr "repo.fork"}}</span>
|
||||||
|
</a>
|
||||||
|
<a class="ui basic label" href="{{$.Repository.Link}}/forks">
|
||||||
|
{{CountFmt $.Repository.NumForks}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{if $.ShowForkModal}}
|
||||||
|
<div class="ui small modal" id="fork-repo-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{ctx.Locale.Tr "repo.already_forked" $.Repository.Name}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="ui relaxed list">
|
||||||
|
{{range $.UserAndOrgForks}}
|
||||||
|
<div class="item">
|
||||||
|
<a class="flex-text-block" href="{{.Link}}">{{svg "octicon-repo-forked"}}{{.FullName}}</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{if $.CanSignedUserFork}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<a href="{{$.RepoLink}}/fork">{{ctx.Locale.Tr "repo.fork_to_different_account"}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
|
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
|
||||||
{{$buttonText := ctx.Locale.Tr "repo.star"}}
|
{{$buttonText := ctx.Locale.Tr "repo.star"}}
|
||||||
{{if $.IsStaringRepo}}{{$buttonText = ctx.Locale.Tr "repo.unstar"}}{{end}}
|
{{if $.IsStaringRepo}}{{$buttonText = ctx.Locale.Tr "repo.unstar"}}{{end}}
|
||||||
<button type="button" class="ui compact small basic button" aria-label="{{$buttonText}}"
|
<a role="button" class="ui compact small basic button" aria-label="{{$buttonText}}"
|
||||||
{{if $.IsSigned}}
|
{{if $.IsSigned}}
|
||||||
data-fetch-method="post"
|
data-fetch-method="post"
|
||||||
data-fetch-url="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}unstar{{else}}star{{end}}"
|
data-fetch-url="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}unstar{{else}}star{{end}}"
|
||||||
data-fetch-sync="$closest(.ui.labeled.button)"
|
data-fetch-sync="$closest(.ui.labeled.button)"
|
||||||
{{else}}
|
{{else}}
|
||||||
disabled
|
href="{{AppSubUrl}}/user/login"
|
||||||
{{end}}
|
{{end}}
|
||||||
>
|
>
|
||||||
{{svg (Iif $.IsStaringRepo "octicon-star-fill" "octicon-star")}}
|
{{svg (Iif $.IsStaringRepo "octicon-star-fill" "octicon-star")}}
|
||||||
<span class="not-mobile" aria-hidden="true">{{$buttonText}}</span>
|
<span class="not-mobile" aria-hidden="true">{{$buttonText}}</span>
|
||||||
</button>
|
</a>
|
||||||
<a class="ui basic label" href="{{$.RepoLink}}/stars">
|
<a class="ui basic label" href="{{$.RepoLink}}/stars">
|
||||||
{{CountFmt .Repository.NumStars}}
|
{{CountFmt .Repository.NumStars}}
|
||||||
</a>
|
</a>
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
|
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
|
||||||
{{$buttonText := ctx.Locale.Tr "repo.watch"}}
|
{{$buttonText := ctx.Locale.Tr "repo.watch"}}
|
||||||
{{if $.IsWatchingRepo}}{{$buttonText = ctx.Locale.Tr "repo.unwatch"}}{{end}}
|
{{if $.IsWatchingRepo}}{{$buttonText = ctx.Locale.Tr "repo.unwatch"}}{{end}}
|
||||||
<button type="button" class="ui compact small basic button" aria-label="{{$buttonText}}"
|
<a role="button" class="ui compact small basic button" aria-label="{{$buttonText}}"
|
||||||
{{if $.IsSigned}}
|
{{if $.IsSigned}}
|
||||||
data-fetch-method="post"
|
data-fetch-method="post"
|
||||||
data-fetch-url="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}unwatch{{else}}watch{{end}}"
|
data-fetch-url="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}unwatch{{else}}watch{{end}}"
|
||||||
data-fetch-sync="$closest(.ui.labeled.button)"
|
data-fetch-sync="$closest(.ui.labeled.button)"
|
||||||
{{else}}
|
{{else}}
|
||||||
disabled
|
href="{{AppSubUrl}}/user/login"
|
||||||
{{end}}
|
{{end}}
|
||||||
>
|
>
|
||||||
{{svg "octicon-eye"}}
|
{{svg "octicon-eye"}}
|
||||||
<span class="not-mobile" aria-hidden="true">{{$buttonText}}</span>
|
<span class="not-mobile" aria-hidden="true">{{$buttonText}}</span>
|
||||||
</button>
|
</a>
|
||||||
<a class="ui basic label" href="{{.RepoLink}}/watchers">
|
<a class="ui basic label" href="{{$.RepoLink}}/watchers">
|
||||||
{{CountFmt .Repository.NumWatches}}
|
{{CountFmt .Repository.NumWatches}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,100 +208,11 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .AllowMerge}} {{/* user is allowed to merge */}}
|
{{if .AllowMerge}} {{/* user is allowed to merge */}}
|
||||||
{{$prUnit := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypePullRequests}}
|
{{if $data.MergeFormProps}}
|
||||||
{{if or $prUnit.PullRequestsConfig.AllowMerge $prUnit.PullRequestsConfig.AllowRebase $prUnit.PullRequestsConfig.AllowRebaseMerge $prUnit.PullRequestsConfig.AllowSquash $prUnit.PullRequestsConfig.AllowFastForwardOnly}}
|
|
||||||
{{$hasPendingPullRequestMergeTip := ""}}
|
|
||||||
{{if .HasPendingPullRequestMerge}}
|
|
||||||
{{$createdPRMergeStr := DateUtils.TimeSince .PendingPullRequestMerge.CreatedUnix}}
|
|
||||||
{{$hasPendingPullRequestMergeTip = ctx.Locale.Tr "repo.pulls.auto_merge_has_pending_schedule" .PendingPullRequestMerge.Doer.Name $createdPRMergeStr}}
|
|
||||||
{{end}}
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<script nonce="{{ctx.CspScriptNonce}}" type="module">
|
|
||||||
(() => {
|
|
||||||
const defaultMergeTitle = {{.DefaultMergeMessage}};
|
|
||||||
const defaultSquashMergeTitle = {{.DefaultSquashMergeMessage}};
|
|
||||||
const defaultMergeMessage = {{.DefaultMergeBody}};
|
|
||||||
const defaultSquashMergeMessage = {{.DefaultSquashMergeBody}};
|
|
||||||
const mergeForm = {
|
|
||||||
'baseLink': {{.Issue.Link}},
|
|
||||||
'textCancel': {{ctx.Locale.Tr "cancel"}},
|
|
||||||
'textDeleteBranch': {{ctx.Locale.Tr "repo.branch.delete" .HeadTarget}},
|
|
||||||
'textAutoMergeButtonWhenSucceed': {{ctx.Locale.Tr "repo.pulls.auto_merge_button_when_succeed"}},
|
|
||||||
'textAutoMergeWhenSucceed': {{ctx.Locale.Tr "repo.pulls.auto_merge_when_succeed"}},
|
|
||||||
'textAutoMergeCancelSchedule': {{ctx.Locale.Tr "repo.pulls.auto_merge_cancel_schedule"}},
|
|
||||||
'textClearMergeMessage': {{ctx.Locale.Tr "repo.pulls.clear_merge_message"}},
|
|
||||||
'textClearMergeMessageHint': {{ctx.Locale.Tr "repo.pulls.clear_merge_message_hint"}},
|
|
||||||
'textMergeCommitId': {{ctx.Locale.Tr "repo.pulls.merge_commit_id"}},
|
|
||||||
|
|
||||||
'canMergeNow': {{$canMergeNow}},
|
|
||||||
'allOverridableChecksOk': {{not $notAllOverridableChecksOk}},
|
|
||||||
'emptyCommit': {{.Issue.PullRequest.IsEmpty}},
|
|
||||||
'pullHeadCommitID': {{.PullHeadCommitID}},
|
|
||||||
'isPullBranchDeletable': {{.IsPullBranchDeletable}},
|
|
||||||
'defaultMergeStyle': {{.MergeStyle}},
|
|
||||||
'defaultDeleteBranchAfterMerge': {{$prUnit.PullRequestsConfig.DefaultDeleteBranchAfterMerge}},
|
|
||||||
'mergeMessageFieldPlaceHolder': {{ctx.Locale.Tr "repo.editor.commit_message_desc"}},
|
|
||||||
'defaultMergeMessage': defaultMergeMessage,
|
|
||||||
|
|
||||||
'hasPendingPullRequestMerge': {{.HasPendingPullRequestMerge}},
|
|
||||||
'hasPendingPullRequestMergeTip': {{$hasPendingPullRequestMergeTip}},
|
|
||||||
};
|
|
||||||
|
|
||||||
const generalHideAutoMerge = mergeForm.canMergeNow && mergeForm.allOverridableChecksOk; // if this pr can be merged now, then hide the auto merge
|
|
||||||
mergeForm['mergeStyles'] = [
|
|
||||||
{
|
|
||||||
'name': 'merge',
|
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowMerge}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.merge_pull_request"}},
|
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
|
||||||
'hideAutoMerge': generalHideAutoMerge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'rebase',
|
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowRebase}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.rebase_merge_pull_request"}},
|
|
||||||
'hideMergeMessageTexts': true,
|
|
||||||
'hideAutoMerge': generalHideAutoMerge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'rebase-merge',
|
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowRebaseMerge}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.rebase_merge_commit_pull_request"}},
|
|
||||||
'mergeTitleFieldText': defaultMergeTitle,
|
|
||||||
'mergeMessageFieldText': defaultMergeMessage,
|
|
||||||
'hideAutoMerge': generalHideAutoMerge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'squash',
|
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowSquash}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.squash_merge_pull_request"}},
|
|
||||||
'mergeTitleFieldText': defaultSquashMergeTitle,
|
|
||||||
'mergeMessageFieldText': {{.GetCommitMessages}} + defaultSquashMergeMessage,
|
|
||||||
'hideAutoMerge': generalHideAutoMerge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'fast-forward-only',
|
|
||||||
'allowed': {{and $prUnit.PullRequestsConfig.AllowFastForwardOnly (eq .Issue.PullRequest.CommitsBehind 0)}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.fast_forward_only_merge_pull_request"}},
|
|
||||||
'hideMergeMessageTexts': true,
|
|
||||||
'hideAutoMerge': generalHideAutoMerge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': 'manually-merged',
|
|
||||||
'allowed': {{$prUnit.PullRequestsConfig.AllowManualMerge}},
|
|
||||||
'textDoMerge': {{ctx.Locale.Tr "repo.pulls.merge_manually"}},
|
|
||||||
'hideMergeMessageTexts': true,
|
|
||||||
'hideAutoMerge': true,
|
|
||||||
}
|
|
||||||
];
|
|
||||||
window.config.pageData.pullRequestMergeForm = mergeForm;
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{$showGeneralMergeForm = true}}
|
{{$showGeneralMergeForm = true}}
|
||||||
{{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}}
|
{{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}}
|
||||||
<div id="pull-request-merge-form" class="tw-min-h-[40px]"></div>
|
<div id="pull-request-merge-form" class="tw-min-h-[40px]" data-merge-form-props="{{JsonUtils.EncodeToString $data.MergeFormProps}}"></div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
|
{{/* no merge style was set in repo setting: not or ($prUnit.PullRequestsConfig.AllowMerge ...) */}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
@@ -396,8 +307,8 @@
|
|||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if and .Issue.PullRequest.HeadRepo (not .Issue.PullRequest.HasMerged) (not .Issue.IsClosed)}}
|
{{if $data.ShowPullCommands}}
|
||||||
{{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "ShowMergeInstructions" .ShowMergeInstructions}}
|
{{template "repo/issue/view_content/pull_merge_instruction" dict "PullRequest" .Issue.PullRequest "MergeBoxData" $data}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,57 +1,59 @@
|
|||||||
|
{{$data := $.MergeBoxData}}
|
||||||
|
{{$pull := $.PullRequest}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<details>
|
<details>
|
||||||
<summary>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_hint"}}</summary>
|
<summary>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_hint"}}</summary>
|
||||||
<div class="tw-mt-2">
|
<div class="tw-mt-2">
|
||||||
<div><h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_title"}}</h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_desc"}}</div>
|
<div><h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_title"}}</h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_checkout_desc"}}</div>
|
||||||
{{$localBranch := .PullRequest.HeadBranch}}
|
{{$localBranch := $pull.HeadBranch}}
|
||||||
{{if ne .PullRequest.HeadRepo.ID .PullRequest.BaseRepo.ID}}
|
{{if ne $pull.HeadRepo.ID $pull.BaseRepo.ID}}
|
||||||
{{$localBranch = print .PullRequest.HeadRepo.OwnerName "-" .PullRequest.HeadBranch}}
|
{{$localBranch = print $pull.HeadRepo.OwnerName "-" $pull.HeadBranch}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="ui secondary segment tw-font-mono">
|
<div class="ui secondary segment tw-font-mono">
|
||||||
{{$gitRemoteName := ctx.RootData.SystemConfig.Repository.GitGuideRemoteName.Value ctx}}
|
{{$gitRemoteName := ctx.RootData.SystemConfig.Repository.GitGuideRemoteName.Value ctx}}
|
||||||
{{if eq .PullRequest.Flow 0}}
|
{{if eq $pull.Flow 0}}
|
||||||
<div>git fetch -u {{if ne .PullRequest.HeadRepo.ID .PullRequest.BaseRepo.ID}}{{ctx.AppFullLink .PullRequest.HeadRepo.Link}}{{else}}{{$gitRemoteName}}{{end}} {{.PullRequest.HeadBranch}}:{{$localBranch}}</div>
|
<div>git fetch -u {{if ne $pull.HeadRepo.ID $pull.BaseRepo.ID}}{{ctx.AppFullLink $pull.HeadRepo.Link}}{{else}}{{$gitRemoteName}}{{end}} {{$pull.HeadBranch}}:{{$localBranch}}</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div>git fetch -u {{$gitRemoteName}} {{.PullRequest.GetGitHeadRefName}}:{{$localBranch}}</div>
|
<div>git fetch -u {{$gitRemoteName}} {{$pull.GetGitHeadRefName}}:{{$localBranch}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div>git checkout {{$localBranch}}</div>
|
<div>git checkout {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .ShowMergeInstructions}}
|
{{if $data.ShowMergeInstructions}}
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_title"}}</h3>
|
<h3>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_title"}}</h3>
|
||||||
{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_desc"}}
|
{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_desc"}}
|
||||||
{{if not .AutodetectManualMerge}}
|
{{if not $data.AutodetectManualMerge}}
|
||||||
<div>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_warning"}}</div>
|
<div>{{ctx.Locale.Tr "repo.pulls.cmd_instruction_merge_warning"}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui secondary segment tw-font-mono">
|
<div class="ui secondary segment tw-font-mono">
|
||||||
<div data-pull-merge-style="merge">
|
<div data-pull-merge-style="merge">
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge --no-ff {{$localBranch}}</div>
|
<div>git merge --no-ff {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-hidden" data-pull-merge-style="rebase">
|
<div class="tw-hidden" data-pull-merge-style="rebase">
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge --ff-only {{$localBranch}}</div>
|
<div>git merge --ff-only {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-hidden" data-pull-merge-style="rebase-merge">
|
<div class="tw-hidden" data-pull-merge-style="rebase-merge">
|
||||||
<div>git checkout {{$localBranch}}</div>
|
<div>git checkout {{$localBranch}}</div>
|
||||||
<div>git rebase {{.PullRequest.BaseBranch}}</div>
|
<div>git rebase {{$pull.BaseBranch}}</div>
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge --no-ff {{$localBranch}}</div>
|
<div>git merge --no-ff {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-hidden" data-pull-merge-style="squash">
|
<div class="tw-hidden" data-pull-merge-style="squash">
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge --squash {{$localBranch}}</div>
|
<div>git merge --squash {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-hidden" data-pull-merge-style="fast-forward-only">
|
<div class="tw-hidden" data-pull-merge-style="fast-forward-only">
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge --ff-only {{$localBranch}}</div>
|
<div>git merge --ff-only {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-hidden" data-pull-merge-style="manually-merged">
|
<div class="tw-hidden" data-pull-merge-style="manually-merged">
|
||||||
<div>git checkout {{.PullRequest.BaseBranch}}</div>
|
<div>git checkout {{$pull.BaseBranch}}</div>
|
||||||
<div>git merge {{$localBranch}}</div>
|
<div>git merge {{$localBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>git push {{$gitRemoteName}} {{.PullRequest.BaseBranch}}</div>
|
<div>git push {{$gitRemoteName}} {{$pull.BaseBranch}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -37,7 +37,6 @@
|
|||||||
|
|
||||||
@import "./shared/flex-list.css";
|
@import "./shared/flex-list.css";
|
||||||
@import "./shared/milestone.css";
|
@import "./shared/milestone.css";
|
||||||
@import "./shared/repoorg.css";
|
|
||||||
@import "./shared/settings.css";
|
@import "./shared/settings.css";
|
||||||
|
|
||||||
@import "./features/dropzone.css";
|
@import "./features/dropzone.css";
|
||||||
|
|||||||
@@ -1,23 +1,3 @@
|
|||||||
.organization .head .ui.header .ui.right {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content.organization .org-avatar {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content.organization #org-info .ui.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 36px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content.organization #org-info .desc {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content.organization .team-item-box > .team-item-header {
|
.page-content.organization .team-item-box > .team-item-header {
|
||||||
min-height: 50px; /* the header sometimes contains a mini button, sometimes not, so we set a min-height to make sure the layout is consistent */
|
min-height: 50px; /* the header sometimes contains a mini button, sometimes not, so we set a min-height to make sure the layout is consistent */
|
||||||
}
|
}
|
||||||
@@ -30,20 +10,3 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: var(--color-text-light-3);
|
color: var(--color-text-light-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.organization.invite .ui.avatar {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.organization.teams .detail .item {
|
|
||||||
padding: 10px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.organization.teams .detail .item:not(:last-child) {
|
|
||||||
border-bottom: 1px solid var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-team-navbar .active.item {
|
|
||||||
background: var(--color-box-body) !important;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
.repository .head .ui.header .text,
|
|
||||||
.organization .head .ui.header .text {
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository .ui.tabs.container,
|
|
||||||
.organization .ui.tabs.container {
|
|
||||||
margin-top: 14px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repository .head .ui.header .org-visibility .label,
|
|
||||||
.organization .head .ui.header .org-visibility .label {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,11 @@ import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue';
|
|||||||
import {SvgIcon} from '../svg.ts';
|
import {SvgIcon} from '../svg.ts';
|
||||||
import {toggleElem} from '../utils/dom.ts';
|
import {toggleElem} from '../utils/dom.ts';
|
||||||
|
|
||||||
const {pageData} = window.config;
|
const props = defineProps<{
|
||||||
|
mergeFormProps: any, // TODO: this is a huge object, need to be refactored in the future
|
||||||
|
}>();
|
||||||
|
|
||||||
const mergeForm = pageData.pullRequestMergeForm!;
|
const mergeForm = props.mergeFormProps;
|
||||||
|
|
||||||
const mergeTitleFieldValue = shallowRef('');
|
const mergeTitleFieldValue = shallowRef('');
|
||||||
const mergeMessageFieldValue = shallowRef('');
|
const mergeMessageFieldValue = shallowRef('');
|
||||||
|
|||||||
@@ -63,27 +63,10 @@ async function initRepoPullRequestMergeForm(box: HTMLElement) {
|
|||||||
const el = box.querySelector('#pull-request-merge-form');
|
const el = box.querySelector('#pull-request-merge-form');
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
|
const data = JSON.parse(el.getAttribute('data-merge-form-props')!);
|
||||||
const {default: PullRequestMergeForm} = await import('../components/PullRequestMergeForm.vue');
|
const {default: PullRequestMergeForm} = await import('../components/PullRequestMergeForm.vue');
|
||||||
const view = createApp(PullRequestMergeForm);
|
const view = createApp(PullRequestMergeForm, {mergeFormProps: data});
|
||||||
view.mount(el);
|
view.mount(el); // TODO: can unmount when reloaded?
|
||||||
}
|
|
||||||
|
|
||||||
function executeScripts(elem: Element) {
|
|
||||||
// find any existing nonce value from the current page and apply it to the new script
|
|
||||||
const scriptNonce = document.querySelector('script[nonce]')!.getAttribute('nonce')!;
|
|
||||||
for (const oldScript of elem.querySelectorAll('script')) {
|
|
||||||
// TODO: that's the only way to load the data for the merge form. In the future
|
|
||||||
// we need to completely decouple the page data and embedded script
|
|
||||||
// eslint-disable-next-line github/no-dynamic-script-tag
|
|
||||||
const newScript = document.createElement('script');
|
|
||||||
for (const attr of oldScript.attributes) {
|
|
||||||
if (attr.name === 'type' && attr.value === 'module') continue;
|
|
||||||
newScript.setAttribute(attr.name, attr.value);
|
|
||||||
}
|
|
||||||
newScript.setAttribute('nonce', scriptNonce);
|
|
||||||
newScript.text = oldScript.text;
|
|
||||||
document.body.append(newScript);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initRepoPullMergeBox(el: HTMLElement) {
|
export function initRepoPullMergeBox(el: HTMLElement) {
|
||||||
@@ -124,7 +107,6 @@ export function initRepoPullMergeBox(el: HTMLElement) {
|
|||||||
}
|
}
|
||||||
document.removeEventListener('visibilitychange', onVisibilityChange);
|
document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||||
const newElem = createElementFromHTML(await resp.text());
|
const newElem = createElementFromHTML(await resp.text());
|
||||||
executeScripts(newElem);
|
|
||||||
el.replaceWith(newElem);
|
el.replaceWith(newElem);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
@@ -53,6 +53,7 @@ interface Window {
|
|||||||
enableTimeTracking: boolean,
|
enableTimeTracking: boolean,
|
||||||
mermaidMaxSourceCharacters: number,
|
mermaidMaxSourceCharacters: number,
|
||||||
i18n: Record<string, string>,
|
i18n: Record<string, string>,
|
||||||
|
frontendInited: boolean,
|
||||||
},
|
},
|
||||||
$: JQueryStatic,
|
$: JQueryStatic,
|
||||||
jQuery: JQueryStatic,
|
jQuery: JQueryStatic,
|
||||||
|
|||||||
@@ -171,3 +171,5 @@ const initDur = performance.now() - initStartTime;
|
|||||||
if (initDur > 500) {
|
if (initDur > 500) {
|
||||||
console.error(`slow init functions took ${initDur.toFixed(3)}ms`);
|
console.error(`slow init functions took ${initDur.toFixed(3)}ms`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.config.frontendInited = true;
|
||||||
@@ -12,6 +12,7 @@ window.config = {
|
|||||||
enableTimeTracking: true,
|
enableTimeTracking: true,
|
||||||
mermaidMaxSourceCharacters: 5000,
|
mermaidMaxSourceCharacters: 5000,
|
||||||
i18n: {},
|
i18n: {},
|
||||||
|
frontendInited: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
window.testModules = {};
|
window.testModules = {};
|
||||||
|
|||||||
Reference in new issue
Block a user