From fd89ceef79208637433028e41f84f1889f54f434 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 11 Feb 2026 11:22:33 +0800 Subject: [PATCH] Fix markup code block layout (#36578) --- web_src/css/index.css | 2 +- web_src/css/markup/codeblock.css | 10 +++++ web_src/css/markup/codecopy.css | 40 ------------------- web_src/css/markup/content.css | 37 ++++++++++++++++++ web_src/js/markup/codecopy.ts | 24 ++++++------ web_src/js/markup/mermaid.ts | 66 +++++++++----------------------- web_src/js/svg.ts | 4 ++ web_src/js/utils/dom.ts | 20 +--------- 8 files changed, 84 insertions(+), 119 deletions(-) create mode 100644 web_src/css/markup/codeblock.css delete mode 100644 web_src/css/markup/codecopy.css diff --git a/web_src/css/index.css b/web_src/css/index.css index 6bfbddeacc..c09d309149 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -46,7 +46,7 @@ @import "./features/captcha.css"; @import "./markup/content.css"; -@import "./markup/codecopy.css"; +@import "./markup/codeblock.css"; @import "./markup/codepreview.css"; @import "./markup/asciicast.css"; diff --git a/web_src/css/markup/codeblock.css b/web_src/css/markup/codeblock.css new file mode 100644 index 0000000000..e7744391f0 --- /dev/null +++ b/web_src/css/markup/codeblock.css @@ -0,0 +1,10 @@ +.markup .ui.button.code-copy { + top: 8px; + right: 6px; + margin: 0; +} + +.markup .mermaid-block .view-controller { + right: 6px; + bottom: 5px; +} diff --git a/web_src/css/markup/codecopy.css b/web_src/css/markup/codecopy.css deleted file mode 100644 index c48f641f68..0000000000 --- a/web_src/css/markup/codecopy.css +++ /dev/null @@ -1,40 +0,0 @@ -.markup .code-copy { - position: absolute; - top: 8px; - right: 6px; - padding: 9px; - visibility: hidden; /* prevent from click events even opacity=0 */ - opacity: 0; - transition: var(--transition-hover-fade); -} - -/* adjustments for comment content having only 14px font size */ -.repository.view.issue .comment-list .comment .markup .code-copy { - right: 5px; - padding: 8px; -} - -/* can not use regular transparent button colors for hover and active states because - we need opaque colors here as code can appear behind the button */ -.markup .code-copy:hover { - background: var(--color-secondary) !important; -} - -.markup .code-copy:active { - background: var(--color-secondary-dark-1) !important; -} - -/* all rendered code-block elements are in their container, -the manually written code-block elements on "packages" pages don't have the container */ -.markup .code-block-container:hover .code-copy, -.markup .code-block:hover .code-copy { - visibility: visible; - opacity: 1; -} - -@media (hover: none) { - .markup .code-copy { - visibility: visible; - opacity: 1; - } -} diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 0e3aeb2ffe..34e7de8c47 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -601,3 +601,40 @@ In markup content, we always use bottom margin for all elements */ .file-view.markup.orgmode li.indeterminate > p { display: inline-block; } + +/* auto-hide-control is a control element or a container for control elements, + it floats over the code-block and only shows when the code-block is hovered. */ +.markup .auto-hide-control { + position: absolute; + z-index: 1; + visibility: hidden; /* prevent from click events even opacity=0 */ + opacity: 0; + transition: var(--transition-hover-fade); +} + +/* all rendered code-block elements are in their container, +the manually written code-block elements on "packages" pages don't have the container */ +.markup .code-block-container:hover .auto-hide-control, +.markup .code-block:hover .auto-hide-control { + visibility: visible; + opacity: 1; +} + +@media (hover: none) { + .markup .auto-hide-control { + visibility: visible; + opacity: 1; + } +} + +/* can not use regular transparent button colors for hover and active states + because we need opaque colors here as code can appear behind the button */ +.markup .auto-hide-control.ui.button:hover, +.markup .auto-hide-control .ui.button:hover { + background: var(--color-secondary) !important; +} + +.markup .auto-hide-control.ui.button:active, +.markup .auto-hide-control .ui.button:active { + background: var(--color-secondary-dark-1) !important; +} diff --git a/web_src/js/markup/codecopy.ts b/web_src/js/markup/codecopy.ts index 870ba7728c..819b2b5474 100644 --- a/web_src/js/markup/codecopy.ts +++ b/web_src/js/markup/codecopy.ts @@ -1,25 +1,25 @@ import {svg} from '../svg.ts'; -import {queryElems} from '../utils/dom.ts'; +import {createElementFromAttrs, queryElems} from '../utils/dom.ts'; export function makeCodeCopyButton(attrs: Record = {}): HTMLButtonElement { - const button = document.createElement('button'); - button.classList.add('code-copy', 'ui', 'button'); - button.innerHTML = svg('octicon-copy'); - for (const [key, value] of Object.entries(attrs)) { - button.setAttribute(key, value); - } - return button; + const btn = createElementFromAttrs('button', { + class: 'ui compact icon button code-copy auto-hide-control', + ...attrs, + }); + btn.innerHTML = svg('octicon-copy'); + return btn; } export function initMarkupCodeCopy(elMarkup: HTMLElement): void { // .markup .code-block code queryElems(elMarkup, '.code-block code', (el) => { if (!el.textContent) return; - const btn = makeCodeCopyButton(); // remove final trailing newline introduced during HTML rendering - btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, '')); + const btn = makeCodeCopyButton({ + '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); + const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block')!; + btnContainer.append(btn); }); } diff --git a/web_src/js/markup/mermaid.ts b/web_src/js/markup/mermaid.ts index cd62990ffd..5148ff377c 100644 --- a/web_src/js/markup/mermaid.ts +++ b/web_src/js/markup/mermaid.ts @@ -1,54 +1,18 @@ import {isDarkTheme, parseDom} from '../utils.ts'; -import {makeCodeCopyButton} from './codecopy.ts'; import {displayError} from './common.ts'; -import {createElementFromAttrs, createElementFromHTML, getCssRootVariablesText, queryElems} from '../utils/dom.ts'; -import {html} from '../utils/html.ts'; +import {createElementFromAttrs, createElementFromHTML, queryElems} from '../utils/dom.ts'; +import {html, htmlRaw} from '../utils/html.ts'; import {load as loadYaml} from 'js-yaml'; import type {MermaidConfig} from 'mermaid'; +import {svg} from '../svg.ts'; const {mermaidMaxSourceCharacters} = window.config; function getIframeCss(): string { - // Inherit some styles (e.g.: root variables) from parent document. - // The buttons should use the same styles as `button.code-copy`, and align with it. return ` -${getCssRootVariablesText()} - html, body { height: 100%; } body { margin: 0; padding: 0; overflow: hidden; } #mermaid { display: block; margin: 0 auto; } - -.view-controller { - position: absolute; - z-index: 1; - right: 5px; - bottom: 5px; - display: flex; - gap: 4px; - visibility: hidden; - opacity: 0; - transition: var(--transition-hover-fade); - margin-right: 0.25em; -} -body:hover .view-controller { visibility: visible; opacity: 1; } -@media (hover: none) { - .view-controller { visibility: visible; opacity: 1; } -} -.view-controller button { - cursor: pointer; - display: inline-flex; - justify-content: center; - align-items: center; - line-height: 1; - padding: 7.5px 10px; - border: 1px solid var(--color-light-border); - border-radius: var(--border-radius); - background: var(--color-button); - color: var(--color-text); - user-select: none; -} -.view-controller button:hover { background: var(--color-secondary); } -.view-controller button:active { background: var(--color-secondary-dark-1); } `; } @@ -117,10 +81,9 @@ async function loadMermaid(needElkRender: boolean) { }; } -function initMermaidViewController(dragElement: SVGSVGElement) { +function initMermaidViewController(viewController: HTMLElement, dragElement: SVGSVGElement) { let inited = false, isDragging = false; let currentScale = 1, initLeft = 0, lastLeft = 0, lastTop = 0, lastPageX = 0, lastPageY = 0; - const container = dragElement.parentElement!; const resetView = () => { currentScale = 1; @@ -136,11 +99,12 @@ function initMermaidViewController(dragElement: SVGSVGElement) { if (inited) return; // if we need to drag or zoom, use absolute position and get the current "left" from the "margin: auto" layout. inited = true; + const container = dragElement.parentElement!; initLeft = container.getBoundingClientRect().width / 2 - dragElement.getBoundingClientRect().width / 2; resetView(); }; - for (const el of queryElems(container, '[data-control-action]')) { + for (const el of viewController.querySelectorAll('[data-control-action]')) { el.addEventListener('click', () => { initAbsolutePosition(); switch (el.getAttribute('data-control-action')) { @@ -161,7 +125,8 @@ function initMermaidViewController(dragElement: SVGSVGElement) { dragElement.addEventListener('mousedown', (e) => { if (e.button !== 0 || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return; // only left mouse button can drag const target = e.target as Element; - if (target.closest('div, p, a, span, button, input')) return; // don't start the drag if the click is on an interactive element (e.g.: link, button) or text element + // don't start the drag if the click is on an interactive element (e.g.: link, button) or text element + if (target.closest('div, p, a, span, button, input, text')) return; initAbsolutePosition(); isDragging = true; @@ -239,7 +204,14 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise`; + const viewControllerHtml = html` +
+ + + +
+ `; + const viewController = createElementFromHTML(viewControllerHtml); // create an iframe to sandbox the svg with styles, and set correct height by reading svg's viewBox height const iframe = document.createElement('iframe'); @@ -262,17 +234,15 @@ export async function initMarkupCodeMermaid(elMarkup: HTMLElement): Promise