Created
September 23, 2015 17:01
-
-
Save tilgovi/59e74d9c5dd0b3c4b8a7 to your computer and use it in GitHub Desktop.
SQLAlchemy-style mapper class beginnings in CoffeeScript, for Hypothesis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit 962fe64b4dd5b0a4b02fda6f08a7c81d0e40d961 | |
Merge: 367c72c 595968e | |
Author: Randall Leeds <[email protected]> | |
Date: Mon Feb 9 14:47:14 2015 -0800 | |
WIP on master: 367c72c Remove selenium test requirement | |
diff --cc h/static/scripts/annotation-mapper-service.coffee | |
index 62d2c08,62d2c08..d95fed1 | |
--- a/h/static/scripts/annotation-mapper-service.coffee | |
+++ b/h/static/scripts/annotation-mapper-service.coffee | |
@@@ -1,30 -1,30 +1,89 @@@ | |
# Wraps the annotation store to trigger events for the CRUD actions | |
class AnnotationMapperService | |
-- this.$inject = ['$rootScope', 'threading', 'store'] | |
-- constructor: ($rootScope, threading, store) -> | |
-- this.setupAnnotation = (ann) -> ann | |
++ this.$inject = ['$q', '$rootScope', 'threading', 'store'] | |
++ constructor: ( $q, $rootScope, threading, store) -> | |
++ this.new = [] | |
++ this.deleted = [] | |
++ this.dirty = [] | |
-- this.loadAnnotations = (annotations) -> | |
-- annotations = for annotation in annotations | |
-- container = threading.idTable[annotation.id] | |
-- if container?.message | |
-- angular.copy(annotation, container.message) | |
-- $rootScope.$emit('annotationUpdated', container.message) | |
++ this.add = (annotation) -> | |
++ if annotation.id | |
++ @dirty.push(annotation) | |
++ else | |
++ @new.push(annotation) | |
++ | |
++ this.delete = (annotation) -> | |
++ @deleted.push annotation | |
++ | |
++ this.expunge = (annotation) -> | |
++ $rootScope.$emit('annotationDeleted', annotation) | |
++ | |
++ this.flush = (annotations) -> | |
++ if annotations | |
++ shouldFlush = (s) -> s in annotations | |
++ else | |
++ shouldFlush = -> true | |
++ | |
++ promises = [] | |
++ | |
++ @new = for pending in @new | |
++ if shouldFlush(pending) | |
++ promise = pending.$create().then (result) -> | |
++ $rootScope.$emit('annotationCreated', result) | |
++ promises.push(promise) | |
continue | |
else | |
-- annotation | |
++ pending | |
-- annotations = (new store.AnnotationResource(a) for a in annotations) | |
-- $rootScope.$emit('annotationsLoaded', annotations) | |
++ @deleted = for pending in @deleted | |
++ if shouldFlush(pending) | |
++ promise = pending.$delete(id: pending.id).then (result) -> | |
++ $rootScope.$emit('annotationDeleted', result) | |
++ promises.push(promise) | |
++ continue | |
++ else | |
++ pending | |
++ | |
++ @dirty = for pending in @dirty | |
++ if shouldFlush(pending) | |
++ promise = pending.$update(id: pending.id).then (result) -> | |
++ $rootScope.$emit('annotationUpdated', result) | |
++ promises.push(promise) | |
++ continue | |
++ else | |
++ pending | |
++ | |
++ $q.all(promises) | |
++ | |
++ this.merge = (annotation, load=true) -> | |
++ if annotation.id | |
++ container = threading.idTable[annotation.id] | |
++ if container?.message | |
++ instance = angular.copy(annotation, container.message) | |
++ $rootScope.$emit('annotationUpdated', instance) | |
++ else if load | |
++ params = id: annotation.id | |
++ instance = store.AnnotationResource.read params, -> | |
++ instance = angular.copy(annotation, instance) | |
++ $rootScope.$emit('annotationsLoaded', [instance]) | |
++ else | |
++ instance = new store.AnnotationResource(annotation) | |
++ else | |
++ instance = new store.AnnotationResource(annotation) | |
++ $rootScope.$emit('beforeAnnotationCreated', instance) | |
++ instance | |
-- this.createAnnotation = (annotation) -> | |
-- annotation = new store.AnnotationResource(annotation) | |
-- $rootScope.$emit('beforeAnnotationCreated', annotation) | |
-- annotation | |
++ this.query = (query) -> | |
++ result = store.SearchResource.get(query) | |
++ result.$promise.then ({rows}) => | |
++ rows = for annotation in rows | |
++ annotation = this.merge(annotation, false) | |
++ if annotation.$promise | |
++ continue | |
++ else | |
++ annotation | |
++ $rootScope.$emit('annotationsLoaded', rows) | |
++ rows | |
-- this.deleteAnnotation = (annotation) -> | |
-- annotation.$delete(id: annotation.id).then -> | |
-- $rootScope.$emit('annotationDeleted', annotation) | |
-- annotation | |
angular.module('h').service('annotationMapper', AnnotationMapperService) | |
diff --cc h/static/scripts/annotation-sync.coffee | |
index 3dde1ec,3dde1ec..97fa02a | |
--- a/h/static/scripts/annotation-sync.coffee | |
+++ b/h/static/scripts/annotation-sync.coffee | |
@@@ -13,7 -13,7 +13,7 @@@ class AnnotationSyn | |
# to reconcile any differences. The default behavior is to merge all | |
# keys of the remote object into the local copy | |
merge: (local, remote) -> | |
-- for k, v of remote | |
++ for own k, v of remote | |
local[k] = v | |
local | |
diff --cc h/static/scripts/auth-service.coffee | |
index d9c6896,d9c6896..c81ddf0 | |
--- a/h/static/scripts/auth-service.coffee | |
+++ b/h/static/scripts/auth-service.coffee | |
@@@ -18,21 -18,21 +18,6 @@@ class Aut | |
_checkingToken = false | |
@user = undefined | |
-- # TODO: Remove this once Auth has been migrated. | |
-- $rootScope.$on 'beforeAnnotationCreated', (event, annotation) => | |
-- annotation.user = @user | |
-- annotation.permissions = {} | |
-- annotator.publish('beforeAnnotationCreated', annotation) | |
-- | |
-- $rootScope.$on 'annotationCreated', (event, annotation) => | |
-- annotator.publish('annotationCreated', annotation) | |
-- | |
-- $rootScope.$on 'annotationUpdated', (event, annotation) => | |
-- annotator.publish('annotationUpdated', annotation) | |
-- | |
-- $rootScope.$on 'beforeAnnotationUpdated', (event, annotation) => | |
-- annotator.publish('beforeAnnotationUpdated', annotation) | |
-- | |
# Fired when the identity-service successfully requests authentication. | |
# Sets up the Annotator.Auth plugin instance and the auth.user property. | |
# It sets a flag between that time period to indicate that the token is | |
diff --cc h/static/scripts/controllers.coffee | |
index 44220d7,44220d7..d7a775c | |
--- a/h/static/scripts/controllers.coffee | |
+++ b/h/static/scripts/controllers.coffee | |
@@@ -1,38 -1,38 +1,12 @@@ | |
--# Watch the UI state and update scope properties. | |
--class AnnotationUIController | |
-- this.$inject = ['$rootScope', '$scope', 'annotationUI'] | |
-- constructor: ( $rootScope, $scope, annotationUI ) -> | |
-- $rootScope.$watch (-> annotationUI.selectedAnnotationMap), (map={}) -> | |
-- count = Object.keys(map).length | |
-- $scope.selectedAnnotationsCount = count | |
-- | |
-- if count | |
-- $scope.selectedAnnotations = map | |
-- else | |
-- $scope.selectedAnnotations = null | |
-- | |
-- $rootScope.$watch (-> annotationUI.focusedAnnotationMap), (map={}) -> | |
-- $scope.focusedAnnotations = map | |
-- | |
-- $rootScope.$on 'annotationDeleted', (event, annotation) -> | |
-- annotationUI.removeSelectedAnnotation(annotation) | |
-- | |
-- | |
class AppController | |
this.$inject = [ | |
-- '$controller', '$document', '$location', '$route', '$scope', '$window', | |
-- 'auth', 'drafts', 'identity', | |
-- 'permissions', 'streamer', 'streamfilter', 'annotationUI', | |
-- 'annotationMapper', 'threading' | |
++ '$document', '$location', '$route', '$scope', '$window', | |
++ 'auth', 'drafts', 'identity', 'streamer', 'streamfilter', 'annotationMapper' | |
] | |
constructor: ( | |
-- $controller, $document, $location, $route, $scope, $window, | |
-- auth, drafts, identity, | |
-- permissions, streamer, streamfilter, annotationUI, | |
-- annotationMapper, threading | |
++ $document, $location, $route, $scope, $window, | |
++ auth, drafts, identity, streamer, streamfilter, annotationMapper | |
) -> | |
-- $controller(AnnotationUIController, {$scope}) | |
-- | |
$scope.auth = auth | |
isFirstRun = $location.search().hasOwnProperty('firstrun') | |
@@@ -45,10 -45,10 +19,14 @@@ | |
return unless data?.length | |
switch action | |
when 'create', 'update', 'past' | |
-- annotationMapper.loadAnnotations data | |
++ load = for annotation in data | |
++ annotation = annotationMapper.merge(annotation, false) | |
++ if annotation.$promise then continue else annotation | |
++ $scope.$emit('annotationsLoaded', load) | |
when 'delete' | |
for annotation in data | |
-- $scope.$emit('annotationDeleted', annotation) | |
++ annotation = annotationMapper.merge(annotation, false) | |
++ annotationMapper.expunge(annotation) | |
streamer.onmessage = (data) -> | |
return if !data or data.type != 'annotation-notification' | |
@@@ -60,18 -60,18 +38,6 @@@ | |
oncancel = -> | |
$scope.dialog.visible = false | |
-- cleanupAnnotations = -> | |
-- # Clean up any annotations that need to be unloaded. | |
-- for id, container of $scope.threading.idTable when container.message | |
-- # Remove annotations not belonging to this user when highlighting. | |
-- if annotationUI.tool is 'highlight' and annotation.user != auth.user | |
-- $scope.$emit('annotationDeleted', container.message) | |
-- drafts.remove annotation | |
-- # Remove annotations the user is not authorized to view. | |
-- else if not permissions.permits 'read', container.message, auth.user | |
-- $scope.$emit('annotationDeleted', container.message) | |
-- drafts.remove container.message | |
-- | |
$scope.$watch 'sort.name', (name) -> | |
return unless name | |
predicate = switch name | |
@@@ -88,17 -88,17 +54,10 @@@ | |
else | |
$scope.dialog.visible = false | |
-- # Update any edits in progress. | |
-- for draft in drafts.all() | |
-- $scope.$emit('beforeAnnotationCreated', draft) | |
-- | |
# Reopen the streamer. | |
streamer.close() | |
streamer.open($window.WebSocket, streamerUrl) | |
-- # Clean up annotations that should be removed | |
-- cleanupAnnotations() | |
-- | |
# Reload the view. | |
$route.reload() | |
@@@ -116,7 -116,7 +75,6 @@@ | |
$scope.clearSelection = -> | |
$scope.search.query = '' | |
-- annotationUI.clearSelectedAnnotations() | |
$scope.dialog = visible: false | |
@@@ -129,21 -129,21 +87,18 @@@ | |
update: (query) -> | |
unless angular.equals $location.search()['q'], query | |
$location.search('q', query or null) | |
-- annotationUI.clearSelectedAnnotations() | |
$scope.sort = name: 'Location' | |
-- $scope.threading = threading | |
-- $scope.threadRoot = $scope.threading?.root | |
class AnnotationViewerController | |
this.$inject = [ | |
'$location', '$routeParams', '$scope', | |
-- 'streamer', 'store', 'streamfilter', 'annotationMapper' | |
++ 'streamer', 'streamfilter', 'threading', 'annotationMapper' | |
] | |
constructor: ( | |
$location, $routeParams, $scope, | |
-- streamer, store, streamfilter, annotationMapper | |
++ streamer, streamfilter, threading, annotationMapper | |
) -> | |
# Tells the view that these annotations are standalone | |
$scope.isEmbedded = false | |
@@@ -159,11 -159,11 +114,9 @@@ | |
$location.path('/stream').search('q', query) | |
id = $routeParams.id | |
-- store.SearchResource.get _id: id, ({rows}) -> | |
-- annotationMapper.loadAnnotations(rows) | |
-- $scope.threadRoot = children: [$scope.threading.getContainer(id)] | |
-- store.SearchResource.get references: id, ({rows}) -> | |
-- annotationMapper.loadAnnotations(rows) | |
++ annotationMapper.query(_id: id).then -> | |
++ $scope.threadRoot = children: [threading.getContainer(id)] | |
++ annotationMapper.query(references: id) | |
streamfilter | |
.setMatchPolicyIncludeAny() | |
@@@ -175,15 -175,15 +128,16 @@@ | |
class ViewerController | |
this.$inject = [ | |
'$scope', '$route', 'annotationUI', 'crossframe', 'annotationMapper', | |
-- 'auth', 'flash', 'streamer', 'streamfilter', 'store' | |
++ 'auth', 'permissions', 'streamer', 'streamfilter', 'store', 'threading' | |
] | |
constructor: ( | |
$scope, $route, annotationUI, crossframe, annotationMapper, | |
-- auth, flash, streamer, streamfilter, store | |
++ auth, permissions, streamer, streamfilter, store, threading | |
) -> | |
# Tells the view that these annotations are embedded into the owner doc | |
$scope.isEmbedded = true | |
$scope.isStream = true | |
++ $scope.threadRoot = threading.root | |
loaded = [] | |
@@@ -196,8 -196,8 +150,7 @@@ | |
for p in crossframe.providers | |
for e in p.entities when e not in loaded | |
loaded.push e | |
-- r = store.SearchResource.get angular.extend(uri: e, query), (results) -> | |
-- annotationMapper.loadAnnotations(results.rows) | |
++ annotationMapper.query(angular.extend(uri: e, query)) | |
streamfilter.resetFilter().addClause('/uri', 'one_of', loaded) | |
@@@ -206,10 -206,10 +159,38 @@@ | |
streamer.send({filter: streamfilter.getFilter()}) | |
++ # Clean up any annotations that need to be unloaded. | |
++ for id, container of threading.idTable when container.message | |
++ annotation = container.message | |
++ # Remove annotations not belonging to this user when highlighting. | |
++ if annotationUI.tool is 'highlight' and annotation.user != auth.user | |
++ annotationMapper.expunge annotation | |
++ # Remove annotations the user is not authorized to view. | |
++ else if not permissions.permits 'read', annotation, auth.user | |
++ annotationMapper.expunge annotation | |
++ | |
++ $scope.$on 'annotationDeleted', (event, annotation) -> | |
++ annotationUI.removeSelectedAnnotation(annotation) | |
++ | |
++ $scope.$on '$routeUpdate', -> | |
++ annotationUI.clearSelectedAnnotations() | |
++ | |
$scope.$watch (-> annotationUI.tool), (newVal, oldVal) -> | |
return if newVal is oldVal | |
$route.reload() | |
++ $scope.$watch (-> annotationUI.selectedAnnotationMap), (map={}) -> | |
++ count = Object.keys(map).length | |
++ $scope.selectedAnnotationsCount = count | |
++ | |
++ if count | |
++ $scope.selectedAnnotations = map | |
++ else | |
++ $scope.selectedAnnotations = null | |
++ | |
++ $scope.$watch (-> annotationUI.focusedAnnotationMap), (map={}) -> | |
++ $scope.focusedAnnotations = map | |
++ | |
$scope.$watchCollection (-> crossframe.providers), loadAnnotations | |
$scope.focus = (annotation) -> | |
@@@ -240,4 -240,4 +221,3 @@@ angular.module('h' | |
.controller('AppController', AppController) | |
.controller('ViewerController', ViewerController) | |
.controller('AnnotationViewerController', AnnotationViewerController) | |
--.controller('AnnotationUIController', AnnotationUIController) | |
diff --cc h/static/scripts/cross-frame-service.coffee | |
index 27421bb,27421bb..e4ea711 | |
--- a/h/static/scripts/cross-frame-service.coffee | |
+++ b/h/static/scripts/cross-frame-service.coffee | |
@@@ -31,12 -31,12 +31,11 @@@ class CrossFrameServic | |
options = | |
formatter: (annotation) -> | |
formatted = {} | |
-- for k, v of annotation when k in whitelist | |
++ for own k, v of annotation when k in whitelist | |
formatted[k] = v | |
formatted | |
parser: (annotation) -> | |
-- parsed = new store.AnnotationResource() | |
-- for k, v of annotation when k in whitelist | |
++ for own k, v of annotation when k in whitelist | |
parsed[k] = v | |
parsed | |
emit: (args...) -> | |
diff --cc h/static/scripts/directives/annotation.coffee | |
index 91e5dd2,91e5dd2..a34a20a | |
--- a/h/static/scripts/directives/annotation.coffee | |
+++ b/h/static/scripts/directives/annotation.coffee | |
@@@ -94,7 -94,7 +94,11 @@@ AnnotationController = | |
### | |
this.delete = -> | |
if confirm "Are you sure you want to delete this annotation?" | |
-- annotationMapper.deleteAnnotation model | |
++ annotationMapper.delete(model) | |
++ annotationMapper.flush([model]) | |
++ .catch -> | |
++ flash 'error', | |
++ 'Something went wrong while deleting the annotation.' | |
###* | |
# @ngdoc method | |
@@@ -115,7 -115,7 +119,7 @@@ | |
this.revert = -> | |
drafts.remove model | |
if @action is 'create' | |
-- $rootScope.$emit('annotationDeleted', model) | |
++ annotationMapper.expunge(model) | |
else | |
this.render() | |
@action = 'view' | |
@@@ -148,16 -148,16 +152,14 @@@ | |
angular.extend model, @annotation, | |
tags: (tag.text for tag in @annotation.tags) | |
-- switch @action | |
-- when 'create' | |
-- model.$create().then -> | |
-- $rootScope.$emit('annotationCreated', model) | |
-- when 'delete', 'edit' | |
-- model.$update(id: model.id).then -> | |
-- $rootScope.$emit('annotationUpdated', model) | |
-- | |
-- @editing = false | |
-- @action = 'view' | |
++ annotationMapper.add(model) | |
++ annotationMapper.flush([model]) | |
++ .then (model) => | |
++ @editing = false | |
++ @action = 'view' | |
++ .catch -> | |
++ flash 'error', | |
++ 'Something went wrong while saving the annotation.' | |
###* | |
# @ngdoc method | |
@@@ -174,7 -174,7 +176,7 @@@ | |
# Construct the reply. | |
references = [references..., id] | |
-- reply = annotationMapper.createAnnotation({references, uri}) | |
++ reply = annotationMapper.merge({references, uri}) | |
if auth.user? | |
if permissions.isPublic model.permissions | |
@@@ -253,6 -253,6 +255,23 @@@ | |
updateTimestamp = angular.noop | |
drafts.remove model | |
++ # Watch the user. | |
++ $scope.$watch (-> auth.user), (user, old) => | |
++ return if model.id | |
++ | |
++ model.permissions = {} | |
++ model.user = user | |
++ | |
++ # Save highlights once logged in. | |
++ if highlight and this.isHighlight() | |
++ highlight = false # skip this on future updates | |
++ if user | |
++ model.permissions = permissions.private() | |
++ annotationMapper.add(model) | |
++ annotationMapper.flush([model]) | |
++ else | |
++ drafts.add model, => this.revert() | |
++ | |
# Watch the model. | |
# XXX: TODO: don't clobber the view when collaborating | |
$scope.$watch (-> model), (model, old) => | |
@@@ -263,18 -263,18 +282,7 @@@ | |
# Propagate an update event up the thread (to pulse changing threads), | |
# but only if this is someone else's annotation. | |
if model.user != auth.user | |
-- $scope.$emit('annotationUpdate') | |
-- | |
-- # Save highlights once logged in. | |
-- if highlight and this.isHighlight() | |
-- if model.user and not model.id | |
-- highlight = false # skip this on future updates | |
-- model.permissions = permissions.private() | |
-- model.$create().then -> | |
-- $rootScope.$emit('annotationCreated', model) | |
-- highlight = false # skip this on future updates | |
-- else | |
-- drafts.add model, => this.revert() | |
++ $scope.$emit('annotationUpdate', model) | |
updateTimestamp(model is old) # repeat on first run | |
this.render() | |
diff --cc h/static/scripts/streamsearch.coffee | |
index b47cd47,b47cd47..9c123fd | |
--- a/h/static/scripts/streamsearch.coffee | |
+++ b/h/static/scripts/streamsearch.coffee | |
@@@ -1,13 -1,13 +1,13 @@@ | |
class StreamSearchController | |
this.inject = [ | |
'$scope', '$rootScope', '$routeParams', | |
-- 'auth', 'queryparser', 'searchfilter', 'store', | |
-- 'streamer', 'streamfilter', 'annotationMapper' | |
++ 'flash', 'queryparser', 'searchfilter', | |
++ 'streamer', 'streamfilter', 'threading', 'annotationMapper' | |
] | |
constructor: ( | |
$scope, $rootScope, $routeParams | |
-- auth, queryparser, searchfilter, store, | |
-- streamer, streamfilter, annotationMapper | |
++ flash, queryparser, searchfilter, | |
++ streamer, streamfilter, threading, annotationMapper | |
) -> | |
# Initialize the base filter | |
streamfilter | |
@@@ -23,11 -23,11 +23,13 @@@ | |
# Perform the search | |
searchParams = searchfilter.toObject $scope.search.query | |
query = angular.extend limit: 10, searchParams | |
-- store.SearchResource.get query, ({rows}) -> | |
-- annotationMapper.loadAnnotations(rows) | |
++ annotationMapper.query(query) | |
++ .catch -> | |
++ flash 'error', 'Something went wrong while fetching annotations.' | |
$scope.isEmbedded = false | |
$scope.isStream = true | |
++ $scope.threadRoot = threading.root | |
$scope.sort.name = 'Newest' | |
diff --cc h/static/scripts/threading-service.coffee | |
index 0002fb8,0002fb8..64704d5 | |
--- a/h/static/scripts/threading-service.coffee | |
+++ b/h/static/scripts/threading-service.coffee | |
@@@ -27,8 -27,8 +27,6 @@@ class ThreadingServic | |
thread = (this.getContainer message.id) | |
thread.message = message | |
else | |
-- # XXX: relies on outside code to update the idTable if the message | |
-- # later acquires an id. | |
thread = mail.messageContainer(message) | |
prev = @root | |
diff --cc tests/js/annotation-ui-sync-test.coffee | |
index a585538,a585538..380a8a5 | |
--- a/tests/js/annotation-ui-sync-test.coffee | |
+++ b/tests/js/annotation-ui-sync-test.coffee | |
@@@ -96,6 -96,6 +96,19 @@@ describe 'AnnotationUISync', - | |
publish({method: 'open'}) | |
assert.called($digest) | |
++ describe 'on "back" event', -> | |
++ it 'sends the "hideFrame" message to the host only', -> | |
++ createAnnotationUISync() | |
++ publish({method: 'back'}) | |
++ assert.calledWith(fakeBridge.links[0].channel.notify, method: 'hideFrame') | |
++ assert.notCalled(fakeBridge.links[1].channel.notify) | |
++ assert.notCalled(fakeBridge.links[2].channel.notify) | |
++ | |
++ it 'triggers a digest', -> | |
++ createAnnotationUISync() | |
++ publish({method: 'back'}) | |
++ assert.called($digest) | |
++ | |
describe 'on "showEditor" event', -> | |
it 'sends the "showFrame" message to the host only', -> | |
createAnnotationUISync() | |
diff --cc tests/js/directives/annotation-test.coffee | |
index eecb0cc,eecb0cc..f235cc1 | |
--- a/tests/js/directives/annotation-test.coffee | |
+++ b/tests/js/directives/annotation-test.coffee | |
@@@ -115,14 -115,14 +115,6 @@@ describe 'h.directives.annotation', - | |
controller.reply() | |
assert.notInclude(reply.permissions.read, 'group:__world__') | |
-- it 'fills the other permissions too', -> | |
-- reply = {} | |
-- fakeAnnotationMapper.createAnnotation.returns(reply) | |
-- controller.reply() | |
-- assert.equal(reply.permissions.update[0], 'acct:bill@localhost') | |
-- assert.equal(reply.permissions.delete[0], 'acct:bill@localhost') | |
-- assert.equal(reply.permissions.admin[0], 'acct:bill@localhost') | |
-- | |
describe '#render', -> | |
controller = null | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment