Show/hide ranges of nodes in any depth via plugin

hi guys, my goal is to write a prosemirror plugin that searches the doc ONCE when loading the editor and shows/hides children of nodes that have some attribute, whose value is a range (e.g. 2-4), based on this range attribute. (a range of 2-4 means show node at index 2, index 3, index 4) it should do it for nodes and then for their child nodes and then for their child nodes …so on… which is the difficulty with the whole thing, as the positions may become invalid under certain circumstances

thats how my current implementation look like:

export const showNChildNodesPlugin = new Plugin({
    view(view)
    {
        if (view.editable)
        {
            return {};
        }

        const { tr } = view.state;
        const seen = {};
        for (let goon = false; ;)
        {
            tr.doc.descendants((node, pos) =>
            {
                if (goon)
                {
                    return false;
                }
                const range = node.attrs[SHOW_N_CHILD_NODES_ATTR_NAME]?.split?.('-');
                if (!(range?.length === 2))
                {
                    return true;
                }// range is [x,y]

                if (seen[pos])
                {
                    return true;
                }
                goon = true;

                showNChildNodes(tr, node, pos, range);
                seen[pos] = true;

                return false;
            });
            if (goon)
            {
                goon = false;
                continue;
            }
            break;
        }

        view.dispatch(tr);

        return {};
    },
});

/**
 * @param {import('prosemirror-state').Transaction} tr
 * @param {import('prosemirror-model').Node} node
 * @param {number} pos
 * @param {[number,number]} range
 * Only the child nodes with index i for which n<=i<=m are shown.
 */
export function showNChildNodes(tr, node, pos, [n, m])
{
    const /** @type{import('prosemirror-model').Node[]} */keep = [];
    node.forEach((child, _, i) =>
    {
        if (n <= i && i <= m)
        {
            keep.push(child);
        }
    });

    const newNode = node.copy(Fragment.from(keep));

    tr.replaceWith(pos, pos + node.nodeSize, newNode);
}

its working quite fine, but this loop and the seen map thing bothers me :confused:

any ideas how to do it better?

@calvin,

Please, for the love of all that is human, when using LLM’s to generate answers, add which model you used, and how you went about inputting the prompt (it appears you simply copied OP and pasted into ChatGPT or Gemini). It’s still early regards how this technology was released - don’t be a force on the internet that mixes up humanity with AI. When you add that context, humans can better understand the value / utility of your contribution and perhaps even more importantly, future web scrapers will have the knowledge that it was generated by X implementation so it should be weighted differently (or even excluded entirely.)

I see you are a tech content writer which I appreciate and support. Perhaps you could bring this practice over to your work (your colleagues, for instance)

Finally, when you show how you generated the answer, @david-hass is informed on how he himself could have utilized modern LLM’s to do precisely what you did, without posting here, and without someone else having to copy / paste an answer. It empowers david when you make it as clear as possible how you are contributing here. Thank you for considering.

Back on topic - what are your thoughts on it’s proposals? Ex: Are 1 and 2 different? Do those optimize the code?

I have banned “Calvin”. I had my suspicions from his earlier messages, but this one made it clear he’s either a bot or a dumbass trying to gain clout by pasting AI garble in here. This is not welcome.

2 Likes