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:
parent
9e04627aca
commit
84daddc2fa
|
@ -993,10 +993,6 @@ ROUTER = console
|
||||||
;; List of file extensions for which lines should be wrapped in the Monaco editor
|
;; 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
|
;; Separate extensions with a comma. To line wrap files without an extension, just put a comma
|
||||||
;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
|
;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
|
Extension string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsErrUnsupportedRenderExtension(err error) bool {
|
||||||
|
_, ok := err.(ErrUnsupportedRenderExtension)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
func (err ErrUnsupportedRenderExtension) Error() string {
|
func (err ErrUnsupportedRenderExtension) Error() string {
|
||||||
return fmt.Sprintf("Unsupported render extension: %s", err.Extension)
|
return fmt.Sprintf("Unsupported render extension: %s", err.Extension)
|
||||||
}
|
}
|
||||||
|
@ -317,3 +322,11 @@ func IsMarkupFile(name, markup string) bool {
|
||||||
}
|
}
|
||||||
return false
|
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
|
// Repository editor settings
|
||||||
Editor struct {
|
Editor struct {
|
||||||
LineWrapExtensions []string
|
LineWrapExtensions []string
|
||||||
PreviewableFileModes []string
|
|
||||||
} `ini:"-"`
|
} `ini:"-"`
|
||||||
|
|
||||||
// Repository upload settings
|
// Repository upload settings
|
||||||
|
@ -168,10 +167,8 @@ var (
|
||||||
// Repository editor settings
|
// Repository editor settings
|
||||||
Editor: struct {
|
Editor: struct {
|
||||||
LineWrapExtensions []string
|
LineWrapExtensions []string
|
||||||
PreviewableFileModes []string
|
|
||||||
}{
|
}{
|
||||||
LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","),
|
LineWrapExtensions: strings.Split(".txt,.md,.markdown,.mdown,.mkd,", ","),
|
||||||
PreviewableFileModes: []string{"markdown"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Repository upload settings
|
// Repository upload settings
|
||||||
|
|
|
@ -15,13 +15,41 @@ type SearchError struct {
|
||||||
Error string `json:"error"`
|
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
|
// MarkdownOption markdown options
|
||||||
type MarkdownOption struct {
|
type MarkdownOption struct {
|
||||||
// Text markdown to render
|
// Text markdown to render
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Text string
|
Text string
|
||||||
// Mode to render
|
// Mode to render (comment, gfm, markdown)
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
|
|
|
@ -711,6 +711,7 @@ func Routes(ctx gocontext.Context) *web.Route {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
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", bind(api.MarkdownOption{}), misc.Markdown)
|
||||||
m.Post("/markdown/raw", misc.MarkdownRaw)
|
m.Post("/markdown/raw", misc.MarkdownRaw)
|
||||||
m.Group("/settings", func() {
|
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).
|
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)
|
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", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkdownOption{}), misc.Markdown)
|
||||||
m.Post("/markdown/raw", reqToken(auth_model.AccessTokenScopeRepo), misc.MarkdownRaw)
|
m.Post("/markdown/raw", reqToken(auth_model.AccessTokenScopeRepo), misc.MarkdownRaw)
|
||||||
m.Group("/milestones", func() {
|
m.Group("/milestones", func() {
|
||||||
|
|
|
@ -5,19 +5,45 @@ package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/common"
|
||||||
"mvdan.cc/xurls/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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
|
// Markdown render markdown document to HTML
|
||||||
func Markdown(ctx *context.APIContext) {
|
func Markdown(ctx *context.APIContext) {
|
||||||
// swagger:operation POST /markdown miscellaneous renderMarkdown
|
// swagger:operation POST /markdown miscellaneous renderMarkdown
|
||||||
|
@ -45,55 +71,12 @@ func Markdown(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Text) == 0 {
|
mode := "markdown"
|
||||||
_, _ = ctx.Write([]byte(""))
|
if form.Mode == "comment" || form.Mode == "gfm" {
|
||||||
return
|
mode = form.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
switch form.Mode {
|
common.RenderMarkup(ctx.Context, mode, form.Text, form.Context, "", form.Wiki)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownRaw render raw markdown HTML
|
// 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
|
setting.AppURL = AppURL
|
||||||
markup.Init(&markup.ProcessorHelper{
|
|
||||||
IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
|
|
||||||
return username == "r-lyeh"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: "gfm",
|
Mode: mode,
|
||||||
Text: "",
|
Text: "",
|
||||||
Context: Repo,
|
Context: Repo,
|
||||||
Wiki: true,
|
Wiki: true,
|
||||||
|
@ -71,7 +92,22 @@ func TestAPI_RenderGFM(t *testing.T) {
|
||||||
m, resp := createContext(req)
|
m, resp := createContext(req)
|
||||||
ctx := wrap(m)
|
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
|
// dear imgui wiki markdown extract: special wiki syntax
|
||||||
`Wiki! Enjoy :)
|
`Wiki! Enjoy :)
|
||||||
- [[Links, Language bindings, Engine bindings|Links]]
|
- [[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>
|
<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>
|
</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
|
// wine-staging wiki home extract: special wiki syntax, images
|
||||||
`## What is Wine Staging?
|
`## What is Wine Staging?
|
||||||
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
**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>
|
<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>
|
<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 {
|
for i := 0; i < len(testCasesCommon); i += 2 {
|
||||||
options.Text = testCases[i]
|
text := testCasesCommon[i]
|
||||||
web.SetForm(ctx, &options)
|
response := testCasesCommon[i+1]
|
||||||
Markdown(ctx)
|
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
||||||
assert.Equal(t, testCases[i+1], resp.Body.String())
|
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
||||||
resp.Body.Reset()
|
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{
|
var simpleCases = []string{
|
|
@ -56,6 +56,8 @@ type swaggerParameterBodies struct {
|
||||||
// in:body
|
// in:body
|
||||||
EditLabelOption api.EditLabelOption
|
EditLabelOption api.EditLabelOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
MarkupOption api.MarkupOption
|
||||||
// in:body
|
// in:body
|
||||||
MarkdownOption api.MarkdownOption
|
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/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/typesniffer"
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/upload"
|
"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["new_branch_name"] = GetUniquePatchBranchName(ctx)
|
||||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
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["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||||
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
|
||||||
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, treePath)
|
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, treePath)
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplEditFile)
|
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["commit_choice"] = form.CommitChoice
|
||||||
ctx.Data["new_branch_name"] = form.NewBranchName
|
ctx.Data["new_branch_name"] = form.NewBranchName
|
||||||
ctx.Data["last_commit"] = ctx.Repo.CommitID
|
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["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||||
ctx.Data["PreviewableFileModes"] = strings.Join(setting.Repository.Editor.PreviewableFileModes, ",")
|
|
||||||
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, form.TreePath)
|
ctx.Data["Editorconfig"] = GetEditorConfig(ctx, form.TreePath)
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
|
|
@ -1115,7 +1115,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Group("/comments/{id}", func() {
|
m.Group("/comments/{id}", func() {
|
||||||
m.Get("/attachments", repo.GetCommentAttachments)
|
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.Group("/labels", func() {
|
||||||
m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel)
|
m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel)
|
||||||
m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel)
|
m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel)
|
||||||
|
|
|
@ -192,7 +192,7 @@
|
||||||
<div class="ui comment form">
|
<div class="ui comment form">
|
||||||
<div class="ui top attached tabular menu">
|
<div class="ui top attached tabular menu">
|
||||||
<a class="active write item">{{$.locale.Tr "write"}}</a>
|
<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>
|
||||||
<div class="ui bottom attached active write tab segment">
|
<div class="ui bottom attached active write tab segment">
|
||||||
<textarea class="review-textarea js-quick-submit" tabindex="1" name="content"></textarea>
|
<textarea class="review-textarea js-quick-submit" tabindex="1" name="content"></textarea>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<input type="hidden" name="diff_base_cid">
|
<input type="hidden" name="diff_base_cid">
|
||||||
<div class="ui top tabular menu" data-write="write" data-preview="preview">
|
<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="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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui active tab" data-tab="write">
|
<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">
|
<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>
|
<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}}
|
{{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>
|
<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}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ui bottom attached active tab segment" data-tab="write">
|
<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}}"
|
<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-context="{{.RepoLink}}"
|
||||||
data-markdown-file-exts="{{.MarkdownFileExts}}"
|
data-previewable-extensions="{{.PreviewableExtensions}}"
|
||||||
data-line-wrap-extensions="{{.LineWrapExtensions}}">
|
data-line-wrap-extensions="{{.LineWrapExtensions}}">
|
||||||
{{.FileContent}}</textarea>
|
{{.FileContent}}</textarea>
|
||||||
<div class="editor-loading is-loading"></div>
|
<div class="editor-loading is-loading"></div>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="ui top tabular menu" data-write="write" data-preview="preview">
|
<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="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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui bottom active tab" data-tab="write">
|
<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 -}}
|
{{- if .BodyQuery}}{{.BodyQuery}}{{else if .IssueTemplate}}{{.IssueTemplate}}{{else if .PullRequestTemplate}}{{.PullRequestTemplate}}{{else}}{{.content}}{{end -}}
|
||||||
</textarea>
|
</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -168,7 +168,7 @@
|
||||||
<div class="ui comment form">
|
<div class="ui comment form">
|
||||||
<div class="ui top tabular menu">
|
<div class="ui top tabular menu">
|
||||||
<a class="active write item">{{$.locale.Tr "write"}}</a>
|
<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>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui bottom active tab write">
|
<div class="ui bottom active tab write">
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
<label>{{.locale.Tr "repo.release.content"}}</label>
|
<label>{{.locale.Tr "repo.release.content"}}</label>
|
||||||
<div class="ui top tabular menu" data-write="write" data-preview="preview">
|
<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="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>
|
||||||
<div class="ui bottom active tab" data-tab="write">
|
<div class="ui bottom active tab" data-tab="write">
|
||||||
<textarea name="content">{{.content}}</textarea>
|
<textarea name="content">{{.content}}</textarea>
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui top attached tabular menu previewtabs" data-write="write" data-preview="preview">
|
<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="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>
|
||||||
<div class="field content" data-loading="{{.locale.Tr "loading"}}">
|
<div class="field content" data-loading="{{.locale.Tr "loading"}}">
|
||||||
<div class="ui bottom active tab" data-tab="write">
|
<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>
|
</div>
|
||||||
<div class="field">
|
<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": {
|
"/nodeinfo": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -17991,7 +18023,7 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Mode": {
|
"Mode": {
|
||||||
"description": "Mode to render\n\nin: body",
|
"description": "Mode to render (comment, gfm, markdown)\n\nin: body",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Text": {
|
"Text": {
|
||||||
|
@ -18005,6 +18037,33 @@
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"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": {
|
"MergePullRequestOption": {
|
||||||
"description": "MergePullRequestForm form for merging Pull Request",
|
"description": "MergePullRequestForm form for merging Pull Request",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -20835,6 +20894,12 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"MarkupRender": {
|
||||||
|
"description": "MarkupRender is a rendered markup document",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Milestone": {
|
"Milestone": {
|
||||||
"description": "Milestone",
|
"description": "Milestone",
|
||||||
"schema": {
|
"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 filename = basename(filenameInput.value);
|
||||||
const previewLink = document.querySelector('a[data-tab=preview]');
|
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 lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
||||||
const isMarkdown = markdownExts.includes(extname(filename));
|
const previewable = previewableExts.includes(extname(filename));
|
||||||
const editorConfig = getEditorconfig(filenameInput);
|
const editorConfig = getEditorconfig(filenameInput);
|
||||||
|
|
||||||
if (previewLink) {
|
if (previewLink) {
|
||||||
if (isMarkdown && (previewFileModes || []).includes('markdown')) {
|
if (previewable) {
|
||||||
const newUrl = (previewLink.getAttribute('data-url') || '').replace(/(.*)\/.*/i, `$1/markdown`);
|
const newUrl = (previewLink.getAttribute('data-url') || '').replace(/(.*)\/.*/i, `$1/markup`);
|
||||||
previewLink.setAttribute('data-url', newUrl);
|
previewLink.setAttribute('data-url', newUrl);
|
||||||
previewLink.style.display = '';
|
previewLink.style.display = '';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,18 +5,16 @@ import {createCodeEditor} from './codeeditor.js';
|
||||||
import {hideElem, showElem} from '../utils/dom.js';
|
import {hideElem, showElem} from '../utils/dom.js';
|
||||||
|
|
||||||
const {csrfToken} = window.config;
|
const {csrfToken} = window.config;
|
||||||
let previewFileModes;
|
|
||||||
|
|
||||||
function initEditPreviewTab($form) {
|
function initEditPreviewTab($form) {
|
||||||
const $tabMenu = $form.find('.tabular.menu');
|
const $tabMenu = $form.find('.tabular.menu');
|
||||||
$tabMenu.find('.item').tab();
|
$tabMenu.find('.item').tab();
|
||||||
const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
|
const $previewTab = $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`);
|
||||||
if ($previewTab.length) {
|
if ($previewTab.length) {
|
||||||
previewFileModes = $previewTab.data('preview-file-modes').split(',');
|
|
||||||
$previewTab.on('click', function () {
|
$previewTab.on('click', function () {
|
||||||
const $this = $(this);
|
const $this = $(this);
|
||||||
let context = `${$this.data('context')}/`;
|
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');
|
const treePathEl = $form.find('input#tree_path');
|
||||||
if (treePathEl.length > 0) {
|
if (treePathEl.length > 0) {
|
||||||
context += treePathEl.val();
|
context += treePathEl.val();
|
||||||
|
@ -27,6 +25,7 @@ function initEditPreviewTab($form) {
|
||||||
mode,
|
mode,
|
||||||
context,
|
context,
|
||||||
text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
|
text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val(),
|
||||||
|
file_path: treePathEl.val(),
|
||||||
}, (data) => {
|
}, (data) => {
|
||||||
const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
|
const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`);
|
||||||
$previewPanel.html(data);
|
$previewPanel.html(data);
|
||||||
|
@ -147,7 +146,7 @@ export function initRepoEditor() {
|
||||||
if (!$editArea.length) return;
|
if (!$editArea.length) return;
|
||||||
|
|
||||||
(async () => {
|
(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
|
// Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage
|
||||||
// to enable or disable the commit button
|
// to enable or disable the commit button
|
||||||
|
|
Loading…
Reference in a new issue