mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-02 20:06:06 +01:00 
			
		
		
		
	Editor preview support for external renderers (#23333)
Remove `[repository.editor] PREVIEWABLE_FILE_MODES` setting that seemed like it was intended to support this but did not work. Instead, whenever viewing a file shows a preview, also have a Preview tab in the file editor. Add new `/markup` web and API endpoints with `comment`, `gfm`, `markdown` and new `file` mode that uses a file path to determine the renderer. Remove `/markdown` web endpoint but keep the API for backwards and GitHub compatibility. ## ⚠️ BREAKING ⚠️ The `[repository.editor] PREVIEWABLE_FILE_MODES` setting was removed. This setting served no practical purpose and was not working correctly. Instead a preview tab is always shown in the file editor when supported. --------- Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							9e04627aca
						
					
				
				
					commit
					84daddc2fa
				
			@@ -993,10 +993,6 @@ ROUTER = console
 | 
			
		||||
;; List of file extensions for which lines should be wrapped in the Monaco editor
 | 
			
		||||
;; Separate extensions with a comma. To line wrap files without an extension, just put a comma
 | 
			
		||||
;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
 | 
			
		||||
;;
 | 
			
		||||
;; Valid file modes that have a preview API associated with them, such as api/v1/markdown
 | 
			
		||||
;; Separate the values by commas. The preview tab in edit mode won't be displayed if the file extension doesn't match
 | 
			
		||||
;PREVIEWABLE_FILE_MODES = markdown
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
 
 | 
			
		||||
@@ -283,6 +283,11 @@ type ErrUnsupportedRenderExtension struct {
 | 
			
		||||
	Extension string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsErrUnsupportedRenderExtension(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrUnsupportedRenderExtension)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrUnsupportedRenderExtension) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Unsupported render extension: %s", err.Extension)
 | 
			
		||||
}
 | 
			
		||||
