Commit 495109ea authored by Randall Leeds's avatar Randall Leeds

break out threading plugin

Woohoo! Threading is a separate plugin now!

- Get threading service from "annotator.threading"
- setup up newly created annotations in the threading
- move draft management around a bit, to make it more automatic
  with the annotation lifecycle events
- simplify the logic of the Store monkey patch
- pass annotations to the Editor controller via showEditor
  rather than relying on on the id in the params, reducing reliance
  on temporary ids, now added by the threading plugin
parent 4c3fb95c
......@@ -13,11 +13,11 @@ class App
this.$inject = [
'$compile', '$element', '$http', '$location', '$scope', '$timeout',
'annotator', 'drafts', 'flash', 'threading'
'annotator', 'drafts', 'flash'
]
constructor: (
$compile, $element, $http, $location, $scope, $timeout
annotator, drafts, flash, threading
annotator, drafts, flash
) ->
{plugins, provider} = annotator
heatmap = annotator.plugins.Heatmap
......@@ -183,11 +183,11 @@ class App
class Annotation
this.$inject = [
'$element', '$location', '$scope', '$rootScope', '$timeout',
'annotator', 'drafts', 'threading'
'annotator', 'drafts'
]
constructor: (
$element, $location, $scope, $rootScope, $timeout
annotator, drafts, threading
annotator, drafts
) ->
publish_ = (args...) ->
# Publish after a timeout to escape this digest
......@@ -199,6 +199,8 @@ class Annotation
{name: 'Private', permissions: { 'read': [] } }
]
threading = annotator.threading
$scope.cancel = ->
$scope.editing = false
drafts.remove $scope.$modelValue
......@@ -220,23 +222,14 @@ class Annotation
return
references =
if $scope.$modelValue.thread
[$scope.$modelValue.thread, $scope.$modelValue.id]
if $scope.thread.message.references
[$scope.thread.message.references, $scope.thread.message.id]
else
[$scope.$modelValue.id]
[$scope.thread.message.id]
reply = angular.extend annotator.createAnnotation(),
thread: references.join '/'
replyThread = angular.extend (threading.getContainer reply.id),
message:
annotation: reply
id: reply.id
references: references
(threading.getContainer $scope.$modelValue.id).addChild replyThread
drafts.add reply
$scope.getPrivacyLevel = (permissions) ->
for level in $scope.privacyLevels
roleSet = {}
......@@ -265,6 +258,8 @@ class Annotation
name: 'Custom'
value: permissions
annotator.setupAnnotation reply
$scope.$on '$routeChangeStart', -> $scope.cancel() if $scope.editing
$scope.$on '$routeUpdate', -> $scope.cancel() if $scope.editing
......@@ -287,14 +282,8 @@ class Annotation
0
class Editor
this.$inject = [
'$location', '$routeParams', '$scope',
'annotator', 'drafts', 'threading'
]
constructor: (
$location, $routeParams, $scope,
annotator, drafts, threading
) ->
this.$inject = ['$location', '$routeParams', '$scope', 'annotator']
constructor: ($location, $routeParams, $scope, annotator) ->
save = ->
$scope.$apply ->
$location.path('/viewer').replace()
......@@ -315,27 +304,22 @@ class Editor
annotator.unsubscribe 'annotationCreated', save
annotator.unsubscribe 'annotationDeleted', cancel
thread = (threading.getContainer $routeParams.id)
annotation = thread.message?.annotation
$scope.annotation = annotation
drafts.add annotation
class Viewer
this.$inject = [
'$location', '$routeParams', '$scope',
'annotator', 'threading'
'annotator'
]
constructor: (
$location, $routeParams, $scope,
annotator, threading
annotator
) ->
{plugins, provider} = annotator
listening = false
refresh = =>
return unless annotator.visible
this.refresh $scope, $routeParams, threading, plugins.Heatmap
this.refresh $scope, $routeParams, annotator
if listening
if $scope.detail
plugins.Heatmap.unsubscribe 'updated', refresh
......@@ -368,10 +352,10 @@ class Viewer
refresh()
refresh: ($scope, $routeParams, threading, heatmap) =>
refresh: ($scope, $routeParams, annotator) =>
if $routeParams.id?
$scope.detail = true
$scope.thread = threading.getContainer $routeParams.id
$scope.thread = annotator.threading.getContainer $routeParams.id
$scope.focus $scope.thread.message.annotation
else
$scope.detail = false
......
class Annotator.Plugin.Threading extends Annotator.Plugin
# These events maintain the awareness of annotations between the two
# communicating annotators.
events:
'annotationDeleted': 'annotationDeleted'
'annotationsLoaded': 'annotationsLoaded'
'beforeAnnotationCreated': 'beforeAnnotationCreated'
# Cache of annotations which have crossed the bridge for fast, encapsulated
# association of annotations received in arguments to window-local copies.
cache: {}
pluginInit: ->
@annotator.threading = mail.messageThread()
thread: (annotation) ->
# Assign a temporary id if necessary. Threading relies on the id.
unless annotation.id?
Object.defineProperty annotation, 'id',
configurable: true
enumerable: false
writable: true
value: window.btoa Math.random()
# Get or create a thread to contain the annotation
thread = (@annotator.threading.getContainer annotation.id)
thread.message =
annotation: annotation
id: annotation.id
references: annotation.thread?.split('/')
# Attach the thread to its parent, if any.
references = thread.message?.references
if references?.length
prev = references[references.length-1]
@annotator.threading.getContainer(prev).addChild thread
# Update the id table
@annotator.threading.idTable[annotation.id] = thread
thread
annotationDeleted: (annotation) =>
thread = (@annotator.threading.getContainer annotation.id)
delete @annotator.threading.idTable[annotation.id]
thread.message = null
if thread.parent? then @annotator.threading.pruneEmpties thread.parent
annotationsLoaded: (annotations) =>
@annotator.threading.thread annotations.map (a) ->
annotation: a
id: a.id
references: a.thread?.split '/'
beforeAnnotationCreated: (annotation) =>
this.thread annotation
......@@ -11,6 +11,7 @@ class Hypothesis extends Annotator
showEditPermissionsCheckbox: false,
showViewPermissionsCheckbox: false,
userString: (user) -> user.replace(/^acct:(.+)@(.+)$/, '$1 on $2')
Threading: {}
# Internal state
visible: false # * Whether the sidebar is visible
......@@ -20,14 +21,8 @@ class Hypothesis extends Annotator
viewer:
addField: (-> )
this.$inject = [
'$document', '$location', '$rootScope',
'threading'
]
constructor: (
$document, $location, $rootScope,
threading
) ->
this.$inject = ['$document', '$location', '$rootScope', '$route', 'drafts']
constructor: ($document, $location, $rootScope, $route, drafts) ->
super ($document.find 'body')
# Load plugins
......@@ -42,38 +37,12 @@ class Hypothesis extends Annotator
this.subscribe 'beforeAnnotationCreated', (annotation) =>
permissions = @plugins.Permissions
annotation.user = permissions.options.userId(permissions.user)
Object.defineProperty annotation, 'draft',
configurable: true
enumerable: false
writable: true
value: true
# Update threads when annotations are deleted
this.subscribe 'annotationDeleted', (annotation) =>
$rootScope.$apply ->
thread = threading.getContainer annotation.id
thread.message = null
if thread.parent then threading.pruneEmpties thread.parent
# Thread the annotations after loading
this.subscribe 'annotationsLoaded', (annotations) =>
$rootScope.$apply ->
threading.thread annotations.map (a) ->
annotation: a
id: a.id
references: a.thread?.split '/'
# Update the thread when an annotation changes
this.subscribe 'annotationUpdated', (annotation) =>
$rootScope.$apply ->
(threading.getContainer annotation.id).message =
annotation: annotation
id: annotation.id
references: annotation.thread?.split '/'
drafts.add annotation
# Update the heatmap when the host is updated or annotations are loaded
bridge = @plugins.Bridge
heatmap = @plugins.Heatmap
threading = @threading
for event in ['hostUpdated', 'annotationsLoaded']
this.subscribe event, =>
@provider.call
......@@ -90,7 +59,7 @@ class Hypothesis extends Annotator
$location = @element.injector().get '$location'
$rootScope = @element.injector().get '$rootScope'
$window = @element.injector().get '$window'
threading = @element.injector().get 'threading'
drafts = @element.injector().get 'drafts'
this.addPlugin 'Bridge',
origin: $location.search().xdm
......@@ -117,56 +86,34 @@ class Hypothesis extends Annotator
# if the annotation has a newly-assigned id and ensures that the id
# is enumerable.
store.updateAnnotation = (annotation, data) =>
bridge = @plugins.Bridge
$rootScope.$apply ->
if annotation.id != data.id
# Remove the old annotation from the threading
thread = (threading.getContainer annotation.id)
if thread.parent
thread.message = null
threading.pruneEmpties thread.parent
else
delete threading.idTable[annotation.id]
# Create the new thread
thread = (threading.getContainer data.id)
references = data.thread?.split('/') or []
thread.message =
annotation: annotation
id: data.id
references: references
if not thread.parent? and thread.message.references.length
threading.getContainer(references[references.length-1])
.addChild thread
# Remove the old annotation from the host.
# XXX in iframe mode it's safe to make these calls because the
# deletion event that gets published in the provider is not
# cross-published back here in the consumer and therefore
# the Store does not delete the annotation.
if annotation.ranges?.length
bridge.deleteAnnotation annotation
bridge.setupAnnotation data
# The id is no longer temporary and should be serialized
# on future Store requests.
Object.defineProperty annotation, 'id',
configurable: true
enumerable: true
writable: true
# If the annotation is loaded in a view, switch the view
# to reference the new id.
search = $location.search()
if search? and search.id == annotation.id
search.id = data.id
$location.search(search).replace()
# Update the annotation with the new data
annotation = angular.extend annotation, data
# Reflect the newest information in the heatmap
if annotation.id? and annotation.id != data.id
# Update the id table for the threading
thread = @threading.getContainer annotation.id
thread.message.id = data.id
@threading.idTable[data.id] = thread
delete @threading.idTable[annotation.id]
# The id is no longer temporary and should be serialized
# on future Store requests.
Object.defineProperty annotation, 'id',
configurable: true
enumerable: true
writable: true
# If the annotation is loaded in a view, switch the view
# to reference the new id.
search = $location.search()
if search? and search.id == annotation.id
search.id = data.id
$location.search(search).replace()
# Update the annotation with the new data
annotation = angular.extend annotation, data
# Give angular a chance to react
$rootScope.$digest()
# Update the heatmap
this.publish 'hostUpdated'
# Get the location of the annotated document
......@@ -262,25 +209,8 @@ class Hypothesis extends Annotator
unless annotation.ranges?
annotation.highlights = []
annotation.ranges = []
# Assign a temporary id if necessary
unless annotation.id?
Object.defineProperty annotation, 'id',
configurable: true
enumerable: false
writable: true
value: window.btoa Math.random()
# Thread it
threading = @element.injector().get 'threading'
thread = (threading.getContainer annotation.id)
if thread.message?.annotation
angular.extend thread.message.annotation, annotation
else
thread.message =
annotation: annotation
id: annotation.id
references: annotation.thread?.split('/')
@plugins.Threading.thread annotation
annotation
showViewer: (annotations=[]) =>
@element.injector().invoke [
......@@ -294,13 +224,17 @@ class Hypothesis extends Annotator
showEditor: (annotation) =>
@element.injector().invoke [
'$location', '$rootScope',
($location, $rootScope) ->
$location.path('/editor')
.search
id: annotation.id
.replace()
'$location', '$rootScope', '$route'
($location, $rootScope, $route) ->
# Set the path
$location.path('/editor').search('id', null).replace()
# Digest the change
$rootScope.$digest()
# Push the annotation into the editor scope
if $route.current.controller is 'EditorController'
$route.current.locals.$scope.$apply (s) -> s.annotation = annotation
]
this.show()
this
......@@ -391,4 +325,3 @@ angular.module('h.services', [])
.provider('drafts', DraftProvider)
.provider('flash', FlashProvider)
.service('annotator', Hypothesis)
.value('threading', mail.messageThread())
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