Inline node with content

Hey, I am trying to create footnotes. In our current code they would be something like

<p>Main text<span class="footnote"><span>The contents of the footnote</span></span>more main text.</p>

We would then use some extra javascript logic to make the inner parts of the footnotes float of to the right hand side, not overlap and kind of appear like margin nodes in the editor.

When I try to create a corresponding schema entry, I get the error message “Can’t create a span node with content”. What’s the recommended way of getting content into such inline nodes?

You can’t. Due to the way the tree model works, as well as the way content is displayed, inline elements can not have content.

For footnotes, what you can do is create a side table with footnote content, and have your footnote elements refer to entries in this table using an attribute. You’d then have to provide your own interface (possibly using a separate ProseMirror editor) for editing footnote contents.

Hmm, that is too bad. The footnotes contain the same as all other text block nodes. And maintaining several prosemirror instances on the same page will complicate collaborative editing as well as copy and pasting of content which contains footnotes. Any chance the coming “locked notes” that can contain editable elements can be used as inline elements?

I am thinking we’ll probably put the entire contents of the footnote into an attribute and then have a second i stance of pm to display and edit the contents. That should take care of copy/paste and basic collaboration. Just when two people edit the same footnote at the same time, it will likely create some unexpected effects. Any other reasons not to do this?

Such an attribute could be the footnote number, as we will need to do numbering anyway. But this then leads to a problem when doing copy/cut/paste: all the footnotes may have to be renumbered. Also when inserting a footnote between footnotes 4 and 5, all footnotes started from 5 will have to be renumbered one higher.

I guess one could use css counters for the footnote numbers, but the JS is then not able to read that footnote number, which it will need to in order to find out what footnote needs to be copied in the footnote table.

I see, yes it would have advantages and disadvantages. Another special thing to handle would be the situation where the user tries to delete the footnote by hitting backspace while the caret is visually directly behind it. The footnote should just disappear entirely, but if it is an inline style, it may instead decide to simply delete the last character inside the footnote itself.

Meanwhile, I am trying your other suggestion of having the footnotes contents on an attribute.

This function, triggered by the “draw” event renders all the footnotes in the correct order for me:

var footnotesHTML = "";
renderFootnotes = function() {
    var allFootnotes = [].slice.call(where.querySelectorAll('.footnote')),
        newFootnotesHTML = "";
    allFootnotes.forEach(footnote => {
        newFootnotesHTML += "<div class='footnote'>" + footnote.getAttribute('footnote-contents') + "</div>"
    })
    if (footnotesHTML != newFootnotesHTML) {
        document.getElementById('footnotes').innerHTML = newFootnotesHTML;
        footnotesHTML = newFootnotesHTML;
    }
};

But I wonder - should one really go through the DOM like that? Would it be better to step through editor.doc and find all the footnotes and the contents of them that way?

To not only display footnotes but also edit them, one will of course have to edit the attribute on the original document. So one would need a transformation. But none of the pre-defined transformations are about setting/deleting an attribute, right?

Your deleted post suggests you are on the right way – definitely don’t just change the doc for an editor, but go through the transforms. The way to change attributes is with the "ancestor" transform, but it seems I haven’t provided a convenient Transform method for that yet. I’ll push something that fixes that later today, at which point you should be able to say:

pm.tr.setAttributes(path, attributes).apply()

And it’ll update the doc and the display, going through the proper mechanisms to ensure the change is recorded in history, broadcast to collaborating editors, etc.

Actually, since you’re changing an inline node, don’t try to update it, since it has no content—just replace it with a changed version.

let replacement = node.type.create(updatedAttrs, null, node.styles)
pm.tr.replaceWith(posBefore, posAfter, replacement)

I see. What about the path/position of the node that is to be replaced? If I have the old node (that is part of the editor.doc), is there some shortcut to get the position of the node (which I need to declare the transformation)? Or should I simply walk the tree and calculate it myself?

Something like this:

    getNodePos = (currentNode, searchedNode, path = [], fromOffset = 0, toOffset = 0) => {
    var index, childPos, childNode, childOffset;
    if (currentNode === searchedNode) {
        return {
            from: new pm.Pos(path, fromOffset),
            to: new pm.Pos(path, toOffset)
        }
    }
    if (currentNode.content) {
        for (index = 0; index < currentNode.content.length; index++) {
            childNode = currentNode.content[index];
            if (childNode.isInline) {
                fromOffset = toOffset;
                toOffset = toOffset + childNode.offset;
                childPos = getNodePos(childNode, searchedNode, path, fromOffset, toOffset);
            } else {
                childPos = getNodePos(childNode, searchedNode, path.concat(index), fromOffset, toOffset);
            }
            if (childPos !== false) {
                return childPos;
            }
        }
    }
    return false;
};

… which doesn’t seem to work after copying from the same document, because the same nodes will be present in two places in the editor.doc . Is that on purpose?

I have put my test code online: http://fiduswriter.github.io/prosemirror-footnotes/

The way to keep track of something in the document is to keep its position, and map that through all transformations applied to the document. The easiest way to do that would be to create a marker around your element (through markRange) and inspect that to find out if your element is still there, and if yes, where it is now.

Why do you need to track the element at all?

When the user changes the footnote text, this change has to be reinserted into the attribute of the footnote node. So say someone changes the contents of footnote 5, that change will have to be put into the footnote attribute of the fifth footnote node in the main text.

It’s working now, but it feels like this is not how it’s supposed to work.

The way I understand the inner workings of PM, there is no way to check whether new footnote elements (or other elements) have been added or whether some have been deleted. At the change event, one simply has to walk through all nodes in editor.doc and see whether the list of footnote elements there still is the same as it was last time. If it’s not, one has to re-layout the footnotes. Correct?

So because one cannot find out if there are new ones created, one cannot put a markRange around them either, right?

But maybe it would work for the second part, when trying to find the footnote marker for a given footnote whose contents have just changed – put markranges around each footnote once it has been found, and then use that information when the footnote is updated. Is that how it should be done?

See my update in another thread

Nice! I will try to create a footnote node on top of those changes.