@@ -317,3 +322,11 @@ func IsMarkupFile(name, markup string) bool {
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func PreviewableExtensions() []string {
 | 
			
		||||
	extensions := make([]string, 0, len(extRenderers))
 | 
			
		||||
	for extension := range extRenderers {
 | 
			
		||||
		extensions = append(extensions, extension)
 | 
			
		||||
	}
 | 
			
		||||
	return extensions
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,6 @@ var (
 | 
			
		||||
		// Repository editor settings
 | 
			
		||||
		Editor struct {
 | 
			
		||||
			LineWrapExtensions []string
 | 
			
		||||
			PreviewableFileModes []string
 | 
			
		||||
		} `ini:"-"`
 | 
			
		||||
 | 
			
		||||
		// Repository upload settings
 | 
			
		||||
@@ -168,10 +167,8 @@ var (
 | 
			
		||||
		// Repository editor settings
 | 
			
		||||
		Editor: struct {
 | 
			
		||||
			LineWrapExtensions []string
 | 
			
		||||
			PreviewableFileModes []string
 | 
			
		||||
		}{
 | 
			
		||||
			LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","),
 | 
			
		||||
			PreviewableFileModes: []string{"markdown"},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// Repository upload settings
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,41 @@ type SearchError struct {
 | 
			
		||||
	Error string `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkupOption markup options
 | 
			
		||||
type MarkupOption struct {
 | 
			
		||||
	// Text markup to render
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Text string
 | 
			
		||||
	// Mode to render (comment, gfm, markdown, file)
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Mode string
 | 
			
		||||
	// Context to render
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Context string
 | 
			
		||||
	// Is it a wiki page ?
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Wiki bool
 | 
			
		||||
	// File path for detecting extension in file mode
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	FilePath string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkupRender is a rendered markup document
 | 
			
		||||
// swagger:response MarkupRender
 | 
			
		||||
type MarkupRender string
 | 
			
		||||
 | 
			
		||||
// MarkdownOption markdown options
 | 
			
		||||
type MarkdownOption struct {
 | 
			
		||||
	// Text markdown to render
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Text string
 | 
			
		||||
	// Mode to render
 | 
			
		||||
	// Mode to render (comment, gfm, markdown)
 | 
			
		||||
	//
 | 
			
		||||
	// in: body
 | 
			
		||||
	Mode string
 | 
			
		||||
 
 | 
			
		||||
@@ -711,6 +711,7 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		m.Get("/signing-key.gpg", misc.SigningKey)
 | 
			
		||||
		m.Post("/markup", bind(api.MarkupOption{}), misc.Markup)
 | 
			
		||||
		m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
 | 
			
		||||
		m.Post("/markdown/raw", misc.MarkdownRaw)
 | 
			
		||||
		m.Group("/settings", func() {
 | 
			
		||||
@@ -1034,6 +1035,7 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
						Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel).
 | 
			
		||||
						Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel)
 | 
			
		||||
				})
 | 
			
		||||
				m.Post("/markup", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkupOption{}), misc.Markup)
 | 
			
		||||
				m.Post("/markdown", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkdownOption{}), misc.Markdown)
 | 
			
		||||
				m.Post("/markdown/raw", reqToken(auth_model.AccessTokenScopeRepo), misc.MarkdownRaw)
 | 
			
		||||
				m.Group("/milestones", func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,19 +5,45 @@ package misc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
 | 
			
		||||
	"mvdan.cc/xurls/v2"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Markup render markup document to HTML
 | 
			
		||||
func Markup(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /markup miscellaneous renderMarkup
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Render a markup document as HTML
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/MarkupOption"
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	//     - text/html
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/MarkupRender"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.MarkupOption)
 | 
			
		||||
 | 
			
		||||
	if ctx.HasAPIError() {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	common.RenderMarkup(ctx.Context, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Markdown render markdown document to HTML
 | 
			
		||||
func Markdown(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /markdown miscellaneous renderMarkdown
 | 
			
		||||
@@ -45,55 +71,12 @@ func Markdown(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(form.Text) == 0 {
 | 
			
		||||
		_, _ = ctx.Write([]byte(""))
 | 
			
		||||
		return
 | 
			
		||||
	mode := "markdown"
 | 
			
		||||
	if form.Mode == "comment" || form.Mode == "gfm" {
 | 
			
		||||
		mode = form.Mode
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch form.Mode {
 | 
			
		||||
	case "comment":
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case "gfm":
 | 
			
		||||
		urlPrefix := form.Context
 | 
			
		||||
		meta := map[string]string{}
 | 
			
		||||
		if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
 | 
			
		||||
			// check if urlPrefix is already set to a URL
 | 
			
		||||
			linkRegex, _ := xurls.StrictMatchingScheme("https?://")
 | 
			
		||||
			m := linkRegex.FindStringIndex(urlPrefix)
 | 
			
		||||
			if m == nil {
 | 
			
		||||
				urlPrefix = util.URLJoin(setting.AppURL, form.Context)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if ctx.Repo != nil && ctx.Repo.Repository != nil {
 | 
			
		||||
			// "gfm" = Github Flavored Markdown - set this to render as a document
 | 
			
		||||
			if form.Mode == "gfm" {
 | 
			
		||||
				meta = ctx.Repo.Repository.ComposeDocumentMetas()
 | 
			
		||||
			} else {
 | 
			
		||||
				meta = ctx.Repo.Repository.ComposeMetas()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if form.Mode == "gfm" {
 | 
			
		||||
			meta["mode"] = "document"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := markdown.Render(&markup.RenderContext{
 | 
			
		||||
			Ctx:       ctx,
 | 
			
		||||
			URLPrefix: urlPrefix,
 | 
			
		||||
			Metas:     meta,
 | 
			
		||||
			IsWiki:    form.Wiki,
 | 
			
		||||
		}, strings.NewReader(form.Text), ctx.Resp); err != nil {
 | 
			
		||||
			ctx.InternalServerError(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if err := markdown.RenderRaw(&markup.RenderContext{
 | 
			
		||||
			Ctx:       ctx,
 | 
			
		||||
			URLPrefix: form.Context,
 | 
			
		||||
		}, strings.NewReader(form.Text), ctx.Resp); err != nil {
 | 
			
		||||
			ctx.InternalServerError(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	common.RenderMarkup(ctx.Context, mode, form.Text, form.Context, "", form.Wiki)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarkdownRaw render raw markdown HTML
 | 
			
		||||
@@ -49,16 +49,37 @@ func wrap(ctx *context.Context) *context.APIContext {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPI_RenderGFM(t *testing.T) {
 | 
			
		||||
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
 | 
			
		||||
	setting.AppURL = AppURL
 | 
			
		||||
 | 
			
		||||
	options := api.MarkupOption{
 | 
			
		||||
		Mode:     mode,
 | 
			
		||||
		Text:     "",
 | 
			
		||||
		Context:  Repo,
 | 
			
		||||
		Wiki:     true,
 | 
			
		||||
		FilePath: filePath,
 | 
			
		||||
	}
 | 
			
		||||
	requrl, _ := url.Parse(util.URLJoin(AppURL, "api", "v1", "markup"))
 | 
			
		||||
	req := &http.Request{
 | 
			
		||||
		Method: "POST",
 | 
			
		||||
		URL:    requrl,
 | 
			
		||||
	}
 | 
			
		||||
	m, resp := createContext(req)
 | 
			
		||||
	ctx := wrap(m)
 | 
			
		||||
 | 
			
		||||
	options.Text = text
 | 
			
		||||
	web.SetForm(ctx, &options)
 | 
			
		||||
	Markup(ctx)
 | 
			
		||||
	assert.Equal(t, responseBody, resp.Body.String())
 | 
			
		||||
	assert.Equal(t, responseCode, resp.Code)
 | 
			
		||||
	resp.Body.Reset()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
 | 
			
		||||
	setting.AppURL = AppURL
 | 
			
		||||
	markup.Init(&markup.ProcessorHelper{
 | 
			
		||||
		IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
 | 
			
		||||
			return username == "r-lyeh"
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	options := api.MarkdownOption{
 | 
			
		||||
		Mode:    "gfm",
 | 
			
		||||
		Mode:    mode,
 | 
			
		||||
		Text:    "",
 | 
			
		||||
		Context: Repo,
 | 
			
		||||
		Wiki:    true,
 | 
			
		||||
@@ -71,7 +92,22 @@ func TestAPI_RenderGFM(t *testing.T) {
 | 
			
		||||
	m, resp := createContext(req)
 | 
			
		||||
	ctx := wrap(m)
 | 
			
		||||
 | 
			
		||||
	testCases := []string{
 | 
			
		||||
	options.Text = text
 | 
			
		||||
	web.SetForm(ctx, &options)
 | 
			
		||||
	Markdown(ctx)
 | 
			
		||||
	assert.Equal(t, responseBody, resp.Body.String())
 | 
			
		||||
	assert.Equal(t, responseCode, resp.Code)
 | 
			
		||||
	resp.Body.Reset()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPI_RenderGFM(t *testing.T) {
 | 
			
		||||
	markup.Init(&markup.ProcessorHelper{
 | 
			
		||||
		IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
 | 
			
		||||
			return username == "r-lyeh"
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	testCasesCommon := []string{
 | 
			
		||||
		// dear imgui wiki markdown extract: special wiki syntax
 | 
			
		||||
		`Wiki! Enjoy :)
 | 
			
		||||
- [[Links, Language bindings, Engine bindings|Links]]
 | 
			
		||||
@@ -85,6 +121,23 @@ func TestAPI_RenderGFM(t *testing.T) {
 | 
			
		||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
 | 
			
		||||
</ul>
 | 
			
		||||
`,
 | 
			
		||||
		// Guard wiki sidebar: special syntax
 | 
			
		||||
		`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
 | 
			
		||||
		// rendered
 | 
			
		||||
		`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
 | 
			
		||||
`,
 | 
			
		||||
		// special syntax
 | 
			
		||||
		`[[Name|Link]]`,
 | 
			
		||||
		// rendered
 | 
			
		||||
		`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
 | 
			
		||||
`,
 | 
			
		||||
		// empty
 | 
			
		||||
		``,
 | 
			
		||||
		// rendered
 | 
			
		||||
		``,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testCasesDocument := []string{
 | 
			
		||||
		// wine-staging wiki home extract: special wiki syntax, images
 | 
			
		||||
		`## What is Wine Staging?
 | 
			
		||||
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
 | 
			
		||||
@@ -103,29 +156,28 @@ Here are some links to the most important topics. You can find the full list of
 | 
			
		||||
<p><a href="` + AppSubURL + `wiki/Configuration" rel="nofollow">Configuration</a>
 | 
			
		||||
<a href="` + AppSubURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + AppSubURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
 | 
			
		||||
`,
 | 
			
		||||
		// Guard wiki sidebar: special syntax
 | 
			
		||||
		`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
 | 
			
		||||
		// rendered
 | 
			
		||||
		`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
 | 
			
		||||
`,
 | 
			
		||||
		// special syntax
 | 
			
		||||
		`[[Name|Link]]`,
 | 
			
		||||
		// rendered
 | 
			
		||||
		`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p>
 | 
			
		||||
`,
 | 
			
		||||
		// empty
 | 
			
		||||
		``,
 | 
			
		||||
		// rendered
 | 
			
		||||
		``,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(testCases); i += 2 {
 | 
			
		||||
		options.Text = testCases[i]
 | 
			
		||||
		web.SetForm(ctx, &options)
 | 
			
		||||
		Markdown(ctx)
 | 
			
		||||
		assert.Equal(t, testCases[i+1], resp.Body.String())
 | 
			
		||||
		resp.Body.Reset()
 | 
			
		||||
	for i := 0; i < len(testCasesCommon); i += 2 {
 | 
			
		||||
		text := testCasesCommon[i]
 | 
			
		||||
		response := testCasesCommon[i+1]
 | 
			
		||||
		testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkdown(t, "comment", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(testCasesDocument); i += 2 {
 | 
			
		||||
		text := testCasesDocument[i]
 | 
			
		||||
		response := testCasesDocument[i+1]
 | 
			
		||||
		testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
 | 
			
		||||
		testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
 | 
			
		||||
	testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var simpleCases = []string{
 | 
			
		||||
@@ -56,6 +56,8 @@ type swaggerParameterBodies struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	EditLabelOption api.EditLabelOption
 | 
			
		||||
 | 
			
		||||
	// in:body
 | 
			
		||||
	MarkupOption api.MarkupOption
 | 
			
		||||
	// in:body
 | 
			
		||||
	MarkdownOption api.MarkdownOption
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								routers/common/markup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								routers/common/markup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
// Copyright 2014 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"mvdan.cc/xurls/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
 | 
			
		||||
func RenderMarkup(ctx *context.Context, mode, text, urlPrefix, filePath string, wiki bool) {
 | 
			
		||||
	markupType := ""
 | 
			
		||||
	relativePath := ""
 | 
			
		||||
 | 
			
		||||
	if len(text) == 0 {
 | 
			
		||||
		_, _ = ctx.Write([]byte(""))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch mode {
 | 
			
		||||
	case "markdown":
 | 
			
		||||
		// Raw markdown
 | 
			
		||||
		if err := markdown.RenderRaw(&markup.RenderContext{
 | 
			
		||||
			Ctx:       ctx,
 | 
			
		||||
			URLPrefix: urlPrefix,
 | 
			
		||||
		}, strings.NewReader(text), ctx.Resp); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	case "comment":
 | 
			
		||||
		// Comment as markdown
 | 
			
		||||
		markupType = markdown.MarkupName
 | 
			
		||||
	case "gfm":
 | 
			
		||||
		// Github Flavored Markdown as document
 | 
			
		||||
		markupType = markdown.MarkupName
 | 
			
		||||
	case "file":
 | 
			
		||||
		// File as document based on file extension
 | 
			
		||||
		markupType = ""
 | 
			
		||||
		relativePath = filePath
 | 
			
		||||
	default:
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
 | 
			
		||||
		// check if urlPrefix is already set to a URL
 | 
			
		||||
		linkRegex, _ := xurls.StrictMatchingScheme("https?://")
 | 
			
		||||
		m := linkRegex.FindStringIndex(urlPrefix)
 | 
			
		||||
		if m == nil {
 | 
			
		||||
			urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	meta := map[string]string{}
 | 
			
		||||
	if ctx.Repo != nil && ctx.Repo.Repository != nil {
 | 
			
		||||
		if mode == "comment" {
 | 
			
		||||
			meta = ctx.Repo.Repository.ComposeMetas()
 | 
			
		||||
		} else {
 | 
			
		||||
			meta = ctx.Repo.Repository.ComposeDocumentMetas()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if mode != "comment" {
 | 
			
		||||
		meta["mode"] = "document"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := markup.Render(&markup.RenderContext{
 | 
			
		||||
		Ctx:          ctx,
 | 
			
		||||
		URLPrefix:    urlPrefix,
 | 
			
		||||
		Metas:        meta,
 | 
			
		||||
		IsWiki:       wiki,
 | 
			
		||||
		Type:         markupType,
 | 
			
		||||
		RelativePath: relativePath,
 | 
			
		||||
	}, strings.NewReader(text), ctx.Resp); err != nil {
 | 
			
		||||
		if markup.IsErrUnsupportedRenderExtension(err) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, err.Error())
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, err.Error())
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,98 +0,0 @@
 | 
			
		||||
// Copyright 2014 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package misc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup/markdown"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
 | 
			
		||||
	"mvdan.cc/xurls/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Markdown render markdown document to HTML
 | 
			
		||||
func Markdown(ctx *context.Context) {
 | 
			
		||||
	// swagger:operation POST /markdown miscellaneous renderMarkdown
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Render a markdown document as HTML
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/MarkdownOption"
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	//     - text/html
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/MarkdownRender"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.MarkdownOption)
 | 
			
		||||
 | 
			
		||||
	if ctx.HasAPIError() {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(form.Text) == 0 {
 | 
			
		||||
		_, _ = ctx.Write([]byte(""))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch form.Mode {
 | 
			
		||||
	case "comment":
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case "gfm":
 | 
			
		||||
		urlPrefix := form.Context
 | 
			
		||||
		meta := map[string]string{}
 | 
			
		||||
		if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
 | 
			
		||||
			// check if urlPrefix is already set to a URL
 | 
			
		||||
			linkRegex, _ := xurls.StrictMatchingScheme("https?://")
 | 
			
		||||
			m := linkRegex.FindStringIndex(urlPrefix)
 | 
			
		||||
			if m == nil {
 | 
			
		||||
				urlPrefix = util.URLJoin(setting.AppURL, form.Context)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if ctx.Repo != nil && ctx.Repo.Repository != nil {
 | 
			
		||||
			// "gfm" = Github Flavored Markdown - set this to render as a document
 | 
			
		||||
			if form.Mode == "gfm" {
 | 
			
		||||
				meta = ctx.Repo.Repository.ComposeDocumentMetas()
 | 
			
		||||
			} else {
 | 
			
		||||
				meta = ctx.Repo.Repository.ComposeMetas()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if form.Mode == "gfm" {
 | 
			
		||||
			meta["mode"] = "document"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := markdown.Render(&markup.RenderContext{
 | 
			
		||||
			Ctx:       ctx,
 | 
			
		||||
			URLPrefix: urlPrefix,
 | 
			
		||||
			Metas:     meta,
 | 
			
		||||
			IsWiki:    form.Wiki,
 | 
			
		||||
		}, strings.NewReader(form.Text), ctx.Resp); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, err.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if err := markdown.RenderRaw(&markup.RenderContext{
 | 
			
		||||
			Ctx:       ctx,
 | 
			
		||||
			URLPrefix: form.Context,
 | 
			
		||||
		}, strings.NewReader(form.Text), ctx.Resp); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, err.Error())
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								routers/web/misc/markup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								routers/web/misc/markup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
// Copyright 2014 The Gogs Authors. All rights reserved.
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package misc
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Markup render markup document to HTML
 | 
			
		||||
func Markup(ctx *context.Context) {
 | 
			
		||||
	// swagger:operation POST /markup miscellaneous renderMarkup
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Render a markup document as HTML
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/MarkupOption"
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	//     - text/html
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/MarkupRender"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.MarkupOption)
 | 
			
		||||
 | 
			
		||||
	if ctx.HasAPIError() {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	common.RenderMarkup(ctx, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +20,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/typesniffer"
 | 
			
		||||
	"code.gitea.io/gitea/modules/upload"
 | 
			
		||||
@@ -155,9 +156,8 @@ func editFile(ctx *context.Context, isNewFile bool) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
 | 
			
		||||
	ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
			
		||||
	ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
 | 
			
		||||
	ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
 | 
			
		||||
	ctx.Data["Editorconfig"] = GetEditorConfig(ctx, treePath)
 | 
			
		||||
 | 
			
		||||
	ctx.HTML(http.StatusOK, tplEditFile)
 | 
			
		||||
@@ -207,9 +207,8 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
 | 
			
		||||
	ctx.Data["commit_choice"] = form.CommitChoice
 | 
			
		||||
	ctx.Data["new_branch_name"] = form.NewBranchName
 | 
			
		||||
	ctx.Data["last_commit"] = ctx.Repo.CommitID
 | 
			
		||||
	ctx.Data["MarkdownFileExts"] = strings.Join(setting.Markdown.FileExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
 | 
			
		||||
	ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
 | 
			
		||||
	ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
 | 
			
		||||
	ctx.Data["Editorconfig"] = GetEditorConfig(ctx, form.TreePath)
 | 
			
		||||
 | 
			
		||||
	if ctx.HasError() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1115,7 +1115,7 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		||||
		m.Group("/comments/{id}", func() {
 | 
			
		||||
			m.Get("/attachments", repo.GetCommentAttachments)
 | 
			
		||||
		})
 | 
			
		||||
		m.Post("/markdown", web.Bind(structs.MarkdownOption{}), misc.Markdown)
 | 
			
		||||
		m.Post("/markup", web.Bind(structs.MarkupOption{}), misc.Markup)
 | 
			
		||||
		m.Group("/labels", func() {
 | 
			
		||||
			m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel)
 | 
			
		||||
			m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel)
 | 
			
		||||
 
 | 
			
		||||
@@ -192,7 +192,7 @@
 | 
			
		||||
				<div class="ui comment form">
 | 
			
		||||
					<div class="ui top attached tabular menu">
 | 
			
		||||
						<a class="active write item">{{$.locale.Tr "write"}}</a>
 | 
			
		||||
						<a class="preview item" data-url="{{$.Repository.Link}}/markdown" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
						<a class="preview item" data-url="{{$.Repository.Link}}/markup" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui bottom attached active write tab segment">
 | 
			
		||||
						<textarea class="review-textarea js-quick-submit" tabindex="1" name="content"></textarea>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
		<input type="hidden" name="diff_base_cid">
 | 
			
		||||
		<div class="ui top tabular menu" data-write="write" data-preview="preview">
 | 
			
		||||
			<a class="active item" data-tab="write">{{$.root.locale.Tr "write"}}</a>
 | 
			
		||||
			<a class="item" data-tab="preview" data-url="{{$.root.Repository.Link}}/markdown" data-context="{{$.root.RepoLink}}">{{$.root.locale.Tr "preview"}}</a>
 | 
			
		||||
			<a class="item" data-tab="preview" data-url="{{$.root.Repository.Link}}/markup" data-context="{{$.root.RepoLink}}">{{$.root.locale.Tr "preview"}}</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="field">
 | 
			
		||||
			<div class="ui active tab" data-tab="write">
 | 
			
		||||
 
 | 
			
		||||
@@ -31,15 +31,15 @@
 | 
			
		||||
				<div class="ui top attached tabular menu" data-write="write" data-preview="preview" data-diff="diff">
 | 
			
		||||
					<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{.locale.Tr "repo.editor.new_file"}}{{else}}{{.locale.Tr "repo.editor.edit_file"}}{{end}}</a>
 | 
			
		||||
					{{if not .IsNewFile}}
 | 
			
		||||
					<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markdown" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL}}" data-preview-file-modes="{{.PreviewableFileModes}}" data-markdown-mode="gfm">{{svg "octicon-eye"}} {{.locale.Tr "preview"}}</a>
 | 
			
		||||
					<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}/src/{{.BranchNameSubURL}}" data-markup-mode="file">{{svg "octicon-eye"}} {{.locale.Tr "preview"}}</a>
 | 
			
		||||
					<a class="item" data-tab="diff" data-url="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}" data-context="{{.BranchLink}}">{{svg "octicon-diff"}} {{.locale.Tr "repo.editor.preview_changes"}}</a>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="ui bottom attached active tab segment" data-tab="write">
 | 
			
		||||
					<textarea id="edit_area" name="content" class="gt-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
 | 
			
		||||
						data-url="{{.Repository.Link}}/markdown"
 | 
			
		||||
						data-url="{{.Repository.Link}}/markup"
 | 
			
		||||
						data-context="{{.RepoLink}}"
 | 
			
		||||
						data-markdown-file-exts="{{.MarkdownFileExts}}"
 | 
			
		||||
						data-previewable-extensions="{{.PreviewableExtensions}}"
 | 
			
		||||
						data-line-wrap-extensions="{{.LineWrapExtensions}}">
 | 
			
		||||
{{.FileContent}}</textarea>
 | 
			
		||||
					<div class="editor-loading is-loading"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
	<div class="ui top tabular menu" data-write="write" data-preview="preview">
 | 
			
		||||
		<a class="active item" data-tab="write">{{.locale.Tr "write"}}</a>
 | 
			
		||||
		<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markdown" data-context="{{.RepoLink}}">{{.locale.Tr "preview"}}</a>
 | 
			
		||||
		<a class="item" data-tab="preview" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}">{{.locale.Tr "preview"}}</a>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<div class="ui bottom active tab" data-tab="write">
 | 
			
		||||
		<textarea id="content" class="edit_area js-quick-submit" name="content" tabindex="4" data-id="issue-{{.RepoName}}" data-url="{{.Repository.Link}}/markdown" data-context="{{.Repo.RepoLink}}">
 | 
			
		||||
		<textarea id="content" class="edit_area js-quick-submit" name="content" tabindex="4" data-id="issue-{{.RepoName}}" data-url="{{.Repository.Link}}/markup" data-context="{{.Repo.RepoLink}}">
 | 
			
		||||
			{{- if .BodyQuery}}{{.BodyQuery}}{{else if .IssueTemplate}}{{.IssueTemplate}}{{else if .PullRequestTemplate}}{{.PullRequestTemplate}}{{else}}{{.content}}{{end -}}
 | 
			
		||||
		</textarea>
 | 
			
		||||
		</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -168,7 +168,7 @@
 | 
			
		||||
	<div class="ui comment form">
 | 
			
		||||
		<div class="ui top tabular menu">
 | 
			
		||||
			<a class="active write item">{{$.locale.Tr "write"}}</a>
 | 
			
		||||
			<a class="preview item" data-url="{{$.Repository.Link}}/markdown" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
			<a class="preview item" data-url="{{$.Repository.Link}}/markup" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="field">
 | 
			
		||||
			<div class="ui bottom active tab write">
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
					<label>{{.locale.Tr "repo.release.content"}}</label>
 | 
			
		||||
					<div class="ui top tabular menu" data-write="write" data-preview="preview">
 | 
			
		||||
						<a class="active write item" data-tab="write">{{$.locale.Tr "write"}}</a>
 | 
			
		||||
						<a class="preview item" data-tab="preview" data-url="{{$.Repository.Link}}/markdown" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
						<a class="preview item" data-tab="preview" data-url="{{$.Repository.Link}}/markup" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="ui bottom active tab" data-tab="write">
 | 
			
		||||
						<textarea name="content">{{.content}}</textarea>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,11 @@
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="ui top attached tabular menu previewtabs" data-write="write" data-preview="preview">
 | 
			
		||||
				<a class="active item" data-tab="write">{{.locale.Tr "write"}}</a>
 | 
			
		||||
				<a class="item" data-tab="preview" data-url="{{$.Repository.Link}}/markdown" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
				<a class="item" data-tab="preview" data-url="{{$.Repository.Link}}/markup" data-context="{{$.RepoLink}}">{{$.locale.Tr "preview"}}</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field content" data-loading="{{.locale.Tr "loading"}}">
 | 
			
		||||
				<div class="ui bottom active tab" data-tab="write">
 | 
			
		||||
					<textarea class="js-quick-submit" id="edit_area" name="content" data-id="wiki-{{.title}}" data-url="{{.Repository.Link}}/markdown" data-context="{{.RepoLink}}">{{if .PageIsWikiEdit}}{{.content}}{{else}}{{.locale.Tr "repo.wiki.welcome"}}{{end}}</textarea>
 | 
			
		||||
					<textarea class="js-quick-submit" id="edit_area" name="content" data-id="wiki-{{.title}}" data-url="{{.Repository.Link}}/markup" data-context="{{.RepoLink}}">{{if .PageIsWikiEdit}}{{.content}}{{else}}{{.locale.Tr "repo.wiki.welcome"}}{{end}}</textarea>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
			<div class="field">
 | 
			
		||||
 
 | 
			
		||||
@@ -951,6 +951,38 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/markup": {
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "text/html"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "miscellaneous"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Render a markup document as HTML",
 | 
			
		||||
        "operationId": "renderMarkup",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/MarkupOption"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/MarkupRender"
 | 
			
		||||
          },
 | 
			
		||||
          "422": {
 | 
			
		||||
            "$ref": "#/responses/validationError"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/nodeinfo": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
@@ -17991,7 +18023,7 @@
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "Mode": {
 | 
			
		||||
          "description": "Mode to render\n\nin: body",
 | 
			
		||||
          "description": "Mode to render (comment, gfm, markdown)\n\nin: body",
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "Text": {
 | 
			
		||||
@@ -18005,6 +18037,33 @@
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "MarkupOption": {
 | 
			
		||||
      "description": "MarkupOption markup options",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "Context": {
 | 
			
		||||
          "description": "Context to render\n\nin: body",
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "FilePath": {
 | 
			
		||||
          "description": "File path for detecting extension in file mode\n\nin: body",
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "Mode": {
 | 
			
		||||
          "description": "Mode to render (comment, gfm, markdown, file)\n\nin: body",
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "Text": {
 | 
			
		||||
          "description": "Text markup to render\n\nin: body",
 | 
			
		||||
          "type": "string"
 | 
			
		||||
        },
 | 
			
		||||
        "Wiki": {
 | 
			
		||||
          "description": "Is it a wiki page ?\n\nin: body",
 | 
			
		||||
          "type": "boolean"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "MergePullRequestOption": {
 | 
			
		||||
      "description": "MergePullRequestForm form for merging Pull Request",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
@@ -20835,6 +20894,12 @@
 | 
			
		||||
        "type": "string"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "MarkupRender": {
 | 
			
		||||
      "description": "MarkupRender is a rendered markup document",
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "type": "string"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "Milestone": {
 | 
			
		||||
      "description": "Milestone",
 | 
			
		||||
      "schema": {
 | 
			
		||||
 
 | 
			
		||||
@@ -130,17 +130,17 @@ function getFileBasedOptions(filename, lineWrapExts) {
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function createCodeEditor(textarea, filenameInput, previewFileModes) {
 | 
			
		||||
export async function createCodeEditor(textarea, filenameInput) {
 | 
			
		||||
  const filename = basename(filenameInput.value);
 | 
			
		||||
  const previewLink = document.querySelector('a[data-tab=preview]');
 | 
			
		||||
  const markdownExts = (textarea.getAttribute('data-markdown-file-exts') || '').split(',');
 | 
			
		||||
  const previewableExts = (textarea.getAttribute('data-previewable-extensions') || '').split(',');
 | 
			
		||||
  const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
 | 
			
		||||
  const isMarkdown = markdownExts.includes(extname(filename));
 | 
			
		||||
  const previewable = previewableExts.includes(extname(filename));
 | 
			
		||||
  const editorConfig = getEditorconfig(filenameInput);
 | 
			
		||||
 | 
			
		||||
  if (previewLink) {
 | 
			
		||||
    if (isMarkdown && (previewFileModes || []).includes('markdown')) {
 | 
			
		||||
      const newUrl = (previewLink.getAttribute('data-url') || '').replace(/(.*)\/.*/i, `$1/markdown`);
 | 
			
		||||
    if (previewable) {
 | 
			
		||||
      const newUrl = (previewLink.getAttribute('data-url') || '').replace(/(.*)\/.*/i, `$1/markup`);
 | 
			
		||||
      previewLink.setAttribute('data-url', newUrl);
 | 
			
		||||
      previewLink.style.display = '';
 | 
			
		||||
    } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,18 +5,16 @@ import {createCodeEditor} from './codeeditor.js';
 | 
			
		||||
import {hideElem, showElem} from '../utils/dom.js';
 | 
			
		||||
 | 
			
		||||
const {csrfToken} = window.config;
 | 
			
		||||
let previewFileModes;
 | 
			
		||||
 | 
			
		||||
function initEditPreviewTab($form) {
 | 
			
		||||
  const $tabMenu = $form.find('.tabular.menu');
 | 
			
		||||
  $tabMenu.find('.item').tab();
 | 
			
		||||
  const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
 | 
			
		||||
  if ($previewTab.length) {
 | 
			
		||||
    previewFileModes = $previewTab.data('preview-file-modes').split(',');
 | 
			
		||||
    $previewTab.on('click', function () {
 | 
			
		||||
      const $this = $(this);
 | 
			
		||||
      let context = `${$this.data('context')}/`;
 | 
			
		||||
      const mode = $this.data('markdown-mode') || 'comment';
 | 
			
		||||
      const mode = $this.data('markup-mode') || 'comment';
 | 
			
		||||
      const treePathEl = $form.find('input#tree_path');
 | 
			
		||||
      if (treePathEl.length > 0) {
 | 
			
		||||
        context += treePathEl.val();
 | 
			
		||||
@@ -27,6 +25,7 @@ function initEditPreviewTab($form) {
 | 
			
		||||
        mode,
 | 
			
		||||
        context,
 | 
			
		||||
        text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
 | 
			
		||||
        file_path: treePathEl.val(),
 | 
			
		||||
      }, (data) => {
 | 
			
		||||
        const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
 | 
			
		||||
        $previewPanel.html(data);
 | 
			
		||||
@@ -147,7 +146,7 @@ export function initRepoEditor() {
 | 
			
		||||
  if (!$editArea.length) return;
 | 
			
		||||
 | 
			
		||||
  (async () => {
 | 
			
		||||
    const editor = await createCodeEditor($editArea[0], $editFilename[0], previewFileModes);
 | 
			
		||||
    const editor = await createCodeEditor($editArea[0], $editFilename[0]);
 | 
			
		||||
 | 
			
		||||
    // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
 | 
			
		||||
    // to enable or disable the commit button
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user