mirror of
https://github.com/docmost/docmost.git
synced 2026-08-28 01:07:08 +08:00
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
//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;
|