Commit fa7c1a7f authored by Sean Hammond's avatar Sean Hammond Committed by GitHub

Merge pull request #349 from hypothesis/extract-annotation-action

Extract annotation action buttons into a separate component
parents 8e1dfb7a c3d85be7
...@@ -131,6 +131,7 @@ module.exports = angular.module('h', [ ...@@ -131,6 +131,7 @@ module.exports = angular.module('h', [
// UI components // UI components
.component('annotation', require('./components/annotation')) .component('annotation', require('./components/annotation'))
.component('annotationHeader', require('./components/annotation-header')) .component('annotationHeader', require('./components/annotation-header'))
.component('annotationActionButton', require('./components/annotation-action-button'))
.component('annotationShareDialog', require('./components/annotation-share-dialog')) .component('annotationShareDialog', require('./components/annotation-share-dialog'))
.component('annotationThread', require('./components/annotation-thread')) .component('annotationThread', require('./components/annotation-thread'))
.component('annotationViewerContent', require('./components/annotation-viewer-content')) .component('annotationViewerContent', require('./components/annotation-viewer-content'))
......
'use strict';
module.exports = {
controllerAs: 'vm',
bindings: {
icon: '<',
isDisabled: '<',
label: '<',
onClick: '&',
},
template: require('../templates/annotation-action-button.html'),
};
...@@ -24,6 +24,21 @@ function annotationComponent() { ...@@ -24,6 +24,21 @@ function annotationComponent() {
}); });
} }
/**
* Returns the controller for the action button with the given `label`.
*
* @param {Element} annotationEl - Annotation element
* @param {string} label - Button label
*/
function findActionButton(annotationEl, label) {
var btns = Array.from(annotationEl[0].querySelectorAll('annotation-action-button'));
var match = btns.find(function (btn) {
var ctrl = angular.element(btn).controller('annotationActionButton');
return ctrl.label === label;
});
return match ? angular.element(match).controller('annotationActionButton') : null;
}
describe('annotation', function() { describe('annotation', function() {
describe('updateModel()', function() { describe('updateModel()', function() {
var updateModel = require('../annotation').updateModel; var updateModel = require('../annotation').updateModel;
...@@ -105,10 +120,11 @@ describe('annotation', function() { ...@@ -105,10 +120,11 @@ describe('annotation', function() {
before(function() { before(function() {
angular.module('h', []) angular.module('h', [])
.component('annotation', annotationComponent()) .component('annotation', annotationComponent())
.component('annotationActionButton', {
bindings: require('../annotation-action-button').bindings,
})
.component('markdown', { .component('markdown', {
bindings: { bindings: require('../markdown').bindings,
customTextClass: '<?',
},
}); });
}); });
...@@ -983,25 +999,25 @@ describe('annotation', function() { ...@@ -983,25 +999,25 @@ describe('annotation', function() {
it('renders hidden annotations with a custom text class', function () { it('renders hidden annotations with a custom text class', function () {
var ann = fixtures.moderatedAnnotation({ hidden: true }); var ann = fixtures.moderatedAnnotation({ hidden: true });
var el = createDirective(ann).element; var el = createDirective(ann).element;
assert.deepEqual(el.find('markdown').controller('markdown'), { assert.match(el.find('markdown').controller('markdown'), sinon.match({
customTextClass: { customTextClass: {
'annotation-body is-hidden': true, 'annotation-body is-hidden': true,
}, },
}); }));
}); });
it('flags the annotation when the user clicks the "Flag" button', function () { it('flags the annotation when the user clicks the "Flag" button', function () {
fakeAnnotationMapper.flagAnnotation.returns(Promise.resolve()); fakeAnnotationMapper.flagAnnotation.returns(Promise.resolve());
var el = createDirective().element; var el = createDirective().element;
var flagBtn = el[0].querySelector('button[aria-label="Flag"]'); var flagBtn = findActionButton(el, 'Flag');
flagBtn.click(); flagBtn.onClick();
assert.called(fakeAnnotationMapper.flagAnnotation); assert.called(fakeAnnotationMapper.flagAnnotation);
}); });
it('highlights the "Flag" button if the annotation is flagged', function () { it('highlights the "Flag" button if the annotation is flagged', function () {
var ann = Object.assign(fixtures.defaultAnnotation(), { flagged: true }); var ann = Object.assign(fixtures.defaultAnnotation(), { flagged: true });
var el = createDirective(ann).element; var el = createDirective(ann).element;
var flaggedBtn = el[0].querySelector('button[aria-label="Annotation has been flagged"]'); var flaggedBtn = findActionButton(el, 'Annotation has been flagged');
assert.ok(flaggedBtn); assert.ok(flaggedBtn);
}); });
}); });
......
<button class="btn btn-clean annotation-action-btn"
ng-click="vm.onClick()"
ng-disabled="vm.isDisabled"
aria-label="{{ vm.label }}"
h-tooltip>
<i class="{{ vm.icon }} btn-icon"></i>
</button>
...@@ -109,37 +109,33 @@ ...@@ -109,37 +109,33 @@
<div class="annotation-actions" ng-if="!vm.isSaving && !vm.editing() && vm.id()"> <div class="annotation-actions" ng-if="!vm.isSaving && !vm.editing() && vm.id()">
<div ng-show="vm.isSaving">Saving…</div> <div ng-show="vm.isSaving">Saving…</div>
<button class="btn btn-clean annotation-action-btn" <annotation-action-button
icon="'h-icon-annotation-edit'"
is-disabled="vm.isDeleted()"
label="'Edit'"
ng-show="vm.authorize('update') && !vm.isSaving" ng-show="vm.authorize('update') && !vm.isSaving"
ng-click="vm.edit()" on-click="vm.edit()"
ng-disabled="vm.isDeleted()" ></annotation-action-button>
aria-label="Edit" <annotation-action-button
h-tooltip> icon="'h-icon-annotation-delete'"
<i class="h-icon-annotation-edit btn-icon "></i> is-disabled="vm.isDeleted()"
</button> label="'Delete'"
<button class="btn btn-clean annotation-action-btn"
ng-show="vm.authorize('delete')" ng-show="vm.authorize('delete')"
ng-click="vm.delete()" on-click="vm.delete()"
ng-disabled="vm.isDeleted()" ></annotation-action-button>
aria-label="Delete" <annotation-action-button
h-tooltip> icon="'h-icon-annotation-reply'"
<i class="h-icon-annotation-delete btn-icon "></i> is-disabled="vm.isDeleted()"
</button> label="'Reply'"
<button class="btn btn-clean annotation-action-btn" on-click="vm.reply()"
ng-click="vm.reply()" ></annotation-action-button>
ng-disabled="vm.isDeleted()"
aria-label="Reply"
h-tooltip>
<i class="h-icon-annotation-reply btn-icon "></i>
</button>
<span class="annotation-share-dialog-wrapper"> <span class="annotation-share-dialog-wrapper">
<button class="btn btn-clean annotation-action-btn" <annotation-action-button
ng-click="vm.showShareDialog = true" icon="'h-icon-annotation-share'"
ng-disabled="vm.isDeleted()" is-disabled="vm.isDeleted()"
aria-label="Share" label="'Share'"
h-tooltip> on-click="vm.showShareDialog = true"
<i class="h-icon-annotation-share btn-icon "></i> ></annotation-action-button>
</button>
<annotation-share-dialog <annotation-share-dialog
group="vm.group()" group="vm.group()"
uri="vm.incontextLink()" uri="vm.incontextLink()"
...@@ -149,21 +145,19 @@ ...@@ -149,21 +145,19 @@
</annotation-share-dialog> </annotation-share-dialog>
</span> </span>
<span ng-if="vm.canFlag()"> <span ng-if="vm.canFlag()">
<button class="btn btn-clean annotation-action-btn" <annotation-action-button
ng-if="!vm.isFlagged()" icon="'h-icon-annotation-flag'"
ng-click="vm.flag()" is-disabled="vm.isDeleted()"
ng-disabled="vm.isDeleted()" label="'Flag'"
aria-label="Flag" ng-if="!vm.isFlagged()"
h-tooltip> on-click="vm.flag()"
<i class="h-icon-annotation-flag btn-icon"></i> ></annotation-action-button>
</button> <annotation-action-button
<button class="btn btn-clean annotation-action-btn" icon="'h-icon-annotation-flag annotation--flagged'"
ng-if="vm.isFlagged()" is-disabled="vm.isDeleted()"
ng-disabled="vm.isDeleted()" label="'Annotation has been flagged'"
aria-label="Annotation has been flagged" ng-if="vm.isFlagged()"
h-tooltip> ></annotation-action-button>
<i class="h-icon-annotation-flag annotation--flagged btn-icon"></i>
</button>
</span> </span>
</div> </div>
</footer> </footer>
......
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