diff options
author | Brecht Van Lommel <brecht@blender.org> | 2023-03-24 07:12:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-24 14:12:23 +0800 |
commit | 84daddc2fa74393cdc13371b0cc44f0444cfdae0 (patch) | |
tree | eaf75d3173ee67d65288accef86bbb7c6c08535e /routers/api | |
parent | 9e04627acaaa853e5269f98f53f2615077cfb028 (diff) | |
download | gitea-84daddc2fa74393cdc13371b0cc44f0444cfdae0.tar.gz gitea-84daddc2fa74393cdc13371b0cc44f0444cfdae0.zip |
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>
Diffstat (limited to 'routers/api')
-rw-r--r-- | routers/api/v1/api.go | 2 | ||||
-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.go | 2 |
4 files changed, 119 insertions, 80 deletions
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 |