Commit a8506c21 authored by Sean Hammond's avatar Sean Hammond

Re-order methods in AnnotationController

Private functions first, then public methods, and each section in alphabetical
order. This also means that all `on*()` event listener functions are together
(except for vm.onKeydown()).

Also always use the `function foo() {}` form instead of mixing it with
`var foo = function() {}`.
parent fa596904
...@@ -290,32 +290,6 @@ function AnnotationController( ...@@ -290,32 +290,6 @@ function AnnotationController(
vm.render(); vm.render();
} }
/**
* @ngdoc method
* @name annotation.AnnotationController#isHighlight.
* @returns {boolean} true if the annotation is a highlight, false otherwise
*/
vm.isHighlight = function() {
if (newlyCreatedByHighlightButton) {
return true;
} else if (!model.id) {
// If an annotation has no model.id (i.e. it has not been saved to the
// server yet) and newlyCreatedByHighlightButton is false, then it must
// be an annotation not a highlight (even though it may not have any
// text or tags yet).
return false;
} else {
// Once an annotation has been saved to the server there's no longer a
// simple property that says whether it's a highlight or not. For
// example there's no model.highlight: true. Instead a highlight is
// defined as an annotation that isn't a page note or a reply and that
// has no text or tags.
var isPageNote = (model.target || []).length === 0;
var isReply = (model.references || []).length !== 0;
return (!isPageNote && !isReply && !vm.hasContent());
}
};
/** Save this annotation if it's a new highlight. /** Save this annotation if it's a new highlight.
* *
* The highlight will be saved to the server if the user is logged in, * The highlight will be saved to the server if the user is logged in,
...@@ -350,102 +324,123 @@ function AnnotationController( ...@@ -350,102 +324,123 @@ function AnnotationController(
} }
/** /**
* @ngdoc method * Create or update the existing draft for this annotation using
* @name annotation.AnnotationController#editing. * the text and tags from the domain model in `draft`.
* @returns {boolean} `true` if this annotation is currently being edited
* (i.e. the annotation editor form should be open), `false` otherwise.
*/ */
vm.editing = function() { function updateDraft(draft) {
if (vm.action === 'create' || vm.action === 'edit') { // Drafts only preserve the text, tags and permissions of the annotation
return true; // (i.e. only the bits that the user can edit), changes to other
} else { // properties are not preserved.
return false; var changes = {};
if (draft.text) {
changes.text = draft.text;
}
if (draft.tags) {
changes.tags = draft.tags;
}
if (draft.permissions) {
changes.permissions = draft.permissions;
}
drafts.update(model, changes);
} }
};
/** // We use `var foo = function() {...}` here instead of `function foo() {...}`
* @ngdoc method // because updateTimestamp gets redefined later on.
* @name annotation.AnnotationController#group. function updateTimestamp(repeat) {
* @returns {Object} The full group object associated with the annotation. repeat = repeat || false;
*/
vm.group = function() {
return groups.get(model.group);
};
// Save on Meta + Enter or Ctrl + Enter. // New (not yet saved to the server) annotations don't have any .updated
vm.onKeydown = function(event) { // yet, so we can't update their timestamp.
if (event.keyCode === 13 && (event.metaKey || event.ctrlKey)) { if (!model.updated) {
event.preventDefault(); return;
vm.save(); }
vm.timestamp = time.toFuzzyString(model.updated);
if (!repeat) {
return;
}
var fuzzyUpdate = time.nextFuzzyUpdate(model.updated);
var nextUpdate = (1000 * fuzzyUpdate) + 500;
$timeout(function() {
updateTimestamp(true);
$scope.$digest();
}, nextUpdate, false);
} }
};
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#tagsAutoComplete. * @name annotation.AnnotationController#authorize
* @returns {Promise} immediately resolved to {string[]} - * @param {string} action The action to authorize.
* the tags to show in autocomplete. * @returns {boolean} True if the action is authorized for the current user.
* @description Checks whether the current user can perform an action on
* the annotation.
*/ */
vm.tagsAutoComplete = function(query) { vm.authorize = function(action) {
return $q.when(tags.filter(query)); // 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
// performance bottleneck and we would need to get the id token into the
// session, which we should probably do anyway (and move to opaque bearer
// tokens for the access token).
return permissions.permits(action, model, session.state.userid);
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#isPrivate * @name annotation.AnnotationController#delete
* @returns {boolean} True if the annotation is private to the current user. * @description Deletes the annotation.
*/ */
vm.isPrivate = function() { vm['delete'] = function() {
return permissions.isPrivate(vm.annotation.permissions, model.user); return $timeout(function() { // Don't use confirm inside the digest cycle.
var msg = 'Are you sure you want to delete this annotation?';
if ($window.confirm(msg)) {
var onRejected = function(reason) {
flash.error(
errorMessage(reason), 'Deleting annotation failed');
};
$scope.$apply(function() {
annotationMapper.deleteAnnotation(model).then(
null, onRejected);
});
}
}, true);
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#isShared * @name annotation.AnnotationController#edit
* @returns {boolean} True if the annotation is shared (either with the * @description Switches the view to an editor.
* current group or with everyone).
*/ */
vm.isShared = function() { vm.edit = function() {
return permissions.isShared(vm.annotation.permissions, model.group); if (!drafts.get(model)) {
updateDraft(model);
}
vm.action = model.id ? 'edit' : 'create';
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#setPrivacy * @name annotation.AnnotationController#editing.
* * @returns {boolean} `true` if this annotation is currently being edited
* Set the privacy settings on the annotation to a predefined * (i.e. the annotation editor form should be open), `false` otherwise.
* level. The supported levels are 'private' which makes the annotation
* visible only to its creator and 'shared' which makes the annotation
* visible to everyone in the group.
*
* The changes take effect when the annotation is saved
*/ */
vm.setPrivacy = function(privacy) { vm.editing = function() {
// When the user changes the privacy level of an annotation they're if (vm.action === 'create' || vm.action === 'edit') {
// creating or editing, we cache that and use the same privacy level the return true;
// next time they create an annotation. } else {
// But _don't_ cache it when they change the privacy level of a reply. return false;
if (!model.references) { // If the annotation is not a reply.
permissions.setDefault(privacy);
}
if (privacy === 'private') {
vm.annotation.permissions = permissions.private();
} else if (privacy === 'shared') {
vm.annotation.permissions = permissions.shared(model.group);
} }
}; };
vm.share = function(event) { /**
var $container = angular.element(event.currentTarget).parent(); * @ngdoc method
$container.addClass('open').find('input').focus().select(); * @name annotation.AnnotationController#group.
* @returns {Object} The full group object associated with the annotation.
// We have to stop propagation here otherwise this click event will */
// re-close the share dialog immediately. vm.group = function() {
event.stopPropagation(); return groups.get(model.group);
$document.one('click', function() {
$container.removeClass('open');
});
}; };
/** /**
...@@ -473,62 +468,117 @@ function AnnotationController( ...@@ -473,62 +468,117 @@ function AnnotationController(
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#authorize * @name annotation.AnnotationController#isHighlight.
* @param {string} action The action to authorize. * @returns {boolean} true if the annotation is a highlight, false otherwise
* @returns {boolean} True if the action is authorized for the current user.
* @description Checks whether the current user can perform an action on
* the annotation.
*/ */
vm.authorize = function(action) { vm.isHighlight = function() {
// TODO: this should use auth instead of permissions but we might need if (newlyCreatedByHighlightButton) {
// an auth cache or the JWT -> userid decoding might start to be a return true;
// performance bottleneck and we would need to get the id token into the } else if (!model.id) {
// session, which we should probably do anyway (and move to opaque bearer // If an annotation has no model.id (i.e. it has not been saved to the
// tokens for the access token). // server yet) and newlyCreatedByHighlightButton is false, then it must
return permissions.permits(action, model, session.state.userid); // be an annotation not a highlight (even though it may not have any
// text or tags yet).
return false;
} else {
// Once an annotation has been saved to the server there's no longer a
// simple property that says whether it's a highlight or not. For
// example there's no model.highlight: true. Instead a highlight is
// defined as an annotation that isn't a page note or a reply and that
// has no text or tags.
var isPageNote = (model.target || []).length === 0;
var isReply = (model.references || []).length !== 0;
return (!isPageNote && !isReply && !vm.hasContent());
}
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#delete * @name annotation.AnnotationController#isPrivate
* @description Deletes the annotation. * @returns {boolean} True if the annotation is private to the current user.
*/ */
vm['delete'] = function() { vm.isPrivate = function() {
return $timeout(function() { // Don't use confirm inside the digest cycle. return permissions.isPrivate(vm.annotation.permissions, model.user);
var msg = 'Are you sure you want to delete this annotation?';
if ($window.confirm(msg)) {
var onRejected = function(reason) {
flash.error(
errorMessage(reason), 'Deleting annotation failed');
}; };
$scope.$apply(function() {
annotationMapper.deleteAnnotation(model).then( /**
null, onRejected); * @ngdoc method
}); * @name annotation.AnnotationController#isShared
* @returns {boolean} True if the annotation is shared (either with the
* current group or with everyone).
*/
vm.isShared = function() {
return permissions.isShared(vm.annotation.permissions, 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();
} }
}, true);
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#edit * @name annotation.AnnotationController#render
* @description Switches the view to an editor. * @description Called to update the view when the model changes.
*/ */
vm.edit = function() { vm.render = function() {
if (!drafts.get(model)) { var draft = drafts.get(model);
updateDraft(model);
// Extend the view model with a copy of the domain model.
// Note that copy is used so that deep properties aren't shared.
vm.annotation = angular.extend({}, angular.copy(model));
// If we have unsaved changes to this annotation, apply them
// to the view model.
if (draft) {
angular.extend(vm.annotation, angular.copy(draft));
} }
vm.action = model.id ? 'edit' : 'create';
vm.annotationURI = new URL(
'/a/' + vm.annotation.id, vm.baseURI).href;
vm.document = extractDocumentMetadata(model);
// Form the tags for ngTagsInput.
vm.annotation.tags = (vm.annotation.tags || []).map(function(tag) {
return {text: tag};
});
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#view * @name annotation.AnnotationController#reply
* @description Switches the view to a viewer, closing the editor controls * @description
* if they are open. * Creates a new message in reply to this annotation.
*/ */
vm.view = function() { vm.reply = function() {
vm.action = 'view'; var id = model.id;
var references = model.references || [];
// TODO: Remove this check once we have server-side code to ensure that
// references is always an array of strings.
if (typeof references === 'string') {
references = [references];
}
references = references.concat(id);
var reply = annotationMapper.createAnnotation({
references: references,
uri: model.uri
});
reply.group = model.group;
if (session.state.userid) {
if (permissions.isShared(model.permissions, model.group)) {
reply.permissions = permissions.shared(reply.group);
} else {
reply.permissions = permissions.private();
}
}
}; };
/** /**
...@@ -546,27 +596,6 @@ function AnnotationController( ...@@ -546,27 +596,6 @@ function AnnotationController(
} }
}; };
/**
* Create or update the existing draft for this annotation using
* the text and tags from the domain model in `draft`.
*/
function updateDraft(draft) {
// Drafts only preserve the text, tags and permissions of the annotation
// (i.e. only the bits that the user can edit), changes to other
// properties are not preserved.
var changes = {};
if (draft.text) {
changes.text = draft.text;
}
if (draft.tags) {
changes.tags = draft.tags;
}
if (draft.permissions) {
changes.permissions = draft.permissions;
}
drafts.update(model, changes);
}
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#save * @name annotation.AnnotationController#save
...@@ -621,90 +650,61 @@ function AnnotationController( ...@@ -621,90 +650,61 @@ function AnnotationController(
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#reply * @name annotation.AnnotationController#setPrivacy
* @description *
* Creates a new message in reply to this annotation. * Set the privacy settings on the annotation to a predefined
* level. The supported levels are 'private' which makes the annotation
* visible only to its creator and 'shared' which makes the annotation
* visible to everyone in the group.
*
* The changes take effect when the annotation is saved
*/ */
vm.reply = function() { vm.setPrivacy = function(privacy) {
var id = model.id; // When the user changes the privacy level of an annotation they're
var references = model.references || []; // creating or editing, we cache that and use the same privacy level the
// next time they create an annotation.
// TODO: Remove this check once we have server-side code to ensure that // But _don't_ cache it when they change the privacy level of a reply.
// references is always an array of strings. if (!model.references) { // If the annotation is not a reply.
if (typeof references === 'string') { permissions.setDefault(privacy);
references = [references]; }
if (privacy === 'private') {
vm.annotation.permissions = permissions.private();
} else if (privacy === 'shared') {
vm.annotation.permissions = permissions.shared(model.group);
} }
};
references = references.concat(id); vm.share = function(event) {
var $container = angular.element(event.currentTarget).parent();
$container.addClass('open').find('input').focus().select();
var reply = annotationMapper.createAnnotation({ // We have to stop propagation here otherwise this click event will
references: references, // re-close the share dialog immediately.
uri: model.uri event.stopPropagation();
});
reply.group = model.group;
if (session.state.userid) { $document.one('click', function() {
if (permissions.isShared(model.permissions, model.group)) { $container.removeClass('open');
reply.permissions = permissions.shared(reply.group); });
} else {
reply.permissions = permissions.private();
}
}
}; };
/** /**
* @ngdoc method * @ngdoc method
* @name annotation.AnnotationController#render * @name annotation.AnnotationController#tagsAutoComplete.
* @description Called to update the view when the model changes. * @returns {Promise} immediately resolved to {string[]} -
* the tags to show in autocomplete.
*/ */
vm.render = function() { vm.tagsAutoComplete = function(query) {
var draft = drafts.get(model); return $q.when(tags.filter(query));
// Extend the view model with a copy of the domain model.
// Note that copy is used so that deep properties aren't shared.
vm.annotation = angular.extend({}, angular.copy(model));
// If we have unsaved changes to this annotation, apply them
// to the view model.
if (draft) {
angular.extend(vm.annotation, angular.copy(draft));
}
vm.annotationURI = new URL(
'/a/' + vm.annotation.id, vm.baseURI).href;
vm.document = extractDocumentMetadata(model);
// Form the tags for ngTagsInput.
vm.annotation.tags = (vm.annotation.tags || []).map(function(tag) {
return {text: tag};
});
}; };
// We use `var foo = function() {...}` here instead of `function foo() {...}` /**
// because updateTimestamp gets redefined later on. * @ngdoc method
var updateTimestamp = function(repeat) { * @name annotation.AnnotationController#view
repeat = repeat || false; * @description Switches the view to a viewer, closing the editor controls
* if they are open.
// New (not yet saved to the server) annotations don't have any .updated */
// yet, so we can't update their timestamp. vm.view = function() {
if (!model.updated) { vm.action = 'view';
return;
}
vm.timestamp = time.toFuzzyString(model.updated);
if (!repeat) {
return;
}
var fuzzyUpdate = time.nextFuzzyUpdate(model.updated);
var nextUpdate = (1000 * fuzzyUpdate) + 500;
$timeout(function() {
updateTimestamp(true);
$scope.$digest();
}, nextUpdate, false);
}; };
init(); init();
......
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