Commit d16f39bb authored by Robert Knight's avatar Robert Knight

Merge pull request #2761 from hypothesis/2728-refactor-annotation-controller

Refactor annotation controller, part 1
parents e13a0492 e53d499c
...@@ -152,7 +152,8 @@ function errorMessage(reason) { ...@@ -152,7 +152,8 @@ function errorMessage(reason) {
// @ngInject // @ngInject
function AnnotationController( function AnnotationController(
$document, $q, $rootScope, $scope, $timeout, $window, annotationUI, $document, $q, $rootScope, $scope, $timeout, $window, annotationUI,
annotationMapper, drafts, flash, groups, permissions, session, tags, time) { annotationMapper, drafts, flash, features, groups, permissions, session,
tags, time) {
var vm = this; var vm = this;
...@@ -162,9 +163,12 @@ function AnnotationController( ...@@ -162,9 +163,12 @@ function AnnotationController(
vm.action = 'view'; vm.action = 'view';
vm.document = null; vm.document = null;
vm.editing = false; // Give the template access to the feature flags.
vm.isSidebar = false; vm.feature = features.flagEnabled;
vm.preview = 'no'; // Copy isSidebar from $scope onto vm for consistency (we want this
// directive's templates to always access variables from vm rather than
// directly from scope).
vm.isSidebar = $scope.isSidebar;
vm.timestamp = null; vm.timestamp = null;
/** The domain model, contains the currently saved version of the annotation /** The domain model, contains the currently saved version of the annotation
...@@ -184,6 +188,20 @@ function AnnotationController( ...@@ -184,6 +188,20 @@ function AnnotationController(
var highlight = model.$highlight; var highlight = model.$highlight;
/**
* @ngdoc method
* @name annotation.AnnotationController#editing.
* @returns {boolean} `true` if this annotation is currently being edited
* (i.e. the annotation editor form should be open), `false` otherwise.
*/
vm.editing = function() {
if (vm.action === 'create' || vm.action === 'edit') {
return true;
} else {
return false;
}
};
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#group. * @name annotation.AnnotationController#group.
...@@ -193,6 +211,14 @@ function AnnotationController( ...@@ -193,6 +211,14 @@ function AnnotationController(
return groups.get(model.group); return groups.get(model.group);
}; };
// Save on Meta + Enter or Ctrl + Enter.
vm.onKeydown = function(event) {
if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
vm.save();
}
};
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#tagsAutoComplete. * @name annotation.AnnotationController#tagsAutoComplete.
...@@ -260,6 +286,19 @@ function AnnotationController( ...@@ -260,6 +286,19 @@ function AnnotationController(
} }
}; };
vm.share = function(event) {
var $container = angular.element(event.currentTarget).parent();
$container.addClass('open').find('input').focus().select();
// We have to stop propagation here otherwise this click event will
// re-close the share dialog immediately.
event.stopPropagation();
$document.one('click', function() {
$container.removeClass('open');
});
};
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotaitonController#hasContent * @name annotation.AnnotaitonController#hasContent
...@@ -292,9 +331,6 @@ function AnnotationController( ...@@ -292,9 +331,6 @@ function AnnotationController(
* the annotation. * the annotation.
*/ */
vm.authorize = function(action) { vm.authorize = function(action) {
if (model === null) {
return false;
}
// TODO: this should use auth instead of permissions but we might need // TODO: this should use auth instead of permissions but we might need
// an auth cache or the JWT -> userid decoding might start to be a // an auth cache or the JWT -> userid decoding might start to be a
// performance bottleneck and we would need to get the id token into the // performance bottleneck and we would need to get the id token into the
...@@ -334,8 +370,6 @@ function AnnotationController( ...@@ -334,8 +370,6 @@ function AnnotationController(
updateDraft(model); updateDraft(model);
} }
vm.action = model.id ? 'edit' : 'create'; vm.action = model.id ? 'edit' : 'create';
vm.editing = true;
vm.preview = 'no';
}; };
/** /**
...@@ -345,7 +379,6 @@ function AnnotationController( ...@@ -345,7 +379,6 @@ function AnnotationController(
* if they are open. * if they are open.
*/ */
vm.view = function() { vm.view = function() {
vm.editing = false;
vm.action = 'view'; vm.action = 'view';
}; };
...@@ -582,7 +615,7 @@ function AnnotationController( ...@@ -582,7 +615,7 @@ function AnnotationController(
// the drafts service. They will be restored when this annotation is // the drafts service. They will be restored when this annotation is
// next loaded. // next loaded.
$scope.$on(events.GROUP_FOCUSED, function() { $scope.$on(events.GROUP_FOCUSED, function() {
if (!vm.editing) { if (!vm.editing()) {
return; return;
} }
...@@ -622,47 +655,19 @@ function AnnotationController( ...@@ -622,47 +655,19 @@ function AnnotationController(
* *
*/ */
// @ngInject // @ngInject
function annotation($document, features) { function annotation($document) {
function linkFn(scope, elem, attrs, controllers) { function linkFn(scope, elem, attrs, controllers) {
var ctrl = controllers[0]; var ctrl = controllers[0];
var thread = controllers[1]; var thread = controllers[1];
var threadFilter = controllers[2]; var threadFilter = controllers[2];
var counter = controllers[3]; var counter = controllers[3];
attrs.$observe('isSidebar', function(value) { elem.on('keydown', ctrl.onKeydown);
if (value && value !== 'false') {
ctrl.isSidebar = true;
} else {
ctrl.isSidebar = false;
}
});
// Save on Meta + Enter or Ctrl + Enter.
elem.on('keydown', function(event) {
if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
scope.$evalAsync(function() {
ctrl.save();
});
}
});
// Give template access to feature flags.
scope.feature = features.flagEnabled;
scope.share = function(event) {
var $container = angular.element(event.currentTarget).parent();
$container.addClass('open').find('input').focus().select();
// We have to stop propagation here otherwise this click event will
// re-close the share dialog immediately.
event.stopPropagation();
$document.one('click', function() {
$container.removeClass('open');
});
};
// FIXME: Replace this counting code with something more sane, and
// something that doesn't involve so much untested logic in the link
// function (as opposed to unit-tested methods on the AnnotationController,
// for example).
// Keep track of edits going on in the thread. // Keep track of edits going on in the thread.
if (counter !== null) { if (counter !== null) {
// Expand the thread if descendants are editing. // Expand the thread if descendants are editing.
...@@ -711,7 +716,8 @@ function annotation($document, features) { ...@@ -711,7 +716,8 @@ function annotation($document, features) {
isLastReply: '=', isLastReply: '=',
replyCount: '@annotationReplyCount', replyCount: '@annotationReplyCount',
replyCountClick: '&annotationReplyCountClick', replyCountClick: '&annotationReplyCountClick',
showReplyCount: '@annotationShowReplyCount' showReplyCount: '@annotationShowReplyCount',
isSidebar: '='
}, },
templateUrl: 'annotation.html' templateUrl: 'annotation.html'
}; };
......
...@@ -358,6 +358,23 @@ describe('annotation', function() { ...@@ -358,6 +358,23 @@ describe('annotation', function() {
}); });
}); });
describe('AnnotationController.editing()', function() {
it('returns true if action is "create"', function() {
controller.action = 'create';
assert(controller.editing());
});
it('returns true if action is "edit"', function() {
controller.action = 'edit';
assert(controller.editing());
});
it('returns false if action is "view"', function() {
controller.action = 'view';
assert(!controller.editing());
});
});
describe('when the annotation is a highlight', function() { describe('when the annotation is a highlight', function() {
beforeEach(function() { beforeEach(function() {
annotation.$highlight = true; annotation.$highlight = true;
...@@ -922,7 +939,7 @@ describe('annotation', function() { ...@@ -922,7 +939,7 @@ describe('annotation', function() {
text: 'unsaved-text' text: 'unsaved-text'
}); });
createDirective(); createDirective();
assert.isTrue(controller.editing); assert.isTrue(controller.editing());
}); });
it('uses the text and tags from the draft if present', function() { it('uses the text and tags from the draft if present', function() {
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
title="This annotation is visible only to you."> 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> <i class="h-icon-lock"></i><span class="annotation-header__group-name" ng-show="!vm.group().url">Only me</span>
</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> <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 class="annotation-citation" <span class="annotation-citation"
ng-bind-html="vm.document | documentTitle" ng-bind-html="vm.document | documentTitle"
ng-if="!vm.isSidebar"> ng-if="!vm.isSidebar">
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<a class="annotation-timestamp" <a class="annotation-timestamp"
target="_blank" target="_blank"
title="{{vm.annotation.updated | moment:'LLLL'}}" title="{{vm.annotation.updated | moment:'LLLL'}}"
ng-if="!vm.editing && vm.annotation.updated" ng-if="!vm.editing() && vm.annotation.updated"
ng-href="{{vm.baseURI}}a/{{vm.annotation.id}}" ng-href="{{vm.baseURI}}a/{{vm.annotation.id}}"
>{{vm.timestamp}}</a> >{{vm.timestamp}}</a>
</header> </header>
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
<section class="annotation-quote-list" <section class="annotation-quote-list"
ng-repeat="target in vm.annotation.target track by $index" ng-repeat="target in vm.annotation.target track by $index"
ng-if="vm.hasQuotes()"> ng-if="vm.hasQuotes()">
<excerpt enabled="feature('truncate_annotations')"> <excerpt enabled="vm.feature('truncate_annotations')">
<blockquote class="annotation-quote" <blockquote class="annotation-quote"
ng-bind-html="selector.exact" ng-bind-html="selector.exact"
ng-repeat="selector in target.selector ng-repeat="selector in target.selector
...@@ -70,16 +70,16 @@ ...@@ -70,16 +70,16 @@
<!-- Body --> <!-- Body -->
<section name="text" class="annotation-body"> <section name="text" class="annotation-body">
<excerpt enabled="feature('truncate_annotations') && !vm.editing"> <excerpt enabled="vm.feature('truncate_annotations') && !vm.editing()">
<markdown ng-model="vm.annotation.text" <markdown ng-model="vm.annotation.text"
read-only="!vm.editing" read-only="!vm.editing()"
></markdown> ></markdown>
</excerpt> </excerpt>
</section> </section>
<!-- / Body --> <!-- / Body -->
<!-- Tags --> <!-- Tags -->
<div class="annotation-body form-field" ng-if="vm.editing"> <div class="annotation-body form-field" ng-if="vm.editing()">
<tags-input ng-model="vm.annotation.tags" <tags-input ng-model="vm.annotation.tags"
name="tags" name="tags"
class="tags" class="tags"
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
</div> </div>
<div class="annotation-body tags tags-read-only" <div class="annotation-body tags tags-read-only"
ng-if="vm.annotation.tags.length && !vm.editing"> ng-if="vm.annotation.tags.length && !vm.editing()">
<ul class="tag-list"> <ul class="tag-list">
<li class="tag-item" ng-repeat="tag in vm.annotation.tags"> <li class="tag-item" ng-repeat="tag in vm.annotation.tags">
<a href="/stream?q=tag:'{{tag.text|urlencode}}'" target="_blank">{{tag.text}}</a> <a href="/stream?q=tag:'{{tag.text|urlencode}}'" target="_blank">{{tag.text}}</a>
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
<!-- / Tags --> <!-- / Tags -->
<footer class="annotation-footer"> <footer class="annotation-footer">
<div class="annotation-form-actions" ng-if="vm.editing" ng-switch="vm.action"> <div class="annotation-form-actions" ng-if="vm.editing()" ng-switch="vm.action">
<button ng-switch-when="delete" <button ng-switch-when="delete"
ng-click="vm.save()" ng-click="vm.save()"
class="dropdown-menu-btn"><i class="h-icon-check btn-icon"></i> Delete</button> class="dropdown-menu-btn"><i class="h-icon-check btn-icon"></i> Delete</button>
...@@ -119,7 +119,7 @@ ...@@ -119,7 +119,7 @@
</div> </div>
<div class="annotation-section annotation-license" <div class="annotation-section annotation-license"
ng-show="vm.isShared() && vm.editing"> ng-show="vm.isShared() && vm.editing()">
<a href="http://creativecommons.org/publicdomain/zero/1.0/" <a href="http://creativecommons.org/publicdomain/zero/1.0/"
title="View more information about the Creative Commons Public Domain license" title="View more information about the Creative Commons Public Domain license"
target="_blank"> target="_blank">
...@@ -135,13 +135,13 @@ ...@@ -135,13 +135,13 @@
when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a> when="{'0': '', 'one': '1 reply', 'other': '{} replies'}"></a>
</div> </div>
<div class="annotation-actions" ng-if="!vm.editing && vm.annotation.id"> <div class="annotation-actions" ng-if="!vm.editing() && vm.annotation.id">
<button class="small btn btn-clean" <button class="small btn btn-clean"
ng-click="vm.reply()" ng-click="vm.reply()"
><i class="h-icon-reply btn-icon"></i> Reply</button> ><i class="h-icon-reply btn-icon"></i> Reply</button>
<span class="share-dialog-wrapper"> <span class="share-dialog-wrapper">
<button class="small btn btn-clean" <button class="small btn btn-clean"
ng-click="share($event)" ng-click="vm.share($event)"
><i class="h-icon-link btn-icon"></i> Link</button> ><i class="h-icon-link btn-icon"></i> Link</button>
<span class="share-dialog" ng-click="$event.stopPropagation()"> <span class="share-dialog" ng-click="$event.stopPropagation()">
<a target="_blank" <a target="_blank"
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
name="annotation" name="annotation"
annotation="vm.container.message" annotation="vm.container.message"
is-last-reply="$last" is-last-reply="$last"
is-sidebar="{{isSidebar}}" is-sidebar="isSidebar"
annotation-show-reply-count="{{vm.shouldShowNumReplies()}}" annotation-show-reply-count="{{vm.shouldShowNumReplies()}}"
annotation-reply-count="{{vm.numReplies()}}" annotation-reply-count="{{vm.numReplies()}}"
annotation-reply-count-click="vm.toggleCollapsed()" annotation-reply-count-click="vm.toggleCollapsed()"
......
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