diff options
author | silverwind <me@silverwind.io> | 2023-04-17 12:10:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-17 12:10:22 +0200 |
commit | dcde4701a5b31c88b3120722c3163af4214264d2 (patch) | |
tree | 8a55e2b3e3d80fb960910ee69f2d052dd7128306 /web_src/js | |
parent | 7681d582cdae42d9322309ddf732117e6d332776 (diff) | |
download | gitea-dcde4701a5b31c88b3120722c3163af4214264d2.tar.gz gitea-dcde4701a5b31c88b3120722c3163af4214264d2.zip |
Fix math and mermaid rendering bugs (#24049)
1. Fix multiple error display for math and mermaid:
![err](https://user-images.githubusercontent.com/115237/231126411-8a21a777-cd53-4b7e-ac67-5332623106e8.gif)
2. Fix height calculation of certain mermaid diagrams by reading the
iframe inner height from it's document instead of parsing it from SVG:
Before:
<img width="866" alt="Screenshot 2023-04-11 at 11 56 27"
src="https://user-images.githubusercontent.com/115237/231126480-b194e02b-ea8c-4ddf-8c79-50c525815d92.png">
After:
<img width="855" alt="Screenshot 2023-04-11 at 11 56 35"
src="https://user-images.githubusercontent.com/115237/231126494-5fe86a48-8d21-455a-8b95-79b6ee27a16f.png">
3. Refactor error handling to a common function
4. Rename to `renderAsciicast` for consistency
5. Improve mermaid loading sequence
Note: I did try `securityLevel: 'sandbox'` to make mermaid output a
iframe directly, but that showed a bug in mermaid where the iframe style
height was set incorrectly. Opened
https://github.com/mermaid-js/mermaid/issues/4289 for this.
---------
Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'web_src/js')
-rw-r--r-- | web_src/js/markup/asciicast.js | 2 | ||||
-rw-r--r-- | web_src/js/markup/common.js | 8 | ||||
-rw-r--r-- | web_src/js/markup/content.js | 4 | ||||
-rw-r--r-- | web_src/js/markup/math.js | 18 | ||||
-rw-r--r-- | web_src/js/markup/mermaid.js | 50 |
5 files changed, 42 insertions, 40 deletions
diff --git a/web_src/js/markup/asciicast.js b/web_src/js/markup/asciicast.js index 902cfcb731..97b18743a1 100644 --- a/web_src/js/markup/asciicast.js +++ b/web_src/js/markup/asciicast.js @@ -1,4 +1,4 @@ -export async function renderAsciinemaPlayer() { +export async function renderAsciicast() { const els = document.querySelectorAll('.asciinema-player-container'); if (!els.length) return; diff --git a/web_src/js/markup/common.js b/web_src/js/markup/common.js new file mode 100644 index 0000000000..aff4a32423 --- /dev/null +++ b/web_src/js/markup/common.js @@ -0,0 +1,8 @@ +export function displayError(el, err) { + el.classList.remove('is-loading'); + const errorNode = document.createElement('pre'); + errorNode.setAttribute('class', 'ui message error markup-block-error'); + errorNode.textContent = err.str || err.message || String(err); + el.before(errorNode); + el.setAttribute('data-render-done', 'true'); +} diff --git a/web_src/js/markup/content.js b/web_src/js/markup/content.js index e4ec3d0b4b..1d29dc07f2 100644 --- a/web_src/js/markup/content.js +++ b/web_src/js/markup/content.js @@ -1,7 +1,7 @@ import {renderMermaid} from './mermaid.js'; import {renderMath} from './math.js'; import {renderCodeCopy} from './codecopy.js'; -import {renderAsciinemaPlayer} from './asciicast.js'; +import {renderAsciicast} from './asciicast.js'; import {initMarkupTasklist} from './tasklist.js'; // code that runs for all markup content @@ -9,7 +9,7 @@ export function initMarkupContent() { renderMermaid(); renderMath(); renderCodeCopy(); - renderAsciinemaPlayer(); + renderAsciicast(); } // code that only runs for comments diff --git a/web_src/js/markup/math.js b/web_src/js/markup/math.js index dcc656ea82..8427637a0f 100644 --- a/web_src/js/markup/math.js +++ b/web_src/js/markup/math.js @@ -1,14 +1,8 @@ -function displayError(el, err) { - const target = targetElement(el); - target.classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - target.before(errorNode); -} +import {displayError} from './common.js'; function targetElement(el) { - // The target element is either the current element if it has the `is-loading` class or the pre that contains it + // The target element is either the current element if it has the + // `is-loading` class or the pre that contains it return el.classList.contains('is-loading') ? el : el.closest('pre'); } @@ -22,6 +16,8 @@ export async function renderMath() { ]); for (const el of els) { + const target = targetElement(el); + if (target.hasAttribute('data-render-done')) continue; const source = el.textContent; const displayMode = el.classList.contains('display'); const nodeName = displayMode ? 'p' : 'span'; @@ -33,9 +29,9 @@ export async function renderMath() { maxExpand: 50, displayMode, }); - targetElement(el).replaceWith(tempEl); + target.replaceWith(tempEl); } catch (error) { - displayError(el, error); + displayError(target, error); } } } diff --git a/web_src/js/markup/mermaid.js b/web_src/js/markup/mermaid.js index b519e2dcdc..865a414c93 100644 --- a/web_src/js/markup/mermaid.js +++ b/web_src/js/markup/mermaid.js @@ -1,21 +1,12 @@ import {isDarkTheme} from '../utils.js'; import {makeCodeCopyButton} from './codecopy.js'; +import {displayError} from './common.js'; const {mermaidMaxSourceCharacters} = window.config; -const iframeCss = ` - :root {color-scheme: normal} - body {margin: 0; padding: 0; overflow: hidden} - #mermaid {display: block; margin: 0 auto} -`; - -function displayError(el, err) { - el.closest('pre').classList.remove('is-loading'); - const errorNode = document.createElement('div'); - errorNode.setAttribute('class', 'ui message error markup-block-error gt-mono'); - errorNode.textContent = err.str || err.message || String(err); - el.closest('pre').before(errorNode); -} +const iframeCss = `:root {color-scheme: normal} +body {margin: 0; padding: 0; overflow: hidden} +#mermaid {display: block; margin: 0 auto}`; export async function renderMermaid() { const els = document.querySelectorAll('.markup code.language-mermaid'); @@ -30,18 +21,19 @@ export async function renderMermaid() { }); for (const el of els) { - const source = el.textContent; + const pre = el.closest('pre'); + if (pre.hasAttribute('data-render-done')) continue; + const source = el.textContent; if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) { - displayError(el, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); + displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); continue; } try { await mermaid.parse(source); } catch (err) { - displayError(el, err); - el.closest('pre').classList.remove('is-loading'); + displayError(pre, err); continue; } @@ -49,26 +41,32 @@ export async function renderMermaid() { // can't use bindFunctions here because we can't cross the iframe boundary. This // means js-based interactions won't work but they aren't intended to work either const {svg} = await mermaid.render('mermaid', source); - const heightStr = (svg.match(/viewBox="(.+?)"/) || ['', ''])[1].split(/\s+/)[3]; - if (!heightStr) return displayError(el, new Error('Could not determine chart height')); const iframe = document.createElement('iframe'); - iframe.classList.add('markup-render'); - iframe.sandbox = 'allow-scripts'; - iframe.style.height = `${Math.ceil(parseFloat(heightStr))}px`; + iframe.classList.add('markup-render', 'gt-invisible'); iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svg}</body></html>`; const mermaidBlock = document.createElement('div'); - mermaidBlock.classList.add('mermaid-block'); + mermaidBlock.classList.add('mermaid-block', 'is-loading', 'gt-hidden'); mermaidBlock.append(iframe); const btn = makeCodeCopyButton(); btn.setAttribute('data-clipboard-text', source); - mermaidBlock.append(btn); - el.closest('pre').replaceWith(mermaidBlock); + + iframe.addEventListener('load', () => { + pre.replaceWith(mermaidBlock); + mermaidBlock.classList.remove('gt-hidden'); + iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`; + setTimeout(() => { // avoid flash of iframe background + mermaidBlock.classList.remove('is-loading'); + iframe.classList.remove('gt-invisible'); + }, 0); + }); + + document.body.append(mermaidBlock); } catch (err) { - displayError(el, err); + displayError(pre, err); } } } |