diff --git a/src/components/AnnotationCard/AnnotationCard.js b/src/components/AnnotationCard/AnnotationCard.js
index 6dba88fc..5c5e0cbc 100644
--- a/src/components/AnnotationCard/AnnotationCard.js
+++ b/src/components/AnnotationCard/AnnotationCard.js
@@ -25,7 +25,7 @@ import DocumentAnnotationsContext from '../../contexts/DocumentAnnotationsContex
import DocumentFiltersContext from '../../contexts/DocumentFiltersContext';
function addHoverEventListenersToAllHighlightedText() {
- // console.log('annotation-highlighted-text', $('.annotation-highlighted-text'));
+
$('.annotation-highlighted-text').on('mouseover', (e) => {
// highlighting all every piece of the annotation a different color by setting it to active
$(`.annotation-highlighted-text[annotation-id='${$(e.target).attr('annotation-id')}']`).addClass('active');
@@ -184,6 +184,9 @@ function AnnotationCard({
newAnnotationData.modified = new Date();
newAnnotationData.new = false;
newAnnotationData.editing = false;
+ $($(`#document-content-container span[annotation-id='${newAnnotationData._id}']`).get(-1)).prepend("");
+ const annotationEnding = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-ending-marker`);
+ newAnnotationData.position.height = (annotationEnding.offset().top - newAnnotationData.position.top) + 18;
setSavingAnnotation(false);
// after setting the annotation data we need to reset the "new" data back to null
setNewAnnotationTags(null);
diff --git a/src/components/Dashboard/DashboardAnnotationList/DashboardAnnotationList.js b/src/components/Dashboard/DashboardAnnotationList/DashboardAnnotationList.js
new file mode 100644
index 00000000..a07adf4a
--- /dev/null
+++ b/src/components/Dashboard/DashboardAnnotationList/DashboardAnnotationList.js
@@ -0,0 +1,148 @@
+/* eslint-disable no-underscore-dangle */
+import React, { useState, useEffect } from 'react';
+import Link from 'next/link';
+import {
+ Badge, Card, Col, ListGroup, Row, Tab, Tabs,
+} from 'react-bootstrap';
+import { format } from 'date-fns';
+import LoadingSpinner from '../../LoadingSpinner';
+import { getSharedAnnotations, getOwnAnnotations } from '../../../utils/annotationUtil';
+import { getGroupNameById } from '../../../utils/groupUtil';
+import { FirstNameLastInitial } from '../../../utils/nameUtil';
+
+const DashboardAnnotationList = ({
+ session,
+ alerts,
+ setAlerts,
+ tab,
+ mode,
+}) => {
+ const [groupState, setGroupState] = useState({});
+ const [key, setKey] = useState(tab || 'mine');
+ const [listLoading, setListLoading] = useState(true);
+ const [annotations, setAnnotations] = useState([]);
+ const limit = mode === 'dashboard' ? 10 : undefined;
+
+ useEffect(() => {
+ async function fetchData() {
+ if (session && (session.user.groups || session.user.id)) {
+ if (key === 'shared') {
+ setAnnotations(
+ await getSharedAnnotations(session.user.groups, limit)
+ .then(setListLoading(false))
+ .catch((err) => setAlerts([...alerts, { text: err.message, variant: 'danger' }])),
+ );
+ } else if (key === 'mine') {
+ setAnnotations(
+ await getOwnAnnotations(session.user.id, limit)
+ .then(setListLoading(false))
+ .catch((err) => setAlerts([...alerts, { text: err.message, variant: 'danger' }])),
+ );
+ }
+ }
+ }
+ fetchData();
+ }, [key]);
+
+
+ useEffect(() => {
+ if (annotations) {
+ const fetchGroupState = async () => {
+ annotations.map((annotation) => annotation.permissions.groups.map(async (group) => {
+ if (!groupState[group]) {
+ setGroupState({ ...groupState, [group]: await getGroupNameById(group) });
+ }
+ }));
+ };
+ fetchGroupState();
+ }
+ }, [annotations, groupState]);
+
+ return (
+
+
+
+ {mode === 'dashboard' && (
+ Annotations
+ )}
+ {mode === 'list' && (
+ <>Annotations>
+ )}
+
+ setKey(k)}
+ >
+
+
+
+
+ {listLoading && (
+
+ )}
+ {!listLoading && annotations && annotations.length > 0 && (
+ <>
+
+ {annotations.sort((a, b) => new Date(b.created) - new Date(a.created)).map(
+ (annotation) => (
+
+
+
+
+ {annotation.target.selector.exact}
+
+
+
+ {format(new Date(annotation.created), 'Ppp')}
+
+
+
+
+ {annotation.body.value}
+
+
+
+
+
+ {annotation.target.document.title}
+ {' ('}
+ {FirstNameLastInitial(annotation.creator.name)}
+ )
+ {annotation.permissions.groups
+ && annotation.permissions.groups.length > 0 && (
+
+ {groupState[annotation.permissions.groups.sort()[0]]}
+
+ )}
+
+
+
+
+ ),
+ )}
+
+ {mode === 'dashboard' && (
+
+
+ {`See all ${key === 'shared' ? key : 'created'} annotations...`}
+
+
+ )}
+ >
+ )}
+ {!listLoading && (!annotations || annotations.length === 0) && (
+
+ {`You have no ${key === 'shared' ? key : 'created'} annotations.`}
+
+ )}
+
+ );
+};
+
+export default DashboardAnnotationList;
diff --git a/src/components/Dashboard/DashboardAnnotationList/index.js b/src/components/Dashboard/DashboardAnnotationList/index.js
new file mode 100644
index 00000000..0ea184ef
--- /dev/null
+++ b/src/components/Dashboard/DashboardAnnotationList/index.js
@@ -0,0 +1,3 @@
+import DashboardAnnotationList from './DashboardAnnotationList';
+
+export default DashboardAnnotationList;
diff --git a/src/components/Dashboard/DashboardGroupList/DashboardGroupList.js b/src/components/Dashboard/DashboardGroupList/DashboardGroupList.js
index 0f23a86a..3fb7f1ce 100644
--- a/src/components/Dashboard/DashboardGroupList/DashboardGroupList.js
+++ b/src/components/Dashboard/DashboardGroupList/DashboardGroupList.js
@@ -12,8 +12,8 @@ const DashboardGroupList = ({
const [groups, setGroups] = useState([]);
useEffect(() => {
- if (session && !deepEqual(session.user.groups, groups)) {
- setGroups(session.user.groups);
+ if (session && !deepEqual(session.user.groups.slice(0, 10), groups)) {
+ setGroups(session.user.groups.slice(0, 10));
}
}, [session]);
diff --git a/src/components/Document/Document.js b/src/components/Document/Document.js
index 573ae84a..6c793095 100644
--- a/src/components/Document/Document.js
+++ b/src/components/Document/Document.js
@@ -192,15 +192,17 @@ export default class Document extends React.Component {
// side channel and then add position data to the annotation object.
$($(`#document-content-container span[annotation-id='${annotation._id}']`).get(0))
.prepend("");
+ $($(`#document-content-container span[annotation-id='${annotation._id}']`).get(-1))
+ .append("");
// so now that we have added the beginning marker we are going to get
// the position of the begginning marker then remove it from the dom
- const annotationBeginning = $(
- `#document-content-container span[annotation-id='${annotation._id}'] .annotation-beginning-marker`,
- );
+ const annotationBeginning = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-beginning-marker`);
+ const annotationEnding = $(`#document-content-container span[annotation-id='${annotation._id}'] .annotation-ending-marker`);
if (annotationBeginning.get(0) === undefined) {
setAlerts([...alerts, { text: 'unable to annotate a piece of text', variant: 'danger' }]);
} else {
const annotationBeginningPosition = annotationBeginning.offset();
+ const annotationEndingPosition = annotationEnding.offset();
// this takes into account if the user was scrolling through the document
// as the it was being populated with annotations
annotationBeginningPosition.top += $('#document-container').scrollTop();
@@ -214,6 +216,7 @@ export default class Document extends React.Component {
{
left: annotationBeginningPosition.left,
top: annotationBeginningPosition.top,
+ height: (annotationEndingPosition.top - annotationBeginningPosition.top) + 18,
},
...annotation,
},
@@ -225,6 +228,7 @@ export default class Document extends React.Component {
{
left: annotationBeginningPosition.left,
top: annotationBeginningPosition.top,
+ height: (annotationEndingPosition.top - annotationBeginningPosition.top) + 18,
},
...annotation,
},
diff --git a/src/components/HeatMap/HeatMap.js b/src/components/HeatMap/HeatMap.js
new file mode 100644
index 00000000..33b8d7ae
--- /dev/null
+++ b/src/components/HeatMap/HeatMap.js
@@ -0,0 +1,89 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+import React, { useState, useContext, useEffect } from 'react';
+import $ from 'jquery';
+import {
+ Nav,
+ Row,
+ Col,
+ Navbar,
+ Breadcrumb,
+ Container,
+ Button,
+ OverlayTrigger,
+ Popover,
+ Form,
+ Card,
+ ButtonGroup,
+ Badge,
+ Spinner,
+} from 'react-bootstrap';
+
+import DocumentFiltersContext from '../../contexts/DocumentFiltersContext';
+import DocumentAnnotationsContext from '../../contexts/DocumentAnnotationsContext';
+
+
+function HeatMap() {
+ const lineHeight = 18;
+ const scaleFactor = $('#document-container').height() / $('#document-card-container').height();
+ const minStrokeHeight = 1;
+ const offsetTop = $('#document-container').offset() === undefined ? 0 : $('#document-container').offset().top;
+ const grandularity = lineHeight * scaleFactor >= minStrokeHeight ? lineHeight : Math.ceil(minStrokeHeight / scaleFactor);
+ const [channelAnnotations] = useContext(DocumentAnnotationsContext);
+ const [documentFilters] = useContext(DocumentFiltersContext);
+ const n = (Math.ceil($('#document-card-container').height() / grandularity));
+ const map = new Array(isNaN(n) ? 0 : n);
+
+ for (const side in channelAnnotations) {
+ if (channelAnnotations[side] !== null) {
+ for (const anno of channelAnnotations[side]) {
+ if (documentFilters.annotationIds[side] === null || documentFilters.annotationIds[side].includes(anno._id)) {
+ if (anno.position.height !== undefined) {
+ // now we have to convert the annotations position and height into starting and ending indexs for the map
+ let startIndex = Math.floor((anno.position.top - offsetTop) / grandularity);
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ const endIndex = Math.floor((anno.position.top + anno.position.height - offsetTop) / grandularity);
+ for (let i = startIndex; i <= endIndex; i += 1) {
+ if (map[i] === undefined) {
+ map[i] = 0;
+ }
+ map[i] += 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ return (
+ <>
+
+ {map.map((v, i) =>
)}
+
+
+
+ >
+ );
+}
+
+export default HeatMap;
diff --git a/src/components/HeatMap/index.js b/src/components/HeatMap/index.js
new file mode 100644
index 00000000..501f4411
--- /dev/null
+++ b/src/components/HeatMap/index.js
@@ -0,0 +1,3 @@
+import HeatMap from './HeatMap';
+
+export default HeatMap;
diff --git a/src/components/SecondNavbar/SecondNavbar.js b/src/components/SecondNavbar/SecondNavbar.js
index a31a26b4..c9677051 100644
--- a/src/components/SecondNavbar/SecondNavbar.js
+++ b/src/components/SecondNavbar/SecondNavbar.js
@@ -1,8 +1,8 @@
import {
- Nav, Row, Col, Navbar, Breadcrumb, Container, Button, OverlayTrigger, Popover, Form, Card, ButtonGroup,
+ Nav, Row, Col, Navbar, Breadcrumb, Container,
} from 'react-bootstrap';
import {
- InfoSquare, Filter, ShieldFillCheck, ChatLeftQuoteFill, PeopleFill, BookmarkFill, CalendarEventFill,
+ InfoSquare,
} from 'react-bootstrap-icons';
import FilterPopover from '../FilterPopover';
@@ -20,10 +20,10 @@ const SecondNavbar = ({