Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion package/src/footnotes/reference.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { mergeAttributes, Node, nodeInputRule } from "@tiptap/core";
import { mergeAttributes, Node, } from "@tiptap/core";
import { Node as PMNode, Fragment as PMFragment, Slice } from "@tiptap/pm/model";
import { NodeSelection, Plugin, PluginKey } from "@tiptap/pm/state";

import { v4 as uuid } from "uuid";
Expand Down Expand Up @@ -93,7 +94,50 @@ const FootnoteReference = Node.create({

addProseMirrorPlugins() {
const { editor } = this;

/**
* A recursive function to traverse a node and its children.
* It rebuilds the node tree, giving any found `footnoteReference`
* a new, unique data-id.
*/
const mapNode = (node: PMNode): PMNode => {
// 1. If this is a footnoteReference, create a new one with a new ID.
if (node.type.name === this.name) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this.name get set to footnoteReference?

Copy link
Author

@Some1Somewhere Some1Somewhere Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const newAttrs = { ...node.attrs, "data-id": uuid() };
return node.type.create(newAttrs, node.content, node.marks);
}

// 2. If this node has children, we must recurse by mapping over its content.
if (node.content && node.content.size > 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if node.type.name === this.name is set, the node can't have children?

Copy link
Author

@Some1Somewhere Some1Somewhere Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

footnoteReference cannot have child nodes. It can only have text within it, according to the schema defined :
https://github.com/Some1Somewhere/tiptap-footnotes/blob/2984459a4eb67167ad65c17fd2647a018fd6c9be/package/src/footnotes/reference.ts#L25

const newChildren: PMNode[] = [];
node.content.forEach(childNode => {
newChildren.push(mapNode(childNode));
});
const newFragment = PMFragment.from(newChildren);
return node.copy(newFragment);
}

// 3. If it's a plain node with no content, return it as-is.
return node;
};


return [
new Plugin({
key: new PluginKey("footnotePasteHandler"),
props: {
transformPasted(slice) {
const newTopLevelNodes: PMNode[] = [];
slice.content.forEach(topNode => {
newTopLevelNodes.push(mapNode(topNode));
});
Comment on lines +129 to +133

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this not use map? feels like its just mapping each node in slice.content

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiptap fragment (which is what slice.content is) does not have a map function. It only has a forEach function to interate through the nodes in the fragment. That's why I used a forEach


// Build a new Fragment and a new Slice
const newFragment = PMFragment.from(newTopLevelNodes);
return new Slice(newFragment, slice.openStart, slice.openEnd);
},
},
}),
new Plugin({
key: new PluginKey("footnoteRefClick"),

Expand Down Expand Up @@ -154,6 +198,7 @@ const FootnoteReference = Node.create({
},
];
},

});

export default FootnoteReference;