Commit fcfde74a authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Display the annotation header on the top-level annotation

This PR ensures that the annotation header is always shown, even if the
top-level annotation is hidden by a filter. The annotation header
contains the document target. This information is specially important in
the context of the notebook, where annotations from different documents
are displayed together.

I created a wrapper around the `AnnotationHeader` component. I couldn't
use it directly because I needed a couple of methods from the store to
mimic 100% the same behaviour as when `AnnotationHeader` is called
through `Annotation` component.
parent cf22b700
...@@ -7,6 +7,7 @@ import { withServices } from '../service-context'; ...@@ -7,6 +7,7 @@ import { withServices } from '../service-context';
import { countHidden, countVisible } from '../helpers/thread'; import { countHidden, countVisible } from '../helpers/thread';
import Annotation from './Annotation'; import Annotation from './Annotation';
import AnnotationHeader from './Annotation/AnnotationHeader';
import ModerationBanner from './ModerationBanner'; import ModerationBanner from './ModerationBanner';
/** @typedef {import('../helpers/build-thread').Thread} Thread */ /** @typedef {import('../helpers/build-thread').Thread} Thread */
...@@ -108,11 +109,22 @@ function Thread({ thread, threadsService }) { ...@@ -108,11 +109,22 @@ function Thread({ thread, threadsService }) {
{annotationContent} {annotationContent}
{showHiddenToggle && ( {showHiddenToggle && (
<>
{!thread.parent && (
<ThreadHeader
annotation={thread.annotation}
replyCount={thread.replyCount}
threadIsCollapsed={thread.collapsed}
/>
)}
<div className="Thread__hidden-toggle-button-container"> <div className="Thread__hidden-toggle-button-container">
<LabeledButton onClick={() => threadsService.forceVisible(thread)}> <LabeledButton
onClick={() => threadsService.forceVisible(thread)}
>
Show {countHidden(thread)} more in conversation Show {countHidden(thread)} more in conversation
</LabeledButton> </LabeledButton>
</div> </div>
</>
)} )}
{showChildren && ( {showChildren && (
...@@ -129,4 +141,36 @@ function Thread({ thread, threadsService }) { ...@@ -129,4 +141,36 @@ function Thread({ thread, threadsService }) {
); );
} }
/**
* This wrapper around AnnotationHeader is to show the document target on the
* top-level annotation (if such exists).
*
* @param {object} props
* @param {Thread['annotation']} props.annotation
* @param {number} props.replyCount
* @param {boolean} props.threadIsCollapsed
*/
function ThreadHeader({ annotation, ...restProps }) {
const store = useStoreProxy();
// These two lines are copied from the AnnotationHeader component to mimic the
// exact same behaviour.
const isSaving = annotation && store.isSavingAnnotation(annotation);
const isEditing = annotation && !!store.getDraft(annotation) && !isSaving;
if (!annotation) {
return null;
}
return (
<div className="Thread__top-annotation-header">
<AnnotationHeader
annotation={annotation}
isEditing={isEditing}
{...restProps}
/>
</div>
);
}
export default withServices(Thread, ['threadsService']); export default withServices(Thread, ['threadsService']);
...@@ -81,6 +81,8 @@ describe('Thread', () => { ...@@ -81,6 +81,8 @@ describe('Thread', () => {
fakeStore = { fakeStore = {
hasAppliedFilter: sinon.stub().returns(false), hasAppliedFilter: sinon.stub().returns(false),
setExpanded: sinon.stub(), setExpanded: sinon.stub(),
isSavingAnnotation: sinon.stub().returns(false),
getDraft: sinon.stub().returns(false),
}; };
fakeThreadsService = { fakeThreadsService = {
...@@ -239,6 +241,29 @@ describe('Thread', () => { ...@@ -239,6 +241,29 @@ describe('Thread', () => {
assert.calledOnce(fakeThreadsService.forceVisible); assert.calledOnce(fakeThreadsService.forceVisible);
assert.calledWith(fakeThreadsService.forceVisible, thread); assert.calledWith(fakeThreadsService.forceVisible, thread);
}); });
it('shows the annotation header on a hidden top-level thread', () => {
const thread = createThread();
const wrapper = createComponent({ thread });
assert.isTrue(wrapper.find('ThreadHeader').exists());
});
it("doesn't show the annotation header if top-level annotation is missing", () => {
const thread = createThread();
thread.annotation = null;
const wrapper = createComponent({ thread });
assert.isTrue(wrapper.find('ThreadHeader').isEmptyRender());
});
it("doesn't show the annotation header if thread is a child", () => {
const thread = createThread();
thread.parent = {}; // child threads have a parent
const wrapper = createComponent({ thread });
assert.isFalse(wrapper.find('ThreadHeader').exists());
});
}); });
context('thread with child threads', () => { context('thread with child threads', () => {
......
...@@ -56,6 +56,12 @@ ...@@ -56,6 +56,12 @@
margin-top: -0.25em; margin-top: -0.25em;
} }
&__top-annotation-header {
// This disables the vertical alignment for the parent thread which contain
// an annotation header.
margin-bottom: 0.5em;
}
&__content { &__content {
flex-grow: 1; flex-grow: 1;
// Prevent annotation content from overflowing the container // Prevent annotation content from overflowing the container
......
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