From eda6d65818d4eb80c35b25be43ce8de9588d7161 Mon Sep 17 00:00:00 2001
From: D <dj8793@gmail.com>
Date: Sat, 19 Apr 2025 14:53:39 +0900
Subject: [PATCH] markup: improve code block readability and isolate copy
 button (#34009)

Fix #33197

Improve the rendering of code blocks in markdown content
for better readability and UI stability across screen sizes.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
---
 modules/markup/markdown/markdown.go  |  9 +---
 templates/devtest/markup-render.tmpl | 71 ++++++++++++++++++++++++++++
 web_src/css/markup/codecopy.css      |  9 +---
 web_src/css/markup/content.css       | 27 +++++++----
 web_src/js/markup/codecopy.ts        |  4 +-
 5 files changed, 95 insertions(+), 25 deletions(-)
 create mode 100644 templates/devtest/markup-render.tmpl

diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 0d7180c6b1..79df547c2c 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -86,20 +86,15 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C
 			preClasses += " is-loading"
 		}
 
-		err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre class="%s">`, 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: <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
-		err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, languageStr)
+		err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<div class="code-block-container code-overflow-scroll"><pre class="%s"><code class="chroma language-%s display">`, preClasses, languageStr)
 		if err != nil {
 			return
 		}
 	} else {
-		_, err := w.WriteString("</code></pre>")
+		_, err := w.WriteString("</code></pre></div>")
 		if err != nil {
 			return
 		}
diff --git a/templates/devtest/markup-render.tmpl b/templates/devtest/markup-render.tmpl
new file mode 100644
index 0000000000..69d29d7829
--- /dev/null
+++ b/templates/devtest/markup-render.tmpl
@@ -0,0 +1,71 @@
+{{template "devtest/devtest-header"}}
+<div class="page-content devtest ui container">
+	{{$longCode := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}}
+	<div class="tw-flex">
+		<div class="tw-w-[50%] tw-p-4">
+			<div class="markup render-content">
+				Inline <code>code</code> content
+			</div>
+
+			<div class="divider"></div>
+
+			<div class="markup render-content">
+				<p>content before</p>
+				<pre><code>Very long line with no code block or container: {{$longCode}}</code></pre>
+				<p>content after</p>
+			</div>
+
+			<div class="divider"></div>
+
+			<div class="markup render-content">
+				<p>content before</p>
+				<div class="code-block-container code-overflow-wrap">
+					<pre class="code-block"><code>Very long line with wrap: {{$longCode}}</code></pre>
+				</div>
+				<p>content after</p>
+			</div>
+
+			<div class="divider"></div>
+
+			<div class="markup render-content">
+				<p>content before</p>
+				<div class="code-block-container code-overflow-scroll">
+					<pre class="code-block"><code>Short line in scroll container</code></pre>
+				</div>
+				<div class="code-block-container code-overflow-scroll">
+					<pre class="code-block"><code>Very long line with scroll: {{$longCode}}</code></pre>
+				</div>
+				<p>content after</p>
+			</div>
+		</div>
+
+		<div class="tw-w-[50%] tw-p-4">
+			<div class="markup render-content">
+				<p>content before</p>
+				<div class="code-block-container">
+					<pre class="code-block"><code class="language-math">
+	\lim\limits_{n\rightarrow\infty}{\left(1+\frac{1}{n}\right)^n}
+					</code></pre>
+				</div>
+				<p>content after</p>
+			</div>
+
+			<div class="divider"></div>
+
+			<div class="markup render-content">
+				<p>content before</p>
+				<div class="code-block-container">
+					<pre class="code-block"><code class="language-mermaid is-loading">
+	graph LR
+			A[Square Rect] -- Link text --> B((Circle))
+			A --> C(Round Rect)
+			B --> D{Rhombus}
+			C --> D
+					</code></pre>
+				</div>
+				<p>content after</p>
+			</div>
+		</div>
+	</div>
+</div>
+{{template "devtest/devtest-footer"}}
diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css
index e3017ae962..5a7b9955e7 100644
--- a/web_src/css/markup/codecopy.css
+++ b/web_src/css/markup/codecopy.css
@@ -1,8 +1,3 @@
-.markup .code-block,
-.markup .mermaid-block {
-  position: relative;
-}
-
 .markup .code-copy {
   position: absolute;
   top: 8px;
@@ -28,8 +23,8 @@
   background: var(--color-secondary-dark-1) !important;
 }
 
-.markup .code-block: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 937224a9d7..8291539b95 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;
+  overflow-wrap: normal;
 }
 
 .markup .highlight {
@@ -470,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 67284bad55..b37aa3a236 100644
--- a/web_src/js/markup/codecopy.ts
+++ b/web_src/js/markup/codecopy.ts
@@ -15,6 +15,8 @@ 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);
+    // 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);
   });
 }