Commit 9d6fb43f authored by Ujvari Gergely's avatar Ujvari Gergely Committed by gergely-ujvari

Simplified page search

parent 1edc0a9e
......@@ -2,7 +2,7 @@ imports = [
'bootstrap'
'h.helpers'
'h.socket'
'h.streamfilter'
'h.searchfilters'
]
SEARCH_FACETS = ['text', 'tags', 'uri', 'quote', 'since', 'user', 'results']
......@@ -228,6 +228,16 @@ class App
$rootScope.applySort "Location"
$scope.query = $location.search()
$scope.searchFacets = SEARCH_FACETS
$scope.searchValues = SEARCH_VALUES
$scope.search = {}
$scope.search.update = angular.noop
$scope.search.clear = angular.noop
#$scope.show_search = Object.keys($scope.query).length > 0
$rootScope.$on '$routeChangeSuccess', (event, next, current) ->
unless next.$$route? then return
......@@ -237,27 +247,8 @@ class App
$scope.search.update = (searchCollection) ->
return unless annotator.discardDrafts()
return unless searchCollection.models.length
models = searchCollection.models
matched = []
query =
tags: []
quote: []
for item in models
{category, value} = item.attributes
# Stuff we need to collect
switch
when category in ['text', 'user', 'time', 'group']
query[category] = value
when category == 'tags'
# Tags are specials, because we collect those into an array
query.tags.push value.toLowerCase()
when category == 'quote'
query.quote = query.quote.concat(value.split(/\s+/))
query = {query: searchCollection}
unless angular.equals $location.search(), query
if $location.path() == '/viewer'
$location.path('/page_search').search(query)
......@@ -846,12 +837,18 @@ class Search
refresh()
refresh = =>
$scope.matches = viewFilter.filter $rootScope.annotations, $routeParams
[$scope.matches, $scope.filters] = viewFilter.filter $rootScope.annotations, $routeParams
# Create the regexps for highlighting the matches inside the annotations' bodies
$scope.text_tokens = $routeParams.text?.split(/\s+/) or []
$scope.text_tokens = $scope.filters.text.terms.slice()
$scope.text_regexp = []
$scope.quote_tokens = $routeParams.quote
$scope.quote_tokens = $scope.filters.quote.terms.slice()
$scope.quote_regexp = []
# Highligh any matches
for term in $scope.filters.any.terms
$scope.text_tokens.push term
$scope.quote_tokens.push term
# Saving the regexps and higlighter to the annotator for highlighttext regeneration
for token in $scope.text_tokens
regexp = new RegExp(token,"ig")
......
......@@ -326,11 +326,10 @@ username = ['$filter', '$window', ($filter, $window) ->
link: (scope, elem, attr) ->
scope.$watch 'user', ->
scope.uname = $filter('persona')(scope.user, 'username')
scope.provider = $filter('persona')(scope.user, 'provider')
scope.uclick = (event) ->
event.preventDefault()
$window.open "/u/#{scope.uname}@#{scope.provider}"
$window.open "/u/#{scope.uname}"
return
scope:
......@@ -439,6 +438,20 @@ visualSearch = ['$parse', ($parse) ->
restrict: 'C'
]
simpleSearch = ['$parse', ($parse) ->
link: (scope, elem, attr, ctrl) ->
scope.dosearch = ->
#_search(scope.generateSearchResults())
scope.search.update(scope.searchtext)
scope.$watch attr.query, (query) ->
scope.searchtext = query
scope.search.update(scope.searchtext)
restrict: 'C'
template: '<form name="searchBox" ng-submit="dosearch()"><input type="text" ng-model="searchtext" name="searchText" style="width: 20em"></form>'
]
whenscrolled = ['$window', ($window) ->
link: (scope, elem, attr) ->
$window = angular.element($window)
......@@ -463,4 +476,5 @@ angular.module('h.directives', ['ngSanitize'])
.directive('userPicker', userPicker)
.directive('repeatAnim', repeatAnim)
.directive('visualSearch', visualSearch)
.directive('simpleSearch', simpleSearch)
.directive('whenscrolled', whenscrolled)
......@@ -4,7 +4,7 @@ imports = [
'h.directives'
'h.helpers'
'h.socket'
'h.streamfilter'
'h.searchfilters'
]
......
#ToDo: write me
# This class will parse the search filter and produce a faceted search filter object
class SearchFilter
# This function will slice the searchtext
# Slice character: space,
# but an expression between quotes (' or ") is considered one
_tokenize: (searchtext) ->
return [] unless searchtext
tokens = searchtext.match /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
# Cut the opening and closing quote characters
for token, index in tokens
start = token.slice 0,1
end = token.slice -1
if (start is '"' or start is "'") and (start is end)
tokens[index] = token.slice 1, token.length - 1
tokens
generateFacetedFilter: (searchtext) ->
return unless searchtext
terms = @_tokenize(searchtext)
any = []
quote = []
result = []
since = []
tag = []
text = []
uri = []
user = []
for term in terms
filter = term.slice 0, term.indexOf ":"
unless filter? then filter = ""
switch filter
when 'quote' then quote.push term[6..]
when 'result' then result.push term[7..]
when 'since'
# We'll turn this into seconds
time = term[6..].toLowerCase()
if time.match /^\d+$/
# Only digits, assuming seconds
since.push time
if time.match /^\d+sec$/
# Time given in seconds
t = /^(\d+)sec$/.exec(time)[1]
since.push t
if time.match /^\d+min$/
# Time given in minutes
t = /^(\d+)min$/.exec(time)[1]
since.push t * 60
if time.match /^\d+hour$/
# Time given in hours
t = /^(\d+)hour$/.exec(time)[1]
since.push t * 60 * 60
if time.match /^\d+day$/
# Time given in days
t = /^(\d+)day$/.exec(time)[1]
since.push t * 60 * 60 * 24
if time.match /^\d+week$/
# Time given in week
t = /^(\d+)week$/.exec(time)[1]
since.push t * 60 * 60 * 24 * 7
if time.match /^\d+month$/
# Time given in month
t = /^(\d+)month$/.exec(time)[1]
since.push t * 60 * 60 * 24 * 30
if time.match /^\d+year$/
# Time given in year
t = /^(\d+)year$/.exec(time)[1]
since.push t * 60 * 60 * 24 * 365
when 'tag' then tag.push term[4..]
when 'text' then text.push term[5..]
when 'uri' then uri.push term[4..]
when 'user' then user.push term[5..]
else any.push term
any:
terms: any
operator: 'and'
lowercase: true
quote:
terms: quote
operator: 'lower'
lowercase: true
result:
terms: result
operator: 'min'
lowercase: false
since:
terms: since
operator: 'min'
lowercase: false
tag:
terms: tag
operator: 'and'
lowercase: true
text:
terms: text
operator: 'and'
lowercase: true
uri:
terms: uri
operator: 'or'
lowercase: true
user:
terms: user
operator: 'or'
lowercase: true
# This class will process the results of search and generate the correct filter
# It expects the following dict format as rules
# { facet_name : {
......@@ -220,6 +332,7 @@ class StreamFilter
this
angular.module('h.streamfilter', [])
angular.module('h.searchfilters', [])
.service('searchfilter', SearchFilter)
.service('queryparser', QueryParser)
.service('streamfilter', StreamFilter)
imports = [
'h.filters'
'h.filters',
'h.searchfilters'
]
......@@ -674,18 +675,105 @@ class DraftProvider
class ViewFilter
this.$inject = ['$filter']
constructor: ($filter) ->
checkers:
quote:
autofalse: (annotation) -> return annotation.references?
value: (annotation) ->
for target in annotation.target
return target.quote if target.quote?
''
match: (term, value) -> return value.indexOf(term) > -1
since:
autofalse: (annotation) -> return not annotation.updated?
value: (annotation) -> return annotation.updated
match: (term, value) ->
delta = Math.round((+new Date - new Date(value)) / 1000)
return delta <= term
tag:
autofalse: (annotation) -> return not annotation.tags?
value: (annotation) -> return annotation.tags
match: (term, value) -> return value in term
text:
autofalse: (annotation) -> return not annotation.text?
value: (annotation) -> return annotation.text
match: (term, value) -> return value.indexOf(term) > -1
uri:
autofalse: (annotation) -> return not annotation.uri?
value: (annotation) -> return annotation.uri
match: (term, value) -> return value is term
user:
autofalse: (annotation) -> return not annotation.user?
value: (annotation) ->
# XXX: Hopefully there is a cleaner solution
# XXX: To reach persona filter from here
return (annotation.user?.match /^acct:([^@]+)@(.+)/)?[1]
match: (term, value) -> return value is term
any:
fields: ['quote', 'text', 'tag', 'user']
this.$inject = ['$filter', 'searchfilter']
constructor: ($filter, searchfilter) ->
@user_filter = $filter('persona')
@searchfilter = searchfilter
_matches: (filter, value, match) ->
matches = true
for term in filter.terms
unless match term, value
matches = false
if filter.operator is 'and'
break
else
matches = true
if filter.operator is 'or'
break
matches
_arrayMatches: (filter, value, match) ->
matches = true
# Make copy for filtering
copy = value.slice()
copy.filter (e) ->
not match filter.terms, e
if (filter.operator is 'and' and copy.length < value.length) or
(filter.operator is 'or' and not copy.length)
matches = false
matches
_anyMatches: (filter, value, match) ->
matchresult = []
for term in filter.terms
if value instanceof Array
matchresult.push match value, term
else
matchresult.push match term, value
matchresult
_checkMatch: (filter, annotation, checker) ->
autofalsefn = checker.autofalse
return false if autofalsefn? and autofalsefn annotation
value = checker.value annotation
if value instanceof Array
if filter.lowercase then value = value.map (e) -> e.toLowerCase()
return @_arrayMatches filter, value, checker.match
else
value = value.toLowerCase() if filter.lowercase
return @_matches filter, value, checker.match
# Filters a set of annotations, according to a given query.
#
# annotations is the input list of annotations (array)
# ToDo: update me I'm not up to date!
# query is the query; it's a map. Supported key values are:
# user: username to search for
# text: text to search for in the body (all the words must be present)
# quote: text to search for in the quote (exact phrease must be present)
# quote: text to search for in the quote (exact phrase must be present)
# tags: list of tags to search for. (all must be present)
# time: maximum age of annotation. Accepted values:
# '5 min', '30 min', '1 hour', '12 hours',
......@@ -694,16 +782,22 @@ class ViewFilter
# All search is case insensitive.
#
# Returns the list of matching annotation IDs.
filter: (annotations, query) ->
filter: (annotations, query) =>
return [] unless query.query
filters = @searchfilter.generateFacetedFilter query.query
results = []
# Convert these fields to lower case, if they exist
for key in ['text', 'user']
if query[key]?
query[key] = query[key].toLowerCase()
# Check for given limit
# Find the minimal
limit = 0
if filters.result.terms.length
limit = filter.result.terms[0]
for term in filter.result.terms
if limit > term then limit = term
# We expect a list for quotes
query.quote.map (e) -> e.toLowerCase()
# Convert terms to lowercase if needed
for _, filter of filters
if filter.lowercase then filter.terms.map (e) -> e.toLowerCase()
# Now that this filter is called with the top level annotations, we have to add the children too
annotationsWithChildren = []
......@@ -716,104 +810,55 @@ class ViewFilter
for annotation in annotationsWithChildren
matches = true
for category, value of query
#ToDo: What about given zero limit?
# Limit reached
if limit and results.length >= limit then break
for category, filter of filters
continue if category is 'timestamp'
break unless matches
terms = filter.terms
# No condition for this category
continue unless terms.length
switch category
when 'user'
username = @user_filter annotation.user
unless username?.toLowerCase() is value
matches = false
break
when 'text'
unless annotation.text?
matches = false
break
lowerCaseText = annotation.text.toLowerCase()
for token in value.split ' '
if lowerCaseText.indexOf(token) is -1
matches = false
break
when 'quote'
unless value.length > 0 then continue
# Reply annotations does not have a quote in this aspect
if annotation.references?
matches = false
break
when 'result'
# Handled above
continue
when 'any'
# Special case
matchterms = []
matchterms.push false for term in terms
for field in @checkers.any.fields
conf = @checkers[field]
continue if conf.autofalse? and conf.autofalse annotation
value = conf.value annotation
if value instanceof Array
if filter.lowercase
value = value.map (e) -> e.toLowerCase()
else
value = value.toLowerCase() if filter.lowercase
matchresult = @_anyMatches filter, value, conf.match
matchterms = matchterms.map (t, i) -> t or matchresult[i]
# Now let's see what we got.
matched = 0
for _, value of matchterms
matched++ if value
if (filter.operator is 'or' and matched > 0) or (filter.operator is 'and' and matched is terms.length)
matches = true
else
found = false
for target in annotation.target
if target.quote?
quote = target.quote.toLowerCase()
for val in value
if quote.indexOf(val) > -1
found = true
else
found = false
break
unless found
matches = false
break
when 'tags'
# Don't bother if we got an empty list for required tags
break unless value.length
# If this has no tags, this is in instant failure
if value.length and not annotation.tags?
matches = false
break
# OK, there are some tags, and we need some tags.
# Gotta check for each wanted tag
for wantedTag in value
found = false
for existingTag in annotation.tags
if existingTag.toLowerCase().indexOf(wantedTag) > -1
found = true
break
unless found
matches = false
break
when 'time'
delta = Math.round((+new Date - new Date(annotation.updated)) / 1000)
switch value
when '5 min'
unless delta <= 60*5
matches = false
when '30 min'
unless delta <= 60*30
matches = false
when '1 hour'
unless delta <= 60*60
matches = false
when '12 hours'
unless delta <= 60*60*12
matches = false
when '1 day'
unless delta <= 60*60*24
matches = false
when '1 week'
unless delta <= 60*60*24*7
matches = false
when '1 month'
unless delta <= 60*60*24*31
matches = false
when '1 year'
unless delta <= 60*60*24*366
matches = false
when 'group'
priv_public = 'group:__world__' in (annotation.permissions.read or [])
switch value
when 'Public'
unless priv_public
matches = false
when 'Private'
if priv_public
matches = false
matches = false
else
# For all other categories
matches = @_checkMatch filter, annotation, @checkers[category]
if matches
results.push annotation.id
results
[results, filters]
angular.module('h.services', imports)
......
......@@ -6,7 +6,7 @@ imports = [
'h.flash'
'h.helpers'
'h.session'
'h.streamfilter'
'h.searchfilters'
]
SEARCH_FACETS = ['text', 'tags', 'uri', 'quote', 'since', 'user', 'results']
......
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