Commit e2dc84be authored by Randall Leeds's avatar Randall Leeds

Turn Stream into App

This refactor turns the stream page a part of our main H application

Main features:
* Stream is now accessible from our app at the /stream angular route
* Stream has the same header (same look, same code) as the main app
* Authentication from the stream page is now possible
* The same visualsearch-bar is used both for page- and stream search
* Stream page now uses our viewer.html to show annotation cards.
* Replying to/editing/deleting annotations can be done in the stream
page too.

Details
* streamsearch.pt is obsolete and has been delete because app.pt is
used instead of it.
* streamviewer.html is obsolete and has been deleted because
viewer.html is used instead of it.
* The QueryParser class is moved into the streamfilter.coffee and
available is a new service for the app.
* The visualsearch directive now uses functions living in the
<current scope>.search object that any part of the app can configure it.
parent e721c4cd
...@@ -12,6 +12,7 @@ imports = [ ...@@ -12,6 +12,7 @@ imports = [
'h.session' 'h.session'
'h.services' 'h.services'
'h.socket' 'h.socket'
'h.streamsearch'
] ]
...@@ -48,6 +49,10 @@ configure = [ ...@@ -48,6 +49,10 @@ configure = [
controller: 'SearchController' controller: 'SearchController'
reloadOnSearch: false reloadOnSearch: false
templateUrl: 'page_search.html' templateUrl: 'page_search.html'
$routeProvider.when '/stream',
controller: 'StreamSearchController'
reloadOnSearch: false
templateUrl: 'viewer.html'
$routeProvider.otherwise $routeProvider.otherwise
redirectTo: '/viewer' redirectTo: '/viewer'
......
...@@ -33,11 +33,11 @@ class App ...@@ -33,11 +33,11 @@ class App
] ]
this.$inject = [ this.$inject = [
'$element', '$filter', '$http', '$location', '$rootScope', '$scope', '$timeout', '$element', '$location', '$q', '$rootScope', '$scope', '$timeout',
'annotator', 'flash', 'session', 'socket', 'streamfilter', 'viewFilter' 'annotator', 'flash', 'session', 'socket', 'streamfilter', 'viewFilter'
] ]
constructor: ( constructor: (
$element, $filter, $http, $location, $rootScope, $scope, $timeout $element, $location, $q, $rootScope, $scope, $timeout
annotator, flash, session, socket, streamfilter, viewFilter annotator, flash, session, socket, streamfilter, viewFilter
) -> ) ->
{plugins, host, providers} = annotator {plugins, host, providers} = annotator
...@@ -255,16 +255,34 @@ class App ...@@ -255,16 +255,34 @@ class App
$scope.searchFacets = SEARCH_FACETS $scope.searchFacets = SEARCH_FACETS
$scope.searchValues = SEARCH_VALUES $scope.searchValues = SEARCH_VALUES
$scope.search = (searchCollection) -> $scope.search = {}
$scope.search.update = angular.noop
$scope.search.clear = angular.noop
$scope.search.query = -> $scope.query
$rootScope.$on '$routeChangeSuccess', (event, next, current) ->
unless next.$$route? then return
unless next.$$route.originalPath is '/stream'
session.$promise.then ->
$scope.updater.then (sock) ->
$timeout ->
entities = Object.keys(plugins.Store?.entities or {})
streamfilter.resetFilter().addClause('/uri', 'one_of', entities)
filter = streamfilter.getFilter()
sock.send(JSON.stringify(filter: filter))
$scope.search.update = (searchCollection) ->
return unless annotator.discardDrafts() return unless annotator.discardDrafts()
return unless searchCollection.models.length return unless searchCollection.models.length
models = searchCollection.models
matched = [] matched = []
query = query =
tags: [] tags: []
quote: [] quote: []
for item in searchCollection.models for item in models
{category, value} = item.attributes {category, value} = item.attributes
# Stuff we need to collect # Stuff we need to collect
...@@ -279,7 +297,7 @@ class App ...@@ -279,7 +297,7 @@ class App
$location.path('/page_search').search(query) $location.path('/page_search').search(query)
$scope.searchClear = -> $scope.search.clear = ->
$location.url('/viewer') $location.url('/viewer')
$scope.show_search = false $scope.show_search = false
...@@ -337,27 +355,25 @@ class App ...@@ -337,27 +355,25 @@ class App
annotations = (a for a in annotations when a not in deleted) annotations = (a for a in annotations when a not in deleted)
if annotations.length is 0 if annotations.length is 0
annotator.unsubscribe 'annotationsLoaded', cleanup annotator.unsubscribe 'annotationsLoaded', cleanup
$scope.$broadcast 'ReRenderPageSearch' $scope.$broadcast 'RefreshSearch'
, 10 , 10
cleanup (a for a in annotations when a.thread) cleanup (a for a in annotations when a.thread)
annotator.subscribe 'annotationsLoaded', cleanup annotator.subscribe 'annotationsLoaded', cleanup
$scope.initUpdater = -> $scope.initUpdater = ->
filter = _dfdSock = $q.defer()
streamfilter _sock = socket()
.setPastDataNone()
.setMatchPolicyIncludeAny() $scope.updater?.then (sock) -> sock.close()
.addClause('/uri', 'one_of', Object.keys(plugins.Store.entities)) $scope.updater = _dfdSock.promise
.getFilter()
$scope.updater = socket() _sock.onopen = ->
$scope.updater.onopen = -> _dfdSock.resolve(_sock)
$scope.updater.send(JSON.stringify({filter}))
$scope.updater.onclose = => _sock.onclose = ->
$timeout $scope.initUpdater, 60000 $scope.initUpdater()
$scope.updater.onmessage = (msg) => _sock.onmessage = (msg) ->
#console.log msg #console.log msg
unless msg.data.type? and msg.data.type is 'annotation-notification' unless msg.data.type? and msg.data.type is 'annotation-notification'
return return
...@@ -376,7 +392,7 @@ class App ...@@ -376,7 +392,7 @@ class App
else else
$scope.applyUpdates action, data $scope.applyUpdates action, data
$scope.markAnnotationUpdate = (data) => $scope.markAnnotationUpdate = (data) ->
for annotation in data for annotation in data
# We need to flag the top level # We need to flag the top level
if annotation.references? if annotation.references?
...@@ -389,9 +405,30 @@ class App ...@@ -389,9 +405,30 @@ class App
else else
annotation._updatedAnnotation = true annotation._updatedAnnotation = true
$scope.applyUpdates = (action, data) => $scope.applyUpdates = (action, data) ->
return unless data?.length
if action == 'past'
action = 'create'
inRootScope = (annotation) ->
for ann in $rootScope.annotations
return true if ann.id is annotation.id
false
switch action switch action
when 'create' when 'create'
# Sorting the data for updates.
# Because sometimes a reply can arrive in the same package as the
# Root annotation, we have to make a len(references, updates sort
data.sort (a,b) ->
ref_a = a.references?.length or 0
ref_b = b.references?.length or 0
return ref_a - ref_b if ref_a != ref_b
a_upd = if a.updated? then new Date(a.updated) else new Date()
b_upd = if b.updated? then new Date(b.updated) else new Date()
a_upd.getTime() - b_upd.getTime()
# XXX: Temporary workaround until solving the race condition for annotationsLoaded event # XXX: Temporary workaround until solving the race condition for annotationsLoaded event
# Between threading and bridge plugins # Between threading and bridge plugins
for annotation in data for annotation in data
...@@ -399,23 +436,37 @@ class App ...@@ -399,23 +436,37 @@ class App
$scope.markAnnotationUpdate data $scope.markAnnotationUpdate data
$scope.$apply =>
if plugins.Store? if plugins.Store?
plugins.Store._onLoadAnnotations data plugins.Store._onLoadAnnotations data
# XXX: Ugly workaround to update the scope content # XXX: Ugly workaround to update the scope content
for annotation in data
switch $rootScope.viewState.view switch $rootScope.viewState.view
when 'Document' when 'Document'
unless annotator.isComment(annotation) or annotation.references? unless annotator.isComment(annotation) or annotation.references?
$rootScope.annotations.push annotation $rootScope.annotations.push annotation if not inRootScope(annotation)
when 'Comments' when 'Comments'
if annotator.isComment(annotation) if annotator.isComment(annotation)
$rootScope.annotations.push annotation $rootScope.annotations.push annotation if not inRootScope(annotation)
else
$rootScope.annotations.push annotation if not inRootScope(annotation)
when 'update' when 'update'
$scope.markAnnotationUpdate data $scope.markAnnotationUpdate data
plugins.Store._onLoadAnnotations data plugins.Store._onLoadAnnotations data
if $location.path() is '/stream'
for annotation in data
$rootScope.annotations.push annotation if not inRootScope(annotation)
when 'delete' when 'delete'
$scope.markAnnotationUpdate data $scope.markAnnotationUpdate data
for annotation in data for annotation in data
# Remove it from the rootScope too
for ann, index in $rootScope.annotations
if ann.id is annotation.id
$rootScope.annotations.splice(index, 1)
break
container = annotator.threading.getContainer annotation.id container = annotator.threading.getContainer annotation.id
if container.message if container.message
# XXX: This is a temporary workaround until real client-side only # XXX: This is a temporary workaround until real client-side only
...@@ -424,6 +475,9 @@ class App ...@@ -424,6 +475,9 @@ class App
plugins.Store.annotations[index..index] = [] if index > -1 plugins.Store.annotations[index..index] = [] if index > -1
annotator.deleteAnnotation container.message annotator.deleteAnnotation container.message
# Refresh page_search
$rootScope.$broadcast 'RefreshSearch' if $location.path() is '/page_search' and data.length
# Finally blink the changed tabs # Finally blink the changed tabs
$timeout => $timeout =>
for p in annotator.providers for p in annotator.providers
...@@ -706,7 +760,7 @@ class Editor ...@@ -706,7 +760,7 @@ class Editor
class Viewer class Viewer
this.$inject = [ this.$inject = [
'$location', '$rootScope', '$routeParams', '$scope', '$location', '$rootScope', '$routeParams', '$scope',
'annotator', 'viewFilter' 'annotator'
] ]
constructor: ( constructor: (
$location, $rootScope, $routeParams, $scope, $location, $rootScope, $routeParams, $scope,
...@@ -929,7 +983,7 @@ class Search ...@@ -929,7 +983,7 @@ class Search
for thread in threads for thread in threads
$rootScope.focus thread.message, true $rootScope.focus thread.message, true
$scope.$on 'ReRenderPageSearch', refresh $scope.$on 'RefreshSearch', refresh
$scope.$on '$routeUpdate', refresh $scope.$on '$routeUpdate', refresh
$scope.getThreadId = (id) -> $scope.getThreadId = (id) ->
......
...@@ -386,15 +386,6 @@ fuzzytime = ['$filter', '$window', ($filter, $window) -> ...@@ -386,15 +386,6 @@ fuzzytime = ['$filter', '$window', ($filter, $window) ->
template: '<a target="_blank" href="{{shared_link}}" title="{{hint}}">{{ftime | date:mediumDate}}</a>' template: '<a target="_blank" href="{{shared_link}}" title="{{hint}}">{{ftime | date:mediumDate}}</a>'
] ]
streamviewer = [ ->
link: (scope, elem, attr, ctrl) ->
return unless ctrl?
require: '?ngModel'
restrict: 'E'
templateUrl: 'streamviewer.html'
]
visualSearch = ['$parse', ($parse) -> visualSearch = ['$parse', ($parse) ->
link: (scope, elem, attr, ctrl) -> link: (scope, elem, attr, ctrl) ->
...@@ -423,6 +414,9 @@ visualSearch = ['$parse', ($parse) -> ...@@ -423,6 +414,9 @@ visualSearch = ['$parse', ($parse) ->
values = _values(scope)?[facet] values = _values(scope)?[facet]
callback(values or [], preserveOrder: true) callback(values or [], preserveOrder: true)
scope.$on 'VSSearch', ->
_search(scope, {"this": _vs.searchQuery})
scope.$watch attr.query, (query) -> scope.$watch attr.query, (query) ->
terms = terms =
for k, v of query for k, v of query
...@@ -462,6 +456,5 @@ angular.module('h.directives', ['ngSanitize']) ...@@ -462,6 +456,5 @@ angular.module('h.directives', ['ngSanitize'])
.directive('username', username) .directive('username', username)
.directive('userPicker', userPicker) .directive('userPicker', userPicker)
.directive('repeatAnim', repeatAnim) .directive('repeatAnim', repeatAnim)
.directive('streamviewer', streamviewer)
.directive('visualSearch', visualSearch) .directive('visualSearch', visualSearch)
.directive('whenscrolled', whenscrolled) .directive('whenscrolled', whenscrolled)
# This class will process the results of search and generate the correct filter
# It expects the following dict format as rules
# { facet_name : {
# formatter: to format the value (optional)
# path: json path mapping to the annotation field
# exact_match: true|false (default: true)
# case_sensitive: true|false (default: false)
# and_or: and|or for multiple values should it threat them as 'or' or 'and' (def: or)
# operator: if given it'll use this operator regardless of other circumstances
#
# options: backend specific options
# options.es: elasticsearch specific options
# options.es.query_type : can be: simple, query_string, match
# defaults to: simple, determines which es query type to use
# options.es.cutoff_frequency: if set, the query will be given a cutoff_frequency for this facet
# options.es.and_or: match queries can use this, defaults to and
# }
# The models is the direct output from visualsearch
class QueryParser
rules:
user:
formatter: (user) ->
'acct:' + user + '@' + window.location.hostname
path: '/user'
exact_match: true
case_sensitive: false
and_or: 'or'
text:
path: '/text'
exact_match: false
case_sensitive: false
and_or: 'and'
tags:
path: '/tags'
exact_match: false
case_sensitive: false
and_or: 'or'
quote:
path: "/quote"
exact_match: false
case_sensitive: false
and_or: 'and'
uri:
formatter: (uri) ->
uri.toLowerCase()
path: '/uri'
exact_match: false
case_sensitive: false
and_or: 'or'
options:
es:
query_type: 'match'
cutoff_frequency: 0.001
and_or: 'and'
since:
formatter: (past) ->
seconds =
switch past
when '5 min' then 5*60
when '30 min' then 30*60
when '1 hour' then 60*60
when '12 hours' then 12*60*60
when '1 day' then 24*60*60
when '1 week' then 7*24*60*60
when '1 month' then 30*24*60*60
when '1 year' then 365*24*60*60
new Date(new Date().valueOf() - seconds*1000)
path: '/created'
exact_match: false
case_sensitive: true
and_or: 'and'
operator: 'ge'
populateFilter: (filter, models) =>
# First cluster the different facets into categories
categories = {}
for searchItem in models
category = searchItem.attributes.category
value = searchItem.attributes.value
limit = 50
if category is 'results' then limit = value
else
if category is 'text'
# Visualsearch sickly automatically cluster the text field
# (and only the text filed) into a space separated string
catlist = []
catlist.push val for val in value.split ' '
categories[category] = catlist
else
if category of categories then categories[category].push value
else categories[category] = [value]
# Now for the categories
for category, values of categories
unless @rules[category]? then continue
unless values.length then continue
rule = @rules[category]
# Now generate the clause with the help of the rule
exact_match = if rule.exact_match? then rule.exact_match else true
case_sensitive = if rule.case_sensitive? then rule.case_sensitive else false
and_or = if rule.and_or? then rule.and_or else 'or'
mapped_field = if rule.path? then rule.path else '/'+category
if values.length is 1
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'equals' else 'matches'
value_part = if rule.formatter then rule.formatter values[0] else values[0]
filter.addClause mapped_field, oper_part, value_part, case_sensitive, rule.options
else
if and_or is 'or'
val_list = ''
first = true
for val in values
unless first then val_list += ',' else first = false
value_part = if rule.formatter then rule.formatter val else val
val_list += value_part
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'one_of' else 'match_of'
filter.addClause mapped_field, oper_part, val_list, case_sensitive, rule.options
else
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'equals' else 'matches'
for val in values
value_part = if rule.formatter then rule.formatter val else val
filter.addClause mapped_field, oper_part, value_part, case_sensitive, rule.options
categories['results'] = [limit]
categories
class StreamFilter class StreamFilter
strategies: ['include_any', 'include_all', 'exclude_any', 'exclude_all'] strategies: ['include_any', 'include_all', 'exclude_any', 'exclude_all']
past_modes: ['none','hits','time'] past_modes: ['none','hits','time']
...@@ -103,5 +239,6 @@ class StreamFilter ...@@ -103,5 +239,6 @@ class StreamFilter
this this
angular.module('h.streamfilter',['bootstrap']) angular.module('h.streamfilter', [])
.service('streamfilter', StreamFilter) .service('queryparser', QueryParser)
\ No newline at end of file .service('streamfilter', StreamFilter)
imports = [ imports = [
'bootstrap' 'bootstrap'
'h.controllers'
'h.directives' 'h.directives'
'h.filters' 'h.filters'
'h.flash'
'h.helpers' 'h.helpers'
'h.session'
'h.socket' 'h.socket'
'h.streamfilter' 'h.streamfilter'
] ]
...@@ -13,295 +16,76 @@ SEARCH_VALUES = ...@@ -13,295 +16,76 @@ SEARCH_VALUES =
since: ['5 min', '30 min', '1 hour', '12 hours', since: ['5 min', '30 min', '1 hour', '12 hours',
'1 day', '1 week', '1 month', '1 year'] '1 day', '1 week', '1 month', '1 year']
get_quote = (annotation) ->
if annotation.quote? then return annotation.quote
if not 'target' in annotation then return ''
quote = '(Reply annotation)'
for target in annotation['target']
for selector in target['selector']
if selector['type'] is 'TextQuoteSelector'
quote = selector['exact'] + ' '
quote
# This class will process the results of search and generate the correct filter
# It expects the following dict format as rules
# { facet_name : {
# formatter: to format the value (optional)
# path: json path mapping to the annotation field
# exact_match: true|false (default: true)
# case_sensitive: true|false (default: false)
# and_or: and|or for multiple values should it threat them as 'or' or 'and' (def: or)
# operator: if given it'll use this operator regardless of other circumstances
#
# options: backend specific options
# options.es: elasticsearch specific options
# options.es.query_type : can be: simple, query_string, match
# defaults to: simple, determines which es query type to use
# options.es.cutoff_frequency: if set, the query will be given a cutoff_frequency for this facet
# options.es.and_or: match queries can use this, defaults to and
# }
# The models is the direct output from visualsearch
# The limit is the default limit
class SearchHelper
populateFilter: (filter, models, rules, limit = 50) ->
# First cluster the different facets into categories
categories = {}
for searchItem in models
category = searchItem.attributes.category
value = searchItem.attributes.value
if category is 'results' then limit = value
else
if category is 'text'
# Visualsearch sickly automatically cluster the text field
# (and only the text filed) into a space separated string
catlist = []
catlist.push val for val in value.split ' '
categories[category] = catlist
else
if category of categories then categories[category].push value
else categories[category] = [value]
filter.setPastDataHits(limit)
# Now for the categories
for category, values of categories
unless rules[category]? then continue
unless values.length then continue
rule = rules[category]
# Now generate the clause with the help of the rule
exact_match = if rule.exact_match? then rule.exact_match else true
case_sensitive = if rule.case_sensitive? then rule.case_sensitive else false
and_or = if rule.and_or? then rule.and_or else 'or'
mapped_field = if rule.path? then rule.path else '/'+category
if values.length is 1
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'equals' else 'matches'
value_part = if rule.formatter then rule.formatter values[0] else values[0]
filter.addClause mapped_field, oper_part, value_part, case_sensitive, rule.options
else
if and_or is 'or'
val_list = ''
first = true
for val in values
unless first then val_list += ',' else first = false
value_part = if rule.formatter then rule.formatter val else val
val_list += value_part
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'one_of' else 'match_of'
filter.addClause mapped_field, oper_part, val_list, case_sensitive, rule.options
else
oper_part =
if rule.operator? then rule.operator
else if exact_match then 'equals' else 'matches'
for val in values
value_part = if rule.formatter then rule.formatter val else val
filter.addClause mapped_field, oper_part, value_part, case_sensitive, rule.options
if limit != 50 then categories['results'] = [limit]
[filter.getFilter(), categories]
class StreamSearch class StreamSearch
facets: ['text','tags', 'uri', 'quote','since','user','results']
rules:
user:
formatter: (user) ->
# FIXME: inject $window
unless '@' in user
user += '@' + window.location.hostname
'acct:' + user
path: '/user'
exact_match: true
case_sensitive: false
and_or: 'or'
text:
path: '/text'
exact_match: false
case_sensitive: false
and_or: 'and'
tags:
path: '/tags'
exact_match: false
case_sensitive: false
and_or: 'or'
quote:
path: "/quote"
exact_match: false
case_sensitive: false
and_or: 'and'
uri:
formatter: (uri) ->
uri.toLowerCase()
path: '/uri'
exact_match: false
case_sensitive: false
and_or: 'or'
options:
es:
query_type: 'match'
cutoff_frequency: 0.001
and_or: 'and'
since:
formatter: (past) ->
seconds =
switch past
when '5 min' then 5*60
when '30 min' then 30*60
when '1 hour' then 60*60
when '12 hours' then 12*60*60
when '1 day' then 24*60*60
when '1 week' then 7*24*60*60
when '1 month' then 30*24*60*60
when '1 year' then 365*24*60*60
new Date(new Date().valueOf() - seconds*1000)
path: '/created'
exact_match: false
case_sensitive: true
and_or: 'and'
operator: 'ge'
this.inject = [ this.inject = [
'$element', '$location', '$scope', '$timeout', '$location', '$rootScope', '$scope', '$timeout',
'baseURI', 'socket', 'streamfilter' 'annotator', 'baseURI', 'queryparser', 'session', 'socket', 'streamfilter'
] ]
constructor: ( constructor: (
$element, $location, $scope, $timeout, $location, $rootScope, $scope, $timeout,
baseURI, socket, streamfilter annotator, baseURI, queryparser, session, socket, streamfilter
) -> ) ->
$scope.empty = false $scope.empty = false
$scope.sortAnnotations = (a, b) -> $scope.removeAnnotations = ->
a_upd = if a.updated? then new Date(a.updated) else new Date() $rootScope.annotations = []
b_upd = if b.updated? then new Date(b.updated) else new Date() if annotator.plugins.Store?
a_upd.getTime() - b_upd.getTime() # Copy annotation list
annotations = annotator.plugins.Store.annotations.splice(0)
# XXX: Temporary workaround until client-side delete only is implemented
annotator.plugins.Store.annotations = []
annotator.deleteAnnotation annotation for annotation in annotations
# Read search params annotations = []
search = $location.search()
for k,v of search
if '+' in v
search[k] = v.replace /\+/g, ' '
$scope.query = search $scope.search.update = (searchCollection) ->
$scope.searchFacets = SEARCH_FACETS return unless searchCollection.models.length
$scope.searchValues = SEARCH_VALUES
# Empty the stream
$scope.removeAnnotations()
$scope.search = (searchCollection) =>
# Assemble the filter json # Assemble the filter json
filter = filter =
streamfilter streamfilter
.resetFilter()
.setMatchPolicyIncludeAll() .setMatchPolicyIncludeAll()
.noClauses() .setPastDataHits(50)
query = queryparser.populateFilter filter, searchCollection.models
filter = streamfilter.getFilter()
[filter, $scope.categories] = session.$promise.then ->
new SearchHelper().populateFilter filter, searchCollection.models, @rules $scope.updater.then (sock) ->
$scope.initStream filter sock.send(JSON.stringify(filter: filter))
# Update the parameters # Update the parameters
$location.search $scope.categories $location.search query
$scope.searchClear = -> $scope.search.clear = ->
$scope.removeAnnotations()
filter = filter =
streamfilter streamfilter
.resetFilter() .resetFilter()
.setPastDataHits(50) .setPastDataHits(50)
$scope.annotations = []
$scope.empty = false
$scope.initStream filter.getFilter()
$location.search({}) $location.search({})
$scope.initStream = (filter) -> $scope.openDetails = (annotation) ->
if $scope.sock? then $scope.sock.close()
$scope.annotations = new Array()
$scope.sock = socket()
$scope.sock.onopen = ->
$scope.sock.send(JSON.stringify({filter}))
$scope.sock.onclose = =>
# stream is closed
$scope.sock.onmessage = (msg) =>
console.log 'Got something'
console.log msg
unless msg.data.type? and msg.data.type is 'annotation-notification'
return
data = msg.data.payload
action = msg.data.options.action
unless data instanceof Array then data = [data]
if data.length
$scope.$apply =>
$scope.empty = false
$scope.manage_new_data data, action
else
unless $scope.annotations.length
$scope.$apply =>
$scope.empty = true
$scope.manage_new_data = (data, action) =>
for annotation in data
annotation.action = action
annotation.quote = get_quote annotation
annotation._share_link = "#{baseURI}a/#{annotation.id}"
if annotation in $scope.annotations then continue
switch action
when 'create', 'past'
unless annotation in $scope.annotations
$scope.annotations.unshift annotation
when 'update'
index = 0
found = false
for ann in $scope.annotations
if ann.id is annotation.id
# Remove the original
$scope.annotations.splice index,1
# Put back the edited
$scope.annotations.unshift annotation
found = true
break
index +=1
# Sometimes editing an annotation makes it appear in the list
# If it wasn't part of it before. (i.e. adding a new tag)
unless found
$scope.annotations.unshift annotation
when 'delete'
index = 0
for ann in $scope.annotations
if ann.id is annotation.id
$scope.annotations.splice index,1
break
index +=1
$scope.annotations = $scope.annotations.sort($scope.sortAnnotations).reverse()
$scope.loadMore = (number) => $scope.loadMore = (number) =>
console.log 'loadMore' unless $scope.updater? then return
unless $scope.sock? then return
sockmsg = sockmsg =
messageType: 'more_hits' messageType: 'more_hits'
moreHits: number moreHits: number
$scope.sock.send JSON.stringify sockmsg $scope.updater.send(JSON.stringify(sockmsg))
$scope.annotations = [] $scope.search.query = -> $scope.query
$scope.query = $location.search()
configure = [
'$locationProvider'
($locationProvider) ->
$locationProvider.html5Mode(true)
]
$scope.$on 'RefreshSearch', ->
$rootScope.$broadcast 'VSSearch'
$rootScope.$broadcast 'VSSearch'
angular.module('h.streamsearch', imports, configure) angular.module('h.streamsearch', imports, configure)
.constant('searchFacets', SEARCH_FACETS)
.constant('searchValues', SEARCH_VALUES)
.controller('StreamSearchController', StreamSearch) .controller('StreamSearchController', StreamSearch)
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