{"action":"created","comment":{"url":"https://api.github.com/repos/hypothesis/h/pulls/comments/46441118","id":46441118,"diff_hunk":"@@ -0,0 +1,701 @@\n+/* jshint node: true */\n+'use strict';\n+\n+var events = require('../events');\n+\n+/** Return truthy if the given annotation is valid, falsy otherwise.\n+ *\n+* A public annotation has to have some text and/or some tags to be valid,\n+* public annotations with no text or tags (i.e. public highlights) are not\n+* valid.\n+*\n+* Non-public annotations just need to have a target to be valid, non-public\n+* highlights are valid.\n+*\n+* @param {object} annotation The annotation to be validated\n+*\n+*/\n+function validate(annotation) {\n+  if (!angular.isObject(annotation)) {\n+    return;\n+  }\n+\n+  var permissions = annotation.permissions || {};\n+  var readPermissions = permissions.read || [];\n+  var targets = annotation.target || [];\n+\n+  if (annotation.tags && annotation.tags.length) {\n+    return annotation.tags.length;\n+  }\n+\n+  if (annotation.text && annotation.text.length) {\n+    return annotation.text.length;\n+  }\n+\n+  var worldReadable = false;\n+  if (readPermissions.indexOf('group:__world__') !== -1) {\n+    worldReadable = true;\n+  }\n+\n+  return (targets.length && !worldReadable);\n+}\n+\n+/** Return a human-readable error message for the given server error.\n+ *\n+ * @param {object} reason The error object from the server. Should have\n+ * `status` and, if `status` is not `0`, `statusText` and (optionally)\n+ * `data.reason` properties.\n+ *\n+ * @returns {string}\n+ */\n+function errorMessage(reason) {\n+  var message;\n+  if (reason.status === 0) {\n+    message = 'Service unreachable.';\n+  } else {\n+    message = reason.status + ' ' + reason.statusText;\n+    if (reason.data.reason) {\n+      message = message + ': ' + reason.data.reason;\n+    }\n+  }\n+  return message;\n+}\n+\n+/**\n+  * @ngdoc type\n+  * @name annotation.AnnotationController\n+  *\n+  * @property {Object} annotation The annotation view model.\n+  * @property {Object} document The document metadata view model.\n+  * @property {string} action One of 'view', 'edit', 'create' or 'delete'.\n+  * @property {string} preview If previewing an edit then 'yes', else 'no'.\n+  * @property {boolean} editing True if editing components are shown.\n+  * @property {boolean} isSidebar True if we are in the sidebar (not on the\n+  *                               stream page or an individual annotation page)\n+  *\n+  * @description\n+  *\n+  * `AnnotationController` provides an API for the annotation directive. It\n+  * manages the interaction between the domain and view models and uses the\n+  * {@link annotationMapper AnnotationMapper service} for persistence.\n+  */\n+// @ngInject\n+function AnnotationController(\n+  $document, $q, $rootScope, $scope, $timeout, $window, annotationUI,\n+  annotationMapper, drafts, flash, groups, permissions, session, tags, time) {\n+\n+  var vm = this;\n+\n+  /** The view model, contains user changes to the annotation that haven't\n+   * been saved to the server yet. */\n+  vm.annotation = {};\n+\n+  vm.action = 'view';\n+  vm.document = null;\n+  vm.editing = false;\n+  vm.isSidebar = false;\n+  vm.preview = 'no';\n+  vm.timestamp = null;\n+\n+  /** The domain model, contains the currently saved version of the annotation\n+   * from the server. */\n+  var model = $scope.annotationGet();\n+  if (!model.user) {\n+    model.user = session.state.userid;\n+  }\n+\n+  // Set the group of new annotations.\n+  if (!model.group) {\n+    model.group = groups.focused().id;\n+  }\n+\n+  // Set the permissions of new annotations.\n+  model.permissions = model.permissions || permissions['default'](model.group);\n+\n+  var highlight = model.$highlight;\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#group.\n+    * @returns {Object} The full group object associated with the annotation.\n+    */\n+  vm.group = function() {\n+    return groups.get(model.group);\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#tagsAutoComplete.\n+    * @returns {Promise} immediately resolved to {string[]} -\n+    * the tags to show in autocomplete.\n+    */\n+  vm.tagsAutoComplete = function(query) {\n+    return $q.when(tags.filter(query));\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#isHighlight.\n+    * @returns {boolean} True if the annotation is a highlight.\n+    */\n+  vm.isHighlight = function() {\n+    var targetLength = (model.target || []).length;\n+    var referencesLength = (model.references || []).length;\n+    var tagsLength = (model.tags || []).length;\n+    return (targetLength && !referencesLength && !(model.text || tagsLength));\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#isPrivate\n+    * @returns {boolean} True if the annotation is private to the current user.\n+    */\n+  vm.isPrivate = function() {\n+    return permissions.isPrivate(vm.annotation.permissions, model.user);\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#isShared\n+    * @returns {boolean} True if the annotation is shared (either with the\n+    * current group or with everyone).\n+    */\n+  vm.isShared = function() {\n+    return permissions.isShared(vm.annotation.permissions, model.group);\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#setPrivacy\n+    *\n+    * Set the privacy settings on the annotation to a predefined\n+    * level. The supported levels are 'private' which makes the annotation\n+    * visible only to its creator and 'shared' which makes the annotation\n+    * visible to everyone in the group.\n+    *\n+    * The changes take effect when the annotation is saved\n+    */\n+  vm.setPrivacy = function(privacy) {\n+    // When the user changes the privacy level of an annotation they're\n+    // creating or editing, we cache that and use the same privacy level the\n+    // next time they create an annotation.\n+    // But _don't_ cache it when they change the privacy level of a reply.\n+    if (!model.references) {  // If the annotation is not a reply.\n+      permissions.setDefault(privacy);\n+    }\n+    if (privacy === 'private') {\n+      vm.annotation.permissions = permissions.private();\n+    } else if (privacy === 'shared') {\n+      vm.annotation.permissions = permissions.shared(model.group);\n+    }\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotaitonController#hasContent\n+    * @returns {boolean} True if the currently edited annotation has\n+    *          content (ie. is not just a highlight)\n+    */\n+  vm.hasContent = function() {\n+    var textLength = (vm.annotation.text || '').length;\n+    var tagsLength = (vm.annotation.tags || []).length;\n+    return (textLength > 0 || tagsLength > 0);\n+  };\n+\n+  /**\n+    * @returns {boolean} True if this annotation has quotes\n+    */\n+  vm.hasQuotes = function() {\n+    return vm.annotation.target.some(function(target) {\n+      return target.selector && target.selector.some(function(selector) {\n+        return selector.type === 'TextQuoteSelector';\n+      });\n+    });\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#authorize\n+    * @param {string} action The action to authorize.\n+    * @returns {boolean} True if the action is authorized for the current user.\n+    * @description Checks whether the current user can perform an action on\n+    * the annotation.\n+    */\n+  vm.authorize = function(action) {\n+    if (model === null) {\n+      return false;\n+    }\n+    // TODO: this should use auth instead of permissions but we might need\n+    // an auth cache or the JWT -> userid decoding might start to be a\n+    // performance bottleneck and we would need to get the id token into the\n+    // session, which we should probably do anyway (and move to opaque bearer\n+    // tokens for the access token).\n+    return permissions.permits(action, model, session.state.userid);\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#delete\n+    * @description Deletes the annotation.\n+    */\n+  vm['delete'] = function() {\n+    return $timeout(function() {  // Don't use confirm inside the digest cycle.\n+      var msg = 'Are you sure you want to delete this annotation?';\n+      if ($window.confirm(msg)) {\n+        var onRejected = function(reason) {\n+          flash.error(\n+            errorMessage(reason), 'Deleting annotation failed');\n+        };\n+        $scope.$apply(function() {\n+          annotationMapper.deleteAnnotation(model).then(\n+            null, onRejected);\n+        });\n+      }\n+    }, true);\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#edit\n+    * @description Switches the view to an editor.\n+    */\n+  vm.edit = function() {\n+    if (!drafts.get(model)) {\n+      updateDraft(model);\n+    }\n+    vm.action = model.id ? 'edit' : 'create';\n+    vm.editing = true;\n+    vm.preview = 'no';\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#view\n+    * @description Switches the view to a viewer, closing the editor controls\n+    *              if they are open.\n+    */\n+  vm.view = function() {\n+    vm.editing = false;\n+    vm.action = 'view';\n+  };\n+\n+  /**\n+    * @ngdoc method\n+    * @name annotation.AnnotationController#revert\n+    * @description Reverts an edit in progress and returns to the viewer.\n+    */\n+  vm.revert = function() {\n+    drafts.remove(model);\n+    if (vm.action === 'create') {\n+      $rootScope.$emit('annotationDeleted', model);\n+    } else {\n+      vm.render();\n+      vm.view();\n+    }\n+  };\n+\n+  /**\n+  * Update the given annotation domain model object with the data from the\n+  * given annotation view model object.\n+  */\n+  function updateDomainModel(domainModel, viewModel) {\n+    var i;\n+    var tagTexts = [];","path":"h/static/scripts/directive/annotation.js","position":null,"original_position":303,"commit_id":"4d5c68c64bae395deb59af1d057f243c82d7e14d","original_commit_id":"d744c3a7d6f14b0c48bc37a77e868844f96fefae","user":{"login":"seanh","id":22498,"avatar_url":"https://avatars.githubusercontent.com/u/22498?v=3","gravatar_id":"","url":"https://api.github.com/users/seanh","html_url":"https://github.com/seanh","followers_url":"https://api.github.com/users/seanh/followers","following_url":"https://api.github.com/users/seanh/following{/other_user}","gists_url":"https://api.github.com/users/seanh/gists{/gist_id}","starred_url":"https://api.github.com/users/seanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanh/subscriptions","organizations_url":"https://api.github.com/users/seanh/orgs","repos_url":"https://api.github.com/users/seanh/repos","events_url":"https://api.github.com/users/seanh/events{/privacy}","received_events_url":"https://api.github.com/users/seanh/received_events","type":"User","site_admin":false},"body":"Done","created_at":"2015-12-02T16:55:38Z","updated_at":"2015-12-02T16:55:38Z","html_url":"https://github.com/hypothesis/h/pull/2756#discussion_r46441118","pull_request_url":"https://api.github.com/repos/hypothesis/h/pulls/2756","_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments/46441118"},"html":{"href":"https://github.com/hypothesis/h/pull/2756#discussion_r46441118"},"pull_request":{"href":"https://api.github.com/repos/hypothesis/h/pulls/2756"}}},"pull_request":{"url":"https://api.github.com/repos/hypothesis/h/pulls/2756","id":52173119,"html_url":"https://github.com/hypothesis/h/pull/2756","diff_url":"https://github.com/hypothesis/h/pull/2756.diff","patch_url":"https://github.com/hypothesis/h/pull/2756.patch","issue_url":"https://api.github.com/repos/hypothesis/h/issues/2756","number":2756,"state":"open","locked":false,"title":"Translate annotation.coffee to JavaScript","user":{"login":"seanh","id":22498,"avatar_url":"https://avatars.githubusercontent.com/u/22498?v=3","gravatar_id":"","url":"https://api.github.com/users/seanh","html_url":"https://github.com/seanh","followers_url":"https://api.github.com/users/seanh/followers","following_url":"https://api.github.com/users/seanh/following{/other_user}","gists_url":"https://api.github.com/users/seanh/gists{/gist_id}","starred_url":"https://api.github.com/users/seanh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/seanh/subscriptions","organizations_url":"https://api.github.com/users/seanh/orgs","repos_url":"https://api.github.com/users/seanh/repos","events_url":"https://api.github.com/users/seanh/events{/privacy}","received_events_url":"https://api.github.com/users/seanh/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-11-30T20:15:24Z","updated_at":"2015-12-02T16:55:38Z","closed_at":null,"merged_at":null,"merge_commit_sha":"04aa93c065a200410a52435d0ffa1e7ff03c7a3a","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/hypothesis/h/pulls/2756/commits","review_comments_url":"https://api.github.com/repos/hypothesis/h/pulls/2756/comments","review_comment_url":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hypothesis/h/issues/2756/comments","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/4d5c68c64bae395deb59af1d057f243c82d7e14d","head":{"label":"hypothesis:2728-translate-annotation-controller-to-js","ref":"2728-translate-annotation-controller-to-js","sha":"4d5c68c64bae395deb59af1d057f243c82d7e14d","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"The Internet, peer reviewed.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","created_at":"2012-04-02T19:56:59Z","updated_at":"2015-12-02T15:28:52Z","pushed_at":"2015-12-02T16:55:10Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":24660,"stargazers_count":531,"watchers_count":531,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":129,"mirror_url":null,"open_issues_count":127,"forks":129,"open_issues":127,"watchers":531,"default_branch":"master"}},"base":{"label":"hypothesis:master","ref":"master","sha":"d418741254f5b336d403e0590e5fbc12a3c742f2","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"The Internet, peer reviewed.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","created_at":"2012-04-02T19:56:59Z","updated_at":"2015-12-02T15:28:52Z","pushed_at":"2015-12-02T16:55:10Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":24660,"stargazers_count":531,"watchers_count":531,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":129,"mirror_url":null,"open_issues_count":127,"forks":129,"open_issues":127,"watchers":531,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/2756"},"html":{"href":"https://github.com/hypothesis/h/pull/2756"},"issue":{"href":"https://api.github.com/repos/hypothesis/h/issues/2756"},"comments":{"href":"https://api.github.com/repos/hypothesis/h/issues/2756/comments"},"review_comments":{"href":"https://api.github.com/repos/hypothesis/h/pulls/2756/comments"},"review_comment":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hypothesis/h/pulls/2756/commits"},"statuses":{"href":"https://api.github.com/repos/hypothesis/h/statuses/4d5c68c64bae395deb59af1d057f243c82d7e14d"}}}}