Commit 08928c94 authored by Robert Knight's avatar Robert Knight

Convert viewFilter service to JS

This is an initial translation of the existing viewFilter code to JS
using decaffeinate plus some manual fixups.
parent e903ebac
module.exports = ['unicode', (unicode) ->
_normalize = (e) ->
if typeof e is 'string'
normed = unicode.normalize(e)
return unicode.fold(normed)
else
e
_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 = filter.terms.slice()
copy = copy.filter (e) ->
match value, e
if (filter.operator is 'and' and copy.length < filter.terms.length) or
(filter.operator is 'or' and not copy.length)
matches = false
matches
_checkMatch = (filter, annotation, checker) ->
autofalsefn = checker.autofalse
return false if autofalsefn? and autofalsefn annotation
value = checker.value annotation
if angular.isArray value
value = value.map (e) -> e.toLowerCase()
value = value.map (e) => _normalize(e)
return _arrayMatches filter, value, checker.match
else
value = value.toLowerCase()
value = _normalize(value)
return _matches filter, value, checker.match
# The field configuration
#
# [facet_name]:
# autofalse: a function for a preliminary false match result
# value: a function to extract to facet value for the annotation.
# match: a function to check if the extracted value matches the facet value
fields:
quote:
autofalse: (annotation) -> return annotation.references?
value: (annotation) ->
quotes = for t in (annotation.target or [])
for s in (t.selector or []) when s.type is 'TextQuoteSelector'
unless s.exact then continue
s.exact
quotes = Array::concat quotes...
quotes.join('\n')
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.indexOf(term) > -1
user:
autofalse: (annotation) -> return not annotation.user?
value: (annotation) -> return annotation.user
match: (term, value) -> return value.indexOf(term) > -1
any:
fields: ['quote', 'text', 'tag', 'user']
# Filters a set of annotations, according to a given query.
#
# Inputs:
# annotations is the input list of annotations (array)
# filters is the query is a faceted filter generated by `searchFilter`.
#
# It'll handle the annotation matching by the returned field configuration.
#
# Returns Array of the matched annotation ids.
filter: (annotations, filters) ->
limit = Math.min((filters.result?.terms or [])...)
count = 0
# Normalizing the filters, need to do only once.
for _, filter of filters
if filter.terms
filter.terms = filter.terms.map (e) =>
e = e.toLowerCase()
e = _normalize e
e
for annotation in annotations
break if count >= limit
match = true
for category, filter of filters
break unless match
continue unless filter.terms.length
switch category
when 'any'
categoryMatch = false
for field in @fields.any.fields
for term in filter.terms
termFilter = {terms: [term], operator: "and"}
if _checkMatch(termFilter, annotation, @fields[field])
categoryMatch = true
break
match = categoryMatch
else
match = _checkMatch filter, annotation, @fields[category]
continue unless match
count++
annotation.id
]
'use strict';
// @ngInject
function viewFilter(unicode) {
function _normalize(e) {
if (typeof e === 'string') {
var normed = unicode.normalize(e);
return unicode.fold(normed);
} else {
return e;
}
}
function _matches(filter, value, match) {
var matches = true;
for (var term of filter.terms) {
if (!match(term, value)) {
matches = false;
if (filter.operator === 'and') {
break;
}
} else {
matches = true;
if (filter.operator === 'or') {
break;
}
}
}
return matches;
}
function _arrayMatches(filter, value, match) {
var matches = true;
// Make copy for filtering
var copy = filter.terms.slice();
copy = copy.filter(e => match(value, e));
if (((filter.operator === 'and') && (copy.length < filter.terms.length)) ||
((filter.operator === 'or') && !copy.length)) {
matches = false;
}
return matches;
}
function _checkMatch(filter, annotation, checker) {
var autofalsefn = checker.autofalse;
if (autofalsefn && autofalsefn(annotation)) {
return false;
}
var value = checker.value(annotation);
if (Array.isArray(value)) {
value = value.map(e => e.toLowerCase());
value = value.map(e => _normalize(e));
return _arrayMatches(filter, value, checker.match);
} else {
value = value.toLowerCase();
value = _normalize(value);
return _matches(filter, value, checker.match);
}
}
// The field configuration
//
// [facet_name]:
// autofalse: a function for a preliminary false match result
// value: a function to extract to facet value for the annotation.
// match: a function to check if the extracted value matches the facet value
this.fields = {
quote: {
autofalse: ann => !Array.isArray(ann.references),
value(annotation) {
var quotes = (annotation.target || []).map((t) =>
(() => {
var result = [];
for (var s of (t.selector || [])) {
if (s.type === 'TextQuoteSelector') {
if (!s.exact) { continue; }
result.push(s.exact);
}
}
return result;
})());
quotes = Array.prototype.concat(...quotes);
return quotes.join('\n');
},
match: (term, value) => value.indexOf(term) > -1,
},
since: {
autofalse: ann => typeof ann.updated !== 'string',
value: ann => ann.updated,
match(term, value) {
var delta = Math.round((+new Date - new Date(value)) / 1000);
return delta <= term;
},
},
tag: {
autofalse: ann => !Array.isArray(ann.tags),
value: ann => ann.tags,
match: (term, value) => term.includes(value),
},
text: {
autofalse: ann => typeof ann.text !== 'string',
value: ann => ann.text,
match: (term, value) => value.indexOf(term) > -1,
},
uri: {
autofalse: ann => typeof ann.uri !== 'string',
value: ann => ann.uri,
match: (term, value) => value.indexOf(term) > -1,
},
user: {
autofalse: ann => typeof ann.user !== 'string',
value: ann => ann.user,
match: (term, value) => value.indexOf(term) > -1,
},
any: {
fields: ['quote', 'text', 'tag', 'user'],
},
};
/**
* Filters a set of annotations.
*
* @param {Annotation[]} annotations
* @param {Object} filters - Faceted filter generated by
* `generateFacetedFilter`.
* @return {string[]} IDs of matching annotations.
*/
this.filter = (annotations, filters) => {
var filter;
var limit = Math.min(...((filters.result ? filters.result.terms : undefined) || []) || []);
var count = 0;
// Normalizing the filters, need to do only once.
for (var f in filters) {
if (!filters.hasOwnProperty(f)) {
continue;
}
filter = filters[f];
if (filter.terms) {
filter.terms = filter.terms.map(e => {
e = e.toLowerCase();
e = _normalize(e);
return e;
});
}
}
var result = [];
for (var annotation of annotations) {
if (count >= limit) { break; }
var match = true;
for (var category in filters) {
if (!filters.hasOwnProperty(category)) {
continue;
}
filter = filters[category];
if (!match) { break; }
if (!filter.terms.length) { continue; }
switch (category) {
case 'any':
var categoryMatch = false;
for (var field of this.fields.any.fields) {
for (var term of filter.terms) {
var termFilter = {terms: [term], operator: 'and'};
if (_checkMatch(termFilter, annotation, this.fields[field])) {
categoryMatch = true;
break;
}
}
}
match = categoryMatch;
break;
default:
match = _checkMatch(filter, annotation, this.fields[category]);
}
}
if (!match) { continue; }
count++;
result.push(annotation.id);
}
return result;
};
}
module.exports = viewFilter;
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