Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions modules/markup/asciicast/asciicast.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,23 @@ func init() {
// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
type Renderer struct{}

// Name implements markup.Renderer
func (Renderer) Name() string {
return "asciicast"
}

// Extensions implements markup.Renderer
func (Renderer) Extensions() []string {
return []string{".cast"}
func (Renderer) FileNamePatterns() []string {
return []string{"*.cast"}
}

const (
playerClassName = "asciinema-player-container"
playerSrcAttr = "data-asciinema-player-src"
)

// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{{Element: "div", AllowAttr: playerSrcAttr}}
}

// Render implements markup.Renderer
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
setting.AppSubURL,
Expand Down
9 changes: 2 additions & 7 deletions modules/markup/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,24 @@ func init() {
markup.RegisterRenderer(Renderer{})
}

// Renderer implements markup.Renderer
type Renderer struct{}

var _ markup.RendererContentDetector = (*Renderer)(nil)

// Name implements markup.Renderer
func (Renderer) Name() string {
return "console"
}

// Extensions implements markup.Renderer
func (Renderer) Extensions() []string {
return []string{".sh-session"}
func (Renderer) FileNamePatterns() []string {
return []string{"*.sh-session"}
}

// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{
{Element: "span", AllowAttr: "class", Regexp: `^term-((fg[ix]?|bg)\d+|container)$`},
}
}

