Commit 4a604b5d authored by Randall Leeds's avatar Randall Leeds

Merge pull request #1281 from hypothesis/stream-search-refactor

Turn Stream into App
parents e721c4cd 2e0d71b1
@import 'compass';
@import 'compass/css3';
@import 'compass/css3/images';
@import 'compass/utilities/color';
@import 'common';
body {
@extend .noise;
font-family: $sansFontFamily;
......@@ -20,6 +16,8 @@ section {
margin: 2em 0;
}
svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
ol {
list-style-type: decimal;
padding-left: 3em;
......@@ -49,11 +47,10 @@ ol {
.content {
margin: 0 auto;
padding: 36px 1em 1em;
padding-top: 36px;
padding: .72em;
@include respond-to(tablets desktops) {
padding: 36px 4em 4em;
padding: .72em 4em 4em;
margin: auto;
max-width: $break-medium;
}
......
......@@ -74,7 +74,7 @@ $neutral: #0171ba;
$highlightColor: rgba(201, 215, 241, .5);
$highlightModeColor: rgba(255, 255, 60, .5);
$thread-padding: 1em;
$threadexp-width: 10px;
$threadexp-width: .72em;
$score-width: 40px;
$score-height: $score-width;
$heatmap-width: 22px;
......@@ -169,18 +169,16 @@ $input-border-radius: 2px;
//FONTICON////////////////////////////////
@mixin fonticon($char, $iconside, $offset: 0) {
@mixin fonticon($char, $iconside, $offset: .5em) {
text-decoration: none;
line-height: 1;
cursor: pointer;
&:hover {
opacity: 1;
}
@if $iconside == left {
&:before {
content: $char + '\a0' !important;
content: $char !important;
font-family: 'icomoon';
font-style: normal;
margin-right: $offset;
speak: none;
font-weight: normal;
......@@ -188,29 +186,18 @@ $input-border-radius: 2px;
&:after {
content: "" !important;
}
&:empty {
&:before {
content: $char !important;
}
}
}
@if $iconside == right {
&:before {
content: "" !important;
}
&:after {
content: '\a0' + $char !important;
content: $char !important;
font-family: 'icomoon';
font-style: normal;
margin-left: $offset;
speak: none;
font-weight: normal;
}
&:empty {
&:after {
content: $char !important;
}
}
}
}
......@@ -238,6 +225,11 @@ $input-border-radius: 2px;
}
}
.annotator-hide {
display: none;
visibility: hidden;
}
.annotator-notice-info {
color: #3a87ad;
background-color: #d9edf7;
......
......@@ -232,6 +232,7 @@ button, input[type=submit], .btn {
//EXCERPT////////////////////////////////
.excerpt {
margin-bottom: 1em;
position: relative;
blockquote {
margin-bottom: 0;
......@@ -558,49 +559,6 @@ blockquote {
}
//TOOL BAR////////////////////////////////
.topbar {
background: $white;
border: solid 1px $grayLighter;
border-style: solid none;
height: 30px;
line-height: 28px;
position: fixed;
left: 0;
right: 0;
z-index: 5;
.barbutton {
&:hover {
@include box-shadow(inset 0 1px 3px hsla(0, 0%, 0%, .1));
}
&:active {
@include box-shadow(inset 0 2px 3px hsla(0, 0%, 0%, .1));
}
}
.inner {
margin: 0 auto;
max-width: $break-medium;
padding-left: 3.6em;
padding-right: 3.6em;
@include respond-to(wide-handhelds handhelds) {
padding-left: .9em;
padding-right: .9em;
}
&.pull-right {
display: inline-block;
font-family: $sansFontFamily;
}
& > * { margin: 0 4px; }
}
}
//ANNOTATION////////////////////////////////
//This is for everything that is formatted as an annotation.
.annotation {
......@@ -706,7 +664,7 @@ blockquote {
}
& > ul {
padding-left: $thread-padding;
padding-left: $thread-padding + .15em;
margin-left: -$thread-padding;
}
......@@ -743,10 +701,12 @@ blockquote {
}
.threadexp {
font-size: 1.1em; // to match the username header
height: $threadexp-width;
width: $threadexp-width;
position: absolute;
top: $threadexp-width / 2;
// .7em is 50% of a line-height of 1.4;
top: .7em - ($threadexp-width / 2);
left: -($threadexp-width / 2);
outline: 1px solid #aaa;
@include icon("minus_1.png");
......@@ -873,7 +833,7 @@ blockquote {
.stream-list {
& > * {
margin-bottom: .75em;
margin-bottom: .72em;
}
.card-emphasis {
......@@ -969,6 +929,24 @@ blockquote {
width: 0em;
}
//SEARCH HIGHLIGHTS////////////////////////////////
.search-hl-active {
background: $highlightColor;
box-shadow:3px 3px 4px -1px #999999;
&::-moz-selection {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
&::-moz-selection, &::-moz-window-inactive, &::window-inactive {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
&::selection, &::selection:window-inactive {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
}
.searchbar-border {
height: auto;
padding-bottom: 2em;
......@@ -1080,11 +1058,14 @@ h3.stream {
}
.VS-search {
* {
@include box-sizing(content-box);
}
.search_facet input,
.search_input input,
.VS-input-width-tester {
@include box-shadow(none);
@include box-sizing(content-box);
border: 0;
}
......
......@@ -295,7 +295,9 @@ $baseFontSize: 14px;
.annotator-toolbar.annotator-hide {
@include transition-delay(.25s);
display: initial;
height: 37px; // 30px height + 2px (top+bottom border) + 5px shadow
visibility: initial;
li {
@include transition-delay(.2s, 0, 0);
......
@import 'compass';
@import 'compass/css3/transform';
@import 'compass/layout/stretching';
@import 'compass/reset/utilities';
@import 'base';
//ELEMENT STYLES////////////////////////////////
body {
background-color: transparent;
height: 100%;
font-size: .8em;
position: fixed;
width: 100%;
}
svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
//ANNOTATOR STYLES////////////////////////////////
.annotator-hide {
display: none;
visibility: hidden;
}
.excerpt {
margin-bottom: 1em;
}
//SIDEBAR LAYOUT////////////////////////////////
#wrapper {
height: 100%;
overflow-y: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
//SEARCH HIGHLIGHTS////////////////////////////////
.search-hl-active {
background: $highlightColor;
box-shadow:3px 3px 4px -1px #999999;
&::-moz-selection {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
&::-moz-selection, &::-moz-window-inactive, &::window-inactive {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
&::selection, &::selection:window-inactive {
background: $highlightColor;
box-shadow:3px 3px 4px #999999;
}
}
@import 'base';
@import 'responsive';
body {
background-color: transparent;
font-size: .8em;
height: 100%;
width: 100%;
padding-top: 30px;
position: fixed;
}
#wrapper {
height: 100%;
overflow-y: auto;
position: relative;
-webkit-overflow-scrolling: touch;
}
.topbar {
background: $white;
border: solid 1px $grayLighter;
border-style: solid none;
height: 30px;
line-height: 28px;
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 5;
.barbutton {
&:hover {
@include box-shadow(inset 0 1px 3px hsla(0, 0%, 0%, .1));
}
&:active {
@include box-shadow(inset 0 2px 3px hsla(0, 0%, 0%, .1));
}
}
.inner {
margin: 0 auto;
max-width: $break-medium;
padding-left: 3.6em;
padding-right: 3.6em;
@include respond-to(wide-handhelds handhelds) {
padding-left: .9em;
padding-right: .9em;
}
&.pull-right {
display: inline-block;
font-family: $sansFontFamily;
}
& > * { margin: 0 4px; }
}
}
......@@ -12,6 +12,7 @@ imports = [
'h.session'
'h.services'
'h.socket'
'h.streamsearch'
]
......@@ -42,12 +43,13 @@ configure = [
templateUrl: 'editor.html'
$routeProvider.when '/viewer',
controller: 'ViewerController'
reloadOnSearch: false
templateUrl: 'viewer.html'
$routeProvider.when '/page_search',
controller: 'SearchController'
reloadOnSearch: false
templateUrl: 'page_search.html'
$routeProvider.when '/stream',
controller: 'StreamSearchController'
templateUrl: 'viewer.html'
$routeProvider.otherwise
redirectTo: '/viewer'
......
This diff is collapsed.
......@@ -386,15 +386,6 @@ fuzzytime = ['$filter', '$window', ($filter, $window) ->
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) ->
link: (scope, elem, attr, ctrl) ->
......@@ -424,15 +415,13 @@ visualSearch = ['$parse', ($parse) ->
callback(values or [], preserveOrder: true)
scope.$watch attr.query, (query) ->
terms =
for k, v of query
continue unless v?.length
if ' ' in v
"#{k}: \"#{v}\""
else
"#{k}: #{v}"
_vs.searchBox.value(terms.join(' '))
p = 0
_vs.searchBox.value('')
for k, values of query
continue unless values?.length
unless angular.isArray values then values = [values]
for v in values
_vs.searchBox.addFacet(k, v, p++)
_search(scope, {"this": _vs.searchQuery})
restrict: 'C'
......@@ -444,9 +433,7 @@ whenscrolled = ['$window', ($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
if elementBottom > 0 and elementBottom - windowBottom <= 0
scope.$apply attr.whenscrolled
]
......@@ -462,6 +449,5 @@ angular.module('h.directives', ['ngSanitize'])
.directive('username', username)
.directive('userPicker', userPicker)
.directive('repeatAnim', repeatAnim)
.directive('streamviewer', streamviewer)
.directive('visualSearch', visualSearch)
.directive('whenscrolled', whenscrolled)
......@@ -8,7 +8,6 @@ class Hypothesis extends Annotator
'annotationCreated': 'updateAncestors'
'annotationUpdated': 'updateAncestors'
'annotationDeleted': 'updateAncestors'
'serviceDiscovery': 'serviceDiscovery'
# Plugin configuration
options:
......@@ -506,14 +505,17 @@ class Hypothesis extends Annotator
query = limit: 1000
@annotator.considerSocialView.call @annotator, query
this.entities ?= {}
entities = {}
for p in @annotator.providers
for uri in p.entities
unless this.entities[uri]?
unless entities[uri]?
console.log "Loading annotations for: " + uri
this.entities[uri] = true
entities[uri] = true
this.loadAnnotationsFromSearch (angular.extend {}, query, uri: uri)
this.entities = Object.keys(entities)
# When the store plugin finishes a request, update the annotation
# using a monkey-patched update function which updates the threading
# if the annotation has a newly-assigned id and ensures that the id
......@@ -577,11 +579,6 @@ class Hypothesis extends Annotator
this.updateAncestors(rel)
break # Only the nearest existing ancestor, the rest is by induction.
serviceDiscovery: (options) =>
@options.Store ?= {}
angular.extend @options.Store, options
this.addPlugin 'Store', @options.Store
setTool: (name) =>
return if name is @tool
return unless this.discardDrafts()
......
# 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'
parseModels: (models) ->
# Cluster facets together
categories = {}
for searchItem in models
category = searchItem.attributes.category
value = searchItem.attributes.value
if category of categories
categories[category].push value
else
categories[category] = [value]
categories
populateFilter: (filter, query) =>
# Populate a filter with a query object
for category, values of query
unless @rules[category]? then continue
unless values.length then continue
rule = @rules[category]
unless angular.isArray values
values = [values]
# 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 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
class StreamFilter
strategies: ['include_any', 'include_all', 'exclude_any', 'exclude_all']
past_modes: ['none','hits','time']
......@@ -80,10 +201,6 @@ class StreamFilter
@filter.clauses = []
this
addClause: (clause) ->
@filter.clauses.push clause
this
addClause: (field, operator, value, case_sensitive = false, options = {}) ->
@filter.clauses.push
field: field
......@@ -103,5 +220,6 @@ class StreamFilter
this
angular.module('h.streamfilter',['bootstrap'])
.service('streamfilter', StreamFilter)
\ No newline at end of file
angular.module('h.streamfilter', [])
.service('queryparser', QueryParser)
.service('streamfilter', StreamFilter)
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