Fix rendering issues around inline and fenced code in Markdown text fields.

Review Request #12225 — Created April 1, 2022 and submitted

Information

Review Board
release-4.0.x

Reviewers

We try to keep a consistent presentation between Markdown text editing
and rendering in text fields, as opposed to the more standard approach
of having a "Preview" tab that's separate from the editor.

In Review Board 4, we tweaked the styling to try to more clearly
highlight any inline code literals by showing a light-grey background
and a rounded box around it. This is a pretty common appearance in many
apps, including Slack and Discord. To get this to render correctly, we
had to put some tricks in like negative margins to compensate for the
borders and keep everything aligned.

The prior approach had a few issues:

  1. During editing, there was a faint border between the highlighting for
    the code literal backticks and the content (and, if you zoomed in,
    you'd see rounding on the border)

  2. If putting in a fenced code block without a language mapped to a
    CodeMirror mode, the content would appear as one big inline literal
    while editing.

  3. This inline literal, due to our styling and CodeMirror behavior,
    would display the cursor with a height of the full code block.

Part of the difficulty is that CodeMirror doesn't tell you when a code
literal starts or ends, and may break it up into multiple pieces
(opening backtick, one or more spans of content, closing backtick). It
also reuses the same styles for plain text in code blocks.

This change switches up the approach.

To eliminate the negative margins, but to keep the borders where we want
them, we no longer apply the border styling on the inline literal
selectors themselves. We instead set up :before styles,
absolutely-positioned relative to the owning span, offset out by -1px
on each side. This is the equivalent of what we had, but doesn't impact
layout.

We apply the top/bottom borders to the inline literals, and then apply
a left border (and radiuses) to the classes representing the
opening/closing backticks. Another rule then carefully matches the
right-most backtick class (using a combination of :not and + to
allow text to break up the adjacent selectors) and undoes the left
border and adds a right border.

The result of that is what appears to be one consistent span of text
styled as one unbroken code literal.

The second problem to solve involves the code blocks. CodeMirror does
not allow us to provide custom CSS classes for code blocks, but it does
allow us to create our own plain text lexer/highlighter mode, which
itself sets a suitable CSS class. We then map text/plain to that
lexer, so it's used as the fallback. This ensures that any fenced code
blocks either without a specific language or with one that's not
supported will edit and render correctly.

Tested in Firefox and in Chrome, zooming far in (around 500%) and checking
the contents of code literals and code blocks in various circumstances.

This includes code blocks which are flush with the left/right edges (in the
review request fields, comment dialog, and draft banners) and ones that are
not (Review Dialog).

Summary ID
Fix rendering issues around inline and fenced code in Markdown text fields.
We try to keep a consistent presentation between Markdown text editing and rendering in text fields, as opposed to the more standard approach of having a "Preview" tab that's separate from the editor. In Review Board 4, we tweaked the styling to try to more clearly highlight any inline code literals by showing a light-grey background and a rounded box around it. This is a pretty common appearance in many apps, including Slack and Discord. To get this to render correctly, we had to put some tricks in like negative margins to compensate for the borders and keep everything aligned. The prior approach had a few issues: 1. During editing, there was a faint border between the highlighting for the code literal backticks and the content (and, if you zoomed in, you'd see rounding on the border) 2. If putting in a fenced code block without a language mapped to a CodeMirror mode, the content would appear as one big inline literal while editing. 3. This inline literal, due to our styling and CodeMirror behavior, would display the cursor with a height of the full code block. Part of the difficulty is that CodeMirror doesn't tell you when a code literal starts or ends, and may break it up into multiple pieces (opening backtick, one or more spans of content, closing backtick). It also reuses the same styles for plain text in code blocks. This change switches up the approach. To eliminate the negative margins, but to keep the borders where we want them, we no longer apply the border styling on the inline literal selectors themselves. We instead set up `:before:` styles, absolutely-positioned relative to the owning span, offset out by `-1px` on each side. This is the equivalent of what we had, but doesn't impact layout. We apply the top/bottom borders to the inline literals, and then apply a left border (and radiuses) to the classes representing the opening/closing backticks. Another rule then carefully matches the right-most backtick class (using a combination of `:not` and `+` to allow text to break up the adjacent selectors) and undoes the left border and adds a right border. The result of that is what appears to be one consistent span of text styled as one unbroken code literal. The second problem to solve involves the code blocks. CodeMirror does not allow us to provide custom CSS classes for code blocks, but it does allow us to create our own plain text lexer/highlighter mode, which itself sets a suitable CSS class. We then map `text/plain` to that lexer, so it's used as the fallback. This ensures that any fenced code blocks either without a specific language or with one that's not supported will edit and render correctly.
5971a76b425c8364456039e6e792ae9b621d6ee7

Description From Last Updated

I don't suppose it's possible to do something where if the cursor is in the block it looks like this, …

daviddavid
chipx86
david
  1. 
      
  2. Show all issues

    I don't suppose it's possible to do something where if the cursor is in the block it looks like this, but if the cursor is on a different line we put some kind of big red thingamabob?

    1. No, not really. At least not without a big chunk of brand new work that I wouldn't even know how to start.

  3. 
      
david
  1. Ship It!
  2. 
      
chipx86
Review request changed
Status:
Completed
Change Summary:
Pushed to release-4.0.x (e385792)