- noted
- I did this today:
import { Extension } from "@tiptap/core";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { Node as ProseMirrorNode } from "@tiptap/pm/model";
export const CharacterCount = Extension.create<{
noteLimit: number;
}>({
name: "characterCount",
addOptions() {
return {
noteLimit: 250,
};
},
addStorage() {
return {
characters: () => 0,
type: "note",
};
},
onBeforeCreate() {
this.storage.characters = (node?: ProseMirrorNode) => {
const currentNode = node || this.editor?.state?.doc;
const text = currentNode.textBetween(
0,
currentNode.content.size,
undefined,
" ",
);
return text.length;
};
},
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey("characterCount"),
filterTransaction: (transaction, state) => {
const { editor } = this;
const type = this.storage.type;
const limit =
type === "note" ? this.options.noteLimit : this.options.pageLimit;
if (
!transaction.docChanged ||
limit === null ||
limit === undefined
) {
return true;
}
const oldSize = this.storage.characters(state.doc);
const newSize = this.storage.characters(transaction.doc);
...
But I appreciate learning how to persist data on a plugin using state instead of storage so I don’t depend on tiptap, because a few times I tried to get storage from places I couldn’t access it.
- noted
- I’m just confused by so many terms, not sure what I was asking either
but I think I meant that if by setting the plugin view somehow that’s what defined the view or state of the editor, so shouldn’t mess with it anywhere else but in the plugin, or if it was the case that you can have as many views as you want and that’s somehow fine.
- this is what I’ve also done
import { Extension, JSONContent } from "@tiptap/react";
function debounce(func: (...args: any[]) => void, wait: number) {
let timeout: NodeJS.Timeout | null = null;
return function (...args: any[]) {
const later = () => {
timeout = null;
func(...args);
};
if (timeout) clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const SaveExtension = Extension.create({
name: "save",
addOptions() {
return {
postID: null,
ownerID: null,
};
},
onCreate() {
const { editor } = this;
const postID = this.options.postID;
const ownerID = this.options.ownerID;
if (!postID || !ownerID) {
console.warn("PostID and ownerID are required to save the document.");
return;
}
const debouncedSave = debounce((doc: JSONContent) => {
const body = JSON.stringify({
id: postID,
ownerID: ownerID,
content: doc,
});
fetch("/api/docs/save", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: body,
})
.then((response) => {
if (response.ok) {
console.log("Document saved successfully.");
} else {
console.warn("Failed to save document.");
}
})
.catch((error) => {
console.error("An error occurred while saving the document:", error);
});
}, 1000);
this.editor.on("transaction", ({ transaction }) => {
if (transaction.docChanged) {
debouncedSave(this.editor.getJSON());
}
});
},
});
export default SaveExtension;
Do you think this approach is fine?
I tried to store things like postID and authorID inside the doc attrs, injecting it into the previously stored doc, and passing it as content when initialising the editor, but when I read the doc again they disappear. Note I don’t need them to change at all during the lifetime of the editor, just for access later. I’ve checked this thread but after re-reading a couple of times I have no idea if it’s even applicable to my case, so I proceed with the code above and set postID and authorID on configure, for this case I think I’m fine, but I think I will need to modify the doc attrs later on and no idea how.
Most importantly I’m now thinking on how to change schemas, or more precisely where should I perform that, should I do that directly in the charCounter plugin inside the filterTransactions? Doesn’t sound like a good idea, as I’m afraid that it may break it or at least lose some transactions, but no idea where is the right place.
Thank you once again @bhl