diff --git a/Resources/markdown-viewer/shell.html b/Resources/markdown-viewer/shell.html index c4f0bd7553bf..cb4cccc1014c 100644 --- a/Resources/markdown-viewer/shell.html +++ b/Resources/markdown-viewer/shell.html @@ -50,6 +50,33 @@ padding: 0; /* hljs class already pads */ overflow: auto; } +/* Line number gutter for fenced code blocks. */ +pre.cmux-code-with-lines { + display: flex; + flex-direction: row; +} +pre.cmux-code-with-lines code.hljs { + flex: 1; + min-width: 0; +} +.cmux-line-gutter { + display: flex; + flex-direction: column; + padding: 16px 0; + padding-right: 12px; + padding-left: 12px; + border-right: 1px solid var(--borderColor-muted); + text-align: right; + -webkit-user-select: none; + user-select: none; + flex-shrink: 0; +} +.cmux-line-num { + font-size: 12px; + line-height: 1.5; + color: var(--fgColor-muted); + opacity: 0.55; +} .cmux-code-block { position: relative; margin: 0 0 16px 0; @@ -225,6 +252,39 @@ margin: 0.6em 0; text-align: left; } +/* GitHub-style callout/admonition blocks. */ +.cmux-callout { + padding: 12px 16px; + margin: 0 0 16px 0; + border-left: 4px solid; + border-radius: 6px; + background: var(--bgColor-muted); +} +.cmux-callout-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + font-size: 14px; + margin-bottom: 6px; +} +.cmux-callout-title svg { + flex-shrink: 0; + width: 16px; + height: 16px; +} +.cmux-callout-body > :first-child { margin-top: 0; } +.cmux-callout-body > :last-child { margin-bottom: 0; } +.cmux-callout-note { border-left-color: #539bf5; } +.cmux-callout-tip { border-left-color: #3fb950; } +.cmux-callout-important { border-left-color: #a371f7; } +.cmux-callout-warning { border-left-color: #d29922; } +.cmux-callout-caution { border-left-color: #f85149; } +.cmux-callout-note .cmux-callout-title { color: #539bf5; } +.cmux-callout-tip .cmux-callout-title { color: #3fb950; } +.cmux-callout-important .cmux-callout-title { color: #a371f7; } +.cmux-callout-warning .cmux-callout-title { color: #d29922; } +.cmux-callout-caution .cmux-callout-title { color: #f85149; } /* Custom selection color that reads on both themes. */ ::selection { background: rgba(56, 139, 253, 0.4); } /* Smooth-anchor scroll for heading links. */ @@ -280,6 +340,22 @@ return value; } + // FNV-1a hash for fast content-change detection. Uses 32-bit arithmetic + // (safe in JS) to avoid the O(n) innerHTML string comparison on re-render. + function fnv1aHash(str) { + var hash = 0x811c9dc5; + for (var i = 0, len = str.length; i < len; i++) { + hash ^= str.charCodeAt(i); + // Math.imul gives C-like 32-bit integer multiply; plain `*` would lose + // precision once the product exceeds 2^53, corrupting the hash. + hash = Math.imul(hash, 0x01000193) >>> 0; + } + return hash; + } + // Sentinel must not collide with any valid fnv1aHash() output. fnv1aHash + // ends with `>>> 0`, which produces 0..2^32-1; null sits outside that range. + var lastRenderedHash = null; + function escapeHtml(s) { // Let WebKit's HTML serializer do escaping rather than trying // to keep a hand-written entity table correct. @@ -885,32 +961,74 @@ } } + // Single source of truth for sanitization. Both the full TreeWalker + // sanitizer (sanitizeRenderedHTML) and the fast pre-check (needsSanitization) + // are derived from these lists, so the fast-path can never drift out of sync + // with what the sanitizer actually strips. Add a tag or attribute here once + // and both paths update together. + var SANITIZER_BLOCKED_TAGS = { + script: true, iframe: true, object: true, embed: true, link: true, + meta: true, base: true, style: true, audio: true, video: true, + picture: true, source: true, track: true, form: true, button: true, + textarea: true, select: true, option: true, svg: true, math: true + }; + // Attributes the sanitizer strips outright. Any on* event handler is also + // stripped (handled by prefix, not listed here). `input` (checkbox-only) and + // remote src are special-cased in both the sanitizer and the pattern. + var SANITIZER_BLOCKED_ATTRS = [ + 'style', 'background', 'srcset', 'srcdoc', 'autofocus', 'formaction', 'xlink:href' + ]; + // Valueless-capable (boolean) attributes match on a word boundary in the + // fast-path regex rather than requiring `=` (e.g. bare `