// CanRender implements markup.RendererContentDetector
func (Renderer) CanRender(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) bool {
if !sniffedType.IsTextPlain() {
return false
Expand Down
8 changes: 2 additions & 6 deletions modules/markup/csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,16 @@ func init() {
markup.RegisterRenderer(Renderer{})
}

// Renderer implements markup.Renderer for csv files
type Renderer struct{}

// Name implements markup.Renderer
func (Renderer) Name() string {
return "csv"
}

// Extensions implements markup.Renderer
func (Renderer) Extensions() []string {
return []string{".csv", ".tsv"}
func (Renderer) FileNamePatterns() []string {
return []string{"*.csv", "*.tsv"}
}

// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{
{Element: "table", AllowAttr: "class", Regexp: `^data-table$`},
Expand Down
13 changes: 4 additions & 9 deletions modules/markup/external/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ import (

// RegisterRenderers registers all supported third part renderers according settings
func RegisterRenderers() {
markup.RegisterRenderer(&openAPIRenderer{})
for _, renderer := range setting.ExternalMarkupRenderers {
if renderer.Enabled && renderer.Command != "" && len(renderer.FileExtensions) > 0 {
markup.RegisterRenderer(&Renderer{renderer})
}
markup.RegisterRenderer(&Renderer{renderer})
}
}

Expand All @@ -38,22 +37,18 @@ var (
_ markup.ExternalRenderer = (*Renderer)(nil)
)

// Name returns the external tool name
func (p *Renderer) Name() string {
return p.MarkupName
}

// NeedPostProcess implements markup.Renderer
func (p *Renderer) NeedPostProcess() bool {
return p.MarkupRenderer.NeedPostProcess
}

// Extensions returns the supported extensions of the tool
func (p *Renderer) Extensions() []string {
return p.FileExtensions
func (p *Renderer) FileNamePatterns() []string {
return p.FilePatterns
}

// SanitizerRules implements markup.Renderer
func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return p.MarkupSanitizerRules
}
Expand Down
79 changes: 79 additions & 0 deletions modules/markup/external/openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package external

import (
"fmt"
"html"
"io"

"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)

type openAPIRenderer struct{}

var (
_ markup.PostProcessRenderer = (*openAPIRenderer)(nil)
_ markup.ExternalRenderer = (*openAPIRenderer)(nil)
)

func (p *openAPIRenderer) Name() string {
return "openapi"
}

func (p *openAPIRenderer) NeedPostProcess() bool {
return false
}

func (p *openAPIRenderer) FileNamePatterns() []string {
return []string{
"openapi.yaml",
"openapi.yml",
"openapi.json",
"swagger.yaml",
"swagger.yml",
"swagger.json",
}
}

func (p *openAPIRenderer) SanitizerRules() []setting.MarkupSanitizerRule {
return nil
}

func (p *openAPIRenderer) GetExternalRendererOptions() (ret markup.ExternalRendererOptions) {
ret.SanitizerDisabled = true
ret.DisplayInIframe = true
ret.ContentSandbox = ""
return ret
}

func (p *openAPIRenderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
content, err := util.ReadWithLimit(input, int(setting.UI.MaxDisplayFileSize))
if err != nil {
return err
}
// TODO: can extract this to a tmpl file later
_, err = io.WriteString(output, fmt.Sprintf(
`<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="%s/assets/css/swagger.css?v=%s">
</head>
<body>
<div id="swagger-ui"><textarea class="swagger-spec-content" data-spec-filename="%s">%s</textarea></div>
<script src="%s/assets/js/swagger.js?v=%s"></script>
</body>
</html>`,
setting.StaticURLPrefix,
setting.AssetVersion,
html.EscapeString(ctx.RenderOptions.RelativePath),
html.EscapeString(util.UnsafeBytesToString(content)),
setting.StaticURLPrefix,
setting.AssetVersion,
))
return err
}
2 changes: 2 additions & 0 deletions modules/markup/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ import (
func TestMain(m *testing.M) {
setting.IsInTesting = true
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
setting.Markdown.FileNamePatterns = []string{"*.md"}
markup.RefreshFileNamePatterns()
os.Exit(m.Run())
}
10 changes: 2 additions & 8 deletions modules/markup/markdown/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,30 +240,24 @@ func init() {
markup.RegisterRenderer(Renderer{})
}

// Renderer implements markup.Renderer
type Renderer struct{}

var _ markup.PostProcessRenderer = (*Renderer)(nil)

// Name implements markup.Renderer
func (Renderer) Name() string {
return MarkupName
}

// NeedPostProcess implements markup.PostProcessRenderer
func (Renderer) NeedPostProcess() bool { return true }

// Extensions implements markup.Renderer
func (Renderer) Extensions() []string {
return setting.Markdown.FileExtensions
func (Renderer) FileNamePatterns() []string {
return setting.Markdown.FileNamePatterns
}

// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{}
}

// Render implements markup.Renderer
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
return render(ctx, input, output)
}
Expand Down
8 changes: 2 additions & 6 deletions modules/markup/orgmode/orgmode.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,16 @@ var (
_ markup.PostProcessRenderer = (*renderer)(nil)
)

// Name implements markup.Renderer
func (renderer) Name() string {
return "orgmode"
}

// NeedPostProcess implements markup.PostProcessRenderer
func (renderer) NeedPostProcess() bool { return true }

// Extensions implements markup.Renderer
func (renderer) Extensions() []string {
return []string{".org"}
func (renderer) FileNamePatterns() []string {
return []string{"*.org"}
}

// SanitizerRules implements markup.Renderer
func (renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{}
}
Expand Down
43 changes: 27 additions & 16 deletions modules/markup/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package markup

import (
"bytes"
"context"
"fmt"
"html/template"
Expand All @@ -16,6 +17,7 @@ import (
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/markup/internal"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"

"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -144,22 +146,29 @@ func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext {
return ctx
}

// FindRendererByContext finds renderer by RenderContext
// TODO: it should be merged with other similar functions like GetRendererByFileName, DetectMarkupTypeByFileName, etc
func FindRendererByContext(ctx *RenderContext) (Renderer, error) {
func (ctx *RenderContext) DetectMarkupRenderer(prefetchBuf []byte) Renderer {
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
if ctx.RenderOptions.MarkupType == "" {
return nil, util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
var sniffedType typesniffer.SniffedType
if len(prefetchBuf) > 0 {
sniffedType = typesniffer.DetectContentType(prefetchBuf)
}
ctx.RenderOptions.MarkupType = DetectRendererTypeByPrefetch(ctx.RenderOptions.RelativePath, sniffedType, prefetchBuf)
}
return renderers[ctx.RenderOptions.MarkupType]
}

renderer := renderers[ctx.RenderOptions.MarkupType]
func (ctx *RenderContext) DetectMarkupRendererByReader(in io.Reader) (Renderer, io.Reader, error) {
prefetchBuf := make([]byte, 512)
n, err := util.ReadAtMost(in, prefetchBuf)
if err != nil && err != io.EOF {
return nil, nil, err
}
prefetchBuf = prefetchBuf[:n]
renderer := ctx.DetectMarkupRenderer(prefetchBuf)
if renderer == nil {
return nil, util.NewNotExistErrorf("unsupported markup type: %q", ctx.RenderOptions.MarkupType)
return nil, nil, util.NewInvalidArgumentErrorf("unable to find a render")
}

return renderer, nil
return renderer, io.MultiReader(bytes.NewReader(prefetchBuf), in), nil
}

func RendererNeedPostProcess(renderer Renderer) bool {
Expand All @@ -170,12 +179,12 @@ func RendererNeedPostProcess(renderer Renderer) bool {
}

// Render renders markup file to HTML with all specific handling stuff.
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
renderer, err := FindRendererByContext(ctx)
func Render(rctx *RenderContext, origInput io.Reader, output io.Writer) error {
renderer, input, err := rctx.DetectMarkupRendererByReader(origInput)
if err != nil {
return err
}
return RenderWithRenderer(ctx, renderer, input, output)
return RenderWithRenderer(rctx, renderer, input, output)
}

// RenderString renders Markup string to HTML with all specific handling stuff and return string
Expand Down Expand Up @@ -287,12 +296,14 @@ func Init(renderHelpFuncs *RenderHelperFuncs) {
}

// since setting maybe changed extensions, this will reload all renderer extensions mapping
extRenderers = make(map[string]Renderer)
fileNameRenderers = make(map[string]Renderer)
for _, renderer := range renderers {
for _, ext := range renderer.Extensions() {
extRenderers[strings.ToLower(ext)] = renderer
for _, pattern := range renderer.FileNamePatterns() {
fileNameRenderers[pattern] = renderer
}
}

RefreshFileNamePatterns()
}

func ComposeSimpleDocumentMetas() map[string]string {
Expand Down
Loading
Loading