mirror of
https://github.com/go-gitea/gitea
synced 2026-02-03 11:10:40 +00:00
@@ -22,8 +22,9 @@ func TestEntriesCustomSort(t *testing.T) {
|
|||||||
&TreeEntry{name: "b-file", entryMode: EntryModeBlob},
|
&TreeEntry{name: "b-file", entryMode: EntryModeBlob},
|
||||||
}
|
}
|
||||||
expected := slices.Clone(entries)
|
expected := slices.Clone(entries)
|
||||||
rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] })
|
for slices.Equal(expected, entries) {
|
||||||
assert.NotEqual(t, expected, entries)
|
rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] })
|
||||||
|
}
|
||||||
entries.CustomSort(strings.Compare)
|
entries.CustomSort(strings.Compare)
|
||||||
assert.Equal(t, expected, entries)
|
assert.Equal(t, expected, entries)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ func init() {
|
|||||||
// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
|
// See https://github.com/asciinema/asciinema/blob/develop/doc/asciicast-v2.md
|
||||||
type Renderer struct{}
|
type Renderer struct{}
|
||||||
|
|
||||||
// Name implements markup.Renderer
|
|
||||||
func (Renderer) Name() string {
|
func (Renderer) Name() string {
|
||||||
return "asciicast"
|
return "asciicast"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions implements markup.Renderer
|
func (Renderer) FileNamePatterns() []string {
|
||||||
func (Renderer) Extensions() []string {
|
return []string{"*.cast"}
|
||||||
return []string{".cast"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -35,12 +33,10 @@ const (
|
|||||||
playerSrcAttr = "data-asciinema-player-src"
|
playerSrcAttr = "data-asciinema-player-src"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{{Element: "div", AllowAttr: playerSrcAttr}}
|
return []setting.MarkupSanitizerRule{{Element: "div", AllowAttr: playerSrcAttr}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render implements markup.Renderer
|
|
||||||
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
|
func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer) error {
|
||||||
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
|
rawURL := fmt.Sprintf("%s/%s/%s/raw/%s/%s",
|
||||||
setting.AppSubURL,
|
setting.AppSubURL,
|
||||||
|
|||||||
@@ -20,29 +20,24 @@ func init() {
|
|||||||
markup.RegisterRenderer(Renderer{})
|
markup.RegisterRenderer(Renderer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer implements markup.Renderer
|
|
||||||
type Renderer struct{}
|
type Renderer struct{}
|
||||||
|
|
||||||
var _ markup.RendererContentDetector = (*Renderer)(nil)
|
var _ markup.RendererContentDetector = (*Renderer)(nil)
|
||||||
|
|
||||||
// Name implements markup.Renderer
|
|
||||||
func (Renderer) Name() string {
|
func (Renderer) Name() string {
|
||||||
return "console"
|
return "console"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions implements markup.Renderer
|
func (Renderer) FileNamePatterns() []string {
|
||||||
func (Renderer) Extensions() []string {
|
return []string{"*.sh-session"}
|
||||||
return []string{".sh-session"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "span", AllowAttr: "class", Regexp: `^term-((fg[ix]?|bg)\d+|container)$`},
|
{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 {
|
func (Renderer) CanRender(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) bool {
|
||||||
if !sniffedType.IsTextPlain() {
|
if !sniffedType.IsTextPlain() {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -20,20 +20,16 @@ func init() {
|
|||||||
markup.RegisterRenderer(Renderer{})
|
markup.RegisterRenderer(Renderer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer implements markup.Renderer for csv files
|
|
||||||
type Renderer struct{}
|
type Renderer struct{}
|
||||||
|
|
||||||
// Name implements markup.Renderer
|
|
||||||
func (Renderer) Name() string {
|
func (Renderer) Name() string {
|
||||||
return "csv"
|
return "csv"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions implements markup.Renderer
|
func (Renderer) FileNamePatterns() []string {
|
||||||
func (Renderer) Extensions() []string {
|
return []string{"*.csv", "*.tsv"}
|
||||||
return []string{".csv", ".tsv"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{
|
return []setting.MarkupSanitizerRule{
|
||||||
{Element: "table", AllowAttr: "class", Regexp: `^data-table$`},
|
{Element: "table", AllowAttr: "class", Regexp: `^data-table$`},
|
||||||
|
|||||||
Vendored
+4
-9
@@ -21,10 +21,9 @@ import (
|
|||||||
|
|
||||||
// RegisterRenderers registers all supported third part renderers according settings
|
// RegisterRenderers registers all supported third part renderers according settings
|
||||||
func RegisterRenderers() {
|
func RegisterRenderers() {
|
||||||
|
markup.RegisterRenderer(&openAPIRenderer{})
|
||||||
for _, renderer := range setting.ExternalMarkupRenderers {
|
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)
|
_ markup.ExternalRenderer = (*Renderer)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name returns the external tool name
|
|
||||||
func (p *Renderer) Name() string {
|
func (p *Renderer) Name() string {
|
||||||
return p.MarkupName
|
return p.MarkupName
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedPostProcess implements markup.Renderer
|
|
||||||
func (p *Renderer) NeedPostProcess() bool {
|
func (p *Renderer) NeedPostProcess() bool {
|
||||||
return p.MarkupRenderer.NeedPostProcess
|
return p.MarkupRenderer.NeedPostProcess
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extensions returns the supported extensions of the tool
|
func (p *Renderer) FileNamePatterns() []string {
|
||||||
func (p *Renderer) Extensions() []string {
|
return p.FilePatterns
|
||||||
return p.FileExtensions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return p.MarkupSanitizerRules
|
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) {
|
func TestMain(m *testing.M) {
|
||||||
setting.IsInTesting = true
|
setting.IsInTesting = true
|
||||||
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
||||||
|
setting.Markdown.FileNamePatterns = []string{"*.md"}
|
||||||
|
markup.RefreshFileNamePatterns()
|
||||||
os.Exit(m.Run())
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,30 +240,24 @@ func init() {
|
|||||||
markup.RegisterRenderer(Renderer{})
|
markup.RegisterRenderer(Renderer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer implements markup.Renderer
|
|
||||||
type Renderer struct{}
|
type Renderer struct{}
|
||||||
|
|
||||||
var _ markup.PostProcessRenderer = (*Renderer)(nil)
|
var _ markup.PostProcessRenderer = (*Renderer)(nil)
|
||||||
|
|
||||||
// Name implements markup.Renderer
|
|
||||||
func (Renderer) Name() string {
|
func (Renderer) Name() string {
|
||||||
return MarkupName
|
return MarkupName
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedPostProcess implements markup.PostProcessRenderer
|
|
||||||
func (Renderer) NeedPostProcess() bool { return true }
|
func (Renderer) NeedPostProcess() bool { return true }
|
||||||
|
|
||||||
// Extensions implements markup.Renderer
|
func (Renderer) FileNamePatterns() []string {
|
||||||
func (Renderer) Extensions() []string {
|
return setting.Markdown.FileNamePatterns
|
||||||
return setting.Markdown.FileExtensions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{}
|
return []setting.MarkupSanitizerRule{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render implements markup.Renderer
|
|
||||||
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
|
||||||
return render(ctx, input, output)
|
return render(ctx, input, output)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,20 +31,16 @@ var (
|
|||||||
_ markup.PostProcessRenderer = (*renderer)(nil)
|
_ markup.PostProcessRenderer = (*renderer)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name implements markup.Renderer
|
|
||||||
func (renderer) Name() string {
|
func (renderer) Name() string {
|
||||||
return "orgmode"
|
return "orgmode"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedPostProcess implements markup.PostProcessRenderer
|
|
||||||
func (renderer) NeedPostProcess() bool { return true }
|
func (renderer) NeedPostProcess() bool { return true }
|
||||||
|
|
||||||
// Extensions implements markup.Renderer
|
func (renderer) FileNamePatterns() []string {
|
||||||
func (renderer) Extensions() []string {
|
return []string{"*.org"}
|
||||||
return []string{".org"}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizerRules implements markup.Renderer
|
|
||||||
func (renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
func (renderer) SanitizerRules() []setting.MarkupSanitizerRule {
|
||||||
return []setting.MarkupSanitizerRule{}
|
return []setting.MarkupSanitizerRule{}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-16
@@ -4,6 +4,7 @@
|
|||||||
package markup
|
package markup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
@@ -16,6 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/htmlutil"
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
"code.gitea.io/gitea/modules/markup/internal"
|
"code.gitea.io/gitea/modules/markup/internal"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/typesniffer"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -144,22 +146,29 @@ func (ctx *RenderContext) WithHelper(helper RenderHelper) *RenderContext {
|
|||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindRendererByContext finds renderer by RenderContext
|
func (ctx *RenderContext) DetectMarkupRenderer(prefetchBuf []byte) Renderer {
|
||||||
// TODO: it should be merged with other similar functions like GetRendererByFileName, DetectMarkupTypeByFileName, etc
|
|
||||||
func FindRendererByContext(ctx *RenderContext) (Renderer, error) {
|
|
||||||
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
|
if ctx.RenderOptions.MarkupType == "" && ctx.RenderOptions.RelativePath != "" {
|
||||||
ctx.RenderOptions.MarkupType = DetectMarkupTypeByFileName(ctx.RenderOptions.RelativePath)
|
var sniffedType typesniffer.SniffedType
|
||||||
if ctx.RenderOptions.MarkupType == "" {
|
if len(prefetchBuf) > 0 {
|
||||||
return nil, util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RenderOptions.RelativePath)
|
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 {
|
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, io.MultiReader(bytes.NewReader(prefetchBuf), in), nil
|
||||||
return renderer, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func RendererNeedPostProcess(renderer Renderer) bool {
|
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.
|
// Render renders markup file to HTML with all specific handling stuff.
|
||||||
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
|
func Render(rctx *RenderContext, origInput io.Reader, output io.Writer) error {
|
||||||
renderer, err := FindRendererByContext(ctx)
|
renderer, input, err := rctx.DetectMarkupRendererByReader(origInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// 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
|
// 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 _, renderer := range renderers {
|
||||||
for _, ext := range renderer.Extensions() {
|
for _, pattern := range renderer.FileNamePatterns() {
|
||||||
extRenderers[strings.ToLower(ext)] = renderer
|
fileNameRenderers[pattern] = renderer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefreshFileNamePatterns()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComposeSimpleDocumentMetas() map[string]string {
|
func ComposeSimpleDocumentMetas() map[string]string {
|
||||||
|
|||||||
+44
-24
@@ -14,8 +14,8 @@ import (
|
|||||||
|
|
||||||
// Renderer defines an interface for rendering markup file to HTML
|
// Renderer defines an interface for rendering markup file to HTML
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Name() string // markup format name
|
Name() string // markup format name, also the renderer type, also the external tool name
|
||||||
Extensions() []string
|
FileNamePatterns() []string
|
||||||
SanitizerRules() []setting.MarkupSanitizerRule
|
SanitizerRules() []setting.MarkupSanitizerRule
|
||||||
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
|
Render(ctx *RenderContext, input io.Reader, output io.Writer) error
|
||||||
}
|
}
|
||||||
@@ -43,26 +43,52 @@ type RendererContentDetector interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
extRenderers = make(map[string]Renderer)
|
fileNameRenderers = make(map[string]Renderer)
|
||||||
renderers = make(map[string]Renderer)
|
renderers = make(map[string]Renderer)
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterRenderer registers a new markup file renderer
|
// RegisterRenderer registers a new markup file renderer
|
||||||
func RegisterRenderer(renderer Renderer) {
|
func RegisterRenderer(renderer Renderer) {
|
||||||
|
// TODO: need to handle conflicts
|
||||||
renderers[renderer.Name()] = renderer
|
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 DetectRendererTypeByFilename(filename string) Renderer {
|
||||||
func GetRendererByFileName(filename string) Renderer {
|
basename := path.Base(strings.ToLower(filename))
|
||||||
extension := strings.ToLower(path.Ext(filename))
|
ext1 := path.Ext(basename)
|
||||||
return extRenderers[extension]
|
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
|
// DetectRendererTypeByPrefetch detects the markup type of the content
|
||||||
func DetectRendererType(filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte) string {
|
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 {
|
for _, renderer := range renderers {
|
||||||
if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, sniffedType, prefetchBuf) {
|
if detector, ok := renderer.(RendererContentDetector); ok && detector.CanRender(filename, sniffedType, prefetchBuf) {
|
||||||
return renderer.Name()
|
return renderer.Name()
|
||||||
@@ -71,18 +97,12 @@ func DetectRendererType(filename string, sniffedType typesniffer.SniffedType, pr
|
|||||||
return ""
|
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 {
|
func PreviewableExtensions() []string {
|
||||||
extensions := make([]string, 0, len(extRenderers))
|
exts := make([]string, 0, len(fileNameRenderers))
|
||||||
for extension := range extRenderers {
|
for p := range fileNameRenderers {
|
||||||
extensions = append(extensions, extension)
|
if s, ok := strings.CutPrefix(p, "*"); ok {
|
||||||
|
exts = append(exts, s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return extensions
|
return exts
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-19
@@ -6,6 +6,7 @@ package setting
|
|||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@@ -43,22 +44,20 @@ var Markdown = struct {
|
|||||||
RenderOptionsRepoFile MarkdownRenderOptions `ini:"-"`
|
RenderOptionsRepoFile MarkdownRenderOptions `ini:"-"`
|
||||||
|
|
||||||
CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` // Actually it is a "markup" option because it is used in "post processor"
|
CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` // Actually it is a "markup" option because it is used in "post processor"
|
||||||
FileExtensions []string
|
FileNamePatterns []string `ini:"-"`
|
||||||
|
|
||||||
EnableMath bool
|
EnableMath bool
|
||||||
MathCodeBlockDetection []string
|
MathCodeBlockDetection []string
|
||||||
MathCodeBlockOptions MarkdownMathCodeBlockOptions `ini:"-"`
|
MathCodeBlockOptions MarkdownMathCodeBlockOptions `ini:"-"`
|
||||||
}{
|
}{
|
||||||
FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","),
|
EnableMath: true,
|
||||||
EnableMath: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkupRenderer defines the external parser configured in ini
|
// MarkupRenderer defines the external parser configured in ini
|
||||||
type MarkupRenderer struct {
|
type MarkupRenderer struct {
|
||||||
Enabled bool
|
|
||||||
MarkupName string
|
MarkupName string
|
||||||
Command string
|
Command string
|
||||||
FileExtensions []string
|
FilePatterns []string
|
||||||
IsInputFile bool
|
IsInputFile bool
|
||||||
NeedPostProcess bool
|
NeedPostProcess bool
|
||||||
MarkupSanitizerRules []MarkupSanitizerRule
|
MarkupSanitizerRules []MarkupSanitizerRule
|
||||||
@@ -77,6 +76,13 @@ type MarkupSanitizerRule struct {
|
|||||||
|
|
||||||
func loadMarkupFrom(rootCfg ConfigProvider) {
|
func loadMarkupFrom(rootCfg ConfigProvider) {
|
||||||
mustMapSetting(rootCfg, "markdown", &Markdown)
|
mustMapSetting(rootCfg, "markdown", &Markdown)
|
||||||
|
|
||||||
|
markdownFileExtensions := rootCfg.Section("markdown").Key("FILE_EXTENSIONS").Strings(",")
|
||||||
|
if len(markdownFileExtensions) == 0 || len(markdownFileExtensions) == 1 && markdownFileExtensions[0] == "" {
|
||||||
|
markdownFileExtensions = []string{".md", ".markdown", ".mdown", ".mkd", ".livemd"}
|
||||||
|
}
|
||||||
|
Markdown.FileNamePatterns = fileExtensionsToPatterns("markdown", markdownFileExtensions)
|
||||||
|
|
||||||
const none = "none"
|
const none = "none"
|
||||||
|
|
||||||
const renderOptionShortIssuePattern = "short-issue-pattern"
|
const renderOptionShortIssuePattern = "short-issue-pattern"
|
||||||
@@ -215,21 +221,30 @@ func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerR
|
|||||||
return rule, true
|
return rule, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMarkupRenderer(name string, sec ConfigSection) {
|
var extensionReg = sync.OnceValue(func() *regexp.Regexp {
|
||||||
extensionReg := regexp.MustCompile(`\.\w`)
|
return regexp.MustCompile(`^(\.[-\w]+)+$`)
|
||||||
|
})
|
||||||
|
|
||||||
extensions := sec.Key("FILE_EXTENSIONS").Strings(",")
|
func fileExtensionsToPatterns(sectionName string, extensions []string) []string {
|
||||||
exts := make([]string, 0, len(extensions))
|
patterns := make([]string, 0, len(extensions))
|
||||||
for _, extension := range extensions {
|
for _, extension := range extensions {
|
||||||
if !extensionReg.MatchString(extension) {
|
if !extensionReg().MatchString(extension) {
|
||||||
log.Warn(sec.Name() + " file extension " + extension + " is invalid. Extension ignored")
|
log.Warn("Config section %s file extension %s is invalid. Extension ignored", sectionName, extension)
|
||||||
} else {
|
} else {
|
||||||
exts = append(exts, extension)
|
patterns = append(patterns, "*"+extension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return patterns
|
||||||
|
}
|
||||||
|
|
||||||
if len(exts) == 0 {
|
func newMarkupRenderer(name string, sec ConfigSection) {
|
||||||
log.Warn(sec.Name() + " file extension is empty, markup " + name + " ignored")
|
if !sec.Key("ENABLED").MustBool(false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fileNamePatterns := fileExtensionsToPatterns(name, sec.Key("FILE_EXTENSIONS").Strings(","))
|
||||||
|
if len(fileNamePatterns) == 0 {
|
||||||
|
log.Warn("Config section %s file extension is empty, markup render is ignored", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,11 +277,10 @@ func newMarkupRenderer(name string, sec ConfigSection) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
|
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
|
||||||
Enabled: sec.Key("ENABLED").MustBool(false),
|
MarkupName: name,
|
||||||
MarkupName: name,
|
FilePatterns: fileNamePatterns,
|
||||||
FileExtensions: exts,
|
Command: command,
|
||||||
Command: command,
|
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
|
||||||
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
|
|
||||||
|
|
||||||
RenderContentMode: renderContentMode,
|
RenderContentMode: renderContentMode,
|
||||||
RenderContentSandbox: renderContentSandbox,
|
RenderContentSandbox: renderContentSandbox,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"htmx.org": "2.0.8",
|
"htmx.org": "2.0.8",
|
||||||
"idiomorph": "0.7.4",
|
"idiomorph": "0.7.4",
|
||||||
"jquery": "4.0.0",
|
"jquery": "4.0.0",
|
||||||
|
"js-yaml": "4.1.1",
|
||||||
"katex": "0.16.27",
|
"katex": "0.16.27",
|
||||||
"mermaid": "11.12.2",
|
"mermaid": "11.12.2",
|
||||||
"mini-css-extract-plugin": "2.10.0",
|
"mini-css-extract-plugin": "2.10.0",
|
||||||
@@ -73,6 +74,7 @@
|
|||||||
"@types/codemirror": "5.60.17",
|
"@types/codemirror": "5.60.17",
|
||||||
"@types/dropzone": "5.7.9",
|
"@types/dropzone": "5.7.9",
|
||||||
"@types/jquery": "3.5.33",
|
"@types/jquery": "3.5.33",
|
||||||
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/katex": "0.16.8",
|
"@types/katex": "0.16.8",
|
||||||
"@types/pdfobject": "2.2.5",
|
"@types/pdfobject": "2.2.5",
|
||||||
"@types/sortablejs": "1.15.9",
|
"@types/sortablejs": "1.15.9",
|
||||||
|
|||||||
Generated
+40
@@ -116,6 +116,9 @@ importers:
|
|||||||
jquery:
|
jquery:
|
||||||
specifier: 4.0.0
|
specifier: 4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
|
js-yaml:
|
||||||
|
specifier: 4.1.1
|
||||||
|
version: 4.1.1
|
||||||
katex:
|
katex:
|
||||||
specifier: 0.16.27
|
specifier: 0.16.27
|
||||||
version: 0.16.27
|
version: 0.16.27
|
||||||
@@ -222,6 +225,9 @@ importers:
|
|||||||
'@types/jquery':
|
'@types/jquery':
|
||||||
specifier: 3.5.33
|
specifier: 3.5.33
|
||||||
version: 3.5.33
|
version: 3.5.33
|
||||||
|
'@types/js-yaml':
|
||||||
|
specifier: 4.0.9
|
||||||
|
version: 4.0.9
|
||||||
'@types/katex':
|
'@types/katex':
|
||||||
specifier: 0.16.8
|
specifier: 0.16.8
|
||||||
version: 0.16.8
|
version: 0.16.8
|
||||||
@@ -930,41 +936,49 @@ packages:
|
|||||||
resolution: {integrity: sha512-qNQk0H6q1CnwS9cnvyjk9a+JN8BTbxK7K15Bb5hYfJcKTG1hfloQf6egndKauYOO0wu9ldCMPBrEP1FNIQEhaA==}
|
resolution: {integrity: sha512-qNQk0H6q1CnwS9cnvyjk9a+JN8BTbxK7K15Bb5hYfJcKTG1hfloQf6egndKauYOO0wu9ldCMPBrEP1FNIQEhaA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-arm64-musl@11.16.4':
|
'@oxc-resolver/binding-linux-arm64-musl@11.16.4':
|
||||||
resolution: {integrity: sha512-wEXSaEaYxGGoVSbw0i2etjDDWcqErKr8xSkTdwATP798efsZmodUAcLYJhN0Nd4W35Oq6qAvFGHpKwFrrhpTrA==}
|
resolution: {integrity: sha512-wEXSaEaYxGGoVSbw0i2etjDDWcqErKr8xSkTdwATP798efsZmodUAcLYJhN0Nd4W35Oq6qAvFGHpKwFrrhpTrA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-ppc64-gnu@11.16.4':
|
'@oxc-resolver/binding-linux-ppc64-gnu@11.16.4':
|
||||||
resolution: {integrity: sha512-CUFOlpb07DVOFLoYiaTfbSBRPIhNgwc/MtlYeg3p6GJJw+kEm/vzc9lohPSjzF2MLPB5hzsJdk+L/GjrTT3UPw==}
|
resolution: {integrity: sha512-CUFOlpb07DVOFLoYiaTfbSBRPIhNgwc/MtlYeg3p6GJJw+kEm/vzc9lohPSjzF2MLPB5hzsJdk+L/GjrTT3UPw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-riscv64-gnu@11.16.4':
|
'@oxc-resolver/binding-linux-riscv64-gnu@11.16.4':
|
||||||
resolution: {integrity: sha512-d8It4AH8cN9ReK1hW6ZO4x3rMT0hB2LYH0RNidGogV9xtnjLRU+Y3MrCeClLyOSGCibmweJJAjnwB7AQ31GEhg==}
|
resolution: {integrity: sha512-d8It4AH8cN9ReK1hW6ZO4x3rMT0hB2LYH0RNidGogV9xtnjLRU+Y3MrCeClLyOSGCibmweJJAjnwB7AQ31GEhg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-riscv64-musl@11.16.4':
|
'@oxc-resolver/binding-linux-riscv64-musl@11.16.4':
|
||||||
resolution: {integrity: sha512-d09dOww9iKyEHSxuOQ/Iu2aYswl0j7ExBcyy14D6lJ5ijQSP9FXcJYJsJ3yvzboO/PDEFjvRuF41f8O1skiPVg==}
|
resolution: {integrity: sha512-d09dOww9iKyEHSxuOQ/Iu2aYswl0j7ExBcyy14D6lJ5ijQSP9FXcJYJsJ3yvzboO/PDEFjvRuF41f8O1skiPVg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-s390x-gnu@11.16.4':
|
'@oxc-resolver/binding-linux-s390x-gnu@11.16.4':
|
||||||
resolution: {integrity: sha512-lhjyGmUzTWHduZF3MkdUSEPMRIdExnhsqv8u1upX3A15epVn6YVwv4msFQPJl1x1wszkACPeDHGOtzHsITXGdw==}
|
resolution: {integrity: sha512-lhjyGmUzTWHduZF3MkdUSEPMRIdExnhsqv8u1upX3A15epVn6YVwv4msFQPJl1x1wszkACPeDHGOtzHsITXGdw==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-x64-gnu@11.16.4':
|
'@oxc-resolver/binding-linux-x64-gnu@11.16.4':
|
||||||
resolution: {integrity: sha512-ZtqqiI5rzlrYBm/IMMDIg3zvvVj4WO/90Dg/zX+iA8lWaLN7K5nroXb17MQ4WhI5RqlEAgrnYDXW+hok1D9Kaw==}
|
resolution: {integrity: sha512-ZtqqiI5rzlrYBm/IMMDIg3zvvVj4WO/90Dg/zX+iA8lWaLN7K5nroXb17MQ4WhI5RqlEAgrnYDXW+hok1D9Kaw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxc-resolver/binding-linux-x64-musl@11.16.4':
|
'@oxc-resolver/binding-linux-x64-musl@11.16.4':
|
||||||
resolution: {integrity: sha512-LM424h7aaKcMlqHnQWgTzO+GRNLyjcNnMpqm8SygEtFRVW693XS+XGXYvjORlmJtsyjo84ej1FMb3U2HE5eyjg==}
|
resolution: {integrity: sha512-LM424h7aaKcMlqHnQWgTzO+GRNLyjcNnMpqm8SygEtFRVW693XS+XGXYvjORlmJtsyjo84ej1FMb3U2HE5eyjg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxc-resolver/binding-openharmony-arm64@11.16.4':
|
'@oxc-resolver/binding-openharmony-arm64@11.16.4':
|
||||||
resolution: {integrity: sha512-8w8U6A5DDWTBv3OUxSD9fNk37liZuEC5jnAc9wQRv9DeYKAXvuUtBfT09aIZ58swaci0q1WS48/CoMVEO6jdCA==}
|
resolution: {integrity: sha512-8w8U6A5DDWTBv3OUxSD9fNk37liZuEC5jnAc9wQRv9DeYKAXvuUtBfT09aIZ58swaci0q1WS48/CoMVEO6jdCA==}
|
||||||
@@ -1047,66 +1061,79 @@ packages:
|
|||||||
resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==}
|
resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.56.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.56.0':
|
||||||
resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==}
|
resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.56.0':
|
'@rollup/rollup-linux-arm64-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==}
|
resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.56.0':
|
'@rollup/rollup-linux-arm64-musl@4.56.0':
|
||||||
resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==}
|
resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.56.0':
|
'@rollup/rollup-linux-loong64-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==}
|
resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-musl@4.56.0':
|
'@rollup/rollup-linux-loong64-musl@4.56.0':
|
||||||
resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==}
|
resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.56.0':
|
'@rollup/rollup-linux-ppc64-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==}
|
resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-musl@4.56.0':
|
'@rollup/rollup-linux-ppc64-musl@4.56.0':
|
||||||
resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==}
|
resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.56.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==}
|
resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.56.0':
|
'@rollup/rollup-linux-riscv64-musl@4.56.0':
|
||||||
resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==}
|
resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.56.0':
|
'@rollup/rollup-linux-s390x-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==}
|
resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.56.0':
|
'@rollup/rollup-linux-x64-gnu@4.56.0':
|
||||||
resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==}
|
resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.56.0':
|
'@rollup/rollup-linux-x64-musl@4.56.0':
|
||||||
resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==}
|
resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-openbsd-x64@4.56.0':
|
'@rollup/rollup-openbsd-x64@4.56.0':
|
||||||
resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==}
|
resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==}
|
||||||
@@ -1325,6 +1352,9 @@ packages:
|
|||||||
'@types/jquery@3.5.33':
|
'@types/jquery@3.5.33':
|
||||||
resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==}
|
resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==}
|
||||||
|
|
||||||
|
'@types/js-yaml@4.0.9':
|
||||||
|
resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
@@ -1477,41 +1507,49 @@ packages:
|
|||||||
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
||||||
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
||||||
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
||||||
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
||||||
@@ -5325,6 +5363,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/sizzle': 2.3.10
|
'@types/sizzle': 2.3.10
|
||||||
|
|
||||||
|
'@types/js-yaml@4.0.9': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/json5@0.0.29': {}
|
'@types/json5@0.0.29': {}
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ Here are some links to the most important topics. You can find the full list of
|
|||||||
<a href="http://localhost:3000/user2/repo1/src/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" alt="Image"/></a></p>
|
<a href="http://localhost:3000/user2/repo1/src/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" alt="Image"/></a></p>
|
||||||
`, http.StatusOK)
|
`, http.StatusOK)
|
||||||
|
|
||||||
testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity)
|
testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unable to find a render\n", http.StatusUnprocessableEntity)
|
||||||
testRenderMarkup(t, "unknown", false, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
testRenderMarkup(t, "unknown", false, "", "## Test", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleCases = []string{
|
var simpleCases = []string{
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/renderhelper"
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
"code.gitea.io/gitea/models/repo"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"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"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@@ -31,7 +32,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
|
|
||||||
if mode == "" || mode == "markdown" {
|
if mode == "" || mode == "markdown" {
|
||||||
// raw markdown doesn't need any special handling
|
// raw Markdown doesn't need any special handling
|
||||||
baseLink := urlPathContext
|
baseLink := urlPathContext
|
||||||
if baseLink == "" {
|
if baseLink == "" {
|
||||||
baseLink = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
baseLink = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||||
@@ -39,7 +40,8 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
rctx := renderhelper.NewRenderContextSimpleDocument(ctx, baseLink).WithUseAbsoluteLink(true).
|
rctx := renderhelper.NewRenderContextSimpleDocument(ctx, baseLink).WithUseAbsoluteLink(true).
|
||||||
WithMarkupType(markdown.MarkupName)
|
WithMarkupType(markdown.MarkupName)
|
||||||
if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil {
|
if err := markdown.RenderRaw(rctx, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
log.Error("RenderMarkupRaw: %v", err)
|
||||||
|
ctx.HTTPError(http.StatusInternalServerError, "failed to render raw markup")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -92,7 +94,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
})
|
})
|
||||||
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension
|
||||||
default:
|
default:
|
||||||
ctx.HTTPError(http.StatusUnprocessableEntity, "Unknown mode: "+mode)
|
ctx.HTTPError(http.StatusUnprocessableEntity, "unsupported render mode: "+mode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rctx = rctx.WithUseAbsoluteLink(true)
|
rctx = rctx.WithUseAbsoluteLink(true)
|
||||||
@@ -100,7 +102,8 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur
|
|||||||
if errors.Is(err, util.ErrInvalidArgument) {
|
if errors.Is(err, util.ErrInvalidArgument) {
|
||||||
ctx.HTTPError(http.StatusUnprocessableEntity, err.Error())
|
ctx.HTTPError(http.StatusUnprocessableEntity, err.Error())
|
||||||
} else {
|
} else {
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
log.Error("RenderMarkup: %v", err)
|
||||||
|
ctx.HTTPError(http.StatusInternalServerError, "failed to render markup")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,24 +32,18 @@ func RenderFile(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dataRc, err := blob.DataAsync()
|
blobReader, err := blob.DataAsync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("DataAsync", err)
|
ctx.ServerError("DataAsync", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer dataRc.Close()
|
defer blobReader.Close()
|
||||||
|
|
||||||
if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType == "" {
|
|
||||||
http.Error(ctx.Resp, "Unsupported file type render", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
}).WithRelativePath(ctx.Repo.TreePath).WithInStandalonePage(true)
|
}).WithRelativePath(ctx.Repo.TreePath).WithInStandalonePage(true)
|
||||||
|
renderer, rendererInput, err := rctx.DetectMarkupRendererByReader(blobReader)
|
||||||
renderer, err := markup.FindRendererByContext(rctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(ctx.Resp, "Unable to find renderer", http.StatusBadRequest)
|
http.Error(ctx.Resp, "Unable to find renderer", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -71,7 +65,7 @@ func RenderFile(ctx *context.Context) {
|
|||||||
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = markup.RenderWithRenderer(rctx, renderer, dataRc, ctx.Resp)
|
err = markup.RenderWithRenderer(rctx, renderer, rendererInput, ctx.Resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
|
||||||
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -151,12 +151,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
|
func markupRenderToHTML(ctx *context.Context, renderCtx *markup.RenderContext, renderer markup.Renderer, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
|
||||||
renderer, err := markup.FindRendererByContext(renderCtx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
markupRd, markupWr := io.Pipe()
|
markupRd, markupWr := io.Pipe()
|
||||||
defer markupWr.Close()
|
defer markupWr.Close()
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/git/attribute"
|
"code.gitea.io/gitea/modules/git/attribute"
|
||||||
"code.gitea.io/gitea/modules/highlight"
|
"code.gitea.io/gitea/modules/highlight"
|
||||||
"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/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
issue_service "code.gitea.io/gitea/services/issue"
|
issue_service "code.gitea.io/gitea/services/issue"
|
||||||
@@ -60,14 +58,19 @@ func prepareFileViewLfsAttrs(ctx *context.Context) (*attribute.Attributes, bool)
|
|||||||
return attrs, true
|
return attrs, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte, utf8Reader io.Reader) bool {
|
func handleFileViewRenderMarkup(ctx *context.Context, prefetchBuf []byte, utf8Reader io.Reader) bool {
|
||||||
markupType := markup.DetectMarkupTypeByFileName(filename)
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
if markupType == "" {
|
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||||
markupType = markup.DetectRendererType(filename, sniffedType, prefetchBuf)
|
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
||||||
}
|
}).WithRelativePath(ctx.Repo.TreePath)
|
||||||
if markupType == "" {
|
|
||||||
return false
|
renderer := rctx.DetectMarkupRenderer(prefetchBuf)
|
||||||
|
if renderer == nil {
|
||||||
|
return false // not supported markup
|
||||||
}
|
}
|
||||||
|
metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
|
||||||
|
metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
|
||||||
|
rctx.WithMetas(metas)
|
||||||
|
|
||||||
ctx.Data["HasSourceRenderedToggle"] = true
|
ctx.Data["HasSourceRenderedToggle"] = true
|
||||||
|
|
||||||
@@ -75,19 +78,10 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = rctx.RenderOptions.MarkupType
|
||||||
metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx)
|
|
||||||
metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL()
|
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
|
||||||
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
|
|
||||||
}).
|
|
||||||
WithMarkupType(markupType).
|
|
||||||
WithRelativePath(ctx.Repo.TreePath).
|
|
||||||
WithMetas(metas)
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, utf8Reader)
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRenderToHTML(ctx, rctx, renderer, utf8Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Render", err)
|
ctx.ServerError("Render", err)
|
||||||
return true
|
return true
|
||||||
@@ -95,7 +89,8 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleFileViewRenderSource(ctx *context.Context, filename string, attrs *attribute.Attributes, fInfo *fileInfo, utf8Reader io.Reader) bool {
|
func handleFileViewRenderSource(ctx *context.Context, attrs *attribute.Attributes, fInfo *fileInfo, utf8Reader io.Reader) bool {
|
||||||
|
filename := ctx.Repo.TreePath
|
||||||
if ctx.FormString("display") == "rendered" || !fInfo.st.IsRepresentableAsText() {
|
if ctx.FormString("display") == "rendered" || !fInfo.st.IsRepresentableAsText() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -246,10 +241,10 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
switch {
|
switch {
|
||||||
case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize:
|
case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize:
|
||||||
ctx.Data["IsFileTooLarge"] = true
|
ctx.Data["IsFileTooLarge"] = true
|
||||||
case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, contentReader):
|
case handleFileViewRenderMarkup(ctx, buf, contentReader):
|
||||||
// it also sets ctx.Data["FileContent"] and more
|
// it also sets ctx.Data["FileContent"] and more
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, contentReader):
|
case handleFileViewRenderSource(ctx, attrs, fInfo, contentReader):
|
||||||
// it also sets ctx.Data["FileContent"] and more
|
// it also sets ctx.Data["FileContent"] and more
|
||||||
ctx.Data["IsDisplayingSource"] = true
|
ctx.Data["IsDisplayingSource"] = true
|
||||||
case handleFileViewRenderImage(ctx, fInfo, buf):
|
case handleFileViewRenderImage(ctx, fInfo, buf):
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/charset"
|
"code.gitea.io/gitea/modules/charset"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"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/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
@@ -190,18 +189,15 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
|
|||||||
|
|
||||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||||
|
|
||||||
if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" {
|
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
||||||
|
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
||||||
|
CurrentTreePath: path.Dir(readmeFullPath),
|
||||||
|
}).WithRelativePath(readmeFullPath)
|
||||||
|
renderer := rctx.DetectMarkupRenderer(buf)
|
||||||
|
if renderer != nil {
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
ctx.Data["MarkupType"] = markupType
|
ctx.Data["MarkupType"] = rctx.RenderOptions.MarkupType
|
||||||
|
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRenderToHTML(ctx, rctx, renderer, rd)
|
||||||
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
|
|
||||||
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
|
|
||||||
CurrentTreePath: path.Dir(readmeFullPath),
|
|
||||||
}).
|
|
||||||
WithMarkupType(markupType).
|
|
||||||
WithRelativePath(readmeFullPath)
|
|
||||||
|
|
||||||
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
|
||||||
delete(ctx.Data, "IsMarkup")
|
delete(ctx.Data, "IsMarkup")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path/filepath"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/renderhelper"
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
@@ -490,9 +490,9 @@ func Wiki(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wikiPath := entry.Name()
|
wikiPath := entry.Name()
|
||||||
if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName {
|
detectedRender := markup.DetectRendererTypeByFilename(wikiPath)
|
||||||
ext := strings.ToUpper(filepath.Ext(wikiPath))
|
if detectedRender == nil || detectedRender.Name() != markdown.MarkupName {
|
||||||
ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown."
|
ctx.Data["FormatWarning"] = "File extension " + path.Ext(wikiPath) + " is not supported at the moment. Rendered as Markdown."
|
||||||
}
|
}
|
||||||
// Get last change information.
|
// Get last change information.
|
||||||
lastCommit, err := wikiGitRepo.GetCommitByPath(wikiPath)
|
lastCommit, err := wikiGitRepo.GetCommitByPath(wikiPath)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func initThemes() {
|
|||||||
setting.LogStartupProblem(1, log.ERROR, "Default theme %q is not available, please correct the '[ui].DEFAULT_THEME' setting in the config file", setting.UI.DefaultTheme)
|
setting.LogStartupProblem(1, log.ERROR, "Default theme %q is not available, please correct the '[ui].DEFAULT_THEME' setting in the config file", setting.UI.DefaultTheme)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
cssFiles, err := public.AssetFS().ListFiles("/assets/css")
|
cssFiles, err := public.AssetFS().ListFiles("assets/css")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to list themes: %v", err)
|
log.Error("Failed to list themes: %v", err)
|
||||||
availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
|
availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ func TestExternalMarkupRenderer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// above tested in-page rendering (no iframe), then we test iframe mode below
|
// above tested in-page rendering (no iframe), then we test iframe mode below
|
||||||
r := markup.GetRendererByFileName("any-file.html").(*external.Renderer)
|
r := markup.DetectRendererTypeByFilename("any-file.html").(*external.Renderer)
|
||||||
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
|
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
|
||||||
assert.True(t, r.NeedPostProcess())
|
assert.True(t, r.NeedPostProcess())
|
||||||
r = markup.GetRendererByFileName("any-file.no-sanitizer").(*external.Renderer)
|
r = markup.DetectRendererTypeByFilename("any-file.no-sanitizer").(*external.Renderer)
|
||||||
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
|
defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)()
|
||||||
assert.False(t, r.NeedPostProcess())
|
assert.False(t, r.NeedPostProcess())
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ body {
|
|||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.swagger-spec-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
background: #1e1e1e;
|
background: #14171a;
|
||||||
}
|
}
|
||||||
.swagger-ui, .swagger-back-link {
|
.swagger-ui, .swagger-back-link {
|
||||||
filter: invert(88%) hue-rotate(180deg);
|
filter: invert(88%) hue-rotate(180deg);
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ async function loadRenderIframeContent(iframe: HTMLIFrameElement) {
|
|||||||
if (!e.data?.giteaIframeCmd || e.data?.giteaIframeId !== iframe.id) return;
|
if (!e.data?.giteaIframeCmd || e.data?.giteaIframeId !== iframe.id) return;
|
||||||
const cmd = e.data.giteaIframeCmd;
|
const cmd = e.data.giteaIframeCmd;
|
||||||
if (cmd === 'resize') {
|
if (cmd === 'resize') {
|
||||||
iframe.style.height = `${e.data.iframeHeight}px`;
|
// TODO: sometimes the reported iframeHeight is not the size we need, need to figure why. Example: openapi swagger.
|
||||||
|
// As a workaround, add some pixels here.
|
||||||
|
iframe.style.height = `${e.data.iframeHeight + 2}px`;
|
||||||
} else if (cmd === 'open-link') {
|
} else if (cmd === 'open-link') {
|
||||||
if (e.data.anchorTarget === '_blank') {
|
if (e.data.anchorTarget === '_blank') {
|
||||||
window.open(e.data.openLink, '_blank');
|
window.open(e.data.openLink, '_blank');
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ function mainExternalRenderIframe() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateIframeHeight = () => postIframeMsg('resize', {iframeHeight: document.documentElement.scrollHeight});
|
const updateIframeHeight = () => postIframeMsg('resize', {iframeHeight: document.documentElement.scrollHeight});
|
||||||
|
const resizeObserver = new ResizeObserver(() => updateIframeHeight());
|
||||||
|
resizeObserver.observe(window.document.documentElement);
|
||||||
|
|
||||||
updateIframeHeight();
|
updateIframeHeight();
|
||||||
window.addEventListener('DOMContentLoaded', updateIframeHeight);
|
window.addEventListener('DOMContentLoaded', updateIframeHeight);
|
||||||
// the easiest way to handle dynamic content changes and easy to debug, can be fine-tuned in the future
|
// the easiest way to handle dynamic content changes and easy to debug, can be fine-tuned in the future
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
import SwaggerUI from 'swagger-ui-dist/swagger-ui-es-bundle.js';
|
import SwaggerUI from 'swagger-ui-dist/swagger-ui-es-bundle.js';
|
||||||
import 'swagger-ui-dist/swagger-ui.css';
|
import 'swagger-ui-dist/swagger-ui.css';
|
||||||
|
import {load as loadYaml} from 'js-yaml';
|
||||||
|
|
||||||
window.addEventListener('load', async () => {
|
window.addEventListener('load', async () => {
|
||||||
const url = document.querySelector('#swagger-ui')!.getAttribute('data-source')!;
|
const elSwaggerUi = document.querySelector('#swagger-ui')!;
|
||||||
const res = await fetch(url);
|
const url = elSwaggerUi.getAttribute('data-source')!;
|
||||||
const spec = await res.json();
|
let spec: any;
|
||||||
|
if (url) {
|
||||||
|
const res = await fetch(url);
|
||||||
|
spec = await res.json();
|
||||||
|
} else {
|
||||||
|
const elSpecContent = elSwaggerUi.querySelector<HTMLTextAreaElement>('.swagger-spec-content')!;
|
||||||
|
const filename = elSpecContent.getAttribute('data-spec-filename');
|
||||||
|
const isJson = filename?.toLowerCase().endsWith('.json');
|
||||||
|
spec = isJson ? JSON.parse(elSpecContent.value) : loadYaml(elSpecContent.value);
|
||||||
|
}
|
||||||
|
|
||||||
// Make the page's protocol be at the top of the schemes list
|
// Make the page's protocol be at the top of the schemes list
|
||||||
const proto = window.location.protocol.slice(0, -1);
|
const proto = window.location.protocol.slice(0, -1);
|
||||||
spec.schemes.sort((a: string, b: string) => {
|
if (spec?.schemes) {
|
||||||
if (a === proto) return -1;
|
spec.schemes.sort((a: string, b: string) => {
|
||||||
if (b === proto) return 1;
|
if (a === proto) return -1;
|
||||||
return 0;
|
if (b === proto) return 1;
|
||||||
});
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
SwaggerUI({
|
SwaggerUI({
|
||||||
spec,
|
spec,
|
||||||
|
|||||||
Reference in New Issue
Block a user