UNPKG

mathpix-markdown-it

Version:

Mathpix-markdown-it is an open source implementation of the mathpix-markdown spec written in Typescript. It relies on the following open source libraries: MathJax v3 (to render math with SVGs), markdown-it (for standard Markdown parsing)

268 lines (190 loc) 11.7 kB
# PR: CSS scoping — `#preview-content`/`#setText` specificity boost for all MMD selectors Status: Implemented Owner: @OlgaRedozubova --- ## Context MMD styles use two scoping mechanisms: 1. **ID selectors** (`#setText h1`, `#preview-content table`) for generic HTML elements — specificity (1,0,1), already beats any host class-based CSS. 2. **Class selectors** (`.tabular`, `.figure_img`, `.itemize`, `.enumerate`, `.hljs-*`, `.proof`, `.author`, etc.) for MMD-specific elements — were bare, vulnerable to host CSS overrides with equal or higher specificity. When MMD content is embedded on a host page (e.g., `.docs-content table { width: 100% }`), the host styles override MMD class-based rules. This caused tabular tables to stretch, figure alignment to break, list spacing to change, and syntax highlighting colors to be overridden. --- ## Goal - Boost specificity of all MMD class selectors by adding `#preview-content`/`#setText` scoped variants. - Keep bare selectors as fallback for `markdownToHTML()` which returns raw HTML without wrapper. - Clean up dead code, fix bugs, restructure style modules for readability. - No new wrapper elements, no breaking changes to HTML output or public API signatures. --- ## Scoping Strategy ### Pattern: bare + scoped Every MMD class selector now follows this pattern: ```css .selector, #preview-content .selector, #setText .selector { /* styles */ } ``` - **Bare** (0,1,0) — fallback for `markdownToHTML()` consumers without `#setText`/`#preview-content` wrapper. - **Scoped** (1,1,0) — beats host `.docs-content .selector` (0,1,x) when inside containers. ### Exceptions (bare only, no scoped) | Selector | Reason | |---|---| | `.math-block`, `.math-inline`, `.math-error` | `math-` prefix is distinctive enough; no known collisions | | `mjx-container` | Custom element, cannot collide with host CSS | | `*[data-has-dotfill]`, `*[data-has-dotfill] .dotfill` | `data-` attribute scoping is sufficient | | `svg .math-inline`, `svg mjx-container`, etc. | SVG context selectors | ### Exceptions (scoped only, no bare) | Selector | Reason | |---|---| | `code`, `pre code`, `pre`, `table`, `blockquote`, `img`, `sup`, `h1``h6` | Generic HTML elements — bare selectors would affect all elements on host page | --- ## Non-Goals - Adding a `.mmd` wrapper class (rejected: `#setText` already serves as wrapper). - Renaming CSS classes `.tabular``.mmd-tabular` (rejected: breaking change with no real benefit). - Shadow DOM encapsulation. --- ## Existing Protection (no changes needed) | Element | Protection | Mechanism | |---|---|---| | Generic `<h1>``<h6>`, `<table>`, `<blockquote>`, `<pre>`, `<code>` | `#setText`, `#preview-content` | ID specificity (1,0,1) | | `.tabular` display | `display: inline-table !important` | `!important` | | `.tabular td` borders, padding | `border-style: none !important`, `padding !important` | `!important` | | `.tabular tr` borders | `border-top/bottom: none !important` | `!important` | | `<td>` text-align | `style="text-align: ..."` | Inline style | | `<ul>/<ol>` list-style-type | `style="list-style-type: ..."` | Inline style | | `<blockquote>` margins, padding | `style="margin: ...; padding: ..."` | Inline style (forDocx) | --- ## Changes by File ### `src/styles/index.ts` — restructured into sub-functions Monolithic `MathpixStyle` function split into 10 named sub-functions composed via array join: ```typescript export const MathpixStyle = (...) => [ layoutStyles({ setTextAlignJustify, maxWidth, isPptx }), mathStyles(maxWidth), imageStyles(), blockquoteStyles(useColors), codeBlockStyles(useColors), tableStyles(useColors), docStructureStyles(), inlineTextStyles(useColors), miscStyles(), printStyles(), ].join(''); ``` Scoped selectors added for: `.proof`, `.theorem`, `.main-title`, `.author`, `.section-title`, `.abstract`, `.text-url`, `mark`, `span[data-underline-type] mark`, `.smiles`, `div.svg-container`. Bare `h1, h2, ...` in `maxWidth` overflow block scoped as `#setText > h1, #setText > h2, ...`. Dead code removed: `.empty` (never generated), `.preview-right` (used as id, not class), `scaleEquation` parameter (accepted but never used in CSS output). Specificity side-effect fix: `.tabular` had `margin: 0` which at (0,1,0) was overridden by `#setText table { margin-bottom: 1em }` (1,0,1). After scoping, `#setText .tabular` (1,1,0) beats `#setText table` (1,0,1), dropping the margin. Fixed by replacing `margin: 0` with `margin: 0 0 1em` so `.tabular` explicitly declares its own bottom margin. Additionally, `font-size: inherit` and other defensive defaults now ensure `.tabular` renders consistently regardless of context (e.g., standalone vs nested inside a list) — previously, list context could affect table width and font size via cascade. `useColors=false` color leaks fixed: blockquote `border-left`, table `border`, and `mark` `background-color` now gated behind `useColors`. Bug fix: `div.svg-container` child combinator consistency (`>` for both `#preview-content` and `#setText`). ### `src/styles/styles-tabular.ts` — replaced `.table_tabular .tabular` with ID scoping Before (commit f0e068a): specificity boosted via `.table_tabular .tabular` compound selector. Now: replaced with `#preview-content .tabular, #setText .tabular` — cleaner, consistent with other files. ```css .tabular, #preview-content .tabular, #setText .tabular { display: inline-table !important; width: auto; /* ... */ } ``` Also scoped: `.table_tabular`, `.tabular th/tr/td/td>p`, `.tabular td._empty`, `.tabular td .f`, `.figure_img`, `div.figure_img img`, dark theme selectors. Note: `.sub-table` rule moved here from `index.ts` (where it didn't belong). ### `src/styles/styles-code.ts` — scoped hljs, normalized indentation All 19 `.hljs-*` rule blocks now follow bare + scoped pattern. Indentation normalized to 0/2 (0 for selectors, 2 for properties). ### `src/styles/styles-lists.ts` — scoped all selectors All list selectors (`ol.enumerate`, `ul.itemize`, `.li_enumerate`, `.li_level`, `.not_number`) now have bare + scoped variants. ### `src/styles/halpers.ts` → `src/styles/helpers.ts` — renamed, cleaned up - Fixed typo in filename: `halpers``helpers` - Fixed `max-width:` formatting (added space after colon) - Added scoped variants for `.math-block`, `.smiles`, `.smiles-inline`, `.table_tabular` - Combined h1-h6 `::-webkit-scrollbar` into single `hideScroll()` call - Normalized indentation ### `src/mathpix-markdown-model/index.ts` - Import path updated: `halpers``helpers` - Removed `scaleEquation` from `StyleBundleOpts` interface, `buildStyles()`, and all public method signatures ### `tests/_styles.js` - Updated `MathpixStyle` calls: removed `scaleEquation` argument - Updated `max-width` assertion: `'max-width:800px;'` → `'max-width: 800px;'` - Added `t()` trim helper for composition/buildStyles assertions (accounts for `parts.map(s => s.trim()).join('\n')` in `buildStyles`) - Added `tabularStyles(isPptx=true)` snapshot test - Added `codeStyles(false)` coverage in `getMathpixStyle(useColors=false)` test - Added `tabularStyles(true, true)` assertion in `buildStyles isPptx` test - All 79 tests pass ### Snapshot files All 17 `tests/_data/_styles/*.snap.css` files regenerated (including `tabularStyles-pptx`). --- ## Style system improvements (from prior commits) ### `buildStyles(opts: StyleBundleOpts)` — single style builder All 4 style assembly points (`loadMathJax`, `getMathpixStyleOnly`, `getMathpixStyle`, `getMathpixMarkdownStyles`) delegate to a single `buildStyles()` method. ```typescript interface StyleBundleOpts { setTextAlignJustify?: boolean; useColors?: boolean; maxWidth?: string; isPptx?: boolean; resetBody?: boolean; container?: boolean; mathjax?: boolean; code?: boolean; // default: true preview?: boolean; toc?: boolean; tocContainerName?: string; menu?: boolean; } ``` Module composition per caller: | Module | loadMathJax | getMathpixStyleOnly | getMathpixStyle | getMathpixMarkdownStyles | |---|:---:|:---:|:---:|:---:| | resetBody | conditional | - | - | - | | container | - | - | + | + | | mathjax | - | + | + | + | | MathpixStyle | + | + | + | + | | code | + | + | + | - | | tabular | + | + | + | + | | lists | + | + | + | + | | preview | - | - | if stylePreview | - | | toc | + | - | if preview+toc | - | | menu+clipboard | + | + | if stylePreview | - | ### `loadMathJax` DOM re-injection fix Previously, if `#Mathpix-styles` already existed in the DOM, `loadMathJax()` skipped style update entirely. Now it updates `innerHTML` of the existing element. ### `useColors` propagation Added `useColors` parameter to `loadMathJax`, `getMathpixStyleOnly`, `getMathpixStyle` — passed through to all style functions. ### `codeStyles` conversion Converted from static string to function accepting `useColors` parameter. ### Pre-existing bug fixes - Missing dot: `math-inline svg` → `.math-inline svg` in `@media print` - Missing dot: `svg math-block` → `svg .math-block` - Missing template interpolation: `#{containerName}` → `#${containerName}` in TocStyle - Dead code: removed empty `if (showToc) {}` ### CSS output cleanup - All style files normalized to 0/2 indentation (0 spaces for selectors, 2 spaces for properties). - `buildStyles` refactored from string concatenation to `parts.map(s => s.trim()).join('\n')` — eliminates blank lines between CSS modules. - Removed duplicate `overflow-x: auto` in `mjx-container` rule (was emitted both unconditionally and conditionally when `maxWidth` is set). - Color constants extracted into `src/styles/colors.ts` (link, text, border, background, hljs, toc, menu, clipboard colors). - `src/contex-menu/styles.ts` and `src/copy-to-clipboard/clipboard-copy-styles.ts` refactored: colors moved to constants, formatting normalized, minor CSS optimizations (`0px` → `0`, padding shorthand). --- ## Downstream Impact - Consumers that override MMD class selectors (e.g., `#preview-main .tabular { width: 100% }`) at specificity (1,1,0) — same as the new scoped selectors. Consumer styles that load after MMD styles win by cascade order. No breakage expected. - Consumers with their own `.math-block`/`.math-inline` SCSS — bare selectors preserved, no breakage. - `auto-render.ts` uses `querySelectorAll('.math-inline, .math-block')` — DOM query, not affected by CSS changes. --- ## Constraints / Invariants - HTML output class names unchanged — no downstream breakage. - Public API method signatures changed: `scaleEquation` parameter removed (was unused). Positional callers must shift arguments. `buildStyles(opts)` available as named-parameter alternative. - Inherited CSS properties (`font-family`, `color`, `line-height`) intentionally cascade from host into MMD content. --- ## Done When - [x] All MMD class selectors scoped via `#preview-content`/`#setText` (or justified exception) - [x] Defensive defaults for tables, figures, lists - [x] `.table_tabular .tabular` replaced with ID scoping - [x] hljs selectors scoped in styles-code.ts - [x] lists selectors scoped in styles-lists.ts - [x] `halpers.ts``helpers.ts` rename + cleanup - [x] `index.ts` restructured into sub-functions - [x] Dead code removed (`scaleEquation`, `.empty`, `.preview-right`) - [x] `buildStyles(opts)` single builder with `StyleBundleOpts` - [x] `loadMathJax` DOM re-injection fix - [x] `useColors` propagated through all style functions - [x] Pre-existing bugs fixed - [x] Snapshot + composition + buildStyles tests (79 tests) - [x] All existing tests pass - [x] PR reviewed and merged