From 0e3bad2be78a97d14763c0ec97e0c5fc3bdc6c37 Mon Sep 17 00:00:00 2001 From: ZhenShuo Leo <98386542+ZhenShuo2021@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:17:35 +0800 Subject: [PATCH] feat(codeblock): add title support --- assets/css/compiled/main.css | 37 +++++++++++++++---- assets/css/components/chroma.css | 24 +++++++++--- assets/css/main.css | 6 +-- assets/js/code.js | 32 ++++++++-------- .../_default/_markup/render-codeblock.html | 10 +++++ 5 files changed, 73 insertions(+), 36 deletions(-) create mode 100644 layouts/_default/_markup/render-codeblock.html diff --git a/assets/css/compiled/main.css b/assets/css/compiled/main.css index c6e734d1..6a856877 100644 --- a/assets/css/compiled/main.css +++ b/assets/css/compiled/main.css @@ -3311,13 +3311,37 @@ } } @layer utilities { - .prose .chroma { - position: static; + .highlight-wrapper { + position: relative; + z-index: 0; + display: block; + overflow: hidden; border-radius: var(--radius-md); - } - .prose .highlight:not(:has(table)) .chroma, .prose .highlight > .chroma { --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + } + .highlight-wrapper pre, .highlight-wrapper table, .highlight-wrapper div { + margin-top: 0; + margin-bottom: 0; + } + .highlight-wrapper:has(.codeblock-title) pre { + border-radius: 0; + } + .codeblock-title { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + border-color: rgba(var(--color-neutral-200), 1); + padding-inline: calc(var(--spacing) * 4); + padding-block: calc(var(--spacing) * 2); + &:is(.dark *) { + border-color: rgba(var(--color-neutral-800), 1); + } + background-color: #fff; + &:is(.dark *) { + background-color: #0d1117; + } } .chroma .lntd, .chroma .lntd pre { margin: calc(var(--spacing) * 0); @@ -4033,14 +4057,11 @@ button, [role="button"] { margin-right: calc(var(--spacing) * 0); } } -.highlight-wrapper { - display: block; -} .highlight { position: relative; z-index: 0; } -.highlight:hover > .copy-button { +.highlight-wrapper:hover > .copy-button { visibility: visible; } .copy-button { diff --git a/assets/css/components/chroma.css b/assets/css/components/chroma.css index 3e42f821..97398da9 100644 --- a/assets/css/components/chroma.css +++ b/assets/css/components/chroma.css @@ -1,12 +1,24 @@ /* -- Chroma Highlight -- */ -/* Background */ -.prose .chroma { - @apply static rounded-md; +/* margins for codeblock title */ +.highlight-wrapper { + @apply block relative z-0 overflow-hidden shadow rounded-md; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; } -.prose .highlight:not(:has(table)) .chroma, -.prose .highlight > .chroma { - @apply shadow; +.highlight-wrapper pre, +.highlight-wrapper table, .highlight-wrapper div { + margin-top: 0; + margin-bottom: 0; +} + +.highlight-wrapper:has(.codeblock-title) pre { + @apply rounded-none; +} + +.codeblock-title { + @apply px-4 py-2 border-b border-neutral-200 dark:border-neutral-800; + @apply bg-white dark:bg-[#0d1117]; } /* LineTableTD */ diff --git a/assets/css/main.css b/assets/css/main.css index 8c19a5d5..b1f409b7 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -94,15 +94,11 @@ button, } /* Code Copy */ -.highlight-wrapper { - @apply block; -} - .highlight { @apply relative z-0; } -.highlight:hover > .copy-button { +.highlight-wrapper:hover > .copy-button { @apply visible; } diff --git a/assets/js/code.js b/assets/js/code.js index 2cc852ec..aae11907 100644 --- a/assets/js/code.js +++ b/assets/js/code.js @@ -2,31 +2,26 @@ var scriptBundle = document.getElementById("script-bundle"); var copyText = scriptBundle?.getAttribute("data-copy") || "Copy"; var copiedText = scriptBundle?.getAttribute("data-copied") || "Copied"; -function createCopyButton(highlightDiv) { +function createCopyButton(highlightWrapper) { const button = document.createElement("button"); button.className = "copy-button"; button.type = "button"; button.ariaLabel = copyText; button.innerText = copyText; - button.addEventListener("click", () => copyCodeToClipboard(button, highlightDiv)); - - highlightDiv.insertBefore(button, highlightDiv.firstChild); - const wrapper = document.createElement("div"); - wrapper.className = "highlight-wrapper"; - highlightDiv.parentNode.insertBefore(wrapper, highlightDiv); - wrapper.appendChild(highlightDiv); + button.addEventListener("click", () => copyCodeToClipboard(button, highlightWrapper)); + highlightWrapper.insertBefore(button, highlightWrapper.firstChild); } -async function copyCodeToClipboard(button, highlightDiv) { - const codeToCopy = getCodeText(highlightDiv); +async function copyCodeToClipboard(button, highlightWrapper) { + const codeToCopy = getCodeText(highlightWrapper); - function fallback(codeToCopy, highlightDiv) { + function fallback(codeToCopy, highlightWrapper) { const textArea = document.createElement("textArea"); textArea.contentEditable = "true"; textArea.readOnly = "false"; textArea.className = "copy-textarea"; textArea.value = codeToCopy; - highlightDiv.insertBefore(textArea, highlightDiv.firstChild); + highlightWrapper.insertBefore(textArea, highlightWrapper.firstChild); const range = document.createRange(); range.selectNodeContents(textArea); const sel = window.getSelection(); @@ -34,7 +29,7 @@ async function copyCodeToClipboard(button, highlightDiv) { sel.addRange(range); textArea.setSelectionRange(0, 999999); document.execCommand("copy"); - highlightDiv.removeChild(textArea); + highlightWrapper.removeChild(textArea); } try { @@ -42,10 +37,10 @@ async function copyCodeToClipboard(button, highlightDiv) { if (result.state == "granted" || result.state == "prompt") { await navigator.clipboard.writeText(codeToCopy); } else { - fallback(codeToCopy, highlightDiv); + fallback(codeToCopy, highlightWrapper); } } catch (_) { - fallback(codeToCopy, highlightDiv); + fallback(codeToCopy, highlightWrapper); } finally { button.blur(); button.innerText = copiedText; @@ -55,7 +50,10 @@ async function copyCodeToClipboard(button, highlightDiv) { } } -function getCodeText(highlightDiv) { +function getCodeText(highlightWrapper) { + const highlightDiv = highlightWrapper.querySelector(".highlight"); + if (!highlightDiv) return ""; + const codeBlock = highlightDiv.querySelector("code"); const inlineLines = codeBlock?.querySelectorAll(".cl"); // linenos=inline const tableCodeCell = highlightDiv?.querySelector(".lntable .lntd:last-child code"); // linenos=table @@ -75,5 +73,5 @@ function getCodeText(highlightDiv) { } window.addEventListener("DOMContentLoaded", (event) => { - document.querySelectorAll(".highlight").forEach((highlightDiv) => createCopyButton(highlightDiv)); + document.querySelectorAll(".highlight-wrapper").forEach((highlightWrapper) => createCopyButton(highlightWrapper)); }); diff --git a/layouts/_default/_markup/render-codeblock.html b/layouts/_default/_markup/render-codeblock.html new file mode 100644 index 00000000..aa7f4bfe --- /dev/null +++ b/layouts/_default/_markup/render-codeblock.html @@ -0,0 +1,10 @@ +{{- $title := or .Attributes.title "" -}} +{{- $lang := or .Type "text" -}} +
+ {{- with $title -}} +
+ {{- $title -}} +
+ {{- end -}} + {{- transform.Highlight .Inner $lang .Options -}} +