Overlapping Text Annotations

I’d like to be able to create overlapping text annotations (as in the image above). I have tried using the Decoration.mark from Codemirror 6, but on account of all the overlapping decorations ending up as classes on the same dom elements, I can’t create separate and overlapping styles (like the underlines and highlights above). Is what I’m looking for possible with Replace Decorations or Widget Decorations? Or some other way? I would like the text to still be editable and would like to retain the ability for the annotation to span multiple lines.

I’m happy to use Codemirror 5 or 6 to implement the extension/addon necessary. But I could use some direction on what the primitives to start with.

Best regards and thanks in advance for your help!

CSS doesn’t have a way to do this automatically. So you’d have to compute the appropriate height of the underlines on each bit of the code yourself, and then use something like SVG backgrounds to display the proper lines at each span.

Right. That makes sense. The issue I’m running into with the MarkDecoration is that I’m getting something like this:

<span class="1">
    te
</span>
<span class="1 2">
    x
</span>
<span class="2">
    t
</span>

When what I would like to be able to do is something more like this:

<span class="1">
    te
    <span class="1 overlay"></span>
</span>
<span class="1 2">
    x
    <span class="1 overlay"></span>
    <span class="2 overlay"></span>
</span>
<span class="2">
    t
    <span class="2 overlay"></span>
</span>

Or something like this:

<span class="1">
    te
</span>
<span class="1">
    <span class="2">
        x
    </span>
</span>
<span class="2">
    t
</span>

Is something like that possible with Decorations?

That’s not supported—line content is managed as a flat sequence of elements.

Gotcha. Thanks for the info, @marijn

@marijn Could this be implemented as an extension or would it need to be part of the core in order to function? I’d be interested in getting this to work. It seems like it could open up a lot of power for Decorations if feasible.

If you’re talking about arbitrarily nested inline nodes, I’m strongly leaning towards that being out of scope. (And I doubt it would help a lot with what you’re trying to accomplish here.)

If you mean the underline thing, you could do that as an extension—do your own splitting into spans and computing of underline heights, and then generate decorations that assign the right style to each of the ranges.

Thanks for the quick reply. To clarify, I don’t want arbitrarily nested inline nodes necessarily.

This example:

<span class="1">
    te
    <span class="1 overlay"></span>
</span>
<span class="1 2">
    x
    <span class="1 overlay"></span>
    <span class="2 overlay"></span>
</span>
<span class="2">
    t
    <span class="2 overlay"></span>
</span>

…could be achieved with an option on the mark decoration to inject a single overlay dom element. That way, the injected overlay elements could be styled individually without conflict. Or, at least, that is naively how I am thinking of it. That’s more or less the approach I used to create the editor in the image above.

Aren’t CSS pseudo-elements more practical for this kind of thing?

@marijn They would be, but not in the case of overlapping annotations.

Take the following example of a section of text with overlapping decorations that we want to give distinct highlight colors:

<span class="annotation-1 annotation-2 annotation-3">text</span>
.annotation-1:before {
    background: rgba(255, 0, 0, 0.1);
}
.annotation-2:before {
    background: rgba(0, 255, 0, 0.1);
}
.annotation-3:before {
    background: rgba(0, 0, 255, 0.1);
}

The span simply gets the :before for annotation-3, with the other annotations not being rendered to the view.

Whereas if each annotation can inject it’s own overlay element, each of those elements can be styled differently without overwriting other annotations.

Ah, right, that’s true. But no, though you can insert elements with decorations, you can’t insert them inside text spans. If you have low maximum number of underlines, you could just generate classws with background images for the 2**n possibilities of each line being present and absent, but I’m not sure if that works for your case.

That doesn’t feel like quite the right solution to me as I’d like to solve for the general case and not just underlines, but any kind of overlapping styling that could ostensibly not override others. Underlines of various colors based on some kind of taxonomy, highlights of similarly varying colors, etc. Perhaps highlights that change color on hover while still overlapping other highlights and underlines.

Could this functionality be added as an external library or would it need to be added as part of the core to function correctly? And if it would need to be added to the core in order to function, would you be open to me potentially pulling together a pull request to add an injection option to the MarkDecorationSpec with the necessary accompanying changes to internals?

That would have to be implemented in the core, and my instinct is to resist that, since this seems a very specific use case and I’d have to keep supporting that forever without knowing if anyone is still going to use it.

I can imagine adding support for multiple tagNames to create additional wrapper nodes for a given piece of content. Though in order to have that we’d need to be able to define a stable ordering for overlapping decorations, which doesn’t currently exist.

@marijn Wouldn’t additional wrapper nodes introduce the problem of arbitrarily nested content? Or would you put a hard limit on the depth?

Are you referring to something like this:

<span class="1">
    te
</span>
<span class="1">
    <span class="2">
        x
    </span>
</span>
<span class="2">
    t
</span>

This seems to cover most of my use case.

I meant only per-fragment, not nodes covering multiple fragments (which I think is what you example expresses as well)

Hmm. I don’t follow. Do you have a code example of the distinction you’re talking about?

You’d be able to do <span class=a><span class=b>foo</span></span><span class=a>bar</span>, but not <span class=a><span class=b>foo</span>bar</span>.

Okay. If you can’t do:

<span class=a>
    <span class=b>
        foo
    </span>
    bar
</span>

can you do:

<span class=a>
    <span class=b>
        foo
    </span>
</span>
<span class=a>
    bar
</span>

?
Because if the latter is possible, that should work for what I’m doing.

That’s… exactly the example I gave. So, yes (in the proposed extension, which doesn’t exist yet).

Haha. Sorry. I somehow completely misread that first example.

And just to clarify, that would be feasible as an extension? It wouldn’t require changes to the core?