Documentation

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.com

Enter 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.com

This 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/quill

Getting 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 type ReactQuill
  • toolbar 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 type ReactQuill

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;
  }
}