How to render a custom element on top of every code block

I’m basically trying to add a little rectangle on top of every code block that would display the language of the code block (stored in node.attrs.params)

I tried making a plugin view like the Tooltip demo on the website, but I can’t get the positioning right with that, as the created elements just stay in one spot on the screen when you scroll through the document.

Here’s the code I tried:

let codeOverlayPlugin = new Plugin({
    view(editorView) { return new CodeOverlay(editorView) }
})

class CodeOverlay {
    constructor(view) {
        this.overlays = [];
        this.update(view, null)        
    }

    update(view, lastState) {

        this.destroy()

        let state = view.state
        // Don't do anything if the document/selection didn't change
        if (lastState && lastState.doc.eq(state.doc) &&
            lastState.selection.eq(state.selection)) return

        state.doc.descendants((node, pos, parent) => {
            if (node.type == state.schema.nodes.code_block) {

                let coords = view.coordsAtPos(pos);

                let span = document.createElement('div');
                span.className="code-overlay";

                view.dom.parentNode.appendChild(span);

                console.log(coords);
                this.overlays.push(span);

                span.style.left = coords.left + "px";
                span.style.top = coords.top + "px";

                span.textContent = node.attrs.params;

            }
        })
    }

    destroy() { this.overlays.forEach((i) => { i.remove() }) }
}

(the code-overlay class gives it position: absolute;, I tried all different types)

I tried all different kinds of positioning, tried appending the div to different things like the editor itself, the window, etc. and can’t get it to just go over the document and stick to the code_block while your scroll.

I don’t know if this would be the right way to achieve this, that’s what I came here to ask. Should I use a plugin view, or make a .codeblock::after css rule that can have the text injected, or is there some other way to just inject HTML into the toDOM method of a code_block?

I’m basically trying to add a little rectangle on top of every code block that would display the language of the code block (stored in node.attrs.params)

I’m doing something with css only. Here’s the end result:

codeblock

The css:

.ProseMirror pre.codeblock[data-lang]:before {
    content: attr(data-lang);
    display: block;
    font-size: .75rem;
    font-weight: 600;
    color: #a3a3a3;
    padding-bottom: .25rem;
}

And the toDOM method

      toDOM(node: PMNode) { 
        return [
          "pre", 
          {class: "codeblock", ...node.attrs.lang && {"data-lang": node.attrs.lang}}, 
          ["code", {spellcheck: "false"}, 0]
        ] 
      },
1 Like