Commit a47da4ae authored by Lyza Danger Gardner's avatar Lyza Danger Gardner

Remove unused `annotation-thread` component

parent be2c63be
...@@ -264,7 +264,7 @@ const defaultOpts = { ...@@ -264,7 +264,7 @@ const defaultOpts = {
/** /**
* Project, filter and sort a list of annotations into a thread structure for * Project, filter and sort a list of annotations into a thread structure for
* display by the <annotation-thread> directive. * display by the <Thread> component.
* *
* buildThread() takes as inputs a flat list of annotations, * buildThread() takes as inputs a flat list of annotations,
* the current visibility filters and sort function and returns * the current visibility filters and sort function and returns
......
import { countVisible, countHidden } from '../util/thread';
function showAllChildren(thread, showFn) {
thread.children.forEach(child => {
showFn(child);
showAllChildren(child, showFn);
});
}
function showAllParents(thread, showFn) {
while (thread.parent && thread.parent.annotation) {
showFn(thread.parent);
thread = thread.parent;
}
}
// @ngInject
function AnnotationThreadController(features, store) {
// Flag that tracks whether the content of the annotation is hovered,
// excluding any replies.
this.annotationHovered = false;
this.toggleCollapsed = function() {
this.onChangeCollapsed({
id: this.thread.id,
collapsed: !this.thread.collapsed,
});
};
this.threadClasses = function() {
return {
'annotation-thread': true,
'annotation-thread--reply': this.thread.depth > 0,
'annotation-thread--top-reply': this.thread.depth === 1,
};
};
this.threadToggleClasses = function() {
return {
'annotation-thread__collapse-toggle': true,
'is-open': !this.thread.collapsed,
'is-hovered': this.annotationHovered,
};
};
this.annotationClasses = function() {
return {
annotation: true,
'annotation--reply': this.thread.depth > 0,
'is-collapsed': this.thread.collapsed,
'is-highlighted': this.thread.highlightState === 'highlight',
'is-dimmed': this.thread.highlightState === 'dim',
};
};
/**
* Show this thread and any of its children. This is available if filtering
* is applied that hides items in the thread.
*/
this.showThreadAndReplies = function() {
showAllParents(this.thread, this.onForceVisible);
this.onForceVisible(this.thread);
showAllChildren(this.thread, this.onForceVisible);
};
this.isTopLevelThread = function() {
return !this.thread.parent;
};
/**
* Return the total number of annotations in the current
* thread which have been hidden because they do not match the current
* search filter.
*/
this.hiddenCount = function() {
return countHidden(this.thread);
};
this.shouldShowReply = function(child) {
return countVisible(child) > 0;
};
this.onForceVisible = function(thread) {
store.setForceVisible(thread.id, true);
if (thread.parent) {
store.setCollapsed(thread.parent.id, false);
}
};
}
export default {
controllerAs: 'vm',
controller: AnnotationThreadController,
bindings: {
/** The annotation thread to render. */
thread: '<',
/**
* Specify whether document information should be shown
* on annotation cards.
*/
showDocumentInfo: '<',
/** Called when the user clicks on the expand/collapse replies toggle. */
onChangeCollapsed: '&',
},
template: require('../templates/annotation-thread.html'),
};
import angular from 'angular';
import * as util from './angular-util';
import * as fixtures from '../../test/annotation-fixtures';
import annotationThread from '../annotation-thread';
import moderationBanner from '../moderation-banner';
function PageObject(element) {
this.annotations = function() {
return Array.from(element[0].querySelectorAll('annotation'));
};
this.visibleReplies = function() {
return Array.from(
element[0].querySelectorAll(
'.annotation-thread__content > ul > li:not(.ng-hide)'
)
);
};
this.replyList = function() {
return element[0].querySelector('.annotation-thread__content > ul');
};
this.isHidden = function(element) {
return element.classList.contains('ng-hide');
};
}
describe('annotationThread', function() {
before(function() {
angular
.module('app', [])
.component('annotationThread', annotationThread)
.component('moderationBanner', {
bindings: moderationBanner.bindings,
});
});
let fakeFeatures;
let fakeStore;
beforeEach(function() {
fakeFeatures = {
flagEnabled: sinon.stub().returns(false),
};
fakeStore = {
setForceVisible: sinon.stub(),
setCollapsed: sinon.stub(),
getState: sinon.stub(),
};
angular.mock.module('app', { features: fakeFeatures, store: fakeStore });
});
it('renders the tree structure of parent and child annotations', function() {
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '1',
annotation: { id: '1', text: 'text' },
children: [
{
id: '2',
annotation: { id: '2', text: 'areply' },
children: [],
visible: true,
},
],
visible: true,
},
});
const pageObject = new PageObject(element);
assert.equal(pageObject.annotations().length, 2);
assert.equal(pageObject.visibleReplies().length, 1);
});
it('does not render hidden threads', function() {
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '1',
annotation: { id: '1' },
visible: false,
children: [],
},
});
const pageObject = new PageObject(element);
assert.equal(pageObject.annotations().length, 0);
});
describe('onForceVisible', () => {
it('shows the thread', () => {
const thread = {
id: '1',
children: [],
};
const element = util.createDirective(document, 'annotationThread', {
thread: thread,
});
element.ctrl.onForceVisible(thread);
assert.calledWith(fakeStore.setForceVisible, thread.id, true);
});
it('uncollapses the parent', () => {
const thread = {
id: '2',
children: [],
parent: { id: '3' },
};
const element = util.createDirective(document, 'annotationThread', {
thread: thread,
});
element.ctrl.onForceVisible(thread);
assert.calledWith(fakeStore.setCollapsed, thread.parent.id, false);
});
});
it('shows replies if not collapsed', function() {
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '1',
annotation: { id: '1' },
visible: true,
children: [
{
id: '2',
annotation: { id: '2' },
children: [],
visible: true,
},
],
collapsed: false,
},
});
const pageObject = new PageObject(element);
assert.isFalse(pageObject.isHidden(pageObject.replyList()));
});
it('does not show replies if collapsed', function() {
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '1',
annotation: { id: '1' },
visible: true,
children: [
{
id: '2',
annotation: { id: '2' },
children: [],
visible: true,
},
],
collapsed: true,
},
});
const pageObject = new PageObject(element);
assert.isTrue(pageObject.isHidden(pageObject.replyList()));
});
it('only shows replies that match the search filter', function() {
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '1',
annotation: { id: '1' },
visible: true,
children: [
{
id: '2',
annotation: { id: '2' },
children: [],
visible: false,
},
{
id: '3',
annotation: { id: '3' },
children: [],
visible: true,
},
],
collapsed: false,
},
});
const pageObject = new PageObject(element);
assert.equal(pageObject.visibleReplies().length, 1);
});
describe('#toggleCollapsed', function() {
it('toggles replies', function() {
const onChangeCollapsed = sinon.stub();
const element = util.createDirective(document, 'annotationThread', {
thread: {
id: '123',
annotation: { id: '123' },
children: [],
collapsed: true,
},
onChangeCollapsed: {
args: ['id', 'collapsed'],
callback: onChangeCollapsed,
},
});
element.ctrl.toggleCollapsed();
assert.calledWith(onChangeCollapsed, '123', false);
});
});
describe('#showThreadAndReplies', function() {
it('reveals all parents and replies', function() {
const thread = {
id: '123',
annotation: { id: '123' },
children: [
{
id: 'child-id',
annotation: { id: 'child-id' },
children: [],
},
],
parent: {
id: 'parent-id',
annotation: { id: 'parent-id' },
},
};
const element = util.createDirective(document, 'annotationThread', {
thread: thread,
});
element.ctrl.showThreadAndReplies();
assert.calledWith(fakeStore.setForceVisible, thread.parent.id, true);
assert.calledWith(fakeStore.setForceVisible, thread.id, true);
assert.calledWith(fakeStore.setForceVisible, thread.children[0].id, true);
assert.calledWith(fakeStore.setCollapsed, thread.parent.id, false);
});
});
it('renders the moderation banner', function() {
const ann = fixtures.moderatedAnnotation({ flagCount: 1 });
const thread = {
annotation: ann,
id: '123',
parent: null,
children: [],
};
const element = util.createDirective(document, 'annotationThread', {
thread: thread,
});
assert.ok(element[0].querySelector('moderation-banner'));
});
it('does not render the annotation or moderation banner if there is no annotation', function() {
const thread = {
annotation: null,
id: '123',
parent: null,
children: [],
};
const element = util.createDirective(document, 'annotationThread', {
thread: thread,
});
assert.notOk(element[0].querySelector('moderation-banner'));
assert.notOk(element[0].querySelector('annotation'));
});
});
...@@ -126,7 +126,6 @@ import TopBar from './components/top-bar'; ...@@ -126,7 +126,6 @@ import TopBar from './components/top-bar';
// Remaining UI components that are still built with Angular. // Remaining UI components that are still built with Angular.
import annotationThread from './components/annotation-thread';
import annotationViewerContent from './components/annotation-viewer-content'; import annotationViewerContent from './components/annotation-viewer-content';
import hypothesisApp from './components/hypothesis-app'; import hypothesisApp from './components/hypothesis-app';
import sidebarContent from './components/sidebar-content'; import sidebarContent from './components/sidebar-content';
...@@ -239,7 +238,6 @@ function startAngularApp(config) { ...@@ -239,7 +238,6 @@ function startAngularApp(config) {
// UI components // UI components
.component('annotation', wrapComponent(Annotation)) .component('annotation', wrapComponent(Annotation))
.component('annotationThread', annotationThread)
.component('annotationViewerContent', annotationViewerContent) .component('annotationViewerContent', annotationViewerContent)
.component('helpPanel', wrapComponent(HelpPanel)) .component('helpPanel', wrapComponent(HelpPanel))
.component('loginPromptPanel', wrapComponent(LoginPromptPanel)) .component('loginPromptPanel', wrapComponent(LoginPromptPanel))
......
<div ng-class="vm.threadClasses()">
<div class="annotation-thread__thread-edge" ng-if="!vm.isTopLevelThread()">
<a href=""
ng-class="vm.threadToggleClasses()"
title="{{vm.thread.collapsed && 'Expand' || 'Collapse'}}"
ng-click="vm.toggleCollapsed()">
<svg-icon name="'caret-right'" ng-if="vm.thread.collapsed"></svg-icon>
<svg-icon name="'expand-menu'" ng-if="!vm.thread.collapsed"></svg-icon>
</a>
<div class="annotation-thread__thread-line"></div>
</div>
<div class="annotation-thread__content">
<moderation-banner
annotation="vm.thread.annotation"
ng-if="vm.thread.annotation">
</moderation-banner>
<annotation ng-if="vm.thread.annotation && vm.thread.visible"
annotation="vm.thread.annotation"
reply-count="vm.thread.replyCount"
on-reply-count-click="vm.toggleCollapsed()"
show-document-info="vm.showDocumentInfo"
thread-is-collapsed="vm.thread.collapsed">
</annotation>
<div ng-if="!vm.thread.annotation" class="thread-deleted">
<p><em>Message not available.</em></p>
</div>
<div ng-if="vm.hiddenCount() > 0">
<a class="small"
href=""
ng-click="vm.showThreadAndReplies()"
ng-pluralize
count="vm.hiddenCount()"
when="{'0': '',
one: 'View one more in conversation',
other: 'View {} more in conversation'}"
></a>
</div>
<!-- Replies -->
<ul ng-show="!vm.thread.collapsed">
<li ng-repeat="child in vm.thread.children track by child.id"
ng-show="vm.shouldShowReply(child)">
<annotation-thread
show-document-info="false"
thread="child"
on-change-collapsed="vm.onChangeCollapsed({id:id, collapsed:collapsed})"
on-force-visible="vm.onForceVisible(thread)">
</annotation-thread>
</li>
</ul>
</div>
</div>
@use "../../variables" as var;
.annotation-thread {
display: flex;
flex-direction: row;
}
// Direct or nested reply to an annotation
.annotation-thread--reply {
// Left margin is set so that left edge of collapse toggle arrow
// for the reply is aligned with the left edge of the parent annotation's
// content.
margin-left: -5px;
}
// Top-level reply to an annotation
.annotation-thread--top-reply {
padding-top: 5px;
padding-bottom: 5px;
}
li:first-child .annotation-thread--top-reply {
// Gap between baseline of 'Hide/Show Replies' for annotation and top
// of first reply should be ~15px
margin-top: 5px;
}
// Container for the toggle arrow and dashed line at the left edge of replies.
.annotation-thread__thread-edge {
display: flex;
flex-direction: column;
width: 8px;
margin-right: 13px;
}
// The dashed line at the left edge of replies
.annotation-thread__thread-line {
border-right: 1px dashed var.$grey-3;
flex-grow: 1;
}
.annotation-thread__content {
flex-grow: 1;
// Prevent annotation content from overflowing the container
max-width: 100%;
}
// Darken expand/collapse toggle when an annotation is hovered. This is only
// when the annotation itself is hovered, not the replies.
.annotation-thread__collapse-toggle:hover,
.annotation-thread__collapse-toggle.is-hovered {
color: var.$grey-7;
}
// Toggle arrow which expands and collapses threads.
// This is aligned so that it appears above a dashed line which appears
// to the left of the threads.
.annotation-thread__collapse-toggle {
width: 10px;
color: var.$grey-4;
display: block;
text-align: center;
margin-left: 3px;
font-size: 15px;
line-height: 22px;
height: 100%;
&.is-open {
// When the thread is expanded, the top of the dashed line is should be
// aligned with the top of the privacy indicator ("Only me") if present
height: 24px;
}
}
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
@use './components/annotation-quote'; @use './components/annotation-quote';
@use './components/annotation-share-control'; @use './components/annotation-share-control';
@use './components/annotation-share-info'; @use './components/annotation-share-info';
@use './components/annotation-thread';
@use './components/annotation-user'; @use './components/annotation-user';
@use './components/autocomplete-list'; @use './components/autocomplete-list';
@use './components/button'; @use './components/button';
......
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