mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 17:27:06 +08:00
@@ -0,0 +1,90 @@
|
||||
//Source MIT - https://github.com/buttondown/tiptap-footnotes
|
||||
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
||||
import { ReplaceStep } from "@tiptap/pm/transform";
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { updateFootnotesList } from "./utils";
|
||||
|
||||
const FootnoteRules = Extension.create({
|
||||
name: "footnoteRules",
|
||||
priority: 1000,
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey("footnoteRules"),
|
||||
filterTransaction(tr) {
|
||||
const { from, to } = tr.selection;
|
||||
|
||||
// Allow full document selections (Mod-a/Ctrl-a)
|
||||
if (from === 0 && to === tr.doc.content.size) return true;
|
||||
|
||||
let selectedFootnotes = false;
|
||||
let selectedContent = false;
|
||||
let footnoteCount = 0;
|
||||
tr.doc.nodesBetween(from, to, (node, _, parent) => {
|
||||
if (parent?.type.name == "doc" && node.type.name != "footnotes") {
|
||||
selectedContent = true;
|
||||
} else if (node.type.name == "footnote") {
|
||||
footnoteCount += 1;
|
||||
} else if (node.type.name == "footnotes") {
|
||||
selectedFootnotes = true;
|
||||
}
|
||||
});
|
||||
const overSelected = selectedContent && selectedFootnotes;
|
||||
/*
|
||||
* Here, we don't allow any transaction that spans between the "content" nodes and the "footnotes" node. This also rejects any transaction that spans between more than 1 footnote.
|
||||
*/
|
||||
return !overSelected && footnoteCount <= 1;
|
||||
},
|
||||
|
||||
// if there are some to the footnote references (added/deleted/dragged), append a transaction that updates the footnotes list accordingly
|
||||
appendTransaction(transactions, oldState, newState) {
|
||||
let newTr = newState.tr;
|
||||
let refsChanged = false; // true if the footnote references have been changed, false otherwise
|
||||
for (let tr of transactions) {
|
||||
if (!tr.docChanged) continue;
|
||||
if (refsChanged) break;
|
||||
|
||||
for (let step of tr.steps) {
|
||||
if (!(step instanceof ReplaceStep)) continue;
|
||||
if (refsChanged) break;
|
||||
|
||||
const isDelete = step.from != step.to; // the user deleted items from the document (from != to & the step is a replace step)
|
||||
const isInsert = step.slice.size > 0;
|
||||
|
||||
// check if any footnote references have been inserted
|
||||
if (isInsert) {
|
||||
step.slice.content.descendants((node) => {
|
||||
if (node?.type.name == "footnoteReference") {
|
||||
refsChanged = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (isDelete && !refsChanged) {
|
||||
// check if any footnote references have been deleted
|
||||
tr.before.nodesBetween(
|
||||
step.from,
|
||||
Math.min(tr.before.content.size, step.to), // make sure to not go over the old document's limit
|
||||
(node) => {
|
||||
if (node.type.name == "footnoteReference") {
|
||||
refsChanged = true;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (refsChanged) {
|
||||
updateFootnotesList(newTr, newState);
|
||||
return newTr;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
export default FootnoteRules;
|
||||
Reference in New Issue
Block a user