Commit 8e1dfb7a authored by Sean Hammond's avatar Sean Hammond Committed by GitHub

Merge pull request #350 from hypothesis/extract-annotation-header

Extract annotation header into a separate component
parents 563d5a5f e2228104
......@@ -130,6 +130,7 @@ module.exports = angular.module('h', [
// UI components
.component('annotation', require('./components/annotation'))
.component('annotationHeader', require('./components/annotation-header'))
.component('annotationShareDialog', require('./components/annotation-share-dialog'))
.component('annotationThread', require('./components/annotation-thread'))
.component('annotationViewerContent', require('./components/annotation-viewer-content'))
......
'use strict';
var annotationMetadata = require('../annotation-metadata');
var memoize = require('../util/memoize');
var persona = require('../filter/persona');
// @ngInject
function AnnotationHeaderController(groups, settings, serviceUrl) {
var self = this;
this.user = function () {
return self.annotation.user;
};
this.username = function () {
return persona.username(self.annotation.user);
};
this.isThirdPartyUser = function () {
return persona.isThirdPartyUser(self.annotation.user, settings.authDomain);
};
this.serviceUrl = serviceUrl;
this.group = function () {
return groups.get(self.annotation.group);
};
var documentMeta = memoize(annotationMetadata.domainAndTitle);
this.documentMeta = function () {
return documentMeta(self.annotation);
};
this.updated = function () {
return self.annotation.updated;
};
this.htmlLink = function () {
if (self.annotation.links && self.annotation.links.html) {
return self.annotation.links.html;
}
return '';
};
}
/**
* Header component for an annotation card.
*
* Header which displays the username, last update timestamp and other key
* metadata about an annotation.
*/
module.exports = {
controller: AnnotationHeaderController,
controllerAs: 'vm',
bindings: {
/**
* The saved annotation
*/
annotation: '<',
/**
* True if the annotation is private or will become private when the user
* saves their changes.
*/
isPrivate: '<',
/** True if the user is currently editing the annotation. */
isEditing: '<',
/**
* True if the annotation is a highlight.
* FIXME: This should determined in AnnotationHeaderController
*/
isHighlight: '<',
onReplyCountClick: '&',
replyCount: '<',
/** True if document metadata should be shown. */
showDocumentInfo: '<',
},
template: require('../templates/annotation-header.html'),
};
......@@ -4,7 +4,6 @@
var annotationMetadata = require('../annotation-metadata');
var events = require('../events');
var memoize = require('../util/memoize');
var persona = require('../filter/persona');
var isNew = annotationMetadata.isNew;
......@@ -90,8 +89,6 @@ function AnnotationController(
// The remaining properties on vm are read-only properties for the
// templates.
vm.serviceUrl = serviceUrl;
/** Determines whether controls to expand/collapse the annotation body
* are displayed adjacent to the tags field.
*/
......@@ -458,10 +455,6 @@ function AnnotationController(
return vm.annotation.$orphan;
};
vm.updated = function() {
return vm.annotation.updated;
};
vm.user = function() {
return vm.annotation.user;
};
......@@ -470,10 +463,6 @@ function AnnotationController(
return persona.isThirdPartyUser(vm.annotation.user, settings.authDomain);
};
vm.username = function() {
return persona.username(vm.annotation.user);
};
vm.isDeleted = function () {
return streamer.hasPendingDeletion(vm.annotation.id);
};
......@@ -497,15 +486,13 @@ function AnnotationController(
return isReply(vm.annotation);
};
vm.links = function () {
vm.incontextLink = function () {
if (vm.annotation.links) {
return {incontext: vm.annotation.links.incontext ||
vm.annotation.links.html ||
'',
html: vm.annotation.links.html};
} else {
return {incontext: '', html: ''};
return vm.annotation.links.incontext ||
vm.annotation.links.html ||
'';
}
return '';
};
/**
......@@ -552,11 +539,6 @@ function AnnotationController(
};
};
var documentMeta = memoize(annotationMetadata.domainAndTitle);
vm.documentMeta = function () {
return documentMeta(vm.annotation);
};
init();
}
......
'use strict';
var angular = require('angular');
var proxyquire = require('proxyquire');
var fixtures = require('../../test/annotation-fixtures');
var fakeDocumentMeta = {
domain: 'docs.io',
titleLink: 'http://docs.io/doc.html',
titleText: 'Dummy title',
};
describe('annotationHeader', function () {
var $componentController;
var fakeGroups;
var fakeSettings;
var fakeServiceUrl;
before(function () {
var annotationHeader = proxyquire('../annotation-header', {
'../annotation-metadata': {
domainAndTitle: function (ann) { // eslint-disable-line no-unused-vars
return fakeDocumentMeta;
},
},
});
angular.module('app', [])
.component('annotationHeader', annotationHeader);
});
beforeEach(function () {
angular.mock.module('app', {
groups: fakeGroups,
settings: fakeSettings,
serviceUrl: fakeServiceUrl,
});
angular.mock.inject(function (_$componentController_) {
$componentController = _$componentController_;
});
});
describe('controller', function () {
describe('#htmlLink()', function () {
it('returns the HTML link when available', function () {
var ann = fixtures.defaultAnnotation();
ann.links = { html: 'https://annotation.service/123' };
var ctrl = $componentController('annotationHeader', {}, {
annotation: ann,
});
assert.equal(ctrl.htmlLink(), ann.links.html);
});
it('returns an empty string when no HTML link is available', function () {
var ann = fixtures.defaultAnnotation();
ann.links = {};
var ctrl = $componentController('annotationHeader', {}, {
annotation: ann,
});
assert.equal(ctrl.htmlLink(), '');
});
});
describe('#documentMeta()', function () {
it('returns the domain, title link and text for the annotation', function () {
var ann = fixtures.defaultAnnotation();
var ctrl = $componentController('annotationHeader', {}, {
annotation: ann,
});
assert.deepEqual(ctrl.documentMeta(), fakeDocumentMeta);
});
});
});
});
......@@ -10,12 +10,6 @@ var util = require('../../directive/test/util');
var inject = angular.mock.inject;
var fakeDocumentMeta = {
domain: 'docs.io',
titleLink: 'http://docs.io/doc.html',
titleText: 'Dummy title',
};
/**
* Returns the annotation directive with helpers stubbed out.
*/
......@@ -27,11 +21,6 @@ function annotationComponent() {
'../filter/persona': {
username: noop,
},
'../annotation-metadata': {
domainAndTitle: function (annot) { // eslint-disable-line no-unused-vars
return fakeDocumentMeta;
},
},
});
}
......@@ -716,14 +705,6 @@ describe('annotation', function() {
});
});
describe('#documentMeta()', function () {
it('returns the domain, title link and text for the annotation', function () {
var annot = fixtures.defaultAnnotation();
var controller = createDirective(annot).controller;
assert.deepEqual(controller.documentMeta(), fakeDocumentMeta);
});
});
describe('#isDeleted', function () {
it('returns true if the annotation has been marked as deleted', function () {
var controller = createDirective().controller;
......@@ -965,12 +946,11 @@ describe('annotation', function() {
it('uses the in-context links when available', function () {
var annotation = Object.assign({}, fixtures.defaultAnnotation(), {
links: {
html: 'https://test.hypothes.is/a/deadbeef',
incontext: 'https://hpt.is/deadbeef',
},
});
var controller = createDirective(annotation).controller;
assert.equal(controller.links().incontext, annotation.links.incontext);
assert.equal(controller.incontextLink(), annotation.links.incontext);
});
it('falls back to the HTML link when in-context links are missing', function () {
......@@ -980,30 +960,13 @@ describe('annotation', function() {
},
});
var controller = createDirective(annotation).controller;
assert.equal(controller.links().html, annotation.links.html);
});
it('uses the HTML link when available', function () {
var annotation = Object.assign({}, fixtures.defaultAnnotation(), {
links: {
html: 'https://test.hypothes.is/a/deadbeef',
incontext: 'https://hpt.is/deadbeef',
},
});
var controller = createDirective(annotation).controller;
assert.equal(controller.links().html, annotation.links.html);
assert.equal(controller.incontextLink(), annotation.links.html);
});
it('in-context link is blank when unknown', function () {
var annotation = fixtures.defaultAnnotation();
var controller = createDirective(annotation).controller;
assert.equal(controller.links().incontext, '');
});
it('HTML is blank when unknown', function () {
var annotation = fixtures.defaultAnnotation();
var controller = createDirective(annotation).controller;
assert.equal(controller.links().html, '');
assert.equal(controller.incontextLink(), '');
});
});
......
<header class="annotation-header">
<!-- User -->
<span ng-if="vm.user()">
<a class="annotation-header__user"
target="_blank"
ng-if="!vm.isThirdPartyUser()"
ng-href="{{vm.serviceUrl('user',{user:vm.user()})}}"
>{{vm.username()}}</a>
<span class="annotation-header__user"
ng-if="vm.isThirdPartyUser()"
>{{vm.username()}}</span>
<span class="annotation-collapsed-replies">
<a class="annotation-link" href=""
ng-click="vm.onReplyCountClick()"
ng-pluralize count="vm.replyCount"
when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a>
</span>
<br>
<span class="annotation-header__share-info">
<a class="annotation-header__group"
target="_blank" ng-if="vm.group() && vm.group().url" href="{{vm.group().url}}">
<i class="h-icon-group"></i><span class="annotation-header__group-name">{{vm.group().name}}</span>
</a>
<span ng-show="vm.isPrivate"
title="This annotation is visible only to you.">
<i class="h-icon-lock"></i><span class="annotation-header__group-name" ng-show="!vm.group().url">Only me</span>
</span>
<i class="h-icon-border-color" ng-show="vm.isHighlight && !vm.isEditing" title="This is a highlight. Click 'edit' to add a note or tag."></i>
<span ng-if="::vm.showDocumentInfo">
<span class="annotation-citation" ng-if="vm.documentMeta().titleLink">
on "<a ng-href="{{vm.documentMeta().titleLink}}">{{vm.documentMeta().titleText}}</a>"
</span>
<span class="annotation-citation" ng-if="!vm.documentMeta().titleLink">
on "{{vm.documentMeta().titleText}}"
</span>
<span class="annotation-citation-domain"
ng-if="vm.documentMeta().domain">({{vm.documentMeta().domain}})</span>
</span>
</span>
</span>
<span class="u-flex-spacer"></span>
<timestamp
class-name="'annotation-header__timestamp'"
timestamp="vm.updated()"
href="vm.htmlLink()"
ng-if="!vm.editing() && vm.updated()"></timestamp>
</header>
......@@ -3,55 +3,15 @@
</header>
<div ng-keydown="vm.onKeydown($event)" ng-if="vm.user()">
<header class="annotation-header">
<!-- User -->
<span ng-if="vm.user()">
<a class="annotation-header__user"
target="_blank"
ng-if="!vm.isThirdPartyUser()"
ng-href="{{vm.serviceUrl('user',{user:vm.user()})}}"
>{{vm.username()}}</a>
<span class="annotation-header__user"
ng-if="vm.isThirdPartyUser()"
>{{vm.username()}}</span>
<span class="annotation-collapsed-replies">
<a class="annotation-link" href=""
ng-click="vm.onReplyCountClick()"
ng-pluralize count="vm.replyCount"
when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a>
</span>
<br>
<span class="annotation-header__share-info">
<a class="annotation-header__group"
target="_blank" ng-if="vm.group() && vm.group().url" href="{{vm.group().url}}">
<i class="h-icon-group"></i><span class="annotation-header__group-name">{{vm.group().name}}</span>
</a>
<span ng-show="vm.state().isPrivate"
title="This annotation is visible only to you.">
<i class="h-icon-lock"></i><span class="annotation-header__group-name" ng-show="!vm.group().url">Only me</span>
</span>
<i class="h-icon-border-color" ng-show="vm.isHighlight() && !vm.editing()" title="This is a highlight. Click 'edit' to add a note or tag."></i>
<span ng-if="::vm.showDocumentInfo">
<span class="annotation-citation" ng-if="vm.documentMeta().titleLink">
on "<a ng-href="{{vm.documentMeta().titleLink}}">{{vm.documentMeta().titleText}}</a>"
</span>
<span class="annotation-citation" ng-if="!vm.documentMeta().titleLink">
on "{{vm.documentMeta().titleText}}"
</span>
<span class="annotation-citation-domain"
ng-if="vm.documentMeta().domain">({{vm.documentMeta().domain}})</span>
</span>
</span>
</span>
<span class="u-flex-spacer"></span>
<timestamp
class-name="'annotation-header__timestamp'"
timestamp="vm.updated()"
href="vm.links().html"
ng-if="!vm.editing() && vm.updated()"></timestamp>
</header>
<annotation-header annotation="vm.annotation"
is-editing="vm.editing()"
is-highlight="vm.isHighlight()"
is-private="vm.state().isPrivate"
on-reply-count-click="vm.onReplyCountClick()"
reply-count="vm.replyCount"
show-document-info="vm.showDocumentInfo">
</annotation-header>
<!-- Excerpts -->
<section class="annotation-quote-list"
......@@ -182,7 +142,7 @@
</button>
<annotation-share-dialog
group="vm.group()"
uri="vm.links().incontext"
uri="vm.incontextLink()"
is-private="vm.state().isPrivate"
is-open="vm.showShareDialog"
on-close="vm.showShareDialog = false">
......
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