@@ -20,14 +20,12 @@ 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 (
|
||||
@@ -35,12 +33,10 @@ const (
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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$`},
|
||||
|
||||
Vendored
+4
-9
@@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Vendored
+79
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
}
|
||||
|
||||
+27
-16
@@ -4,6 +4,7 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -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"
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
+44
-24
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
// Renderer defines an interface for rendering markup file to HTML
|
||||
type Renderer interface {
|
||||
Name() string // markup format name
|
||||
Extensions() []string
|
||||
Name() string // markup format name, also the renderer type, also the external tool name
|
||||
FileNamePatterns() []string
|
||||
SanitizerRules() []setting.MarkupSanitizerRule
|
||||
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
|
||||
}
|
||||
@@ -43,26 +43,52 @@ type RendererContentDetector interface {
|
||||
}
|
||||
|
||||
var (
|
||||
extRenderers = make(map[string]Renderer)
|
||||
renderers = make(map[string]Renderer)
|
||||
fileNameRenderers = make(map[string]Renderer)
|
||||
renderers = make(map[string]Renderer)
|
||||
)
|
||||
|
||||
// RegisterRenderer registers a new markup file renderer
|
||||
func RegisterRenderer(renderer Renderer) {
|
||||
// TODO: need to handle conflicts
|
||||
renderers[renderer.Name()] = renderer
|
||||
for _, ext := range renderer.Extensions() {
|
||||
extRenderers[strings.ToLower(ext)] = renderer
|
||||
}
|
||||
|
||||
func RefreshFileNamePatterns() {
|
||||
// TODO: need to handle conflicts
|
||||
fileNameRenderers = make(map[string]Renderer)
|
||||
for _, renderer := range renderers {
|
||||
for _, ext := range renderer.FileNamePatterns() {
|
||||
fileNameRenderers[strings.ToLower(ext)] = renderer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRendererByFileName get renderer by filename
|
||||
func GetRendererByFileName(filename string) Renderer {
|
||||
extension := strings.ToLower(path.Ext(filename))
|
||||
return extRenderers[extension]
|
||||
func DetectRendererTypeByFilename(filename string) Renderer {
|
||||
basename := path.Base(strings.ToLower(filename))
|
||||
ext1 := path.Ext(basename)
|
||||
if renderer := fileNameRenderers[basename]; renderer != nil {
|
||||
return renderer
|
||||
}
|
||||
if renderer := fileNameRenderers["*"+ext1]; renderer != nil {
|
||||
return renderer
|
||||
}
|
||||
if basename, ok := strings.CutSuffix(basename, ext1); ok {
|
||||
ext2 := path.Ext(basename)
|
||||
if renderer := fileNameRenderers["*"+ext2+ext1]; renderer != nil {
|
||||
return renderer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectRendererType detects the markup type of the content
|
||||
func DetectRendererType(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) string {
|
||||
// DetectRendererTypeByPrefetch detects the markup type of the content
|
||||
func DetectRendererTypeByPrefetch(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) string {
|
||||
if filename != "" {
|
||||
byExt := DetectRendererTypeByFilename(filename)
|
||||
if byExt != nil {
|
||||
return byExt.Name()
|
||||
}
|
||||
}
|
||||
for _, renderer := range renderers {
|
||||
if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, sniffedType, prefetchBuf) {
|
||||
return renderer.Name()
|
||||
@@ -71,18 +97,12 @@ func DetectRendererType(filename string, sniffedType typesniffer.SniffedType, pr
|
||||
return ""
|
||||
}
|
||||
|
||||
// DetectMarkupTypeByFileName returns the possible markup format type via the filename
|
||||
func DetectMarkupTypeByFileName(filename string) string {
|
||||
if parser := GetRendererByFileName(filename); parser != nil {
|
||||
return parser.Name()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func PreviewableExtensions() []string {
|
||||
extensions := make([]string, 0, len(extRenderers))
|
||||
for extension := range extRenderers {
|
||||
extensions = append(extensions, extension)
|
||||
exts := make([]string, 0, len(fileNameRenderers))
|
||||
for p := range fileNameRenderers {
|
||||
if s, ok := strings.CutPrefix(p, "*"); ok {
|
||||
exts = append(exts, s)
|
||||
}
|
||||
}
|
||||
return extensions
|
||||
return exts
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user