Skip to content
Snippets Groups Projects
RightArea.js 4.53 KiB
Newer Older
chris's avatar
chris committed
import React, {
  useContext,
  useState,
  useEffect,
  useMemo,
chris's avatar
chris committed
  Fragment,
  useCallback
chris's avatar
chris committed
} from "react";
import styled from "styled-components";
chris's avatar
chris committed
import { WaxContext } from "wax-prosemirror-core";
chris's avatar
chris committed
import { DocumentHelpers } from "wax-prosemirror-utilities";
import CommentsBoxList from "./../comments/CommentsBoxList";
chris's avatar
chris committed
import { each } from "lodash";
export default ({ area }) => {
chris's avatar
chris committed
  const { view: { main }, app, activeView } = useContext(WaxContext);
  const activeCommentPlugin = app.PmPlugins.get("activeComment");
  const [marks, setMarks] = useState([]);
chris's avatar
chris committed
  const [position, setPosition] = useState();

  const setTops = useCallback(() => {
    const result = [];
chris's avatar
chris committed
    let annotationTop = 0;
    let boxHeight = 0;
    let top = 0;
    const allCommentsTop = [];
chris's avatar
chris committed

    each(marks[area], (mark, pos) => {
chris's avatar
chris committed
      const WaxSurface = main.dom.getBoundingClientRect();
      const { attrs: { id } } = mark;
chris's avatar
chris committed
      const activeComment = activeCommentPlugin.getState(activeView.state)
        .comment;

chris's avatar
chris committed
      let isActive = false;
      if (activeComment && mark.attrs.id === activeComment.attrs.id)
chris's avatar
chris committed

      //annotation top
      if (area === "main") {
        markEl = document.querySelector(`span[data-id="${id}"]`);
        if (markEl)
          annotationTop = markEl.getBoundingClientRect().top - WaxSurface.top;
chris's avatar
chris committed
      } else {
        const panelWrapper = document.getElementsByClassName("panelWrapper");
        const panelWrapperHeight = panelWrapper[0].getBoundingClientRect()
          .height;
chris's avatar
chris committed
          .querySelector("#notes-container")
          .querySelector(`span[data-id="${id}"]`);
chris's avatar
chris committed
          annotationTop =
            markEl.getBoundingClientRect().top - panelWrapperHeight - 50;
chris's avatar
chris committed
      }

      // get height of this mark box
chris's avatar
chris committed
      const boxEl = document.querySelector(`div[data-comment="comment-${id}"]`);
      if (boxEl) boxHeight = parseInt(boxEl.offsetHeight);

      // where the box should move to
      top = annotationTop;

      // if the above comment box has already taken up the height, move down
      if (pos > 0) {
        const previousBox = marks[area][pos - 1];
chris's avatar
chris committed
        const previousEndHeight = previousBox.endHeight;
        if (annotationTop < previousEndHeight) {
          top = previousEndHeight + 2;
        }
      }
      // store where the box ends to be aware of overlaps in the next box
      mark.endHeight = top + boxHeight + 2;
chris's avatar
chris committed
      result[pos] = top;
      allCommentsTop.push({ [id]: result[pos] });
chris's avatar
chris committed

      // if active, move as many boxes above as needed to bring it to the annotation's height
      if (isActive) {
        mark.endHeight = annotationTop + boxHeight + 2;
chris's avatar
chris committed
        result[pos] = annotationTop;
        allCommentsTop[pos][id] = result[pos];
chris's avatar
chris committed
        let b = true;
        let i = pos;

        // first one active, none above
chris's avatar
chris committed
        if (i === 0) b = false;
chris's avatar
chris committed

        while (b) {
          const boxAbove = marks[area][i - 1];
chris's avatar
chris committed
          const boxAboveEnds = boxAbove.endHeight;
          const currentTop = result[i];

          const doesOverlap = boxAboveEnds > currentTop;
chris's avatar
chris committed
          if (doesOverlap) {
            const overlap = boxAboveEnds - currentTop;
            result[i - 1] -= overlap;
            allCommentsTop[i - 1][marks[area][i - 1].attrs.id] = result[i - 1];
chris's avatar
chris committed
          }

          if (!doesOverlap) b = false;
          if (i <= 1) b = false;
          i -= 1;
        }
      }
    });
chris's avatar
chris committed
    return allCommentsTop;
  });

chris's avatar
chris committed
  useEffect(
    () => {
      setMarks(updateMarks(main));
chris's avatar
chris committed
      setPosition(setTops());
    [JSON.stringify(updateMarks(main)), JSON.stringify(setTops())]
  const CommentTrackComponent = useMemo(
chris's avatar
chris committed
    () => (
      <CommentsBoxList
        comments={marks[area] || []}
chris's avatar
chris committed
        area={area}
        view={main}
chris's avatar
chris committed
        position={position}
    [marks[area] || [], position]
chris's avatar
chris committed
  );
  return <Fragment>{CommentTrackComponent}</Fragment>;
const updateMarks = view => {
chris's avatar
chris committed
  if (view) {
    const commentNodes = DocumentHelpers.findChildrenByMark(
      view.state.doc,
      view.state.schema.marks.comment,
      true
    );
    const allComments = commentNodes.map(node => {
      return node.node.marks.filter(comment => {
        return comment.type.name === "comment";
      });
    });

    const groupedNodes = {};
    allComments.forEach(comment => {
      if (!groupedNodes[comment[0].attrs.group]) {
        groupedNodes[comment[0].attrs.group] = [comment[0]];
      } else {
        groupedNodes[comment[0].attrs.group].push(comment[0]);
    return groupedNodes;
chris's avatar
chris committed
  }
  return [];
};