Commit 51c56980 authored by Randall Leeds's avatar Randall Leeds

Merge branch '719-stream-and-search-2' into develop

parents 00c8b946 9cc0e7a7
...@@ -642,6 +642,15 @@ blockquote { ...@@ -642,6 +642,15 @@ blockquote {
display: inline-block; display: inline-block;
font-family: $sansFontFamily; font-family: $sansFontFamily;
} }
&.search-upper {
padding: 0em;
padding-top: .15em;
}
}
.right-border {
padding-right: 3.7em;
} }
} }
......
imports = [ imports = [
'bootstrap' 'bootstrap'
'ngRoute'
'h.controllers' 'h.controllers'
'h.directives' 'h.directives'
'h.app_directives' 'h.app_directives'
......
...@@ -384,6 +384,26 @@ fuzzytime = ['$filter', '$window', ($filter, $window) -> ...@@ -384,6 +384,26 @@ fuzzytime = ['$filter', '$window', ($filter, $window) ->
template: '<span class="small">{{ftime | date:mediumDate}}</span>' template: '<span class="small">{{ftime | date:mediumDate}}</span>'
] ]
streamviewer = [ ->
link: (scope, elem, attr, ctrl) ->
return unless ctrl?
require: '?ngModel'
restrict: 'E'
templateUrl: 'streamviewer.html'
]
whenscrolled = ['$window', ($window) ->
link: (scope, elem, attr) ->
$window = angular.element($window)
$window.on 'scroll', ->
windowBottom = $window.height() + $window.scrollTop()
elementBottom = elem.offset().top + elem.height()
remaining = elementBottom - windowBottom
shouldScroll = remaining <= $window.height() * 0
if shouldScroll
scope.$apply attr.whenscrolled
]
angular.module('h.directives', ['ngSanitize']) angular.module('h.directives', ['ngSanitize'])
.directive('authentication', authentication) .directive('authentication', authentication)
...@@ -401,4 +421,5 @@ angular.module('h.directives', ['ngSanitize']) ...@@ -401,4 +421,5 @@ angular.module('h.directives', ['ngSanitize'])
.directive('ngBlur', ngBlur) .directive('ngBlur', ngBlur)
.directive('repeatAnim', repeatAnim) .directive('repeatAnim', repeatAnim)
.directive('notification', notification) .directive('notification', notification)
.directive('streamviewer', streamviewer)
.directive('whenscrolled', whenscrolled)
...@@ -39,25 +39,28 @@ class FlashProvider ...@@ -39,25 +39,28 @@ class FlashProvider
] ]
flashInterceptor = ['$q', 'flash', ($q, flash) ->
response: (response) ->
data = response.data
format = response.headers 'content-type'
if format?.match /^application\/json/
if data.flash?
flash q, msgs for q, msgs of data.flash
if data.status is 'failure'
flash 'error', data.reason
$q.reject(data.reason)
else if data.status is 'okay'
response.data = data.model
response
else
response
]
angular.module('h.flash', ['ngResource']) angular.module('h.flash', ['ngResource'])
.provider('flash', FlashProvider) .provider('flash', FlashProvider)
.factory('flashInterceptor', flashInterceptor)
.config(['$httpProvider', ($httpProvider) -> .config(['$httpProvider', ($httpProvider) ->
$httpProvider.responseInterceptors.push ['$q', 'flash', ($q, flash) -> $httpProvider.interceptors.push 'flashInterceptor'
(promise) ->
promise.then (response) ->
data = response.data
format = response.headers 'content-type'
if format?.match /^application\/json/
if data.flash?
flash q, msgs for q, msgs of data.flash
if data.status is 'failure'
flash 'error', data.reason
$q.reject(data.reason)
else if data.status is 'okay'
response.data = data.model
response
else
response
]
]) ])
\ No newline at end of file
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
class Stream
path: window.location.protocol + '//' + window.location.hostname + ':' +
window.location.port + '/__streamer__'
this.$inject = ['$location','$scope','$timeout','streamfilter']
constructor: ($location, $scope, $timeout, streamfilter) ->
$scope.annotations = []
urlParts = $location.absUrl().split('/')
$scope.filterValue = urlParts.pop()
filterType = urlParts.pop()
if filterType == "t"
$scope.filterDescription = "Annotations with tag '#{ $scope.filterValue }'"
filterClause = 'tags:i#' + $scope.filterValue
else
$scope.filterDescription = "Annotations by user '#{ $scope.filterValue }'"
filterClause = 'user:i=' + $scope.filterValue
# Generate client ID
buffer = new Array(16)
uuid.v4 null, buffer, 0
@clientID = uuid.unparse buffer
$scope.filter =
streamfilter
.setPastDataHits(150)
.setMatchPolicyIncludeAny()
.setClausesParse(filterClause)
.getFilter()
$scope.manage_new_data = (data, action) =>
for annotation in data
annotation.action = action
annotation.quote = get_quote annotation
annotation._share_link = window.location.protocol +
'//' + window.location.hostname + ':' + window.location.port + "/a/" + annotation.id
annotation._anim = 'fade'
switch action
when 'create', 'past'
unless annotation in $scope.annotations
$scope.annotations.unshift annotation
when 'update'
index = 0
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
break
index +=1
when 'delete'
for ann in $scope.annotations
if ann.id is annotation.id
$scope.annotations.splice index,1
break
index +=1
$scope.open = =>
$scope.sock = new SockJS(@path)
$scope.sock.onopen = =>
sockmsg =
filter: $scope.filter
clientID: @clientID
$scope.sock.send JSON.stringify sockmsg
$scope.sock.onclose = =>
$timeout $scope.open, 5000
$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]
$scope.$apply =>
$scope.manage_new_data data, action
$scope.open()
angular.module('h.stream',['h.streamfilter', 'h.filters','h.directives','bootstrap'])
.controller('StreamCtrl', Stream)
get_quote = (annotation) ->
if not 'target' in annotation then return ''
quote = '(This is a reply annotation)'
for target in annotation['target']
for selector in target['selector']
if selector['type'] is 'TextQuoteSelector'
quote = selector['exact'] + ' '
quote
syntaxHighlight = (json) ->
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) ->
cls = 'number'
if /^"/.test(match)
if /:$/.test(match) then cls = 'key'
else cls = 'string'
else
if /true|false/.test(match) then cls = 'boolean'
else
if /null/.test(match) then cls = 'null'
return '<span class="' + cls + '">' + match + '</span>'
)
class Streamer
path: window.location.protocol + '//' + window.location.hostname + ':' +
window.location.port + '/__streamer__'
strategies: ['include_any', 'include_all', 'exclude_any', 'exclude_all']
past_modes: ['none','hits','time']
this.$inject = ['$location', '$scope', 'streamfilter', 'clauseparser']
constructor: ($location, $scope, streamfilter, clauseparser) ->
$scope.streaming = false
$scope.annotations = []
$scope.bads = []
$scope.time = 5
$scope.hits = 100
@sfilter = streamfilter
@sfilter.setPastDataHits(100)
$scope.filter = @sfilter.filter
#parse for route params
params = $location.search()
if params.match_policy in @strategies
$scope.filter.match_policy = params.match_policy
if params.action_create
if (typeof params.action_create) is 'boolean'
@sfilter.setActionCreate(params.action_create)
else
@sfilter.setActionCreate(params.action_create is 'true')
if params.action_update
if (typeof params.action_update) is 'boolean'
@sfilter.setActionUpdate(params.action_update)
else
@sfilter.setActionUpdate(params.action_update is 'true')
if params.action_delete
if (typeof params.action_delete) is 'boolean'
@sfilter.setActionDelete(params.action_delete)
else
@sfilter.setActionDelete(params.action_delete is 'true')
if params.load_past in @past_modes
if params.hits? and parseInt(params.hits) is not NaN
@sfilter.setPastDataHits(parseInt(params.hits))
if params.go_back? and parseInt(params.go_back) is not NaN
@sfilter.setPastDataTime(parseInt(params.go_back))
if params.clauses
test_clauses = params.clauses.replace ",", " "
@sfilter.setClausesParse(test_clauses)
$scope.clauses = test_clauses
else
$scope.clauses = ''
console.log $scope.filter
$scope.toggle_past = ->
switch $scope.filter.past_data.load_past
when 'none' then @sfilter.setPastDataTime($scope.time)
when 'time' then @sfilter.setPastDataHits($scope.hits)
when 'hits' then @sfilter.setPastDataNone()
$scope.$watch 'filter', (newValue, oldValue) =>
json = JSON.stringify $scope.filter, undefined, 2
$scope.json_content = syntaxHighlight json
,true
$scope.clause_change = =>
if $scope.clauses.slice(-1) is ' ' or $scope.clauses.length is 0
res = clauseparser.parse_clauses($scope.clauses)
if res?
$scope.filter.clauses = res[0]
$scope.bads = res[1]
else
$scope.filter.clauses = []
$scope.bads = []
$scope.start_streaming = =>
if $scope.streaming
$scope.sock.close()
$scope.streaming = false
res = clauseparser.parse_clauses($scope.clauses)
if res
$scope.filter.clauses = res[0]
$scope.bads = res[1]
unless $scope.bads.length is 0
return
$scope.open()
$scope.open = =>
$scope.sock = new SockJS @path
$scope.sock.onopen = =>
$scope.sock.send JSON.stringify $scope.filter
$scope.streaming = true
$scope.sock.onclose = =>
$scope.streaming = false
$scope.sock.onmessage = (msg) =>
console.log 'Got something'
console.log msg
data = msg.data[0]
action = msg.data[1]
unless data instanceof Array then data = [data]
$scope.$apply =>
$scope.manage_new_data data, action
$scope.manage_new_data = (data, action) =>
for annotation in data
annotation.action = action
annotation.quote = get_quote annotation
$scope.annotations.splice 0,0,annotation
#Update the parameters
$location.search
'match_policy': $scope.filter.match_policy
'action_create': $scope.filter.actions.create
'action_update': $scope.filter.actions.update
'action_delete': $scope.filter.actions.delete
'load_past': $scope.filter.past_data.load_past
'go_back': $scope.filter.past_data.go_back
'hits': $scope.filter.past_data.hits
'clauses' : $scope.clauses.replace " ", ","
$scope.stop_streaming = ->
$scope.sock.close()
$scope.streaming = false
angular.module('h.streamer',['h.streamfilter','h.filters','bootstrap'])
.controller('StreamerCtrl', Streamer)
class ClauseParser class ClauseParser
filter_fields : ['references', 'text', 'user','uri', 'id', 'tags'] filter_fields : ['references', 'text', 'user', 'uri', 'id', 'tags', 'created', 'updated']
operators: ['=', '>', '<', '=>', '>=', '<=', '=<', '[', '#', '^'] operators: ['=','=>', '>=', '<=', '=<', '>', '<', '[', '#', '^', '{']
operator_mapping: operator_mapping:
'=': 'equals' '=': 'equals'
'>': 'gt' '>': 'gt'
'<': 'lt' '<': 'lt'
'=>' : 'ge' '=>': 'ge'
'>=' : 'ge' '>=': 'ge'
'=<': 'le' '=<': 'le'
'<=' : 'le' '<=': 'le'
'[' : 'one_of' '[' : 'one_of'
'#' : 'matches' '#' : 'matches'
'^' : 'first_of' '^' : 'first_of'
'{' : 'match_of' # one_of but not exact search
insensitive_operator : 'i' insensitive_operator : 'i'
parse_clauses: (clauses) -> parse_clauses: (clauses) ->
...@@ -155,12 +156,13 @@ class StreamFilter ...@@ -155,12 +156,13 @@ class StreamFilter
@filter.clauses.push clause @filter.clauses.push clause
this this
addClause: (field, operator, value, case_sensitive = false) -> addClause: (field, operator, value, case_sensitive = false, es_query_string = false) ->
@filter.clauses.push @filter.clauses.push
field: field field: field
operator: operator operator: operator
value: value value: value
case_sensitive: case_sensitive case_sensitive: case_sensitive
es_query_string: es_query_string
this this
setClausesParse: (clauses_to_parse, error_checking = false) -> setClausesParse: (clauses_to_parse, error_checking = false) ->
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/** /**
* @license AngularJS v1.1.4 * @license AngularJS v1.2.0-rc.2
* (c) 2010-2012 Google, Inc. http://angularjs.org * (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular, undefined) { (function(window, angular, undefined) {'use strict';
'use strict';
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/** /**
* @ngdoc overview * @ngdoc overview
* @name ngSanitize * @name ngSanitize
* @description * @description
*
* # ngSanitize
*
* The `ngSanitize` module provides functionality to sanitize HTML.
*
* {@installModule sanitize}
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/ */
/* /*
...@@ -48,68 +57,71 @@ ...@@ -48,68 +57,71 @@
<doc:example module="ngSanitize"> <doc:example module="ngSanitize">
<doc:source> <doc:source>
<script> <script>
function Ctrl($scope) { function Ctrl($scope, $sce) {
$scope.snippet = $scope.snippet =
'<p style="color:blue">an html\n' + '<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>'; 'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
} }
</script> </script>
<div ng-controller="Ctrl"> <div ng-controller="Ctrl">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table> <table>
<tr> <tr>
<td>Filter</td> <td>Directive</td>
<td>How</td>
<td>Source</td> <td>Source</td>
<td>Rendered</td> <td>Rendered</td>
</tr> </tr>
<tr id="html-filter"> <tr id="bind-html-with-sanitize">
<td>html filter</td> <td>ng-bind-html</td>
<td> <td>Automatically uses $sanitize</td>
<pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre> <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
</td> <td><div ng-bind-html="snippet"></div></td>
<td> </tr>
<div ng-bind-html="snippet"></div> <tr id="bind-html-with-trust">
</td> <td>ng-bind-html</td>
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
<td><pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
</tr> </tr>
<tr id="escaped-html"> <tr id="bind-default">
<td>no filter</td> <td>ng-bind</td>
<td>Automatically escapes</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td> <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td> <td><div ng-bind="snippet"></div></td>
</tr> </tr>
<tr id="html-unsafe-filter">
<td>unsafe html filter</td>
<td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html-unsafe="snippet"></div></td>
</tr>
</table> </table>
</div> </div>
</doc:source> </doc:source>
<doc:scenario> <doc:scenario>
it('should sanitize the html snippet ', function() { it('should sanitize the html snippet by default', function() {
expect(using('#html-filter').element('div').html()). expect(using('#bind-html-with-sanitize').element('div').html()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
}); });
it('should inline raw snippet if bound to a trusted value', function() {
expect(using('#bind-html-with-trust').element("div").html()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() { it('should escape snippet without any filter', function() {
expect(using('#escaped-html').element('div').html()). expect(using('#bind-default').element('div').html()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" + toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" + "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;"); "snippet&lt;/p&gt;");
}); });
it('should inline raw snippet if filtered as unsafe', function() {
expect(using('#html-unsafe-filter').element("div").html()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should update', function() { it('should update', function() {
input('snippet').enter('new <b>text</b>'); input('snippet').enter('new <b onclick="alert(1)">text</b>');
expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>'); expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;"); expect(using('#bind-html-with-trust').element('div').html()).toBe('new <b onclick="alert(1)">text</b>');
expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>'); expect(using('#bind-default').element('div').html()).toBe("new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
}); });
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
...@@ -129,7 +141,7 @@ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?: ...@@ -129,7 +141,7 @@ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:
BEGING_END_TAGE_REGEXP = /^<\s*\//, BEGING_END_TAGE_REGEXP = /^<\s*\//,
COMMENT_REGEXP = /<!--(.*?)-->/g, COMMENT_REGEXP = /<!--(.*?)-->/g,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/, URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/i,
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
...@@ -256,7 +268,7 @@ function htmlParser( html, handler ) { ...@@ -256,7 +268,7 @@ function htmlParser( html, handler ) {
} }
if ( html == last ) { if ( html == last ) {
throw "Parse Error: " + html; throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block of html: {0}", html);
} }
last = html; last = html;
} }
...@@ -283,10 +295,10 @@ function htmlParser( html, handler ) { ...@@ -283,10 +295,10 @@ function htmlParser( html, handler ) {
var attrs = {}; var attrs = {};
rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) { rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
var value = doubleQuotedValue var value = doubleQuotedValue
|| singleQoutedValue || singleQuotedValue
|| unqoutedValue || unquotedValue
|| ''; || '';
attrs[name] = decodeEntities(value); attrs[name] = decodeEntities(value);
...@@ -400,37 +412,16 @@ function htmlSanitizeWriter(buf){ ...@@ -400,37 +412,16 @@ function htmlSanitizeWriter(buf){
// define ngSanitize module and register $sanitize service // define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).value('$sanitize', $sanitize); angular.module('ngSanitize', []).value('$sanitize', $sanitize);
/**
* @ngdoc directive
* @name ngSanitize.directive:ngBindHtml
*
* @description
* Creates a binding that will sanitize the result of evaluating the `expression` with the
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
*
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
*
* @element ANY
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
*/
angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
return function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
value = $sanitize(value);
element.html(value || '');
});
};
}]);
/** /**
* @ngdoc filter * @ngdoc filter
* @name ngSanitize.filter:linky * @name ngSanitize.filter:linky
* @function * @function
* *
* @description * @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links. * plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
* *
* @param {string} text Input text. * @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
......
This diff is collapsed.
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