From ae828d8702f999c78bbd6588c7ac874393ab204f Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:25:55 +0900 Subject: [PATCH 01/10] markup: add horizontal scroll to code blocks and isolate copy button Previously, code blocks did not support horizontal scrolling, leading to poor readability on mobile or narrow displays due to forced line wrapping. This patch updates the HTML structure and CSS so that: - Code blocks now scroll horizontally instead of wrapping lines - The copy button is placed in a separate wrapper to avoid layout interference These changes improve overall accessibility and usability for markdown-rendered content in the Gitea interface. --- modules/highlight/highlight.go | 2 +- modules/markup/markdown/markdown.go | 6 +++--- web_src/css/markup/codecopy.css | 15 ++++++++++++--- web_src/css/markup/content.css | 4 +--- web_src/js/markup/codecopy.ts | 4 ++-- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 77f24fa3f380b..2a5b9c0a4dc7b 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -131,7 +131,7 @@ func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { _ = htmlw.Flush() // Chroma will add newlines for certain lexers in order to highlight them properly - // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output + // // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index ace31eb540778..8338e87bda2be 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -86,7 +86,7 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C preClasses += " is-loading" } - err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
+		err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
 		if err != nil {
 			return
 		}
@@ -99,7 +99,7 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C
 			return
 		}
 	} else {
-		_, err := w.WriteString("
") + _, err := w.WriteString("
") if err != nil { return } @@ -109,7 +109,7 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C // SpecializedMarkdown sets up the Gitea specific markdown extensions func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender { // TODO: it could use a pool to cache the renderers to reuse them with different contexts - // at the moment it is fast enough (see the benchmarks) + // at the moment it is fast enough (see the benchmarks r := &GlodmarkRender{ctx: ctx} r.goldmarkMarkdown = goldmark.New( goldmark.WithExtensions( diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css index e3017ae962ec1..fce7dbd2f23a2 100644 --- a/web_src/css/markup/codecopy.css +++ b/web_src/css/markup/codecopy.css @@ -1,14 +1,23 @@ -.markup .code-block, +.markup .code-wrapper, .markup .mermaid-block { position: relative; } +.code-wrapper { + background-color: var(--color-markup-code-block); + border-radius: var(--border-radius); +} + +.code-block { + margin-right: 56px; +} + .markup .code-copy { position: absolute; top: 8px; right: 6px; padding: 9px; - visibility: hidden; + /*visibility: hidden;*/ animation: fadeout 0.2s both; } @@ -28,7 +37,7 @@ background: var(--color-secondary-dark-1) !important; } -.markup .code-block:hover .code-copy, +.markup .code-wrapper:hover .code-copy, .markup .mermaid-block:hover .code-copy { visibility: visible; animation: fadein 0.2s both; diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 865ac0536afa9..42c368da2cd34 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -455,7 +455,7 @@ padding: 0; margin: 0; font-size: 100%; - white-space: pre-wrap; + white-space: pre; word-break: break-all; overflow-wrap: break-word; background: transparent; @@ -471,8 +471,6 @@ padding: 16px; font-size: 85%; line-height: 1.45; - background-color: var(--color-markup-code-block); - border-radius: var(--border-radius); } .markup .highlight pre { diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts index 4430256848c8b..0dc5e40368f3d 100644 --- a/web_src/js/markup/codecopy.ts +++ b/web_src/js/markup/codecopy.ts @@ -8,11 +8,11 @@ export function makeCodeCopyButton(): HTMLButtonElement { } export function initMarkupCodeCopy(elMarkup: HTMLElement): void { - const el = elMarkup.querySelector('.code-block code'); // .markup .code-block code + const el = elMarkup.querySelector('.code-wrapper'); // .markup .code-block code if (!el || !el.textContent) return; const btn = makeCodeCopyButton(); // remove final trailing newline introduced during HTML rendering btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); - el.after(btn); + el.append(btn); } From 95f7abb321860d087158e490a1bee3b203328f77 Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:39:37 +0900 Subject: [PATCH 02/10] Delete modules/highlight/highlight.go --- modules/highlight/highlight.go | 225 --------------------------------- 1 file changed, 225 deletions(-) delete mode 100644 modules/highlight/highlight.go diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go deleted file mode 100644 index 2a5b9c0a4dc7b..0000000000000 --- a/modules/highlight/highlight.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright 2015 The Gogs Authors. All rights reserved. -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package highlight - -import ( - "bufio" - "bytes" - "fmt" - gohtml "html" - "html/template" - "io" - "path" - "path/filepath" - "strings" - "sync" - - "code.gitea.io/gitea/modules/analyze" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - - "github.com/alecthomas/chroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/alecthomas/chroma/v2/lexers" - "github.com/alecthomas/chroma/v2/styles" - lru "github.com/hashicorp/golang-lru/v2" -) - -// don't index files larger than this many bytes for performance purposes -const sizeLimit = 1024 * 1024 - -var ( - // For custom user mapping - highlightMapping = map[string]string{} - - once sync.Once - - cache *lru.TwoQueueCache[string, any] - - githubStyles = styles.Get("github") -) - -// NewContext loads custom highlight map from local config -func NewContext() { - once.Do(func() { - highlightMapping = setting.GetHighlightMapping() - - // The size 512 is simply a conservative rule of thumb - c, err := lru.New2Q[string, any](512) - if err != nil { - panic(fmt.Sprintf("failed to initialize LRU cache for highlighter: %s", err)) - } - cache = c - }) -} - -// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name -func Code(fileName, language, code string) (output template.HTML, lexerName string) { - NewContext() - - // diff view newline will be passed as empty, change to literal '\n' so it can be copied - // preserve literal newline in blame view - if code == "" || code == "\n" { - return "\n", "" - } - - if len(code) > sizeLimit { - return template.HTML(template.HTMLEscapeString(code)), "" - } - - var lexer chroma.Lexer - - if len(language) > 0 { - lexer = lexers.Get(language) - - if lexer == nil { - // Attempt stripping off the '?' - if idx := strings.IndexByte(language, '?'); idx > 0 { - lexer = lexers.Get(language[:idx]) - } - } - } - - if lexer == nil { - if val, ok := highlightMapping[path.Ext(fileName)]; ok { - // use mapped value to find lexer - lexer = lexers.Get(val) - } - } - - if lexer == nil { - if l, ok := cache.Get(fileName); ok { - lexer = l.(chroma.Lexer) - } - } - - if lexer == nil { - lexer = lexers.Match(fileName) - if lexer == nil { - lexer = lexers.Fallback - } - cache.Add(fileName, lexer) - } - - return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name) -} - -// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes -func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { - formatter := html.New(html.WithClasses(true), - html.WithLineNumbers(false), - html.PreventSurroundingPre(true), - ) - - htmlbuf := bytes.Buffer{} - htmlw := bufio.NewWriter(&htmlbuf) - - iterator, err := lexer.Tokenise(nil, code) - if err != nil { - log.Error("Can't tokenize code: %v", err) - return template.HTML(template.HTMLEscapeString(code)) - } - // style not used for live site but need to pass something - err = formatter.Format(htmlw, githubStyles, iterator) - if err != nil { - log.Error("Can't format code: %v", err) - return template.HTML(template.HTMLEscapeString(code)) - } - - _ = htmlw.Flush() - // Chroma will add newlines for certain lexers in order to highlight them properly - // // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output - return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) -} - -// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name -func File(fileName, language string, code []byte) ([]template.HTML, string, error) { - NewContext() - - if len(code) > sizeLimit { - return PlainText(code), "", nil - } - - formatter := html.New(html.WithClasses(true), - html.WithLineNumbers(false), - html.PreventSurroundingPre(true), - ) - - var lexer chroma.Lexer - - // provided language overrides everything - if language != "" { - lexer = lexers.Get(language) - } - - if lexer == nil { - if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { - lexer = lexers.Get(val) - } - } - - if lexer == nil { - guessLanguage := analyze.GetCodeLanguage(fileName, code) - - lexer = lexers.Get(guessLanguage) - if lexer == nil { - lexer = lexers.Match(fileName) - if lexer == nil { - lexer = lexers.Fallback - } - } - } - - lexerName := formatLexerName(lexer.Config().Name) - - iterator, err := lexer.Tokenise(nil, string(code)) - if err != nil { - return nil, "", fmt.Errorf("can't tokenize code: %w", err) - } - - tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) - htmlBuf := &bytes.Buffer{} - - lines := make([]template.HTML, 0, len(tokensLines)) - for _, tokens := range tokensLines { - iterator = chroma.Literator(tokens...) - err = formatter.Format(htmlBuf, githubStyles, iterator) - if err != nil { - return nil, "", fmt.Errorf("can't format code: %w", err) - } - lines = append(lines, template.HTML(htmlBuf.String())) - htmlBuf.Reset() - } - - return lines, lexerName, nil -} - -// PlainText returns non-highlighted HTML for code -func PlainText(code []byte) []template.HTML { - r := bufio.NewReader(bytes.NewReader(code)) - m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) - for { - content, err := r.ReadString('\n') - if err != nil && err != io.EOF { - log.Error("failed to read string from buffer: %v", err) - break - } - if content == "" && err == io.EOF { - break - } - s := template.HTML(gohtml.EscapeString(content)) - m = append(m, s) - } - return m -} - -func formatLexerName(name string) string { - if name == "fallback" { - return "Plaintext" - } - - return util.ToTitleCaseNoLower(name) -} From bb1efdbcea69f1d999c7f5fd1c6d35f4147f4e54 Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:43:10 +0900 Subject: [PATCH 03/10] Revert "Delete modules/highlight/highlight.go" This reverts commit 95f7abb321860d087158e490a1bee3b203328f77. --- modules/highlight/highlight.go | 225 +++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 modules/highlight/highlight.go diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go new file mode 100644 index 0000000000000..2a5b9c0a4dc7b --- /dev/null +++ b/modules/highlight/highlight.go @@ -0,0 +1,225 @@ +// Copyright 2015 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package highlight + +import ( + "bufio" + "bytes" + "fmt" + gohtml "html" + "html/template" + "io" + "path" + "path/filepath" + "strings" + "sync" + + "code.gitea.io/gitea/modules/analyze" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "github.com/alecthomas/chroma/v2" + "github.com/alecthomas/chroma/v2/formatters/html" + "github.com/alecthomas/chroma/v2/lexers" + "github.com/alecthomas/chroma/v2/styles" + lru "github.com/hashicorp/golang-lru/v2" +) + +// don't index files larger than this many bytes for performance purposes +const sizeLimit = 1024 * 1024 + +var ( + // For custom user mapping + highlightMapping = map[string]string{} + + once sync.Once + + cache *lru.TwoQueueCache[string, any] + + githubStyles = styles.Get("github") +) + +// NewContext loads custom highlight map from local config +func NewContext() { + once.Do(func() { + highlightMapping = setting.GetHighlightMapping() + + // The size 512 is simply a conservative rule of thumb + c, err := lru.New2Q[string, any](512) + if err != nil { + panic(fmt.Sprintf("failed to initialize LRU cache for highlighter: %s", err)) + } + cache = c + }) +} + +// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name +func Code(fileName, language, code string) (output template.HTML, lexerName string) { + NewContext() + + // diff view newline will be passed as empty, change to literal '\n' so it can be copied + // preserve literal newline in blame view + if code == "" || code == "\n" { + return "\n", "" + } + + if len(code) > sizeLimit { + return template.HTML(template.HTMLEscapeString(code)), "" + } + + var lexer chroma.Lexer + + if len(language) > 0 { + lexer = lexers.Get(language) + + if lexer == nil { + // Attempt stripping off the '?' + if idx := strings.IndexByte(language, '?'); idx > 0 { + lexer = lexers.Get(language[:idx]) + } + } + } + + if lexer == nil { + if val, ok := highlightMapping[path.Ext(fileName)]; ok { + // use mapped value to find lexer + lexer = lexers.Get(val) + } + } + + if lexer == nil { + if l, ok := cache.Get(fileName); ok { + lexer = l.(chroma.Lexer) + } + } + + if lexer == nil { + lexer = lexers.Match(fileName) + if lexer == nil { + lexer = lexers.Fallback + } + cache.Add(fileName, lexer) + } + + return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name) +} + +// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes +func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { + formatter := html.New(html.WithClasses(true), + html.WithLineNumbers(false), + html.PreventSurroundingPre(true), + ) + + htmlbuf := bytes.Buffer{} + htmlw := bufio.NewWriter(&htmlbuf) + + iterator, err := lexer.Tokenise(nil, code) + if err != nil { + log.Error("Can't tokenize code: %v", err) + return template.HTML(template.HTMLEscapeString(code)) + } + // style not used for live site but need to pass something + err = formatter.Format(htmlw, githubStyles, iterator) + if err != nil { + log.Error("Can't format code: %v", err) + return template.HTML(template.HTMLEscapeString(code)) + } + + _ = htmlw.Flush() + // Chroma will add newlines for certain lexers in order to highlight them properly + // // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output + return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) +} + +// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name +func File(fileName, language string, code []byte) ([]template.HTML, string, error) { + NewContext() + + if len(code) > sizeLimit { + return PlainText(code), "", nil + } + + formatter := html.New(html.WithClasses(true), + html.WithLineNumbers(false), + html.PreventSurroundingPre(true), + ) + + var lexer chroma.Lexer + + // provided language overrides everything + if language != "" { + lexer = lexers.Get(language) + } + + if lexer == nil { + if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { + lexer = lexers.Get(val) + } + } + + if lexer == nil { + guessLanguage := analyze.GetCodeLanguage(fileName, code) + + lexer = lexers.Get(guessLanguage) + if lexer == nil { + lexer = lexers.Match(fileName) + if lexer == nil { + lexer = lexers.Fallback + } + } + } + + lexerName := formatLexerName(lexer.Config().Name) + + iterator, err := lexer.Tokenise(nil, string(code)) + if err != nil { + return nil, "", fmt.Errorf("can't tokenize code: %w", err) + } + + tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) + htmlBuf := &bytes.Buffer{} + + lines := make([]template.HTML, 0, len(tokensLines)) + for _, tokens := range tokensLines { + iterator = chroma.Literator(tokens...) + err = formatter.Format(htmlBuf, githubStyles, iterator) + if err != nil { + return nil, "", fmt.Errorf("can't format code: %w", err) + } + lines = append(lines, template.HTML(htmlBuf.String())) + htmlBuf.Reset() + } + + return lines, lexerName, nil +} + +// PlainText returns non-highlighted HTML for code +func PlainText(code []byte) []template.HTML { + r := bufio.NewReader(bytes.NewReader(code)) + m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) + for { + content, err := r.ReadString('\n') + if err != nil && err != io.EOF { + log.Error("failed to read string from buffer: %v", err) + break + } + if content == "" && err == io.EOF { + break + } + s := template.HTML(gohtml.EscapeString(content)) + m = append(m, s) + } + return m +} + +func formatLexerName(name string) string { + if name == "fallback" { + return "Plaintext" + } + + return util.ToTitleCaseNoLower(name) +} From 0cedac033b2aa7e9c926f184796e7e6ddef8c8a4 Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:47:05 +0900 Subject: [PATCH 04/10] fix comment --- modules/highlight/highlight.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 2a5b9c0a4dc7b..77f24fa3f380b 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -131,7 +131,7 @@ func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { _ = htmlw.Flush() // Chroma will add newlines for certain lexers in order to highlight them properly - // // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output + // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) } From f2bf15308e3b7a0a18f3d9a70065ff7fb74efff6 Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:47:59 +0900 Subject: [PATCH 05/10] fix comment --- modules/markup/markdown/markdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 8338e87bda2be..0f597696ccfd1 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -109,7 +109,7 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C // SpecializedMarkdown sets up the Gitea specific markdown extensions func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender { // TODO: it could use a pool to cache the renderers to reuse them with different contexts - // at the moment it is fast enough (see the benchmarks + // at the moment it is fast enough (see the benchmarks) r := &GlodmarkRender{ctx: ctx} r.goldmarkMarkdown = goldmark.New( goldmark.WithExtensions( From 5ba3a881c02d05d47d372ac5c4e30759850cede8 Mon Sep 17 00:00:00 2001 From: D Date: Tue, 25 Mar 2025 15:53:03 +0900 Subject: [PATCH 06/10] fix visibility --- web_src/css/markup/codecopy.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css index fce7dbd2f23a2..0b42b3dadb195 100644 --- a/web_src/css/markup/codecopy.css +++ b/web_src/css/markup/codecopy.css @@ -17,7 +17,7 @@ top: 8px; right: 6px; padding: 9px; - /*visibility: hidden;*/ + visibility: hidden; animation: fadeout 0.2s both; } From 1be4b3e1382b07ac9ac3037c9bf5b745cda1d056 Mon Sep 17 00:00:00 2001 From: D Date: Wed, 26 Mar 2025 14:02:46 +0900 Subject: [PATCH 07/10] =?UTF-8?q?=EC=B2=AB=EB=B2=88=EC=A7=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=B8=94=EB=9F=AD=EC=97=90=EB=A7=8C=20copy-button=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=EB=90=98=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web_src/js/markup/codecopy.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts index 0dc5e40368f3d..3ab14471e09be 100644 --- a/web_src/js/markup/codecopy.ts +++ b/web_src/js/markup/codecopy.ts @@ -8,11 +8,13 @@ export function makeCodeCopyButton(): HTMLButtonElement { } export function initMarkupCodeCopy(elMarkup: HTMLElement): void { - const el = elMarkup.querySelector('.code-wrapper'); // .markup .code-block code - if (!el || !el.textContent) return; + const els = elMarkup.querySelectorAll('.code-wrapper'); // .markup .code-block code - const btn = makeCodeCopyButton(); - // remove final trailing newline introduced during HTML rendering - btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); - el.append(btn); + for (const el of els) { + if (!el || !el.textContent) return; + const btn = makeCodeCopyButton(); + // remove final trailing newline introduced during HTML rendering + btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); + el.append(btn); + } } From e843113752724145f66ddb5e5cf8dce249acd17a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 16 Apr 2025 17:26:30 +0800 Subject: [PATCH 08/10] fix --- modules/markup/markdown/markdown.go | 2 +- templates/devtest/markup-render.tmpl | 71 ++++++++++++++++++++++++++++ web_src/css/markup/codecopy.css | 18 +------ web_src/css/markup/content.css | 29 ++++++++---- web_src/js/markup/codecopy.ts | 3 +- 5 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 templates/devtest/markup-render.tmpl diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index fc5c19cd6d137..8206ef337b86c 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -86,7 +86,7 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C preClasses += " is-loading" } - err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
+		err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
 		if err != nil {
 			return
 		}
diff --git a/templates/devtest/markup-render.tmpl b/templates/devtest/markup-render.tmpl
new file mode 100644
index 0000000000000..69d29d7829933
--- /dev/null
+++ b/templates/devtest/markup-render.tmpl
@@ -0,0 +1,71 @@
+{{template "devtest/devtest-header"}}
+
+ {{$longCode := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}} +
+
+
+ Inline code content +
+ +
+ +
+

content before

+
Very long line with no code block or container: {{$longCode}}
+

content after

+
+ +
+ +
+

content before

+
+
Very long line with wrap: {{$longCode}}
+
+

content after

+
+ +
+ +
+

content before

+
+
Short line in scroll container
+
+
+
Very long line with scroll: {{$longCode}}
+
+

content after

+
+
+ +
+
+

content before

+
+

+	\lim\limits_{n\rightarrow\infty}{\left(1+\frac{1}{n}\right)^n}
+					
+
+

content after

+
+ +
+ +
+

content before

+
+

+	graph LR
+			A[Square Rect] -- Link text --> B((Circle))
+			A --> C(Round Rect)
+			B --> D{Rhombus}
+			C --> D
+					
+
+

content after

+
+
+
+
+{{template "devtest/devtest-footer"}} diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css index 0b42b3dadb195..5a7b9955e7ad2 100644 --- a/web_src/css/markup/codecopy.css +++ b/web_src/css/markup/codecopy.css @@ -1,17 +1,3 @@ -.markup .code-wrapper, -.markup .mermaid-block { - position: relative; -} - -.code-wrapper { - background-color: var(--color-markup-code-block); - border-radius: var(--border-radius); -} - -.code-block { - margin-right: 56px; -} - .markup .code-copy { position: absolute; top: 8px; @@ -37,8 +23,8 @@ background: var(--color-secondary-dark-1) !important; } -.markup .code-wrapper:hover .code-copy, -.markup .mermaid-block:hover .code-copy { +.markup .code-block-container:hover .code-copy, +.markup .code-block:hover .code-copy { visibility: visible; animation: fadein 0.2s both; } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 3552f27959e8f..f2e67336d4042 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -443,13 +443,25 @@ } .markup pre > code { - padding: 0; - margin: 0; font-size: 100%; +} + +.markup .code-block, +.markup .code-block-container { + position: relative; +} + +.markup .code-block-container.code-overflow-wrap pre > code { white-space: pre-wrap; - overflow-wrap: anywhere; - background: transparent; - border: 0; +} + +.markup .code-block-container.code-overflow-scroll pre { + overflow-x: auto; +} + +.markup .code-block-container.code-overflow-scroll pre > code { + white-space: pre-wrap; + overflow-wrap: normal; } .markup .highlight { @@ -461,6 +473,8 @@ padding: 16px; font-size: 85%; line-height: 1.45; + background-color: var(--color-markup-code-block); + border-radius: var(--border-radius); } .markup .highlight pre { @@ -468,16 +482,11 @@ word-break: normal; } -.markup pre { - word-wrap: normal; -} - .markup pre code, .markup pre tt { display: inline; padding: 0; line-height: inherit; - word-wrap: normal; background-color: transparent; border: 0; } diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts index 67284bad55ed8..85415c780219c 100644 --- a/web_src/js/markup/codecopy.ts +++ b/web_src/js/markup/codecopy.ts @@ -15,6 +15,7 @@ export function initMarkupCodeCopy(elMarkup: HTMLElement): void { const btn = makeCodeCopyButton(); // remove final trailing newline introduced during HTML rendering btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); - el.after(btn); + const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block'); + btnContainer.append(btn); }); } From 0a4a29a3c424641203dc86c09afcc6be4224c18a Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 16 Apr 2025 18:18:02 +0800 Subject: [PATCH 09/10] fine tune --- modules/markup/markdown/markdown.go | 7 +------ web_src/css/markup/content.css | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 8206ef337b86c..79df547c2c7c8 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -86,15 +86,10 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C preClasses += " is-loading" } - err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses)
-		if err != nil {
-			return
-		}
-
 		// include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
 		// the "display" class is used by "js/markup/math.ts" to render the code element as a block
 		// the "math.ts" strictly depends on the structure: 
...
- err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, ``, languageStr) + err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `
`, preClasses, languageStr)
 		if err != nil {
 			return
 		}
diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css
index f2e67336d4042..8291539b9518a 100644
--- a/web_src/css/markup/content.css
+++ b/web_src/css/markup/content.css
@@ -460,7 +460,7 @@
 }
 
 .markup .code-block-container.code-overflow-scroll pre > code {
-  white-space: pre-wrap;
+  white-space: pre;
   overflow-wrap: normal;
 }
 

From 4188181475bbb9e4ae4f7b4edbf57f7e20b96c4f Mon Sep 17 00:00:00 2001
From: wxiaoguang 
Date: Thu, 17 Apr 2025 07:07:37 +0800
Subject: [PATCH 10/10] Update web_src/js/markup/codecopy.ts

---
 web_src/js/markup/codecopy.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts
index 85415c780219c..b37aa3a2369a2 100644
--- a/web_src/js/markup/codecopy.ts
+++ b/web_src/js/markup/codecopy.ts
@@ -15,6 +15,7 @@ export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
     const btn = makeCodeCopyButton();
     // remove final trailing newline introduced during HTML rendering
     btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
+    // we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not.
     const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block');
     btnContainer.append(btn);
   });