Description
Quill Footnotes is a ready-made solution for React/Next.js projects, allowing you to insert and edit footnotes inside the Quill rich editor.
Installation
The Quill-footnotes module is published on the PrivJs.com registry. To access the module, please visit https://www.privjs.com/packages/quill-footnotes (opens in a new tab) and acquire the package. Following this, instructions will be sent to your email.
To configure the PrivJs access token, run the following command in your terminal:
npm login --registry https://r.privjs.comEnter your email, username, and password when prompted. Afterward, you can install the package using the following command:
npm install quill-footnotes --registry https://r.privjs.comThis completes the installation process on your computer.
Note: The license key for your purchase can be found in your email.
Make sure you have react-quill and quill installed in your
React/NextJs app. If not, copy the command and run it in your
terminal:
npm i react-quill quill @types/quillGetting Started
Import necessary modules from quill-footnotes:
import {
footnoteModules,
footnoteFormats,
onEditorContentChange,
} from "quill-footnotes";Import necessary styles:
import "quill-footnotes/dist/styles/index.css";FootnoteModules:
Modules allow you to customize quills default behaviour. FootnoteModules is a necessary object which contains instructions for a quill editor how to handle footnotes functionality.
Params:
ref: React ref of typeReactQuilltoolbar id: Your toolbar id in format#YOUR_TOOLBAR_ID
Usage:
In React components, Quill modules must be wrapped in the
useMemo hook.
const modules = React.useMemo(() => {
return footnoteModules(ref, "#YOUR_TOOLBAR_ID");
}, []);FootnoteFormats:
FootnoteFormats is an array which contains necessary footnote
formats for your quill editor.
Usage:
You need to spread the footnoteFormats array into your formats
array. For example:
const formats = ["header", "bold", ...YOUR_OTHER_FORMATS, ...footnoteFormats];OnEditorContentChange:
OnEditorContentChange is a function that should be called on every
editor change. This function implements logic for recalculating footnote positions.
Params
ref: React ref of typeReactQuill
Usage:
You should add this function to your Quill's onChangeHandler.
const handleChange = (newValue: string) => {
setValue(newValue);
onEditorContentChange(ref);
};Note! Recommended to debounce the handleChange function.
Summary
Add necessary imports:
import ReactQuill from "react-quill";
import {
footnoteModules,
footnoteFormats,
onEditorContentChange,
} from "quill-footnotes";
import "quill-footnotes/dist/styles/index.css";
import "react-quill/dist/quill.snow.css";Declare a formats array with the formats you need and spread
footnoteFormats into your formats array:
const formats = ["header", "bold", ...YOUR_OTHER_FORMATS, ...footnoteFormats];Inside your React component declare your editor state and a quill ref:
const [value, setValue] = React.useState("");
const ref = React.useRef<ReactQuill | null>(null);Declare a handleChange function:
const handleChange = (newValue: string) => {
setValue(newValue);
onEditorContentChange(ref);
};Declare a modules object:
const modules = React.useMemo(() => {
return footnoteModules(ref, "#YOUR_TOOLBAR_ID");
}, []);Add a custom toolbar for your editor to your jsx:
<div id="YOUR_TOOLBAR_ID">
<span className="ql-formats">
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
</span>
<span className="ql-formats">
<button className="ql-link" />
<button className="ql-blockquote" />
<button className="ql-code" />
<button className="ql-image" />
</span>
<span className="ql-formats">
<button className="ql-list" value="ordered" />
<button className="ql-list" value="bullet" />
</span>
<span className="ql-formats">
<button className="ql-align" value="center" />
<button className="ql-align" value="right" />
<button className="ql-align" value="justify" />
</span>
<span className="ql-formats">
<button className="ql-insertFootnote">YOUR_INSERT_FOOTNOTE_ICON</button>
</span>
</div>Add a reactQuill to your jsx:
<ReactQuill
value={editorValue}
onChange={(newValue, delta, source) => {
if (source === "user") {
handleChange(newValue);
}
}}
modules={modules}
formats={formats}
ref={(el: ReactQuill) => {
ref.current = el;
}}
/>NextJs Usage Note
Make sure you have added use client at the top of your Quill
editor component. Then import the component into your parent
component where you want to use the editor using dynamic import:
"use client";
import dynamic from "next/dynamic";
const Editor = dynamic(() => import("./components/Editor/Editor.js"), {
ssr: false,
});HTML Usage
When adding a footnote, the corresponding footnote link gets the
data-ref attribute with a value of the form #footnoteBottom-{FOOTNOTE_NUMBER}. You may use this attribute to highlight the
corresponding footnote and scroll the page to its position. For
example:
React.useEffect(() => {
const footnotesArr: HTMLElement[] =
document.querySelectorAll("span.footnote");
footnotesArr.forEach((el: HTMLElement) =>
el.addEventListener("click", (e) => {
e.preventDefault();
const dataRef = el.getAttribute("data-ref");
const ftn = document.getElementById(dataRef.substring(1));
ftn.classList.remove("highlight");
void ftn.offsetWidth;
ftn.classList.add("highlight");
ftn.scrollIntoView({
behavior: "smooth",
block: "center",
});
})
);
}, [editorValue]);And styles.css:
.highlight {
animation-name: backgroundColorPalette;
animation-duration: 3s;
animation-iteration-count: 1;
animation-direction: alternate;
animation-timing-function: linear;
border-radius: 3px;
}
@keyframes backgroundColorPalette {
0% {
background: #b3e5fc;
}
100% {
background: transparent;
}
}