mirror of
https://github.com/go-gitea/gitea
synced 2025-01-08 00:44:26 +00:00
Merge branch 'main' into lunny/issue_dev
This commit is contained in:
commit
66dbadcf68
@ -617,7 +617,7 @@ func (repo *Repository) CanEnableEditor() bool {
|
|||||||
|
|
||||||
// DescriptionHTML does special handles to description and return HTML string.
|
// DescriptionHTML does special handles to description and return HTML string.
|
||||||
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML {
|
||||||
desc, err := markup.RenderDescriptionHTML(markup.NewRenderContext(ctx), repo.Description)
|
desc, err := markup.PostProcessDescriptionHTML(markup.NewRenderContext(ctx), repo.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
log.Error("Failed to render description for %s (ID: %d): %v", repo.Name, repo.ID, err)
|
||||||
return template.HTML(markup.SanitizeDescription(repo.Description))
|
return template.HTML(markup.SanitizeDescription(repo.Description))
|
||||||
|
@ -159,9 +159,9 @@ func PostProcessDefault(ctx *RenderContext, input io.Reader, output io.Writer) e
|
|||||||
return postProcess(ctx, procs, input, output)
|
return postProcess(ctx, procs, input, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderCommitMessage will use the same logic as PostProcess, but will disable
|
// PostProcessCommitMessage will use the same logic as PostProcess, but will disable
|
||||||
// the shortLinkProcessor.
|
// the shortLinkProcessor.
|
||||||
func RenderCommitMessage(ctx *RenderContext, content string) (string, error) {
|
func PostProcessCommitMessage(ctx *RenderContext, content string) (string, error) {
|
||||||
procs := []processor{
|
procs := []processor{
|
||||||
fullIssuePatternProcessor,
|
fullIssuePatternProcessor,
|
||||||
comparePatternProcessor,
|
comparePatternProcessor,
|
||||||
@ -183,11 +183,11 @@ var emojiProcessors = []processor{
|
|||||||
emojiProcessor,
|
emojiProcessor,
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderCommitMessageSubject will use the same logic as PostProcess and
|
// PostProcessCommitMessageSubject will use the same logic as PostProcess and
|
||||||
// RenderCommitMessage, but will disable the shortLinkProcessor and
|
// PostProcessCommitMessage, but will disable the shortLinkProcessor and
|
||||||
// emailAddressProcessor, will add a defaultLinkProcessor if defaultLink is set,
|
// emailAddressProcessor, will add a defaultLinkProcessor if defaultLink is set,
|
||||||
// which changes every text node into a link to the passed default link.
|
// which changes every text node into a link to the passed default link.
|
||||||
func RenderCommitMessageSubject(ctx *RenderContext, defaultLink, content string) (string, error) {
|
func PostProcessCommitMessageSubject(ctx *RenderContext, defaultLink, content string) (string, error) {
|
||||||
procs := []processor{
|
procs := []processor{
|
||||||
fullIssuePatternProcessor,
|
fullIssuePatternProcessor,
|
||||||
comparePatternProcessor,
|
comparePatternProcessor,
|
||||||
@ -211,15 +211,33 @@ func RenderCommitMessageSubject(ctx *RenderContext, defaultLink, content string)
|
|||||||
return postProcessString(ctx, procs, content)
|
return postProcessString(ctx, procs, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderIssueTitle to process title on individual issue/pull page
|
// PostProcessIssueTitle to process title on individual issue/pull page
|
||||||
func RenderIssueTitle(ctx *RenderContext, title string) (string, error) {
|
func PostProcessIssueTitle(ctx *RenderContext, title string) (string, error) {
|
||||||
// do not render other issue/commit links in an issue's title - which in most cases is already a link.
|
|
||||||
return postProcessString(ctx, []processor{
|
return postProcessString(ctx, []processor{
|
||||||
|
issueIndexPatternProcessor,
|
||||||
|
commitCrossReferencePatternProcessor,
|
||||||
|
hashCurrentPatternProcessor,
|
||||||
emojiShortCodeProcessor,
|
emojiShortCodeProcessor,
|
||||||
emojiProcessor,
|
emojiProcessor,
|
||||||
}, title)
|
}, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PostProcessDescriptionHTML will use similar logic as PostProcess, but will
|
||||||
|
// use a single special linkProcessor.
|
||||||
|
func PostProcessDescriptionHTML(ctx *RenderContext, content string) (string, error) {
|
||||||
|
return postProcessString(ctx, []processor{
|
||||||
|
descriptionLinkProcessor,
|
||||||
|
emojiShortCodeProcessor,
|
||||||
|
emojiProcessor,
|
||||||
|
}, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostProcessEmoji for when we want to just process emoji and shortcodes
|
||||||
|
// in various places it isn't already run through the normal markdown processor
|
||||||
|
func PostProcessEmoji(ctx *RenderContext, content string) (string, error) {
|
||||||
|
return postProcessString(ctx, emojiProcessors, content)
|
||||||
|
}
|
||||||
|
|
||||||
func postProcessString(ctx *RenderContext, procs []processor, content string) (string, error) {
|
func postProcessString(ctx *RenderContext, procs []processor, content string) (string, error) {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
if err := postProcess(ctx, procs, strings.NewReader(content), &buf); err != nil {
|
if err := postProcess(ctx, procs, strings.NewReader(content), &buf); err != nil {
|
||||||
@ -228,23 +246,10 @@ func postProcessString(ctx *RenderContext, procs []processor, content string) (s
|
|||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderDescriptionHTML will use similar logic as PostProcess, but will
|
|
||||||
// use a single special linkProcessor.
|
|
||||||
func RenderDescriptionHTML(ctx *RenderContext, content string) (string, error) {
|
|
||||||
return postProcessString(ctx, []processor{
|
|
||||||
descriptionLinkProcessor,
|
|
||||||
emojiShortCodeProcessor,
|
|
||||||
emojiProcessor,
|
|
||||||
}, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenderEmoji for when we want to just process emoji and shortcodes
|
|
||||||
// in various places it isn't already run through the normal markdown processor
|
|
||||||
func RenderEmoji(ctx *RenderContext, content string) (string, error) {
|
|
||||||
return postProcessString(ctx, emojiProcessors, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
|
func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output io.Writer) error {
|
||||||
|
if !ctx.usedByRender && ctx.RenderHelper != nil {
|
||||||
|
defer ctx.RenderHelper.CleanUp()
|
||||||
|
}
|
||||||
// FIXME: don't read all content to memory
|
// FIXME: don't read all content to memory
|
||||||
rawHTML, err := io.ReadAll(input)
|
rawHTML, err := io.ReadAll(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -252,7 +252,7 @@ func TestRender_IssueIndexPattern_NoShortPattern(t *testing.T) {
|
|||||||
testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas))
|
testRenderIssueIndexPattern(t, "!1", "!1", NewTestRenderContext(metas))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_RenderIssueTitle(t *testing.T) {
|
func TestRender_PostProcessIssueTitle(t *testing.T) {
|
||||||
setting.AppURL = TestAppURL
|
setting.AppURL = TestAppURL
|
||||||
metas := map[string]string{
|
metas := map[string]string{
|
||||||
"format": "https://someurl.com/{user}/{repo}/{index}",
|
"format": "https://someurl.com/{user}/{repo}/{index}",
|
||||||
@ -260,7 +260,7 @@ func TestRender_RenderIssueTitle(t *testing.T) {
|
|||||||
"repo": "someRepo",
|
"repo": "someRepo",
|
||||||
"style": IssueNameStyleNumeric,
|
"style": IssueNameStyleNumeric,
|
||||||
}
|
}
|
||||||
actual, err := RenderIssueTitle(NewTestRenderContext(metas), "#1")
|
actual, err := PostProcessIssueTitle(NewTestRenderContext(metas), "#1")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "#1", actual)
|
assert.Equal(t, "#1", actual)
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,9 @@ type RenderOptions struct {
|
|||||||
type RenderContext struct {
|
type RenderContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|
||||||
|
// the context might be used by the "render" function, but it might also be used by "postProcess" function
|
||||||
|
usedByRender bool
|
||||||
|
|
||||||
SidebarTocNode ast.Node
|
SidebarTocNode ast.Node
|
||||||
|
|
||||||
RenderHelper RenderHelper
|
RenderHelper RenderHelper
|
||||||
@ -182,6 +185,7 @@ func pipes() (io.ReadCloser, io.WriteCloser, func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
|
func render(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
|
||||||
|
ctx.usedByRender = true
|
||||||
if ctx.RenderHelper != nil {
|
if ctx.RenderHelper != nil {
|
||||||
defer ctx.RenderHelper.CleanUp()
|
defer ctx.RenderHelper.CleanUp()
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,9 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string)
|
|||||||
cleanMsg := template.HTMLEscapeString(msg)
|
cleanMsg := template.HTMLEscapeString(msg)
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
fullMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
|
fullMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
log.Error("PostProcessCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
|
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
|
||||||
@ -65,9 +65,9 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
|
|||||||
|
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
renderedMessage, err := markup.RenderCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
|
renderedMessage, err := markup.PostProcessCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessageSubject: %v", err)
|
log.Error("PostProcessCommitMessageSubject: %v", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return renderCodeBlock(template.HTML(renderedMessage))
|
return renderCodeBlock(template.HTML(renderedMessage))
|
||||||
@ -87,9 +87,9 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
renderedMessage, err := markup.RenderCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
|
renderedMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderCommitMessage: %v", err)
|
log.Error("PostProcessCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return template.HTML(renderedMessage)
|
return template.HTML(renderedMessage)
|
||||||
@ -106,12 +106,19 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
|||||||
|
|
||||||
// RenderIssueTitle renders issue/pull title with defined post processors
|
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||||
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
||||||
renderedText, err := markup.RenderIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
|
renderedText, err := markup.PostProcessIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderIssueTitle: %v", err)
|
log.Error("PostProcessIssueTitle: %v", err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return template.HTML(renderedText)
|
return renderCodeBlock(template.HTML(renderedText))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderIssueSimpleTitle only renders with emoji and inline code block
|
||||||
|
func (ut *RenderUtils) RenderIssueSimpleTitle(text string) template.HTML {
|
||||||
|
ret := ut.RenderEmoji(text)
|
||||||
|
ret = renderCodeBlock(ret)
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderLabel renders a label
|
// RenderLabel renders a label
|
||||||
@ -174,7 +181,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
|
|||||||
|
|
||||||
// RenderEmoji renders html text with emoji post processors
|
// RenderEmoji renders html text with emoji post processors
|
||||||
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
func (ut *RenderUtils) RenderEmoji(text string) template.HTML {
|
||||||
renderedText, err := markup.RenderEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text))
|
renderedText, err := markup.PostProcessEmoji(markup.NewRenderContext(ut.ctx), template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("RenderEmoji: %v", err)
|
log.Error("RenderEmoji: %v", err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -164,11 +164,11 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||||||
<span class="emoji" aria-label="thumbs up">👍</span>
|
<span class="emoji" aria-label="thumbs up">👍</span>
|
||||||
mail@domain.com
|
mail@domain.com
|
||||||
@mention-user test
|
@mention-user test
|
||||||
#123
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
space<SPACE><SPACE>
|
space<SPACE><SPACE>
|
||||||
`
|
`
|
||||||
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
||||||
assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), nil)))
|
assert.EqualValues(t, expected, string(newTestRenderUtils().RenderIssueTitle(testInput(), testMetas)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
|
|
||||||
@ -214,7 +215,9 @@ func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Reques
|
|||||||
normalizedPath = "/"
|
normalizedPath = "/"
|
||||||
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
||||||
// do not respond to other requests, to simulate a real sub-path environment
|
// do not respond to other requests, to simulate a real sub-path environment
|
||||||
http.Error(resp, "404 page not found, sub-path is: "+setting.AppSubURL, http.StatusNotFound)
|
resp.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||||
|
resp.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = resp.Write([]byte(htmlutil.HTMLFormat(`404 page not found, sub-path is: <a href="%s">%s</a>`, setting.AppSubURL, setting.AppSubURL)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
normalized = true
|
normalized = true
|
||||||
|
@ -2601,7 +2601,6 @@ diff.generated = generated
|
|||||||
diff.vendored = vendored
|
diff.vendored = vendored
|
||||||
diff.comment.add_line_comment = Add line comment
|
diff.comment.add_line_comment = Add line comment
|
||||||
diff.comment.placeholder = Leave a comment
|
diff.comment.placeholder = Leave a comment
|
||||||
diff.comment.markdown_info = Styling with markdown is supported.
|
|
||||||
diff.comment.add_single_comment = Add single comment
|
diff.comment.add_single_comment = Add single comment
|
||||||
diff.comment.add_review_comment = Add comment
|
diff.comment.add_review_comment = Add comment
|
||||||
diff.comment.start_review = Start review
|
diff.comment.start_review = Start review
|
||||||
|
@ -150,11 +150,6 @@ func DeleteBranch(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsMirror {
|
|
||||||
ctx.Error(http.StatusForbidden, "IsMirrored", fmt.Errorf("can not delete branch of an mirror repository"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
||||||
switch {
|
switch {
|
||||||
case git.IsErrBranchNotExist(err):
|
case git.IsErrBranchNotExist(err):
|
||||||
|
@ -1057,49 +1057,54 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
log.Trace("Pull request merged: %d", pr.ID)
|
log.Trace("Pull request merged: %d", pr.ID)
|
||||||
|
|
||||||
if form.DeleteBranchAfterMerge {
|
// for agit flow, we should not delete the agit reference after merge
|
||||||
// Don't cleanup when there are other PR's that use this branch as head branch.
|
if form.DeleteBranchAfterMerge && pr.Flow == issues_model.PullRequestFlowGithub {
|
||||||
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
|
// check permission even it has been checked in repo_service.DeleteBranch so that we don't need to
|
||||||
if err != nil {
|
// do RetargetChildrenOnMerge
|
||||||
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
if err := repo_service.CanDeleteBranch(ctx, pr.HeadRepo, pr.HeadBranch, ctx.Doer); err == nil {
|
||||||
return
|
// Don't cleanup when there are other PR's that use this branch as head branch.
|
||||||
}
|
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
|
||||||
if exist {
|
|
||||||
ctx.Status(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var headRepo *git.Repository
|
|
||||||
if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
|
|
||||||
headRepo = ctx.Repo.GitRepo
|
|
||||||
} else {
|
|
||||||
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
|
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer headRepo.Close()
|
if exist {
|
||||||
}
|
ctx.Status(http.StatusOK)
|
||||||
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
|
return
|
||||||
ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
|
}
|
||||||
return
|
|
||||||
}
|
var headRepo *git.Repository
|
||||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
|
if ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == pr.HeadRepoID && ctx.Repo.GitRepo != nil {
|
||||||
switch {
|
headRepo = ctx.Repo.GitRepo
|
||||||
case git.IsErrBranchNotExist(err):
|
} else {
|
||||||
ctx.NotFound(err)
|
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
||||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
if err != nil {
|
||||||
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
|
||||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
return
|
||||||
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
}
|
||||||
default:
|
defer headRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
}
|
||||||
|
if err := pull_service.RetargetChildrenOnMerge(ctx, ctx.Doer, pr); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "RetargetChildrenOnMerge", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil {
|
||||||
|
switch {
|
||||||
|
case git.IsErrBranchNotExist(err):
|
||||||
|
ctx.NotFound(err)
|
||||||
|
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||||
|
ctx.Error(http.StatusForbidden, "DefaultBranch", fmt.Errorf("can not delete default branch"))
|
||||||
|
case errors.Is(err, git_model.ErrBranchIsProtected):
|
||||||
|
ctx.Error(http.StatusForbidden, "IsProtectedBranch", fmt.Errorf("branch protected"))
|
||||||
|
default:
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteBranch", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
||||||
|
// Do not fail here as branch has already been deleted
|
||||||
|
log.Error("DeleteBranch: %v", err)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := issues_model.AddDeletePRBranchComment(ctx, ctx.Doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil {
|
|
||||||
// Do not fail here as branch has already been deleted
|
|
||||||
log.Error("DeleteBranch: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,9 +394,9 @@ func Diff(ctx *context.Context) {
|
|||||||
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{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))})
|
||||||
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RenderCommitMessage", err)
|
ctx.ServerError("PostProcessCommitMessage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1185,32 +1185,34 @@ func MergePullRequest(ctx *context.Context) {
|
|||||||
|
|
||||||
log.Trace("Pull request merged: %d", pr.ID)
|
log.Trace("Pull request merged: %d", pr.ID)
|
||||||
|
|
||||||
if form.DeleteBranchAfterMerge {
|
if !form.DeleteBranchAfterMerge {
|
||||||
// Don't cleanup when other pr use this branch as head branch
|
ctx.JSONRedirect(issue.Link())
|
||||||
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
|
return
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if exist {
|
|
||||||
ctx.JSONRedirect(issue.Link())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var headRepo *git.Repository
|
|
||||||
if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
|
|
||||||
headRepo = ctx.Repo.GitRepo
|
|
||||||
} else {
|
|
||||||
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer headRepo.Close()
|
|
||||||
}
|
|
||||||
deleteBranch(ctx, pr, headRepo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't cleanup when other pr use this branch as head branch
|
||||||
|
exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pr.HeadRepoID, pr.HeadBranch)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
ctx.JSONRedirect(issue.Link())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var headRepo *git.Repository
|
||||||
|
if ctx.Repo != nil && ctx.Repo.Repository != nil && pr.HeadRepoID == ctx.Repo.Repository.ID && ctx.Repo.GitRepo != nil {
|
||||||
|
headRepo = ctx.Repo.GitRepo
|
||||||
|
} else {
|
||||||
|
headRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError(fmt.Sprintf("OpenRepository[%s]", pr.HeadRepo.FullName()), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer headRepo.Close()
|
||||||
|
}
|
||||||
|
deleteBranch(ctx, pr, headRepo)
|
||||||
ctx.JSONRedirect(issue.Link())
|
ctx.JSONRedirect(issue.Link())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1403,8 +1405,8 @@ func CleanUpPullRequest(ctx *context.Context) {
|
|||||||
|
|
||||||
pr := issue.PullRequest
|
pr := issue.PullRequest
|
||||||
|
|
||||||
// Don't cleanup unmerged and unclosed PRs
|
// Don't cleanup unmerged and unclosed PRs and agit PRs
|
||||||
if !pr.HasMerged && !issue.IsClosed {
|
if !pr.HasMerged && !issue.IsClosed && pr.Flow != issues_model.PullRequestFlowGithub {
|
||||||
ctx.NotFound("CleanUpPullRequest", nil)
|
ctx.NotFound("CleanUpPullRequest", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1435,13 +1437,12 @@ func CleanUpPullRequest(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, ctx.Doer)
|
if err := repo_service.CanDeleteBranch(ctx, pr.HeadRepo, pr.HeadBranch, ctx.Doer); err != nil {
|
||||||
if err != nil {
|
if errors.Is(err, util.ErrPermissionDenied) {
|
||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.NotFound("CanDeleteBranch", nil)
|
||||||
return
|
} else {
|
||||||
}
|
ctx.ServerError("CanDeleteBranch", err)
|
||||||
if !perm.CanWrite(unit.TypeCode) {
|
}
|
||||||
ctx.NotFound("CleanUpPullRequest", nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,6 +485,8 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Methods("GET, HEAD", "/*", public.FileHandlerFunc())
|
m.Methods("GET, HEAD", "/*", public.FileHandlerFunc())
|
||||||
}, optionsCorsHandler())
|
}, optionsCorsHandler())
|
||||||
|
|
||||||
|
m.Post("/-/markup", reqSignIn, web.Bind(structs.MarkupOption{}), misc.Markup)
|
||||||
|
|
||||||
m.Group("/explore", func() {
|
m.Group("/explore", func() {
|
||||||
m.Get("", func(ctx *context.Context) {
|
m.Get("", func(ctx *context.Context) {
|
||||||
ctx.Redirect(setting.AppSubURL + "/explore/repos")
|
ctx.Redirect(setting.AppSubURL + "/explore/repos")
|
||||||
|
@ -14,7 +14,9 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
@ -463,15 +465,17 @@ var (
|
|||||||
ErrBranchIsDefault = errors.New("branch is default")
|
ErrBranchIsDefault = errors.New("branch is default")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteBranch delete branch
|
func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchName string, doer *user_model.User) error {
|
||||||
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
|
if branchName == repo.DefaultBranch {
|
||||||
err := repo.MustNotBeArchived()
|
return ErrBranchIsDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
perm, err := access_model.GetUserRepoPermission(ctx, repo, doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !perm.CanWrite(unit.TypeCode) {
|
||||||
if branchName == repo.DefaultBranch {
|
return util.NewPermissionDeniedErrorf("permission denied to access repo %d unit %s", repo.ID, unit.TypeCode.LogString())
|
||||||
return ErrBranchIsDefault
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName)
|
isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName)
|
||||||
@ -481,6 +485,19 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
|
|||||||
if isProtected {
|
if isProtected {
|
||||||
return git_model.ErrBranchIsProtected
|
return git_model.ErrBranchIsProtected
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBranch delete branch
|
||||||
|
func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error {
|
||||||
|
err := repo.MustNotBeArchived()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CanDeleteBranch(ctx, repo, branchName, doer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName)
|
||||||
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
if err != nil && !git_model.IsErrBranchNotExist(err) {
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{{/* TODO: the devtest.js is isolated from index.js, so no module is shared and many index.js functions do not work in devtest.ts */}}
|
{{/* TODO: the devtest.js is isolated from index.js, so no module is shared and many index.js functions do not work in devtest.ts */}}
|
||||||
<script src="{{AssetUrlPrefix}}/js/devtest.js?v={{AssetVersion}}"></script>
|
<script src="{{AssetUrlPrefix}}/js/devtest.js?v={{AssetVersion}}"></script>
|
||||||
{{template "base/footer" dict}}
|
{{template "base/footer" ctx.RootData}}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
{{template "base/head" dict}}
|
{{template "base/head" ctx.RootData}}
|
||||||
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
|
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/devtest.css?v={{AssetVersion}}">
|
||||||
|
@ -183,8 +183,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1>ComboMarkdownEditor</h1>
|
<h1>ComboMarkdownEditor</h1>
|
||||||
<div>ps: no JS code attached, so just a layout</div>
|
{{template "shared/combomarkdowneditor" dict "MarkdownPreviewContext" "/owner/path"}}
|
||||||
{{template "shared/combomarkdowneditor" .}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>Tailwind CSS Demo</h1>
|
<h1>Tailwind CSS Demo</h1>
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
<input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
|
<input id="email" name="email" type="email" value="{{.Org.Email}}" maxlength="255">
|
||||||
</div>
|
</div>
|
||||||
<div class="field {{if .Err_Description}}error{{end}}">
|
<div class="field {{if .Err_Description}}error{{end}}">
|
||||||
|
{{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
|
||||||
<label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
|
<label for="description">{{ctx.Locale.Tr "org.org_desc"}}</label>
|
||||||
<textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
|
<textarea id="description" name="description" rows="2" maxlength="255">{{.Org.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{ctx.Locale.Tr "repo.projects.description"}}</label>
|
<label>{{ctx.Locale.Tr "repo.projects.description"}}</label>
|
||||||
<textarea name="content" placeholder="{{ctx.Locale.Tr "repo.projects.description_placeholder"}}">{{.content}}</textarea>
|
{{/* TODO: repo-level project and org-level project have different behaviros to render */}}
|
||||||
|
{{/* the "Repository" is nil when the project is org-level */}}
|
||||||
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewContext" (Iif $.Repository "" .HomeLink)
|
||||||
|
"MarkdownPreviewMode" (Iif $.Repository "comment")
|
||||||
|
"TextareaName" "content"
|
||||||
|
"TextareaContent" .content
|
||||||
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.projects.description_placeholder")
|
||||||
|
)}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if not .PageIsEditProjects}}
|
{{if not .PageIsEditProjects}}
|
||||||
|
@ -240,8 +240,9 @@
|
|||||||
<template id="issue-comment-editor-template">
|
<template id="issue-comment-editor-template">
|
||||||
<div class="ui form comment">
|
<div class="ui form comment">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print $.Repository.Link "/markup")
|
"CustomInit" true
|
||||||
"MarkdownPreviewContext" $.RepoLink
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"DropzoneParentContainer" ".ui.form"
|
"DropzoneParentContainer" ".ui.form"
|
||||||
)}}
|
)}}
|
||||||
|
@ -9,24 +9,24 @@
|
|||||||
<input type="hidden" name="diff_start_cid">
|
<input type="hidden" name="diff_start_cid">
|
||||||
<input type="hidden" name="diff_end_cid">
|
<input type="hidden" name="diff_end_cid">
|
||||||
<input type="hidden" name="diff_base_cid">
|
<input type="hidden" name="diff_base_cid">
|
||||||
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print $.root.Repository.Link "/markup")
|
"CustomInit" true
|
||||||
"MarkdownPreviewContext" $.root.RepoLink
|
"MarkdownPreviewInRepo" $.root.Repository
|
||||||
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.comment.placeholder")
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.comment.placeholder")
|
||||||
"DropzoneParentContainer" "form"
|
"DropzoneParentContainer" "form"
|
||||||
"DisableAutosize" "true"
|
"DisableAutosize" "true"
|
||||||
)}}
|
)}}
|
||||||
|
</div>
|
||||||
{{if $.root.IsAttachmentEnabled}}
|
{{if $.root.IsAttachmentEnabled}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "repo/upload" $.root}}
|
{{template "repo/upload" $.root}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="field footer tw-mx-2">
|
<div class="field footer">
|
||||||
<span class="markup-info">{{svg "octicon-markdown"}} {{ctx.Locale.Tr "repo.diff.comment.markdown_info"}}</span>
|
|
||||||
<div class="tw-text-right">
|
<div class="tw-text-right">
|
||||||
{{if $.reply}}
|
{{if $.reply}}
|
||||||
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
|
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
|
||||||
|
@ -187,7 +187,7 @@
|
|||||||
<div class="ui segment flex-text-block tw-gap-4">
|
<div class="ui segment flex-text-block tw-gap-4">
|
||||||
{{template "shared/issueicon" .}}
|
{{template "shared/issueicon" .}}
|
||||||
<div class="issue-title tw-break-anywhere">
|
<div class="issue-title tw-break-anywhere">
|
||||||
{{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
|
{{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}}
|
||||||
<span class="index">#{{.PullRequest.Issue.Index}}</span>
|
<span class="index">#{{.PullRequest.Issue.Index}}</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
|
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
"MarkdownPreviewContext" .RepoLink
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.review.placeholder")
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.review.placeholder")
|
||||||
"DropzoneParentContainer" "form"
|
"DropzoneParentContainer" "form"
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<div class="issue-card-icon">
|
<div class="issue-card-icon">
|
||||||
{{template "shared/issueicon" .}}
|
{{template "shared/issueicon" .}}
|
||||||
</div>
|
</div>
|
||||||
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
<a class="issue-card-title muted issue-title tw-break-anywhere" href="{{.Link}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}}
|
{{if and $.isPinnedIssueCard $.Page.IsRepoAdmin}}
|
||||||
<a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}">
|
<a role="button" class="issue-card-unpin muted tw-flex tw-items-center" data-tooltip-content={{ctx.Locale.Tr "repo.issues.unpin_issue"}} data-issue-id="{{.ID}}" data-unpin-url="{{$.Page.Link}}/unpin/{{.Index}}">
|
||||||
{{svg "octicon-x" 16}}
|
{{svg "octicon-x" 16}}
|
||||||
|
@ -5,11 +5,12 @@
|
|||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
"CustomInit" true
|
||||||
"MarkdownPreviewContext" .RepoLink
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"TextareaContent" $textareaContent
|
"TextareaContent" $textareaContent
|
||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.comment.placeholder")
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.diff.comment.placeholder")
|
||||||
"DropzoneParentContainer" "form, .ui.form"
|
"DropzoneParentContainer" "form, .ui.form"
|
||||||
)}}
|
)}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
|
|
||||||
{{if $useMarkdownEditor}}
|
{{if $useMarkdownEditor}}
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
|
"CustomInit" true
|
||||||
"ContainerClasses" "tw-hidden"
|
"ContainerClasses" "tw-hidden"
|
||||||
"MarkdownPreviewUrl" (print .root.RepoLink "/markup")
|
"MarkdownPreviewInRepo" $.root.Repository
|
||||||
"MarkdownPreviewContext" .root.RepoLink
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaContent" .item.Attributes.value
|
"TextareaContent" .item.Attributes.value
|
||||||
"TextareaPlaceholder" .item.Attributes.placeholder
|
"TextareaPlaceholder" .item.Attributes.placeholder
|
||||||
"DropzoneParentContainer" ".combo-editor-dropzone"
|
"DropzoneParentContainer" ".combo-editor-dropzone"
|
||||||
)}}
|
)}}
|
||||||
|
|
||||||
|
@ -36,9 +36,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{ctx.Locale.Tr "repo.milestones.desc"}}</label>
|
<label>{{ctx.Locale.Tr "repo.milestones.desc"}}</label>
|
||||||
<textarea name="content">{{.content}}</textarea>
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewMode" "comment"
|
||||||
|
"TextareaName" "content"
|
||||||
|
"TextareaContent" .content
|
||||||
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.milestones.desc")
|
||||||
|
)}}
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
|
||||||
<div class="tw-text-right">
|
<div class="tw-text-right">
|
||||||
{{if .PageIsEditMilestone}}
|
{{if .PageIsEditMilestone}}
|
||||||
<a class="ui primary basic button" href="{{.RepoLink}}/milestones">
|
<a class="ui primary basic button" href="{{.RepoLink}}/milestones">
|
||||||
|
@ -142,8 +142,9 @@
|
|||||||
<div class="ui form comment">
|
<div class="ui form comment">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
"CustomInit" true
|
||||||
"MarkdownPreviewContext" .RepoLink
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"DropzoneParentContainer" ".ui.form"
|
"DropzoneParentContainer" ".ui.form"
|
||||||
)}}
|
)}}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
|
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
|
||||||
<div class="issue-title" id="issue-title-display">
|
<div class="issue-title" id="issue-title-display">
|
||||||
<h1 class="tw-break-anywhere">
|
<h1 class="tw-break-anywhere">
|
||||||
{{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeMetas ctx) | RenderCodeBlock}}
|
{{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeMetas ctx)}}
|
||||||
<span class="index">#{{.Issue.Index}}</span>
|
<span class="index">#{{.Issue.Index}}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="issue-title-buttons">
|
<div class="issue-title-buttons">
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.published_release_label"}}</span>
|
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.published_release_label"}}</span>
|
||||||
{{.TagName}}
|
{{.TagName}}
|
||||||
{{if not .IsTag}}
|
{{if not .IsTag}}
|
||||||
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{DateUtils.TimeSince .CreatedUnix}}
|
{{DateUtils.TimeSince .CreatedUnix}}
|
||||||
</p>
|
</p>
|
||||||
@ -145,7 +145,7 @@
|
|||||||
{{range .Activity.MergedPRs}}
|
{{range .Activity.MergedPRs}}
|
||||||
<p class="desc">
|
<p class="desc">
|
||||||
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
|
<span class="ui purple label">{{ctx.Locale.Tr "repo.activity.merged_prs_label"}}</span>
|
||||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{DateUtils.TimeSince .MergedUnix}}
|
{{DateUtils.TimeSince .MergedUnix}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -164,7 +164,7 @@
|
|||||||
{{range .Activity.OpenedPRs}}
|
{{range .Activity.OpenedPRs}}
|
||||||
<p class="desc">
|
<p class="desc">
|
||||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
|
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.opened_prs_label"}}</span>
|
||||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
#{{.Index}} <a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{DateUtils.TimeSince .Issue.CreatedUnix}}
|
{{DateUtils.TimeSince .Issue.CreatedUnix}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -183,7 +183,7 @@
|
|||||||
{{range .Activity.ClosedIssues}}
|
{{range .Activity.ClosedIssues}}
|
||||||
<p class="desc">
|
<p class="desc">
|
||||||
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
|
<span class="ui red label">{{ctx.Locale.Tr "repo.activity.closed_issue_label"}}</span>
|
||||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{DateUtils.TimeSince .ClosedUnix}}
|
{{DateUtils.TimeSince .ClosedUnix}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -202,7 +202,7 @@
|
|||||||
{{range .Activity.OpenedIssues}}
|
{{range .Activity.OpenedIssues}}
|
||||||
<p class="desc">
|
<p class="desc">
|
||||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
|
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.new_issue_label"}}</span>
|
||||||
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
#{{.Index}} <a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{DateUtils.TimeSince .CreatedUnix}}
|
{{DateUtils.TimeSince .CreatedUnix}}
|
||||||
</p>
|
</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
@ -220,9 +220,9 @@
|
|||||||
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
|
<span class="ui green label">{{ctx.Locale.Tr "repo.activity.unresolved_conv_label"}}</span>
|
||||||
#{{.Index}}
|
#{{.Index}}
|
||||||
{{if .IsPull}}
|
{{if .IsPull}}
|
||||||
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
<a class="title" href="{{$.RepoLink}}/pulls/{{.Index}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
<a class="title" href="{{$.RepoLink}}/issues/{{.Index}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{DateUtils.TimeSince .UpdatedUnix}}
|
{{DateUtils.TimeSince .UpdatedUnix}}
|
||||||
</p>
|
</p>
|
||||||
|
@ -50,12 +50,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
"MarkdownPreviewContext" .RepoLink
|
"MarkdownPreviewMode" "comment"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"TextareaContent" .content
|
"TextareaContent" .content
|
||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.release.message")
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.release.message")
|
||||||
"TextareaAriaLabel" (ctx.Locale.Tr "repo.release.message")
|
|
||||||
"DropzoneParentContainer" "form"
|
"DropzoneParentContainer" "form"
|
||||||
)}}
|
)}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,12 +23,12 @@
|
|||||||
{{$content = ctx.Locale.Tr "repo.wiki.welcome"}}
|
{{$content = ctx.Locale.Tr "repo.wiki.welcome"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"MarkdownPreviewUrl" (print .Repository.Link "/markup")
|
"CustomInit" true
|
||||||
"MarkdownPreviewContext" .RepoLink
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
|
"MarkdownPreviewMode" "wiki"
|
||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.wiki.page_content")
|
|
||||||
"TextareaAriaLabel" (ctx.Locale.Tr "repo.wiki.page_content")
|
|
||||||
"TextareaContent" $content
|
"TextareaContent" $content
|
||||||
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.wiki.page_content")
|
||||||
)}}
|
)}}
|
||||||
|
|
||||||
<div class="field tw-mt-4">
|
<div class="field tw-mt-4">
|
||||||
|
@ -1,23 +1,39 @@
|
|||||||
{{/*
|
{{/*
|
||||||
Template Attributes:
|
Template Attributes:
|
||||||
|
* CustomInit: do not initialize the editor automatically
|
||||||
* ContainerId: id attribute for the container element
|
* ContainerId: id attribute for the container element
|
||||||
* ContainerClasses: additional classes for the container element
|
* ContainerClasses: additional classes for the container element
|
||||||
* MarkdownPreviewUrl: preview url for the preview tab
|
* MarkdownPreviewInRepo: the repo to preview markdown
|
||||||
* MarkdownPreviewContext: preview context for the preview tab
|
* MarkdownPreviewContext: preview context (the related url path when rendering) for the preview tab, eg: repo link or user home link
|
||||||
|
* MarkdownPreviewMode: content mode for the editor, eg: wiki, comment or default
|
||||||
* TextareaName: name attribute for the textarea
|
* TextareaName: name attribute for the textarea
|
||||||
* TextareaContent: content for the textarea
|
* TextareaContent: content for the textarea
|
||||||
|
* TextareaMaxLength: maxlength attribute for the textarea
|
||||||
* TextareaPlaceholder: placeholder attribute for the textarea
|
* TextareaPlaceholder: placeholder attribute for the textarea
|
||||||
* TextareaAriaLabel: aria-label attribute for the textarea
|
* TextareaAriaLabel: aria-label attribute for the textarea
|
||||||
* DropzoneParentContainer: container for file upload (leave it empty if no upload)
|
* DropzoneParentContainer: container for file upload (leave it empty if no upload)
|
||||||
* DisableAutosize: whether to disable automatic height resizing
|
* DisableAutosize: whether to disable automatic height resizing
|
||||||
*/}}
|
*/}}
|
||||||
<div {{if .ContainerId}}id="{{.ContainerId}}"{{end}} class="combo-markdown-editor {{.ContainerClasses}}" data-dropzone-parent-container="{{.DropzoneParentContainer}}">
|
{{$ariaLabel := or .TextareaAriaLabel .TextareaPlaceholder}}
|
||||||
{{if .MarkdownPreviewUrl}}
|
{{$repo := .MarkdownPreviewInRepo}}
|
||||||
|
{{$previewContext := .MarkdownPreviewContext}}
|
||||||
|
{{$previewMode := .MarkdownPreviewMode}}
|
||||||
|
{{$previewUrl := print AppSubUrl "/-/markup"}}
|
||||||
|
{{if $repo}}
|
||||||
|
{{$previewUrl = print $repo.Link "/markup"}}
|
||||||
|
{{end}}
|
||||||
|
{{$supportEasyMDE := or (eq $previewMode "comment") (eq $previewMode "wiki")}}
|
||||||
|
<div {{if .ContainerId}}id="{{.ContainerId}}"{{end}} class="combo-markdown-editor {{if .CustomInit}}custom-init{{end}} {{.ContainerClasses}}"
|
||||||
|
data-dropzone-parent-container="{{.DropzoneParentContainer}}"
|
||||||
|
data-content-mode="{{$previewMode}}"
|
||||||
|
data-support-easy-mde="{{$supportEasyMDE}}"
|
||||||
|
data-preview-url="{{$previewUrl}}"
|
||||||
|
data-preview-context="{{$previewContext}}"
|
||||||
|
>
|
||||||
<div class="ui top tabular menu">
|
<div class="ui top tabular menu">
|
||||||
<a class="active item" data-tab-for="markdown-writer">{{template "shared/misc/tabtitle" (ctx.Locale.Tr "write")}}</a>
|
<a class="active item" data-tab-for="markdown-writer">{{template "shared/misc/tabtitle" (ctx.Locale.Tr "write")}}</a>
|
||||||
<a class="item" data-tab-for="markdown-previewer" data-preview-url="{{.MarkdownPreviewUrl}}" data-preview-context="{{.MarkdownPreviewContext}}">{{template "shared/misc/tabtitle" (ctx.Locale.Tr "preview")}}</a>
|
<a class="item" data-tab-for="markdown-previewer">{{template "shared/misc/tabtitle" (ctx.Locale.Tr "preview")}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
<div class="ui tab active" data-tab-panel="markdown-writer">
|
<div class="ui tab active" data-tab-panel="markdown-writer">
|
||||||
<markdown-toolbar>
|
<markdown-toolbar>
|
||||||
<div class="markdown-toolbar-group">
|
<div class="markdown-toolbar-group">
|
||||||
@ -40,17 +56,25 @@ Template Attributes:
|
|||||||
<md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list>
|
<md-task-list class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.list.task.tooltip"}}">{{svg "octicon-tasklist"}}</md-task-list>
|
||||||
<button class="markdown-toolbar-button markdown-button-table-add" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.table.add.tooltip"}}">{{svg "octicon-table"}}</button>
|
<button class="markdown-toolbar-button markdown-button-table-add" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.table.add.tooltip"}}">{{svg "octicon-table"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
{{if eq $previewMode "comment"}}
|
||||||
<div class="markdown-toolbar-group">
|
<div class="markdown-toolbar-group">
|
||||||
<md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention>
|
<md-mention class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.mention.tooltip"}}">{{svg "octicon-mention"}}</md-mention>
|
||||||
<md-ref class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.ref.tooltip"}}">{{svg "octicon-cross-reference"}}</md-ref>
|
<md-ref class="markdown-toolbar-button" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.ref.tooltip"}}">{{svg "octicon-cross-reference"}}</md-ref>
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
<div class="markdown-toolbar-group">
|
<div class="markdown-toolbar-group">
|
||||||
<button class="markdown-toolbar-button markdown-switch-monospace" role="switch" data-enable-text="{{ctx.Locale.Tr "editor.buttons.enable_monospace_font"}}" data-disable-text="{{ctx.Locale.Tr "editor.buttons.disable_monospace_font"}}">{{svg "octicon-typography"}}</button>
|
<button class="markdown-toolbar-button markdown-switch-monospace" role="switch" data-enable-text="{{ctx.Locale.Tr "editor.buttons.enable_monospace_font"}}" data-disable-text="{{ctx.Locale.Tr "editor.buttons.disable_monospace_font"}}">{{svg "octicon-typography"}}</button>
|
||||||
|
{{if $supportEasyMDE}}
|
||||||
<button class="markdown-toolbar-button markdown-switch-easymde" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.switch_to_legacy.tooltip"}}">{{svg "octicon-arrow-switch"}}</button>
|
<button class="markdown-toolbar-button markdown-switch-easymde" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.switch_to_legacy.tooltip"}}">{{svg "octicon-arrow-switch"}}</button>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</markdown-toolbar>
|
</markdown-toolbar>
|
||||||
<text-expander keys=": @ #" multiword="#" suffix="">
|
<text-expander keys=": @ #" multiword="#" suffix="">
|
||||||
<textarea class="markdown-text-editor"{{if .TextareaName}} name="{{.TextareaName}}"{{end}}{{if .TextareaPlaceholder}} placeholder="{{.TextareaPlaceholder}}"{{end}}{{if .TextareaAriaLabel}} aria-label="{{.TextareaAriaLabel}}"{{end}}{{if .DisableAutosize}} data-disable-autosize="{{.DisableAutosize}}"{{end}}>{{.TextareaContent}}</textarea>
|
<textarea class="markdown-text-editor"
|
||||||
|
{{if .TextareaName}}name="{{.TextareaName}}"{{end}} {{if .TextareaMaxLength}}maxlength="{{.TextareaMaxLength}}"{{end}}
|
||||||
|
{{if .TextareaPlaceholder}}placeholder="{{.TextareaPlaceholder}}"{{end}} {{if $ariaLabel}}aria-label="{{$ariaLabel}}"{{end}}
|
||||||
|
{{if .DisableAutosize}}data-disable-autosize="{{.DisableAutosize}}"{{end}}
|
||||||
|
>{{.TextareaContent}}</textarea>
|
||||||
</text-expander>
|
</text-expander>
|
||||||
<script>
|
<script>
|
||||||
if (localStorage?.getItem('markdown-editor-monospace') === 'true') {
|
if (localStorage?.getItem('markdown-editor-monospace') === 'true') {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<div class="flex-item-main">
|
<div class="flex-item-main">
|
||||||
<div class="flex-item-header">
|
<div class="flex-item-header">
|
||||||
<div class="flex-item-title">
|
<div class="flex-item-title">
|
||||||
<a class="tw-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{ctx.RenderUtils.RenderEmoji .Title | RenderCodeBlock}}</a>
|
<a class="tw-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{if .IsPull}}
|
{{if .IsPull}}
|
||||||
{{if (index $.CommitStatuses .PullRequest.ID)}}
|
{{if (index $.CommitStatuses .PullRequest.ID)}}
|
||||||
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
|
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}}
|
||||||
|
@ -100,11 +100,11 @@
|
|||||||
<a href="{{AppSubUrl}}/{{$push.CompareURL}}">{{ctx.Locale.Tr "action.compare_commits" $push.Len}} »</a>
|
<a href="{{AppSubUrl}}/{{$push.CompareURL}}">{{ctx.Locale.Tr "action.compare_commits" $push.Len}} »</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else if .GetOpType.InActions "create_issue"}}
|
{{else if .GetOpType.InActions "create_issue"}}
|
||||||
<span class="text truncate issue title">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</span>
|
<span class="text truncate issue title">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderIssueSimpleTitle}}</span>
|
||||||
{{else if .GetOpType.InActions "create_pull_request"}}
|
{{else if .GetOpType.InActions "create_pull_request"}}
|
||||||
<span class="text truncate issue title">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</span>
|
<span class="text truncate issue title">{{index .GetIssueInfos 1 | ctx.RenderUtils.RenderIssueSimpleTitle}}</span>
|
||||||
{{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}}
|
{{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}}
|
||||||
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</a>
|
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</a>
|
||||||
{{$comment := index .GetIssueInfos 1}}
|
{{$comment := index .GetIssueInfos 1}}
|
||||||
{{if $comment}}
|
{{if $comment}}
|
||||||
<div class="markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
|
<div class="markup tw-text-14">{{ctx.RenderUtils.MarkdownToHtml $comment}}</div>
|
||||||
@ -112,7 +112,7 @@
|
|||||||
{{else if .GetOpType.InActions "merge_pull_request"}}
|
{{else if .GetOpType.InActions "merge_pull_request"}}
|
||||||
<div class="flex-item-body text black">{{index .GetIssueInfos 1}}</div>
|
<div class="flex-item-body text black">{{index .GetIssueInfos 1}}</div>
|
||||||
{{else if .GetOpType.InActions "close_issue" "reopen_issue" "close_pull_request" "reopen_pull_request"}}
|
{{else if .GetOpType.InActions "close_issue" "reopen_issue" "close_pull_request" "reopen_pull_request"}}
|
||||||
<span class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}</span>
|
<span class="text truncate issue title">{{(.GetIssueTitle ctx) | ctx.RenderUtils.RenderIssueSimpleTitle}}</span>
|
||||||
{{else if .GetOpType.InActions "pull_review_dismissed"}}
|
{{else if .GetOpType.InActions "pull_review_dismissed"}}
|
||||||
<div class="flex-item-body text black">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</div>
|
<div class="flex-item-body text black">{{ctx.Locale.Tr "action.review_dismissed_reason"}}</div>
|
||||||
<div class="flex-item-body text black">{{index .GetIssueInfos 2 | ctx.RenderUtils.RenderEmoji}}</div>
|
<div class="flex-item-body text black">{{index .GetIssueInfos 2 | ctx.RenderUtils.RenderEmoji}}</div>
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
|
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
|
||||||
<span class="issue-title tw-break-anywhere">
|
<span class="issue-title tw-break-anywhere">
|
||||||
{{if .Issue}}
|
{{if .Issue}}
|
||||||
{{.Issue.Title | ctx.RenderUtils.RenderEmoji | RenderCodeBlock}}
|
{{.Issue.Title | ctx.RenderUtils.RenderIssueSimpleTitle}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{.Repository.FullName}}
|
{{.Repository.FullName}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
<p id="signed-user-email">{{.SignedUser.Email}}</p>
|
<p id="signed-user-email">{{.SignedUser.Email}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field {{if .Err_Description}}error{{end}}">
|
<div class="field {{if .Err_Description}}error{{end}}">
|
||||||
|
{{/* it is rendered as markdown, but the length is limited, so at the moment we do not use the markdown editor here */}}
|
||||||
<label for="description">{{ctx.Locale.Tr "user.user_bio"}}</label>
|
<label for="description">{{ctx.Locale.Tr "user.user_bio"}}</label>
|
||||||
<textarea id="description" name="description" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea>
|
<textarea id="description" name="description" rows="2" placeholder="{{ctx.Locale.Tr "settings.biography_placeholder"}}" maxlength="255">{{.SignedUser.Description}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -554,6 +554,10 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) {
|
|||||||
|
|
||||||
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
|
||||||
|
branchBasePR := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "base-pr"})
|
||||||
|
assert.True(t, branchBasePR.IsDeleted)
|
||||||
|
|
||||||
// Check child PR
|
// Check child PR
|
||||||
req := NewRequest(t, "GET", test.RedirectURL(respChildPR))
|
req := NewRequest(t, "GET", test.RedirectURL(respChildPR))
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
@ -584,6 +588,10 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) {
|
|||||||
|
|
||||||
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
|
||||||
|
branchBasePR := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "base-pr"})
|
||||||
|
assert.True(t, branchBasePR.IsDeleted)
|
||||||
|
|
||||||
// Check child PR
|
// Check child PR
|
||||||
req := NewRequest(t, "GET", test.RedirectURL(respChildPR))
|
req := NewRequest(t, "GET", test.RedirectURL(respChildPR))
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
@ -598,6 +606,27 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPullRequestMergedWithNoPermissionDeleteBranch(t *testing.T) {
|
||||||
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
|
session := loginUser(t, "user4")
|
||||||
|
testRepoFork(t, session, "user2", "repo1", "user4", "repo1", "")
|
||||||
|
testEditFileToNewBranch(t, session, "user4", "repo1", "master", "base-pr", "README.md", "Hello, World\n(Edited - TestPullDontRetargetChildOnWrongRepo - base PR)\n")
|
||||||
|
|
||||||
|
respBasePR := testPullCreate(t, session, "user4", "repo1", false, "master", "base-pr", "Base Pull Request")
|
||||||
|
elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/")
|
||||||
|
assert.EqualValues(t, "pulls", elemBasePR[3])
|
||||||
|
|
||||||
|
// user2 has no permission to delete branch of repo user1/repo1
|
||||||
|
session2 := loginUser(t, "user2")
|
||||||
|
testPullMerge(t, session2, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true)
|
||||||
|
|
||||||
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user4", Name: "repo1"})
|
||||||
|
branchBasePR := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo1.ID, Name: "base-pr"})
|
||||||
|
// branch has not been deleted
|
||||||
|
assert.False(t, branchBasePR.IsDeleted)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPullMergeIndexerNotifier(t *testing.T) {
|
func TestPullMergeIndexerNotifier(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
|
||||||
// create a pull request
|
// create a pull request
|
||||||
|
@ -96,6 +96,11 @@
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.combo-markdown-editor .ui.tab.markup[data-tab-panel="markdown-previewer"] {
|
||||||
|
border-bottom: 1px solid var(--color-secondary);
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
text-expander {
|
text-expander {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1005,7 +1005,7 @@ td .commit-summary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .code-comment .comment-content {
|
.repository.view.issue .comment-list .code-comment .comment-content {
|
||||||
margin-left: 36px;
|
margin-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repository.view.issue .comment-list .comment > .avatar {
|
.repository.view.issue .comment-list .comment > .avatar {
|
||||||
|
@ -102,19 +102,11 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-code-cloud .ui.active.tab {
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-code-cloud .ui.active.tab.markup {
|
.comment-code-cloud .ui.active.tab.markup {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
min-height: 168px;
|
min-height: 168px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-code-cloud .ui.tabular.menu {
|
|
||||||
margin: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-code-cloud .editor-statusbar {
|
.comment-code-cloud .editor-statusbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -123,23 +115,6 @@
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-code-cloud .footer .markup-info {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 5px 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-code-cloud .footer .ui.right.floated {
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-code-cloud .footer::after {
|
|
||||||
clear: both;
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diff-file-body .comment-form {
|
.diff-file-body .comment-form {
|
||||||
margin: 0 0 0 3em;
|
margin: 0 0 0 3em;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
import {applyAreYouSure, initAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||||
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.ts';
|
||||||
|
import {queryElems} from '../utils/dom.ts';
|
||||||
|
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
|
|
||||||
export function initGlobalFormDirtyLeaveConfirm() {
|
export function initGlobalFormDirtyLeaveConfirm() {
|
||||||
initAreYouSure(window.jQuery);
|
initAreYouSure(window.jQuery);
|
||||||
@ -11,7 +13,7 @@ export function initGlobalFormDirtyLeaveConfirm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function initGlobalEnterQuickSubmit() {
|
export function initGlobalEnterQuickSubmit() {
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e: KeyboardEvent & {target: HTMLElement}) => {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
const hasCtrlOrMeta = ((e.ctrlKey || e.metaKey) && !e.altKey);
|
||||||
if (hasCtrlOrMeta && e.target.matches('textarea')) {
|
if (hasCtrlOrMeta && e.target.matches('textarea')) {
|
||||||
@ -27,3 +29,7 @@ export function initGlobalEnterQuickSubmit() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initGlobalComboMarkdownEditor() {
|
||||||
|
queryElems<HTMLElement>(document, '.combo-markdown-editor:not(.custom-init)', (el) => initComboMarkdownEditor(el));
|
||||||
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import '@github/markdown-toolbar-element';
|
import '@github/markdown-toolbar-element';
|
||||||
import '@github/text-expander-element';
|
import '@github/text-expander-element';
|
||||||
import $ from 'jquery';
|
|
||||||
import {attachTribute} from '../tribute.ts';
|
import {attachTribute} from '../tribute.ts';
|
||||||
import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts';
|
import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.ts';
|
||||||
import {
|
import {
|
||||||
@ -23,6 +22,8 @@ import {
|
|||||||
} from './EditorMarkdown.ts';
|
} from './EditorMarkdown.ts';
|
||||||
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
|
import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
|
||||||
import {createTippy} from '../../modules/tippy.ts';
|
import {createTippy} from '../../modules/tippy.ts';
|
||||||
|
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||||
|
import type EasyMDE from 'easymde';
|
||||||
|
|
||||||
let elementIdCounter = 0;
|
let elementIdCounter = 0;
|
||||||
|
|
||||||
@ -48,18 +49,23 @@ export function validateTextareaNonEmpty(textarea) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ComboMarkdownEditorOptions = {
|
||||||
|
editorHeights?: {minHeight?: string, height?: string, maxHeight?: string},
|
||||||
|
easyMDEOptions?: EasyMDE.Options,
|
||||||
|
};
|
||||||
|
|
||||||
export class ComboMarkdownEditor {
|
export class ComboMarkdownEditor {
|
||||||
static EventEditorContentChanged = EventEditorContentChanged;
|
static EventEditorContentChanged = EventEditorContentChanged;
|
||||||
static EventUploadStateChanged = EventUploadStateChanged;
|
static EventUploadStateChanged = EventUploadStateChanged;
|
||||||
|
|
||||||
public container : HTMLElement;
|
public container : HTMLElement;
|
||||||
|
|
||||||
// TODO: use correct types to replace these "any" types
|
options: ComboMarkdownEditorOptions;
|
||||||
options: any;
|
|
||||||
|
|
||||||
tabEditor: HTMLElement;
|
tabEditor: HTMLElement;
|
||||||
tabPreviewer: HTMLElement;
|
tabPreviewer: HTMLElement;
|
||||||
|
|
||||||
|
supportEasyMDE: boolean;
|
||||||
easyMDE: any;
|
easyMDE: any;
|
||||||
easyMDEToolbarActions: any;
|
easyMDEToolbarActions: any;
|
||||||
easyMDEToolbarDefault: any;
|
easyMDEToolbarDefault: any;
|
||||||
@ -71,11 +77,12 @@ export class ComboMarkdownEditor {
|
|||||||
dropzone: HTMLElement;
|
dropzone: HTMLElement;
|
||||||
attachedDropzoneInst: any;
|
attachedDropzoneInst: any;
|
||||||
|
|
||||||
|
previewMode: string;
|
||||||
previewUrl: string;
|
previewUrl: string;
|
||||||
previewContext: string;
|
previewContext: string;
|
||||||
previewMode: string;
|
|
||||||
|
|
||||||
constructor(container, options = {}) {
|
constructor(container, options:ComboMarkdownEditorOptions = {}) {
|
||||||
|
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
|
||||||
container._giteaComboMarkdownEditor = this;
|
container._giteaComboMarkdownEditor = this;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.container = container;
|
this.container = container;
|
||||||
@ -99,6 +106,10 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupContainer() {
|
setupContainer() {
|
||||||
|
this.supportEasyMDE = this.container.getAttribute('data-support-easy-mde') === 'true';
|
||||||
|
this.previewMode = this.container.getAttribute('data-content-mode');
|
||||||
|
this.previewUrl = this.container.getAttribute('data-preview-url');
|
||||||
|
this.previewContext = this.container.getAttribute('data-preview-context');
|
||||||
initTextExpander(this.container.querySelector('text-expander'));
|
initTextExpander(this.container.querySelector('text-expander'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,12 +148,14 @@ export class ComboMarkdownEditor {
|
|||||||
monospaceButton.setAttribute('aria-checked', String(enabled));
|
monospaceButton.setAttribute('aria-checked', String(enabled));
|
||||||
});
|
});
|
||||||
|
|
||||||
const easymdeButton = this.container.querySelector('.markdown-switch-easymde');
|
if (this.supportEasyMDE) {
|
||||||
easymdeButton.addEventListener('click', async (e) => {
|
const easymdeButton = this.container.querySelector('.markdown-switch-easymde');
|
||||||
e.preventDefault();
|
easymdeButton.addEventListener('click', async (e) => {
|
||||||
this.userPreferredEditor = 'easymde';
|
e.preventDefault();
|
||||||
await this.switchToEasyMDE();
|
this.userPreferredEditor = 'easymde';
|
||||||
});
|
await this.switchToEasyMDE();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.initMarkdownButtonTableAdd();
|
this.initMarkdownButtonTableAdd();
|
||||||
|
|
||||||
@ -187,6 +200,7 @@ export class ComboMarkdownEditor {
|
|||||||
|
|
||||||
setupTab() {
|
setupTab() {
|
||||||
const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item');
|
const tabs = this.container.querySelectorAll<HTMLElement>('.tabular.menu > .item');
|
||||||
|
if (!tabs.length) return;
|
||||||
|
|
||||||
// Fomantic Tab requires the "data-tab" to be globally unique.
|
// Fomantic Tab requires the "data-tab" to be globally unique.
|
||||||
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
|
// So here it uses our defined "data-tab-for" and "data-tab-panel" to generate the "data-tab" attribute for Fomantic.
|
||||||
@ -207,11 +221,8 @@ export class ComboMarkdownEditor {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$(tabs).tab();
|
fomanticQuery(tabs).tab();
|
||||||
|
|
||||||
this.previewUrl = this.tabPreviewer.getAttribute('data-preview-url');
|
|
||||||
this.previewContext = this.tabPreviewer.getAttribute('data-preview-context');
|
|
||||||
this.previewMode = this.options.previewMode ?? 'comment';
|
|
||||||
this.tabPreviewer.addEventListener('click', async () => {
|
this.tabPreviewer.addEventListener('click', async () => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('mode', this.previewMode);
|
formData.append('mode', this.previewMode);
|
||||||
@ -219,7 +230,7 @@ export class ComboMarkdownEditor {
|
|||||||
formData.append('text', this.value());
|
formData.append('text', this.value());
|
||||||
const response = await POST(this.previewUrl, {data: formData});
|
const response = await POST(this.previewUrl, {data: formData});
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
renderPreviewPanelContent($(panelPreviewer), data);
|
renderPreviewPanelContent(panelPreviewer, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +295,7 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async switchToUserPreference() {
|
async switchToUserPreference() {
|
||||||
if (this.userPreferredEditor === 'easymde') {
|
if (this.userPreferredEditor === 'easymde' && this.supportEasyMDE) {
|
||||||
await this.switchToEasyMDE();
|
await this.switchToEasyMDE();
|
||||||
} else {
|
} else {
|
||||||
this.switchToTextarea();
|
this.switchToTextarea();
|
||||||
@ -304,7 +315,7 @@ export class ComboMarkdownEditor {
|
|||||||
if (this.easyMDE) return;
|
if (this.easyMDE) return;
|
||||||
// EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles.
|
// EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles.
|
||||||
const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde');
|
const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde');
|
||||||
const easyMDEOpt = {
|
const easyMDEOpt: EasyMDE.Options = {
|
||||||
autoDownloadFontAwesome: false,
|
autoDownloadFontAwesome: false,
|
||||||
element: this.textarea,
|
element: this.textarea,
|
||||||
forceSync: true,
|
forceSync: true,
|
||||||
@ -384,19 +395,20 @@ export class ComboMarkdownEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get userPreferredEditor() {
|
get userPreferredEditor() {
|
||||||
return window.localStorage.getItem(`markdown-editor-${this.options.useScene ?? 'default'}`);
|
return window.localStorage.getItem(`markdown-editor-${this.previewMode ?? 'default'}`);
|
||||||
}
|
}
|
||||||
set userPreferredEditor(s) {
|
set userPreferredEditor(s) {
|
||||||
window.localStorage.setItem(`markdown-editor-${this.options.useScene ?? 'default'}`, s);
|
window.localStorage.setItem(`markdown-editor-${this.previewMode ?? 'default'}`, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getComboMarkdownEditor(el) {
|
export function getComboMarkdownEditor(el) {
|
||||||
if (el instanceof $) el = el[0];
|
if (!el) return null;
|
||||||
return el?._giteaComboMarkdownEditor;
|
if (el.length) el = el[0];
|
||||||
|
return el._giteaComboMarkdownEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initComboMarkdownEditor(container: HTMLElement, options = {}) {
|
export async function initComboMarkdownEditor(container: HTMLElement, options:ComboMarkdownEditorOptions = {}) {
|
||||||
if (!container) {
|
if (!container) {
|
||||||
throw new Error('initComboMarkdownEditor: container is null');
|
throw new Error('initComboMarkdownEditor: container is null');
|
||||||
}
|
}
|
||||||
|
@ -201,10 +201,8 @@ export function initRepoEditor() {
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPreviewPanelContent($previewPanel, data) {
|
export function renderPreviewPanelContent(previewPanel: Element, content: string) {
|
||||||
$previewPanel.html(data);
|
previewPanel.innerHTML = content;
|
||||||
initMarkupContent();
|
initMarkupContent();
|
||||||
|
attachRefIssueContextPopup(previewPanel.querySelectorAll('p .ref-issue'));
|
||||||
const $refIssues = $previewPanel.find('p .ref-issue');
|
|
||||||
attachRefIssueContextPopup($refIssues);
|
|
||||||
}
|
}
|
||||||
|
@ -414,11 +414,6 @@ export function initRepoPullRequestReview() {
|
|||||||
await handleReply(this);
|
await handleReply(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
const elReviewBox = document.querySelector('.review-box-panel');
|
|
||||||
if (elReviewBox) {
|
|
||||||
initComboMarkdownEditor(elReviewBox.querySelector('.combo-markdown-editor'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following part is only for diff views
|
// The following part is only for diff views
|
||||||
if (!$('.repository.pull.diff').length) return;
|
if (!$('.repository.pull.diff').length) return;
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {hideElem, showElem} from '../utils/dom.ts';
|
import {hideElem, showElem} from '../utils/dom.ts';
|
||||||
import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
|
||||||
|
|
||||||
export function initRepoRelease() {
|
export function initRepoRelease() {
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
@ -16,7 +15,6 @@ export function initRepoReleaseNew() {
|
|||||||
if (!document.querySelector('.repository.new.release')) return;
|
if (!document.querySelector('.repository.new.release')) return;
|
||||||
|
|
||||||
initTagNameEditor();
|
initTagNameEditor();
|
||||||
initRepoReleaseEditor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTagNameEditor() {
|
function initTagNameEditor() {
|
||||||
@ -48,11 +46,3 @@ function initTagNameEditor() {
|
|||||||
hideTargetInput(e.target);
|
hideTargetInput(e.target);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initRepoReleaseEditor() {
|
|
||||||
const editor = document.querySelector<HTMLElement>('.repository.new.release .combo-markdown-editor');
|
|
||||||
if (!editor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
initComboMarkdownEditor(editor);
|
|
||||||
}
|
|
||||||
|
@ -2,6 +2,7 @@ import {initMarkupContent} from '../markup/content.ts';
|
|||||||
import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
import {validateTextareaNonEmpty, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
import {fomanticMobileScreen} from '../modules/fomantic.ts';
|
import {fomanticMobileScreen} from '../modules/fomantic.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
|
import type {ComboMarkdownEditor} from './comp/ComboMarkdownEditor.ts';
|
||||||
|
|
||||||
async function initRepoWikiFormEditor() {
|
async function initRepoWikiFormEditor() {
|
||||||
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
|
const editArea = document.querySelector<HTMLTextAreaElement>('.repository.wiki .combo-markdown-editor textarea');
|
||||||
@ -9,7 +10,7 @@ async function initRepoWikiFormEditor() {
|
|||||||
|
|
||||||
const form = document.querySelector('.repository.wiki.new .ui.form');
|
const form = document.querySelector('.repository.wiki.new .ui.form');
|
||||||
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor');
|
const editorContainer = form.querySelector<HTMLElement>('.combo-markdown-editor');
|
||||||
let editor;
|
let editor: ComboMarkdownEditor;
|
||||||
|
|
||||||
let renderRequesting = false;
|
let renderRequesting = false;
|
||||||
let lastContent;
|
let lastContent;
|
||||||
@ -45,12 +46,10 @@ async function initRepoWikiFormEditor() {
|
|||||||
renderEasyMDEPreview();
|
renderEasyMDEPreview();
|
||||||
|
|
||||||
editor = await initComboMarkdownEditor(editorContainer, {
|
editor = await initComboMarkdownEditor(editorContainer, {
|
||||||
useScene: 'wiki',
|
|
||||||
// EasyMDE has some problems of height definition, it has inline style height 300px by default, so we also use inline styles to override it.
|
// EasyMDE has some problems of height definition, it has inline style height 300px by default, so we also use inline styles to override it.
|
||||||
// And another benefit is that we only need to write the style once for both editors.
|
// And another benefit is that we only need to write the style once for both editors.
|
||||||
// TODO: Move height style to CSS after EasyMDE removal.
|
// TODO: Move height style to CSS after EasyMDE removal.
|
||||||
editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'},
|
editorHeights: {minHeight: '300px', height: 'calc(100vh - 600px)'},
|
||||||
previewMode: 'wiki',
|
|
||||||
easyMDEOptions: {
|
easyMDEOptions: {
|
||||||
previewRender: (_content, previewTarget) => previewTarget.innerHTML, // disable builtin preview render
|
previewRender: (_content, previewTarget) => previewTarget.innerHTML, // disable builtin preview render
|
||||||
toolbar: ['bold', 'italic', 'strikethrough', '|',
|
toolbar: ['bold', 'italic', 'strikethrough', '|',
|
||||||
@ -59,7 +58,7 @@ async function initRepoWikiFormEditor() {
|
|||||||
'unordered-list', 'ordered-list', '|',
|
'unordered-list', 'ordered-list', '|',
|
||||||
'link', 'image', 'table', 'horizontal-rule', '|',
|
'link', 'image', 'table', 'horizontal-rule', '|',
|
||||||
'preview', 'fullscreen', 'side-by-side', '|', 'gitea-switch-to-textarea',
|
'preview', 'fullscreen', 'side-by-side', '|', 'gitea-switch-to-textarea',
|
||||||
],
|
] as any, // to use custom toolbar buttons
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -83,7 +83,11 @@ import {
|
|||||||
initGlobalButtons,
|
initGlobalButtons,
|
||||||
initGlobalDeleteButton,
|
initGlobalDeleteButton,
|
||||||
} from './features/common-button.ts';
|
} from './features/common-button.ts';
|
||||||
import {initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
import {
|
||||||
|
initGlobalComboMarkdownEditor,
|
||||||
|
initGlobalEnterQuickSubmit,
|
||||||
|
initGlobalFormDirtyLeaveConfirm,
|
||||||
|
} from './features/common-form.ts';
|
||||||
|
|
||||||
initGiteaFomantic();
|
initGiteaFomantic();
|
||||||
initDirAuto();
|
initDirAuto();
|
||||||
@ -127,6 +131,7 @@ onDomReady(() => {
|
|||||||
initGlobalCopyToClipboardListener,
|
initGlobalCopyToClipboardListener,
|
||||||
initGlobalEnterQuickSubmit,
|
initGlobalEnterQuickSubmit,
|
||||||
initGlobalFormDirtyLeaveConfirm,
|
initGlobalFormDirtyLeaveConfirm,
|
||||||
|
initGlobalComboMarkdownEditor,
|
||||||
initGlobalDeleteButton,
|
initGlobalDeleteButton,
|
||||||
|
|
||||||
initCommonOrganization,
|
initCommonOrganization,
|
||||||
|
Loading…
Reference in New Issue
Block a user