summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--custom/conf/app.example.ini4
-rw-r--r--modules/markup/renderer.go13
-rw-r--r--modules/setting/repository.go9
-rw-r--r--modules/structs/miscellaneous.go30
-rw-r--r--routers/api/v1/api.go2
-rw-r--r--routers/api/v1/misc/markup.go (renamed from routers/api/v1/misc/markdown.go)87
-rw-r--r--routers/api/v1/misc/markup_test.go (renamed from routers/api/v1/misc/markdown_test.go)108
-rw-r--r--routers/api/v1/swagger/options.go2
-rw-r--r--routers/common/markup.go92
-rw-r--r--routers/web/misc/markdown.go98
-rw-r--r--routers/web/misc/markup.go44
-rw-r--r--routers/web/repo/editor.go7
-rw-r--r--routers/web/web.go2
-rw-r--r--templates/repo/diff/box.tmpl2
-rw-r--r--templates/repo/diff/comment_form.tmpl2
-rw-r--r--templates/repo/editor/edit.tmpl6
-rw-r--r--templates/repo/issue/comment_tab.tmpl4
-rw-r--r--templates/repo/issue/view_content.tmpl2
-rw-r--r--templates/repo/release/new.tmpl2
-rw-r--r--templates/repo/wiki/new.tmpl4
-rw-r--r--templates/swagger/v1_json.tmpl67
-rw-r--r--web_src/js/features/codeeditor.js10
-rw-r--r--web_src/js/features/repo-editor.js7
23 files changed, 389 insertions, 215 deletions
diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 131fb3401e..d1cfcd70e5 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go
index df2c9ebfc6..e934aed925 100644
--- a/modules/markup/renderer.go
+++ b/modules/markup/renderer.go
@@ -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
+}
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 4964704dba..bae3c658a4 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -53,8 +53,7 @@ var (
// Repository editor settings
Editor struct {
- LineWrapExtensions []string
- PreviewableFileModes []string
+ LineWrapExtensions []string
} `ini:"-"`
// Repository upload settings
@@ -167,11 +166,9 @@ var (
// Repository editor settings
Editor: struct {
- LineWrapExtensions []string
- PreviewableFileModes []string
+ LineWrapExtensions []string
}{
- LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","),
- PreviewableFileModes: []string{"markdown"},
+ LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","),
},
// Repository upload settings
diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go
index 596a551e0d..8acea84d6c 100644
--- a/modules/structs/miscellaneous.go
+++ b/modules/structs/miscellaneous.go
@@ -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
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index c3a875e737..8fd824640f 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -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() {
diff --git a/routers/api/v1/misc/markdown.go b/routers/api/v1/misc/markup.go
index 3ff42f08d6..93d5754444 100644
--- a/routers/api/v1/misc/markdown.go
+++ b/routers/api/v1/misc/markup.go
@@ -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
diff --git a/routers/api/v1/misc/markdown_test.go b/routers/api/v1/misc/markup_test.go
index 025f2f44b0..301f51eea2 100644
--- a/routers/api/v1/misc/markdown_test.go
+++ b/routers/api/v1/misc/markup_test.go
@@ -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{
diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go
index 0c8d3d353f..1ddc93c383 100644
--- a/routers/api/v1/swagger/options.go
+++ b/routers/api/v1/swagger/options.go
@@ -57,6 +57,8 @@ type swaggerParameterBodies struct {
EditLabelOption api.EditLabelOption
// in:body
+ MarkupOption api.MarkupOption
+ // in:body
MarkdownOption api.MarkdownOption
// in:body
diff --git a/routers/common/markup.go b/routers/common/markup.go
new file mode 100644
index 0000000000..89f24e0007
--- /dev/null
+++ b/routers/common/markup.go
@@ -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
+ }
+}
diff --git a/routers/web/misc/markdown.go b/routers/web/misc/markdown.go
deleted file mode 100644
index aaa3ed0781..0000000000
--- a/routers/web/misc/markdown.go
+++ /dev/null
@@ -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
- }
- }
-}
diff --git a/routers/web/misc/markup.go b/routers/web/misc/markup.go
new file mode 100644
index 0000000000..f678316f44
--- /dev/null
+++ b/routers/web/misc/markup.go
@@ -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)
+}
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index 07241b8870..2b66be22ae 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -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() {
diff --git a/routers/web/web.go b/routers/web/web.go
index 292268dc80..4bd2f76c57 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -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)
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 6dafe6f896..8926c518ae 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -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>
diff --git a/templates/repo/diff/comment_form.tmpl b/templates/repo/diff/comment_form.tmpl
index 225e40f7f0..deea6baffb 100644
--- a/templates/repo/diff/comment_form.tmpl
+++ b/templates/repo/diff/comment_form.tmpl
@@ -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">
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl
index 19c5cb007a..737027f590 100644
--- a/templates/repo/editor/edit.tmpl
+++ b/templates/repo/editor/edit.tmpl
@@ -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>
diff --git a/templates/repo/issue/comment_tab.tmpl b/templates/repo/issue/comment_tab.tmpl
index b8e8d2d9aa..47d6ca9587 100644
--- a/templates/repo/issue/comment_tab.tmpl
+++ b/templates/repo/issue/comment_tab.tmpl
@@ -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>
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl
index 3e43701b63..8a55d4e30e 100644
--- a/templates/repo/issue/view_content.tmpl
+++ b/templates/repo/issue/view_content.tmpl
@@ -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">
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index 8c4df98d19..692a373dc6 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -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>
diff --git a/templates/repo/wiki/new.tmpl b/templates/repo/wiki/new.tmpl
index 9e9de99022..085af4cbc9 100644
--- a/templates/repo/wiki/new.tmpl
+++ b/templates/repo/wiki/new.tmpl
@@ -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">
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index ddcdc94b81..0f7e60c598 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -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": {
diff --git a/web_src/js/features/codeeditor.js b/web_src/js/features/codeeditor.js
index 23a26ba2b0..4f5ea317b4 100644
--- a/web_src/js/features/codeeditor.js
+++ b/web_src/js/features/codeeditor.js
@@ -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 {
diff --git a/web_src/js/features/repo-editor.js b/web_src/js/features/repo-editor.js
index b3e9b65f21..a7c59fb039 100644
--- a/web_src/js/features/repo-editor.js
+++ b/web_src/js/features/repo-editor.js
@@ -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