Unable to add highlights in epubjs using a context Menu(React)

13 views Asked by At

I am creating an epub viewer and using react-reader as my npm package to render the file. I am trying to highlight the text when selected. The documentation does provide a way to do it. But the approach I am trying is to open a context menu and only when the "Hightlight" button is selected then it should highlight the text. This is how the context menu looks like

This is the code for the reader page:

"use client";
import React, { useState, useEffect } from "react";
import { ReactReader } from "react-reader";
import Notes from "../_components/Notes";
import Discussions from "../_components/Discussions";
import { Rendition, Contents } from "epubjs";
import { get } from "http";
import ContextMenu from "../_lib/ContextMenu";

type ITextSelection = {
  text: string;
  cfiRange: string;
};

const initialContextMenu = {
  show: false,
  x: 0,
  y: 0,
};

function Reader() {
  const [location, setLocation] = useState<string | number>(0);
  const [discussions, setDiscussions] = useState("showDiscussions");
  const [searchText, setSearchText] = useState("");
  const [selections, setSelections] = useState<ITextSelection[]>([]);
  const [rendition, setRendition] = useState<Rendition | undefined>(undefined);

  const handleSearch = () => {
    // Perform search logic here
  };
  function setRenderSelection(cfiRange: string, contents: Contents) {
    if (rendition) {
      setSelections((list) =>
        list.concat({
          text: rendition.getRange(cfiRange).toString(),
          cfiRange,
        })
      );
      rendition.annotations.add("highlight", cfiRange, {}, undefined, "hl", {
        fill: "yellow",
        "fill-opacity": "0.3",
        "mix-blend-mode": "multiply",
      });
      const selection = contents.window.getSelection();
      selection?.removeAllRanges();
      console.log(selection);
    }
  }
  useEffect(() => {
    if (rendition) {
      // rendition.on("selected", setRenderSelection);
      rendition.on("selected", (rendition: Rendition, iframe: Window) => {
        console.log("Rendition rendered");
        iframe.document.documentElement.addEventListener(
          "contextmenu",
          (event: MouseEvent) => {
            console.log("Stopping contextual menu coming from epubjs iframe");
            event.preventDefault();
            setcontextMenu((prev) => ({
              show: true,
              x: event.pageX,
              y: event.pageY,
            }));
          }
        );
      });
      console.log(selections);
      return () => {
        rendition?.off("selected", setRenderSelection);
      };
    }
  }, [setSelections, rendition]);

  const [contextMenu, setcontextMenu] = useState(initialContextMenu);

  function handleContextMenu(e: React.MouseEvent) {
    e.preventDefault();
    console.log("right click");
    const { pageX, pageY } = e;
    setcontextMenu((prev) => ({
      show: true,
      x: pageX,
      y: pageY,
    }));
  }

  return (
    <div className="h-screen">
      <div className="grid grid-cols-2 grid-rows-3 gap-3 h-screen">
        <div className="row-span-3 h-screen py-2 rounded-lg">
          {/* Reader Section */}
          <div className="h-full rounded-lg" onContextMenu={handleContextMenu}>
            <ReactReader
              url="https://react-reader.metabits.no/files/alice.epub"
              location={location}
              locationChanged={(epubcfi: string) => setLocation(epubcfi)}
              getRendition={(_rendition: Rendition) => {
                _rendition.themes.default({
                  "::selection": {
                    background: "rgba(255,255,0, 0.3)",
                  },
                  body: {
                    color: "black",
                    fontSize: "1.2em",
                    textIndent: "2em",
                    lineHeight: "1.6",
                    padding: "0",
                    margin: "0",
                  },
                });
                setRendition(_rendition);
              }}
            />
          </div>
        </div>
        <div className="row-span-2 bg-white mt-2 mr-2 rounded-lg overflow-hidden ">
          {contextMenu.show && (
            <ContextMenu x={contextMenu.x} y={contextMenu.y} />
          )}
          {/* Discussions/ Notes Section */}
          <div className="w-full h-16 rounded-s-lg">
            <div className="h-full bg-white rounded-lg">
              <div className="h-full  rounded-lg">
                <div className="flex justify-evenly items-center pt-4 h-full w-full">
                  <button
                    className={`h-full w-full m-2 rounded-lg ${
                      discussions === "showDiscussions"
                        ? "bg-textColor text-white"
                        : "bg-white"
                    } `}
                    onClick={() => setDiscussions("showDiscussions")}
                  >
                    Discussions
                  </button>
                  <button
                    className={`h-full w-full m-2 rounded-lg ${
                      discussions === "showNotes"
                        ? "bg-textColor text-white"
                        : "bg-white"
                    }`}
                    onClick={() => setDiscussions("showNotes")}
                  >
                    Notes
                  </button>
                </div>
              </div>
            </div>
          </div>
          <div className="h-full mb-4 overflow-scroll scroll-m-0">
            {discussions === "showDiscussions" ? <Discussions /> : <Notes />}
          </div>
        </div>
        <div className="col-start-2 row-start-3 mb-2 mr-2 bg-white rounded-lg">
          {/* 
          Profile Section
          */}
        </div>
      </div>
    </div>
  );
}

export default Reader;

And here is the code for the context Menu:

import { FC } from "react";

interface IContextMenu {
  x: number;
  y: number;
}

const ContextMenu: FC<IContextMenu> = ({ x, y }) => {
  return (
    <div
      className="context-menu"
      style={{
        position: "absolute",
        top: y + 50,
        left: x + 250,
        zIndex: 10000,
        backgroundColor: "white",
        border: "1px solid #ccc",
        borderRadius: "5px",
        padding: "0",
        margin: "0",
      }}
    >
      <ul className="list-none">
        <li
          className="hover:bg-gray-200 p-2 cursor-pointer"
          onClick={() => {
            console.log("Copy");
          }}
        >
          Copy
        </li>
        <li
          className="hover:bg-gray-200 p-2 cursor-pointer"
          onClick={() => {
            console.log("Highlight");
          }}
        >
          Highlight
        </li>
        <li
          className="hover:bg-gray-200 p-2 cursor-pointer"
          onClick={() => {
            console.log("Bookmark");
          }}
        >
          Bookmark
        </li>
        <li
          className="hover:bg-gray-200 p-2 cursor-pointer"
          onClick={() => {
            console.log("Note");
          }}
        >
          Note
        </li>
      </ul>
    </div>
  );
};

export default ContextMenu;

Does anyone have any intuition as to how should i go about it?

0

There are 0 answers