diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go index 8e3fb5ff99..3df6eeab68 100644 --- a/modules/git/tree_entry_test.go +++ b/modules/git/tree_entry_test.go @@ -22,8 +22,9 @@ func TestEntriesCustomSort(t *testing.T) { &TreeEntry{name: "b-file", entryMode: EntryModeBlob}, } expected := slices.Clone(entries) - rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] }) - assert.NotEqual(t, expected, entries) + for slices.Equal(expected, entries) { + rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] }) + } entries.CustomSort(strings.Compare) assert.Equal(t, expected, entries) } diff --git a/modules/markup/asciicast/asciicast.go b/modules/markup/asciicast/asciicast.go index d86d61d7c4..b3af5eef09 100644 --- a/modules/markup/asciicast/asciicast.go +++ b/modules/markup/asciicast/asciicast.go @@ -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, diff --git a/modules/markup/console/console.go b/modules/markup/console/console.go index 492579b0a5..bcd6cfce77 100644 --- a/modules/markup/console/console.go +++ b/modules/markup/console/console.go @@ -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 diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go index 61977d458a..910a1518bc 100644 --- a/modules/markup/csv/csv.go +++ b/modules/markup/csv/csv.go @@ -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$`}, diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 3cbe14b86a..4d447e301a 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -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 } diff --git a/modules/markup/external/openapi.go b/modules/markup/external/openapi.go new file mode 100644 index 0000000000..ac5eae53ff --- /dev/null +++ b/modules/markup/external/openapi.go @@ -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( + ` + +
+ + + + + + + +`, + setting.StaticURLPrefix, + setting.AssetVersion, + html.EscapeString(ctx.RenderOptions.RelativePath), + html.EscapeString(util.UnsafeBytesToString(content)), + setting.StaticURLPrefix, + setting.AssetVersion, + )) + return err +} diff --git a/modules/markup/main_test.go b/modules/markup/main_test.go index 564f55ac11..a8dcff475d 100644 --- a/modules/markup/main_test.go +++ b/modules/markup/main_test.go @@ -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()) } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 792fb1f6d5..cca44a8774 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -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) } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index ff2b7600eb..17d994734a 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -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{} } diff --git a/modules/markup/render.go b/modules/markup/render.go index 96c9c5f0df..5785dc5ad5 100644 --- a/modules/markup/render.go +++ b/modules/markup/render.go @@ -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 { diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index 82e9348706..c62c28ad2a 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -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 } diff --git a/modules/setting/markup.go b/modules/setting/markup.go index caf0d5f8d9..921af60ff5 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -6,6 +6,7 @@ package setting import ( "regexp" "strings" + "sync" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/util" @@ -43,22 +44,20 @@ var Markdown = struct { RenderOptionsRepoFile MarkdownRenderOptions `ini:"-"` 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 MathCodeBlockDetection []string MathCodeBlockOptions MarkdownMathCodeBlockOptions `ini:"-"` }{ - FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","), - EnableMath: true, + EnableMath: true, } // MarkupRenderer defines the external parser configured in ini type MarkupRenderer struct { - Enabled bool MarkupName string Command string - FileExtensions []string + FilePatterns []string IsInputFile bool NeedPostProcess bool MarkupSanitizerRules []MarkupSanitizerRule @@ -77,6 +76,13 @@ type MarkupSanitizerRule struct { func loadMarkupFrom(rootCfg ConfigProvider) { 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 renderOptionShortIssuePattern = "short-issue-pattern" @@ -215,21 +221,30 @@ func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerR return rule, true } -func newMarkupRenderer(name string, sec ConfigSection) { - extensionReg := regexp.MustCompile(`\.\w`) +var extensionReg = sync.OnceValue(func() *regexp.Regexp { + return regexp.MustCompile(`^(\.[-\w]+)+$`) +}) - extensions := sec.Key("FILE_EXTENSIONS").Strings(",") - exts := make([]string, 0, len(extensions)) +func fileExtensionsToPatterns(sectionName string, extensions []string) []string { + patterns := make([]string, 0, len(extensions)) for _, extension := range extensions { - if !extensionReg.MatchString(extension) { - log.Warn(sec.Name() + " file extension " + extension + " is invalid. Extension ignored") + if !extensionReg().MatchString(extension) { + log.Warn("Config section %s file extension %s is invalid. Extension ignored", sectionName, extension) } else { - exts = append(exts, extension) + patterns = append(patterns, "*"+extension) } } + return patterns +} - if len(exts) == 0 { - log.Warn(sec.Name() + " file extension is empty, markup " + name + " ignored") +func newMarkupRenderer(name string, sec ConfigSection) { + 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 } @@ -262,11 +277,10 @@ func newMarkupRenderer(name string, sec ConfigSection) { } ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{ - Enabled: sec.Key("ENABLED").MustBool(false), - MarkupName: name, - FileExtensions: exts, - Command: command, - IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false), + MarkupName: name, + FilePatterns: fileNamePatterns, + Command: command, + IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false), RenderContentMode: renderContentMode, RenderContentSandbox: renderContentSandbox, diff --git a/package.json b/package.json index b9ede1ea09..acac860a39 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "htmx.org": "2.0.8", "idiomorph": "0.7.4", "jquery": "4.0.0", + "js-yaml": "4.1.1", "katex": "0.16.27", "mermaid": "11.12.2", "mini-css-extract-plugin": "2.10.0", @@ -73,6 +74,7 @@ "@types/codemirror": "5.60.17", "@types/dropzone": "5.7.9", "@types/jquery": "3.5.33", + "@types/js-yaml": "4.0.9", "@types/katex": "0.16.8", "@types/pdfobject": "2.2.5", "@types/sortablejs": "1.15.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29a6dcbd70..4e39cff537 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: jquery: specifier: 4.0.0 version: 4.0.0 + js-yaml: + specifier: 4.1.1 + version: 4.1.1 katex: specifier: 0.16.27 version: 0.16.27 @@ -222,6 +225,9 @@ importers: '@types/jquery': specifier: 3.5.33 version: 3.5.33 + '@types/js-yaml': + specifier: 4.0.9 + version: 4.0.9 '@types/katex': specifier: 0.16.8 version: 0.16.8 @@ -930,41 +936,49 @@ packages: resolution: {integrity: sha512-qNQk0H6q1CnwS9cnvyjk9a+JN8BTbxK7K15Bb5hYfJcKTG1hfloQf6egndKauYOO0wu9ldCMPBrEP1FNIQEhaA==} cpu: [arm64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-arm64-musl@11.16.4': resolution: {integrity: sha512-wEXSaEaYxGGoVSbw0i2etjDDWcqErKr8xSkTdwATP798efsZmodUAcLYJhN0Nd4W35Oq6qAvFGHpKwFrrhpTrA==} cpu: [arm64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-ppc64-gnu@11.16.4': resolution: {integrity: sha512-CUFOlpb07DVOFLoYiaTfbSBRPIhNgwc/MtlYeg3p6GJJw+kEm/vzc9lohPSjzF2MLPB5hzsJdk+L/GjrTT3UPw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-gnu@11.16.4': resolution: {integrity: sha512-d8It4AH8cN9ReK1hW6ZO4x3rMT0hB2LYH0RNidGogV9xtnjLRU+Y3MrCeClLyOSGCibmweJJAjnwB7AQ31GEhg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-riscv64-musl@11.16.4': resolution: {integrity: sha512-d09dOww9iKyEHSxuOQ/Iu2aYswl0j7ExBcyy14D6lJ5ijQSP9FXcJYJsJ3yvzboO/PDEFjvRuF41f8O1skiPVg==} cpu: [riscv64] os: [linux] + libc: [musl] '@oxc-resolver/binding-linux-s390x-gnu@11.16.4': resolution: {integrity: sha512-lhjyGmUzTWHduZF3MkdUSEPMRIdExnhsqv8u1upX3A15epVn6YVwv4msFQPJl1x1wszkACPeDHGOtzHsITXGdw==} cpu: [s390x] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-gnu@11.16.4': resolution: {integrity: sha512-ZtqqiI5rzlrYBm/IMMDIg3zvvVj4WO/90Dg/zX+iA8lWaLN7K5nroXb17MQ4WhI5RqlEAgrnYDXW+hok1D9Kaw==} cpu: [x64] os: [linux] + libc: [glibc] '@oxc-resolver/binding-linux-x64-musl@11.16.4': resolution: {integrity: sha512-LM424h7aaKcMlqHnQWgTzO+GRNLyjcNnMpqm8SygEtFRVW693XS+XGXYvjORlmJtsyjo84ej1FMb3U2HE5eyjg==} cpu: [x64] os: [linux] + libc: [musl] '@oxc-resolver/binding-openharmony-arm64@11.16.4': resolution: {integrity: sha512-8w8U6A5DDWTBv3OUxSD9fNk37liZuEC5jnAc9wQRv9DeYKAXvuUtBfT09aIZ58swaci0q1WS48/CoMVEO6jdCA==} @@ -1047,66 +1061,79 @@ packages: resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.56.0': resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.56.0': resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.56.0': resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.56.0': resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.56.0': resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.56.0': resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.56.0': resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.56.0': resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.56.0': resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.56.0': resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.56.0': resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.56.0': resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.56.0': resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==} @@ -1325,6 +1352,9 @@ packages: '@types/jquery@3.5.33': 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': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1477,41 +1507,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -5325,6 +5363,8 @@ snapshots: dependencies: '@types/sizzle': 2.3.10 + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index 38a1a3be9e..4d61b287ae 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -173,8 +173,8 @@ Here are some links to the most important topics. You can find the full list of
`, http.StatusOK)
- testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity)
- testRenderMarkup(t, "unknown", false, "", "## Test", "Unknown mode: 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", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity)
}
var simpleCases = []string{
diff --git a/routers/common/markup.go b/routers/common/markup.go
index 00b2dd07c6..e189bcdecf 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/renderhelper"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/httplib"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"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"
if mode == "" || mode == "markdown" {
- // raw markdown doesn't need any special handling
+ // raw Markdown doesn't need any special handling
baseLink := urlPathContext
if baseLink == "" {
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).
WithMarkupType(markdown.MarkupName)
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
}
@@ -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
default:
- ctx.HTTPError(http.StatusUnprocessableEntity, "Unknown mode: "+mode)
+ ctx.HTTPError(http.StatusUnprocessableEntity, "unsupported render mode: "+mode)
return
}
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) {
ctx.HTTPError(http.StatusUnprocessableEntity, err.Error())
} else {
- ctx.HTTPError(http.StatusInternalServerError, err.Error())
+ log.Error("RenderMarkup: %v", err)
+ ctx.HTTPError(http.StatusInternalServerError, "failed to render markup")
}
return
}
diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go
index d6447795bf..b1299c7047 100644
--- a/routers/web/repo/render.go
+++ b/routers/web/repo/render.go
@@ -32,24 +32,18 @@ func RenderFile(ctx *context.Context) {
return
}
- dataRc, err := blob.DataAsync()
+ blobReader, err := blob.DataAsync()
if err != nil {
ctx.ServerError("DataAsync", err)
return
}
- defer dataRc.Close()
-
- if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType == "" {
- http.Error(ctx.Resp, "Unsupported file type render", http.StatusBadRequest)
- return
- }
+ defer blobReader.Close()
rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
CurrentTreePath: path.Dir(ctx.Repo.TreePath),
}).WithRelativePath(ctx.Repo.TreePath).WithInStandalonePage(true)
-
- renderer, err := markup.FindRendererByContext(rctx)
+ renderer, rendererInput, err := rctx.DetectMarkupRendererByReader(blobReader)
if err != nil {
http.Error(ctx.Resp, "Unable to find renderer", http.StatusBadRequest)
return
@@ -71,7 +65,7 @@ func RenderFile(ctx *context.Context) {
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 {
log.Error("Failed to render file %q: %v", ctx.Repo.TreePath, err)
http.Error(ctx.Resp, "Failed to render file", http.StatusInternalServerError)
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 8e85cc3278..8aeb1a0af8 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -151,12 +151,7 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
return true
}
-func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
- renderer, err := markup.FindRendererByContext(renderCtx)
- if err != nil {
- return nil, "", err
- }
-
+func markupRenderToHTML(ctx *context.Context, renderCtx *markup.RenderContext, renderer markup.Renderer, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
markupRd, markupWr := io.Pipe()
defer markupWr.Close()
diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go
index aca5df944d..44bc8543b0 100644
--- a/routers/web/repo/view_file.go
+++ b/routers/web/repo/view_file.go
@@ -21,9 +21,7 @@ import (
"code.gitea.io/gitea/modules/git/attribute"
"code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
@@ -60,14 +58,19 @@ func prepareFileViewLfsAttrs(ctx *context.Context) (*attribute.Attributes, bool)
return attrs, true
}
-func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedType typesniffer.SniffedType, prefetchBuf []byte, utf8Reader io.Reader) bool {
- markupType := markup.DetectMarkupTypeByFileName(filename)
- if markupType == "" {
- markupType = markup.DetectRendererType(filename, sniffedType, prefetchBuf)
- }
- if markupType == "" {
- return false
+func handleFileViewRenderMarkup(ctx *context.Context, prefetchBuf []byte, utf8Reader io.Reader) bool {
+ rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
+ CurrentRefPath: ctx.Repo.RefTypeNameSubURL(),
+ CurrentTreePath: path.Dir(ctx.Repo.TreePath),
+ }).WithRelativePath(ctx.Repo.TreePath)
+
+ 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
@@ -75,19 +78,10 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
return false
}
- ctx.Data["MarkupType"] = 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)
+ ctx.Data["MarkupType"] = rctx.RenderOptions.MarkupType
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 {
ctx.ServerError("Render", err)
return true
@@ -95,7 +89,8 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
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() {
return false
}
@@ -246,10 +241,10 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) {
switch {
case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize:
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
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
ctx.Data["IsDisplayingSource"] = true
case handleFileViewRenderImage(ctx, fInfo, buf):
diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go
index f1fa5732f0..830709422e 100644
--- a/routers/web/repo/view_readme.go
+++ b/routers/web/repo/view_readme.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"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{})
- 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["MarkupType"] = markupType
-
- 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)
+ ctx.Data["MarkupType"] = rctx.RenderOptions.MarkupType
+ ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRenderToHTML(ctx, rctx, renderer, rd)
if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
delete(ctx.Data, "IsMarkup")
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 90d95bd250..5f775efb22 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -10,7 +10,7 @@ import (
"io"
"net/http"
"net/url"
- "path/filepath"
+ "path"
"strings"
"code.gitea.io/gitea/models/renderhelper"
@@ -490,9 +490,9 @@ func Wiki(ctx *context.Context) {
}
wikiPath := entry.Name()
- if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName {
- ext := strings.ToUpper(filepath.Ext(wikiPath))
- ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown."
+ detectedRender := markup.DetectRendererTypeByFilename(wikiPath)
+ if detectedRender == nil || detectedRender.Name() != markdown.MarkupName {
+ ctx.Data["FormatWarning"] = "File extension " + path.Ext(wikiPath) + " is not supported at the moment. Rendered as Markdown."
}
// Get last change information.
lastCommit, err := wikiGitRepo.GetCommitByPath(wikiPath)
diff --git a/services/webtheme/webtheme.go b/services/webtheme/webtheme.go
index 57d63f4e07..8091c25713 100644
--- a/services/webtheme/webtheme.go
+++ b/services/webtheme/webtheme.go
@@ -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)
}
}()
- cssFiles, err := public.AssetFS().ListFiles("/assets/css")
+ cssFiles, err := public.AssetFS().ListFiles("assets/css")
if err != nil {
log.Error("Failed to list themes: %v", err)
availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
diff --git a/tests/integration/markup_external_test.go b/tests/integration/markup_external_test.go
index b965766b5c..691ffcc62b 100644
--- a/tests/integration/markup_external_test.go
+++ b/tests/integration/markup_external_test.go
@@ -77,10 +77,10 @@ func TestExternalMarkupRenderer(t *testing.T) {
})
// 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)()
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)()
assert.False(t, r.NeedPostProcess())
diff --git a/web_src/css/standalone/swagger.css b/web_src/css/standalone/swagger.css
index c32e392036..4285fc0551 100644
--- a/web_src/css/standalone/swagger.css
+++ b/web_src/css/standalone/swagger.css
@@ -29,9 +29,13 @@ body {
margin-right: 0.5rem;
}
+.swagger-spec-content {
+ display: none;
+}
+
@media (prefers-color-scheme: dark) {
body {
- background: #1e1e1e;
+ background: #14171a;
}
.swagger-ui, .swagger-back-link {
filter: invert(88%) hue-rotate(180deg);
diff --git a/web_src/js/markup/render-iframe.ts b/web_src/js/markup/render-iframe.ts
index 1bd6b3233f..1291dea4f8 100644
--- a/web_src/js/markup/render-iframe.ts
+++ b/web_src/js/markup/render-iframe.ts
@@ -9,7 +9,9 @@ async function loadRenderIframeContent(iframe: HTMLIFrameElement) {
if (!e.data?.giteaIframeCmd || e.data?.giteaIframeId !== iframe.id) return;
const cmd = e.data.giteaIframeCmd;
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') {
if (e.data.anchorTarget === '_blank') {
window.open(e.data.openLink, '_blank');
diff --git a/web_src/js/standalone/external-render-iframe.ts b/web_src/js/standalone/external-render-iframe.ts
index 8776710cda..dcfeb50541 100644
--- a/web_src/js/standalone/external-render-iframe.ts
+++ b/web_src/js/standalone/external-render-iframe.ts
@@ -21,6 +21,9 @@ function mainExternalRenderIframe() {
};
const updateIframeHeight = () => postIframeMsg('resize', {iframeHeight: document.documentElement.scrollHeight});
+ const resizeObserver = new ResizeObserver(() => updateIframeHeight());
+ resizeObserver.observe(window.document.documentElement);
+
updateIframeHeight();
window.addEventListener('DOMContentLoaded', updateIframeHeight);
// the easiest way to handle dynamic content changes and easy to debug, can be fine-tuned in the future
diff --git a/web_src/js/standalone/swagger.ts b/web_src/js/standalone/swagger.ts
index 62d1a56306..2b90dcfd41 100644
--- a/web_src/js/standalone/swagger.ts
+++ b/web_src/js/standalone/swagger.ts
@@ -1,18 +1,30 @@
import SwaggerUI from 'swagger-ui-dist/swagger-ui-es-bundle.js';
import 'swagger-ui-dist/swagger-ui.css';
+import {load as loadYaml} from 'js-yaml';
window.addEventListener('load', async () => {
- const url = document.querySelector('#swagger-ui')!.getAttribute('data-source')!;
- const res = await fetch(url);
- const spec = await res.json();
+ const elSwaggerUi = document.querySelector('#swagger-ui')!;
+ const url = elSwaggerUi.getAttribute('data-source')!;
+ let spec: any;
+ if (url) {
+ const res = await fetch(url);
+ spec = await res.json();
+ } else {
+ const elSpecContent = elSwaggerUi.querySelector