Unverified Commit d8ef38ef authored by Lyza Gardner's avatar Lyza Gardner Committed by GitHub

Merge pull request #1780 from hypothesis/annotation-omega-replies

AnnotationOmega: Add very basic expand/collapse for reply threads
parents a4d44f01 086d317b
...@@ -3,7 +3,12 @@ import { useEffect, useState } from 'preact/hooks'; ...@@ -3,7 +3,12 @@ import { useEffect, useState } from 'preact/hooks';
import propTypes from 'prop-types'; import propTypes from 'prop-types';
import useStore from '../store/use-store'; import useStore from '../store/use-store';
import { isHighlight, isNew, quote } from '../util/annotation-metadata'; import {
isHighlight,
isNew,
isReply,
quote,
} from '../util/annotation-metadata';
import { isShared } from '../util/permissions'; import { isShared } from '../util/permissions';
import { withServices } from '../util/service-context'; import { withServices } from '../util/service-context';
...@@ -13,6 +18,7 @@ import AnnotationHeader from './annotation-header'; ...@@ -13,6 +18,7 @@ import AnnotationHeader from './annotation-header';
import AnnotationLicense from './annotation-license'; import AnnotationLicense from './annotation-license';
import AnnotationPublishControl from './annotation-publish-control'; import AnnotationPublishControl from './annotation-publish-control';
import AnnotationQuote from './annotation-quote'; import AnnotationQuote from './annotation-quote';
import Button from './button';
import TagEditor from './tag-editor'; import TagEditor from './tag-editor';
import TagList from './tag-list'; import TagList from './tag-list';
...@@ -26,8 +32,10 @@ function AnnotationOmega({ ...@@ -26,8 +32,10 @@ function AnnotationOmega({
onReplyCountClick, onReplyCountClick,
replyCount, replyCount,
showDocumentInfo, showDocumentInfo,
threadIsCollapsed,
}) { }) {
const createDraft = useStore(store => store.createDraft); const createDraft = useStore(store => store.createDraft);
const setCollapsed = useStore(store => store.setCollapsed);
// An annotation will have a draft if it is being edited // An annotation will have a draft if it is being edited
const draft = useStore(store => store.getDraft(annotation)); const draft = useStore(store => store.getDraft(annotation));
...@@ -43,6 +51,9 @@ function AnnotationOmega({ ...@@ -43,6 +51,9 @@ function AnnotationOmega({
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const isEditing = !!draft && !isSaving; const isEditing = !!draft && !isSaving;
const toggleAction = threadIsCollapsed ? 'Show replies' : 'Hide replies';
const toggleText = `${toggleAction} (${replyCount})`;
useEffect(() => { useEffect(() => {
// TEMPORARY. Create a new draft for new (non-highlight) annotations // TEMPORARY. Create a new draft for new (non-highlight) annotations
// to put the component in "edit mode." // to put the component in "edit mode."
...@@ -57,6 +68,7 @@ function AnnotationOmega({ ...@@ -57,6 +68,7 @@ function AnnotationOmega({
const shouldShowActions = !isEditing && !isNew(annotation); const shouldShowActions = !isEditing && !isNew(annotation);
const shouldShowLicense = isEditing && !isPrivate && group.type !== 'private'; const shouldShowLicense = isEditing && !isPrivate && group.type !== 'private';
const shouldShowReplyToggle = replyCount > 0 && !isReply(annotation);
const onEditTags = ({ tags }) => { const onEditTags = ({ tags }) => {
createDraft(annotation, { ...draft, tags }); createDraft(annotation, { ...draft, tags });
...@@ -77,6 +89,8 @@ function AnnotationOmega({ ...@@ -77,6 +89,8 @@ function AnnotationOmega({
} }
}; };
const onToggleReplies = () => setCollapsed(annotation.id, !threadIsCollapsed);
// TODO // TODO
const fakeOnReply = () => alert('Reply: TBD'); const fakeOnReply = () => alert('Reply: TBD');
...@@ -109,14 +123,23 @@ function AnnotationOmega({ ...@@ -109,14 +123,23 @@ function AnnotationOmega({
)} )}
</div> </div>
{shouldShowLicense && <AnnotationLicense />} {shouldShowLicense && <AnnotationLicense />}
<div className="annotation-omega__controls">
{shouldShowReplyToggle && (
<Button
className="annotation-omega__reply-toggle"
onClick={onToggleReplies}
buttonText={toggleText}
/>
)}
{shouldShowActions && ( {shouldShowActions && (
<div className="annotation-actions"> <div className="annotation-omega__actions">
<AnnotationActionBar <AnnotationActionBar
annotation={annotation} annotation={annotation}
onReply={fakeOnReply} onReply={fakeOnReply}
/> />
</div> </div>
)} )}
</div>
</footer> </footer>
</div> </div>
); );
...@@ -131,6 +154,8 @@ AnnotationOmega.propTypes = { ...@@ -131,6 +154,8 @@ AnnotationOmega.propTypes = {
replyCount: propTypes.number.isRequired, replyCount: propTypes.number.isRequired,
/** Should extended document info be rendered (e.g. in non-sidebar contexts)? */ /** Should extended document info be rendered (e.g. in non-sidebar contexts)? */
showDocumentInfo: propTypes.bool.isRequired, showDocumentInfo: propTypes.bool.isRequired,
/** Is the thread to which this annotation belongs currently collapsed? */
threadIsCollapsed: propTypes.bool.isRequired,
/* Injected services */ /* Injected services */
annotationsService: propTypes.object.isRequired, annotationsService: propTypes.object.isRequired,
......
...@@ -42,6 +42,7 @@ describe('AnnotationOmega', () => { ...@@ -42,6 +42,7 @@ describe('AnnotationOmega', () => {
onReplyCountClick={fakeOnReplyCountClick} onReplyCountClick={fakeOnReplyCountClick}
replyCount={0} replyCount={0}
showDocumentInfo={false} showDocumentInfo={false}
threadIsCollapsed={true}
{...props} {...props}
/> />
); );
...@@ -60,6 +61,7 @@ describe('AnnotationOmega', () => { ...@@ -60,6 +61,7 @@ describe('AnnotationOmega', () => {
fakeMetadata = { fakeMetadata = {
isNew: sinon.stub(), isNew: sinon.stub(),
isReply: sinon.stub(),
quote: sinon.stub(), quote: sinon.stub(),
}; };
...@@ -73,6 +75,7 @@ describe('AnnotationOmega', () => { ...@@ -73,6 +75,7 @@ describe('AnnotationOmega', () => {
getGroup: sinon.stub().returns({ getGroup: sinon.stub().returns({
type: 'private', type: 'private',
}), }),
setCollapsed: sinon.stub(),
}; };
$imports.$mock(mockImportedComponents()); $imports.$mock(mockImportedComponents());
...@@ -268,6 +271,66 @@ describe('AnnotationOmega', () => { ...@@ -268,6 +271,66 @@ describe('AnnotationOmega', () => {
}); });
}); });
describe('reply thread toggle button', () => {
const findRepliesButton = wrapper =>
wrapper.find('Button').filter('.annotation-omega__reply-toggle');
it('should render a toggle button if the annotation has replies', () => {
fakeMetadata.isReply.returns(false);
const wrapper = createComponent({
replyCount: 5,
threadIsCollapsed: true,
});
assert.isTrue(findRepliesButton(wrapper).exists());
assert.equal(
findRepliesButton(wrapper).props().buttonText,
'Show replies (5)'
);
});
it('should not render a toggle button if the annotation has no replies', () => {
fakeMetadata.isReply.returns(false);
const wrapper = createComponent({
replyCount: 0,
threadIsCollapsed: true,
});
assert.isFalse(findRepliesButton(wrapper).exists());
});
it('should not render a toggle button if the annotation itself is a reply', () => {
fakeMetadata.isReply.returns(true);
const wrapper = createComponent({
replyCount: 5,
threadIsCollapsed: true,
});
assert.isFalse(findRepliesButton(wrapper).exists());
});
it('should toggle the collapsed state of the thread on click', () => {
fakeMetadata.isReply.returns(false);
const wrapper = createComponent({
replyCount: 5,
threadIsCollapsed: true,
});
act(() => {
findRepliesButton(wrapper)
.props()
.onClick();
});
wrapper.setProps({ threadIsCollapsed: false });
assert.calledOnce(fakeStore.setCollapsed);
assert.equal(
findRepliesButton(wrapper).props().buttonText,
'Hide replies (5)'
);
});
});
describe('annotation actions', () => { describe('annotation actions', () => {
it('should show annotation actions', () => { it('should show annotation actions', () => {
const wrapper = createComponent(); const wrapper = createComponent();
......
...@@ -15,11 +15,12 @@ ...@@ -15,11 +15,12 @@
ng-if="vm.thread.annotation"> ng-if="vm.thread.annotation">
</moderation-banner> </moderation-banner>
<div ng-if="vm.shouldShowAnnotationOmega()"><em>Viewing AnnotationOmega</em></div> <div ng-if="vm.shouldShowAnnotationOmega()"><em>Viewing AnnotationOmega</em></div>
<annotation-omega ng-if="vm.shouldShowAnnotationOmega()" <annotation-omega ng-if="vm.shouldShowAnnotationOmega() && vm.thread.annotation"
annotation="vm.thread.annotation" annotation="vm.thread.annotation"
reply-count="vm.thread.replyCount" reply-count="vm.thread.replyCount"
on-reply-count-click="vm.toggleCollapsed()" on-reply-count-click="vm.toggleCollapsed()"
show-document-info="vm.showDocumentInfo"> show-document-info="vm.showDocumentInfo"
thread-is-collapsed="vm.thread.collapsed">
</annotation-omega> </annotation-omega>
<annotation ng-class="vm.annotationClasses()" <annotation ng-class="vm.annotationClasses()"
annotation="vm.thread.annotation" annotation="vm.thread.annotation"
......
.annotation-omega {
&__reply-toggle.button {
background-color: transparent;
padding: 0;
font-weight: 400;
&:hover {
background-color: transparent;
text-decoration: underline;
}
}
&__controls {
display: flex;
}
&__actions {
margin-left: auto;
}
}
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
@use './components/annotation-document-info'; @use './components/annotation-document-info';
@use './components/annotation-header'; @use './components/annotation-header';
@use './components/annotation-license'; @use './components/annotation-license';
@use './components/annotation-omega';
@use './components/annotation-publish-control'; @use './components/annotation-publish-control';
@use './components/annotation-quote'; @use './components/annotation-quote';
@use './components/annotation-share-control'; @use './components/annotation-share-control';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment