Commit 33bf5458 authored by Ujvari Gergely's avatar Ujvari Gergely

Merge branch 'develop' of https://github.com/hypothesis/h into...

Merge branch 'develop' of https://github.com/hypothesis/h into 242_URLs_with_escaped_chars_dont_match
parents a4ff18c4 9ce9f318
......@@ -9,6 +9,7 @@ $grayDark: darken($gray, 12%) !default;
$grayDarker: darken($gray, 36%) !default;
$grayLight: lighten($gray, 12%) !default;
$grayLighter: lighten($gray, 36%) !default;
$grayLightest: #f9f9f9 !default;
//COLORS
$hypothered: #bd1c2b !default;
......@@ -94,7 +95,7 @@ $em: 14 / 1em !default;
@mixin plainform {
color: $gray;
background-color: #ffffff;
background-color: white;
border: 1px solid $grayLighter;
border-radius: .2em;
font-family: "Source Sans Pro", "Open Sans", sans-serif;
......@@ -102,14 +103,17 @@ $em: 14 / 1em !default;
padding: .33em .5em;
&:focus {
outline: 0;
border: 1px solid $grayLight;
border: 1px solid $gray;
@include box-shadow( inset 1px 1px 6px -1px $grayLighter);
}
&:disabled {
background-color: $grayLightest;
}
}
@mixin sweetbutton {
@include background-image(
linear-gradient(top, rgb(255, 255, 255) 0%, rgb(249, 249, 249) 100%));
linear-gradient(top, rgb(255, 255, 255) 0%, rgb(255, 255, 255) 10%, rgb(240, 240, 240) 100%));
@include border-radius(.15em);
@include box-shadow(
inset 0 .2em 0 rgba(255, 255, 255, .2),
......@@ -121,12 +125,12 @@ $em: 14 / 1em !default;
position: relative;
text-decoration: none;
text-shadow: 0 1px 2px rgba(255, 255, 255, .9);
color: $gray;
color: $grayDark;
border-color: $grayLight $grayLight $gray;
&:hover {
@include background-image(
linear-gradient(top, #fefefe 0%, #f4f4f4 50%, #e2e2e2 51%, #fdfdfd 100%));
border-color: $grayLight $grayLight $gray;
color: black;
& > a {
opacity: 1;
......@@ -140,23 +144,14 @@ $em: 14 / 1em !default;
inset 0 .15em .25em rgba(0, 0, 0, .15),
0 .05em .1em rgba(0, 0, 0, .0));
border-color: $grayLight $grayLight $gray;
top: .1em;
top: 1px;
}
}
@mixin tabbox {
@include box-shadow(2px -10px 13px -8px hsla(0, 0%, 0%, .1) inset);
background: hsla(0, 0%, 99%, 1);
border-top-right-radius: 2em 5em;
border-top-left-radius: 2em 5em;
color: #999;
border: 1px solid $grayLighter;
border-bottom: none;
&:hover {
background: white;
}
&:active {
@include box-shadow( none );
&:disabled {
color: $grayLight;
border-color: $grayLighter;
background: $grayLightest;
cursor: default;
}
}
......
......@@ -124,7 +124,7 @@ button, input[type=submit], .btn {
@include border-radius(.5em);
@include smallshadow;
@include single-transition(opacity, .2s);
border: solid thin;
border: solid 1px;
left: 50%;
margin-left: -15%;
opacity: 0;
......@@ -197,10 +197,6 @@ button, input[type=submit], .btn {
}
}
input.ng-invalid.ng-dirty {
border-color: lighten($red, 35%);
}
.req {
display: none;
}
......@@ -354,28 +350,88 @@ blockquote {
//TABS////////////////////////////////
.nav-tabs {
@include pie-clearfix;
margin: 0 .5em;
& > li > a {
cursor: pointer;
float: left;
line-height: 1;
padding: .5em 1em;
padding-top: .25em;
margin-right: -.5em;
margin-bottom: 0px;
width: 100%;
font-family: sans-serif;
& > li {
position: relative;
text-decoration: none;
@include tabbox;
}
& > .active > a {
@include box-shadow(none);
color: #333;
font-weight: bold;
background: hsla(0, 0%, 100%, 1);
z-index: 90;
display: inline-block;
overflow: hidden;
margin-right: -.5em;
background: hsla(0, 0%, 99%, 1);
border-top-right-radius: 2em 5em;
border-top-left-radius: 2em 5em;
border: 1px solid $grayLighter;
border-bottom: none;
& > a {
overflow: hidden;
display: inline-block;
white-space: nowrap;
padding: .5em 1em;
color: #999;
text-decoration: none;
}
&::before {
content: "";
@include background( linear-gradient(right, hsla(0, 0%, 99%, 1), hsla(0, 0%, 99%, 1), hsla(0, 0%, 99%, 0)) );
width: 1em;
height: 100%;
position: absolute;
right: 0;
top: 0;
border-top-right-radius: 2em 5em;
}
&::after {
content: "";
@include box-shadow(0px -10px 13px -8px hsla(0, 0%, 0%, .1) inset);
border-top-right-radius: 2em 5em;
border-top-left-radius: 2em 5em;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
&:hover {
background: white;
}
&.active {
background: hsla(0, 0%, 100%, 1);
z-index: 90;
& > a {
color: #333;
font-weight: bold;
}
&::before {
@include background( linear-gradient(right, hsla(0, 0%, 100%, 1), hsla(0, 0%, 100%, 1), hsla(0, 0%, 100%, 0)) );
}
&::after {
@include box-shadow( none );
}
}
.tabs-below & {
border: 1px solid $grayLighter;
border-top: none;
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 2em 5em;
border-bottom-left-radius: 2em 5em;
&::before {
border-top-right-radius: 0;
border-bottom-right-radius: 2em 5em;
}
&::after {
@include box-shadow(0px 10px 13px -8px hsla(0, 0%, 0%, .1) inset);
border-top-right-radius: 0;
border-top-left-radius: 0;
border-bottom-right-radius: 2em 5em;
border-bottom-left-radius: 2em 5em;
}
&.active {
&::after {
@include box-shadow( none );
}
}
}
}
}
......@@ -386,7 +442,6 @@ blockquote {
margin-top: -1px; // Pull up and under tabs
padding: 1em;
position: relative;
&.active {
display: inherit !important;
}
......@@ -394,7 +449,6 @@ blockquote {
//PAPER////////////////////////////////
//Provides the white background upon which items sit
.paper {
......
......@@ -12,12 +12,12 @@ class App
token: null
this.$inject = [
'$compile', '$element', '$http', '$location', '$scope',
'annotator', 'flash', 'threading'
'$compile', '$element', '$http', '$location', '$scope', '$timeout',
'annotator', 'drafts', 'flash', 'threading'
]
constructor: (
$compile, $element, $http, $location, $scope,
annotator, flash, threading
$compile, $element, $http, $location, $scope, $timeout
annotator, drafts, flash, threading
) ->
{plugins, provider} = annotator
heatmap = annotator.plugins.Heatmap
......@@ -25,6 +25,8 @@ class App
heatmap.element.bind 'click', =>
$scope.$apply ->
return unless drafts.discard()
$location.search('id', null).replace()
dynamicBucket = true
annotator.showViewer()
annotator.show()
......@@ -90,11 +92,13 @@ class App
else
dynamicBucket = false
$scope.$apply ->
return unless drafts.discard()
$location.search('id', null)
annotator.showViewer heatmap.buckets[bucket]
annotator.show()
$scope.submit = (form) ->
return unless form.$valid
params = for name, control of form when control.$modelValue?
[name, control.$modelValue]
params.push ['__formid__', form.$name]
......@@ -105,7 +109,7 @@ class App
'Content-Type': 'application/x-www-form-urlencoded'
withCredentials: true
.success (data) =>
if data.model? then angular.extend($scope, data.model)
if data.model? then angular.extend $scope, data.model
if data.flash? then flash q, msgs for q, msgs of data.flash
if data.status is 'failure' then flash 'error', data.reason
......@@ -170,73 +174,99 @@ class App
$scope.$broadcast '$reset'
# Update scope with auto-filled form field values
$timeout ->
for i in $element.find('input') when i.value
$i = angular.element(i)
$i.triggerHandler('change')
$i.triggerHandler('input')
, 200 # We hope this is long enough
class Annotation
this.$inject = [
'$element', '$scope', '$rootScope', '$timeout',
'annotator', 'threading'
'$element', '$location', '$scope', '$rootScope', '$timeout',
'annotator', 'drafts', 'threading'
]
constructor: (
$element, $scope, $rootScope, $timeout
annotator, threading
$element, $location, $scope, $rootScope, $timeout
annotator, drafts, threading
) ->
publish = (args...) ->
# Publish after a timeout to escape this digest
# Annotator event callbacks don't expect a digest to be active
$timeout (-> annotator.publish args...), 0, false
$scope.cancel = ->
$scope.editing = false
if $scope.$modelValue.draft
annotator.publish 'annotationDeleted', $scope.$modelValue
drafts.remove $scope.$modelValue
if $scope.unsaved
publish 'annotationDeleted', $scope.$modelValue
$scope.save = ->
$scope.editing = false
$scope.$modelValue.draft = false
if $scope.edited
annotator.publish 'annotationUpdated', $scope.$modelValue
drafts.remove $scope.$modelValue
if $scope.unsaved
publish 'annotationCreated', $scope.$modelValue
else
annotator.publish 'annotationCreated', $scope.$modelValue
publish 'annotationUpdated', $scope.$modelValue
$scope.reply = ->
unless annotator.plugins.Auth.haveValidToken()
$rootScope.$broadcast 'showAuth', true
return
reply = annotator.createAnnotation()
Object.defineProperty reply, 'draft',
value: true
writable: true
if $scope.$modelValue.thread
references = [$scope.$modelValue.thread, $scope.$modelValue.id]
else
references = [$scope.$modelValue.id]
reply.thread = references.join '/'
parentThread = (threading.getContainer $scope.$modelValue.id)
replyThread = (threading.getContainer reply.id)
replyThread.message =
annotation: reply
id: reply.id
references: references
parentThread.addChild replyThread
$scope.$watch '$modelValue.draft', (newValue) ->
$scope.editing = newValue
references =
if $scope.$modelValue.thread
[$scope.$modelValue.thread, $scope.$modelValue.id]
else
[$scope.$modelValue.id]
reply = angular.extend annotator.createAnnotation(),
thread: references.join '/'
replyThread = angular.extend (threading.getContainer reply.id),
message:
annotation: reply
id: reply.id
references: references
(threading.getContainer $scope.$modelValue.id).addChild replyThread
drafts.add reply
$scope.$on '$routeChangeStart', -> $scope.cancel() if $scope.editing
$scope.$on '$routeUpdate', -> $scope.cancel() if $scope.editing
$scope.$watch 'editing', (newValue) ->
if newValue then $timeout -> $element.find('textarea').focus()
# Check if this is a brand new annotation
if drafts.contains $scope.$modelValue
$scope.editing = true
$scope.unsaved = true
class Editor
this.$inject = [
'$location', '$routeParams', '$scope',
'annotator', 'threading'
'annotator', 'drafts', 'threading'
]
constructor: (
$location, $routeParams, $scope,
annotator, threading
annotator, drafts, threading
) ->
save = ->
$location.path('/viewer').replace()
annotator.provider.onEditorSubmit()
annotator.provider.onEditorHide()
$scope.$apply ->
$location.path('/viewer').replace()
annotator.provider.onEditorSubmit()
annotator.provider.onEditorHide()
cancel = ->
search = $location.search() or {}
delete search.id
$location.path('/viewer').search(search).replace()
annotator.provider.onEditorHide()
$scope.$apply ->
search = $location.search() or {}
delete search.id
$location.path('/viewer').search(search).replace()
annotator.provider.onEditorHide()
annotator.subscribe 'annotationCreated', save
annotator.subscribe 'annotationDeleted', cancel
......@@ -247,9 +277,8 @@ class Editor
thread = (threading.getContainer $routeParams.id)
annotation = thread.message?.annotation
if annotation?
annotation.draft = true
$scope.annotation = annotation
$scope.annotation = annotation
drafts.add annotation
class Viewer
......@@ -267,11 +296,11 @@ class Viewer
refresh = =>
this.refresh $scope, $routeParams, threading, plugins.Heatmap
if listening
if $scope.detail or $routeParams.bucket?
if $scope.detail
plugins.Heatmap.unsubscribe 'updated', refresh
listening = false
else
unless $scope.detail or $routeParams.bucket?
unless $scope.detail
plugins.Heatmap.subscribe 'updated', refresh
listening = true
......
......@@ -78,7 +78,6 @@ resettable = ->
transclude scope, (el) ->
iElement.replaceWith el
iElement = el
iElement[0].$reset = reset
reset()
scope.$on '$reset', reset
priority: 5000
......@@ -130,6 +129,17 @@ tabReveal = ['$parse', ($parse) ->
]
tabTruncate = ->
link: (scope, iElement) ->
angular.element(iElement).addClass("truncate")
tabs = angular.element(iElement.children()[0].childNodes)
percentage = 100 / tabs.length + "%"
for i in [0..tabs.length-1]
angular.element(tabs[i]).css "width", percentage
a_width = tabs[i].childNodes[0].clientWidth
angular.element(tabs[i]).css "max-width", a_width
thread = ->
link: (scope, iElement, iAttrs, controller) ->
scope.collapsed = false
......@@ -143,3 +153,4 @@ angular.module('h.directives', ['ngSanitize'])
.directive('resettable', resettable)
.directive('tabReveal', tabReveal)
.directive('thread', thread)
.directive('tabTruncate', tabTruncate)
......@@ -2,7 +2,7 @@ class Converter extends Markdown.Converter
constructor: ->
super
this.hooks.chain "postConversion", (text) ->
text.replace /<a href=/, "<a target=\"_blank\" href="
text.replace /<a href=/g, "<a target=\"_blank\" href="
fuzzyTime = (date) ->
......
......@@ -58,7 +58,7 @@ class Annotator.Host extends Annotator
annotator.frame = frame
swf: options.swf
props:
className: 'annotator-frame annotator-collapsed'
className: 'annotator-frame annotator-outer annotator-collapsed'
style:
visibility: 'hidden'
remote: @app
......@@ -66,6 +66,7 @@ class Annotator.Host extends Annotator
local:
publish: (args..., k, fk) => this.publish args...
setupAnnotation: => this.setupAnnotation arguments...
deleteAnnotation: (annotation) =>
toDelete = []
@wrapper.find('.annotator-hl')
......@@ -74,17 +75,21 @@ class Annotator.Host extends Annotator
if data.id == annotation.id and data not in toDelete
toDelete.push data
this.deleteAnnotation d for d in toDelete
loadAnnotations: => this.loadAnnotations arguments...
onEditorHide: this.onEditorHide
onEditorSubmit: this.onEditorSubmit
showFrame: =>
@frame.css 'margin-left': "#{-1 * @frame.width()}px"
@frame.removeClass 'annotator-no-transition'
@frame.removeClass 'annotator-collapsed'
hideFrame: =>
@frame.css 'margin-left': ''
@frame.removeClass 'annotator-no-transition'
@frame.addClass 'annotator-collapsed'
dragFrame: (screenX) =>
if screenX > 0
if @drag.last?
......@@ -92,7 +97,8 @@ class Annotator.Host extends Annotator
@drag.last = screenX
unless @drag.tick
@drag.tick = true
window.requestAnimationFrame this.dragRefresh
window.requestAnimationFrame this._dragRefresh
getHighlights: =>
highlights: $(@wrapper).find('.annotator-hl').map ->
offset: $(this).offset()
......@@ -100,6 +106,7 @@ class Annotator.Host extends Annotator
data: $(this).data('annotation')
.get()
offset: $(window).scrollTop()
setActiveHighlights: (ids=[]) =>
@wrapper.find('.annotator-hl')
.each ->
......@@ -107,6 +114,7 @@ class Annotator.Host extends Annotator
$(this).addClass('annotator-hl-active')
else if not $(this).hasClass('annotator-hl-temporary')
$(this).removeClass('annotator-hl-active')
getHref: =>
uri = document.location.href
if document.location.hash
......@@ -114,6 +122,7 @@ class Annotator.Host extends Annotator
$('meta[property^="og:url"]').each -> uri = this.content
$('link[rel^="canonical"]').each -> uri = this.href
return uri
getMaxBottom: =>
sel = '*' + (":not(.annotator-#{x})" for x in [
'adder', 'outer', 'notice', 'filter', 'frame'
......@@ -133,8 +142,10 @@ class Annotator.Host extends Annotator
else
0
Math.max.apply(Math, all)
scrollTop: (y) =>
$('html, body').stop().animate {scrollTop: y}, 600
remote:
publish: {}
addPlugin: {}
......@@ -191,37 +202,23 @@ class Annotator.Host extends Annotator
@drag.last = event.screenX
unless @drag.tick
@drag.tick = true
window.requestAnimationFrame this.dragRefresh
window.requestAnimationFrame this._dragRefresh
document.addEventListener 'dragleave', (event) =>
if @drag.last?
@drag.delta += event.screenX - @drag.last
@drag.last = event.screenX
unless @drag.tick
@drag.tick = true
window.requestAnimationFrame this.dragRefresh
window.requestAnimationFrame this._dragRefresh
$(window).on 'resize scroll', update
$(document.body).on 'resize scroll', '*', update
super
# These methods aren't used in the iframe-hosted configuration of Annotator.
_setupViewer: ->
this
_setupViewer: -> this
_setupEditor: -> this
_setupEditor: ->
true
setupAnnotation: (annotation) ->
annotation = super
# Highlights are jQuery collections which have a circular references to the
# annotation via data stored with `.data()`. Therefore, reconfigure the
# property to hide them from serialization.
Object.defineProperty annotation, 'highlights',
enumerable: false
annotation
dragRefresh: =>
_dragRefresh: =>
d = @drag.delta
@drag.delta = 0
@drag.tick = false
......@@ -236,6 +233,17 @@ class Annotator.Host extends Annotator
'margin-left': "#{m}px"
width: "#{w}px"
setupAnnotation: (annotation) ->
annotation = super
# Highlights are jQuery collections which have a circular references to the
# annotation via data stored with `.data()`. Therefore, reconfigure the
# property to hide them from serialization.
Object.defineProperty annotation, 'highlights',
enumerable: false
annotation
showEditor: (annotation) =>
if not annotation.id?
@consumer.createAnnotation (id) =>
......
......@@ -81,7 +81,18 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
.reduce ({annotations, buckets, index, max}, [x, d, a], i, points) =>
# remove all instances of this annotation from the accumulator
annotations = d3.merge(d3.split(annotations, (b) => a is b))
annotations = annotations.reduce (acc, value) ->
{values, arrays} = acc
if value is a
arrays.push values
acc.values = []
else
values.push value
acc
,
values: []
arrays: []
annotations = d3.merge annotations.arrays
if d > 0
# if this is a +1 control point, (re-)include the current annotation
......@@ -213,7 +224,6 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
.style 'display', (d) =>
if (@buckets[d].length is 0) then 'none' else ''
this.publish('updated')
isUpper: (i) => i == 0
......
......@@ -82,6 +82,7 @@ class Hypothesis extends Annotator
_setupXDM: ->
$scope = @element.scope()
$location = @element.injector().get '$location'
drafts = @element.injector().get 'drafts'
threading = @element.injector().get 'threading'
@provider = new easyXDM.Rpc
......@@ -131,6 +132,7 @@ class Hypothesis extends Annotator
# deletion event that gets published in the provider is not
# cross-published back here in the consumer and therefore
# the Store does not delete the annotation.
# XXX Maybe add provider function for updating id
deleteAnnotation
id: annotation.id
loadAnnotations [
......@@ -201,6 +203,7 @@ class Hypothesis extends Annotator
# This guy does stuff when you "back out" of the interface.
# (Currently triggered by a click on the source page.)
back: =>
return unless drafts.discard()
if $location.path() == '/viewer' and $location.search()?.id?
$scope.$apply => $location.search('id', null).replace()
else
......@@ -268,41 +271,10 @@ class Hypothesis extends Annotator
this
_setupDynamicStyle: ->
this
_setupViewer: ->
# Not used in the angular version.
this
# Creates an instance of the Annotator.Editor and assigns it to @editor.
# Appends this to the @wrapper and sets up event listeners.
#
# Returns itself for chaining.
_setupEditor: ->
@editor = this._createEditor()
.on 'hide save', =>
if @unsaved_drafts.indexOf(@editor) > -1
@unsaved_drafts.splice(@unsaved_drafts.indexOf(@editor), 1)
.on 'hide', =>
@provider.onEditorHide()
.on 'save', =>
@provider.onEditorSubmit()
this
_createEditor: ->
editor = new Annotator.Editor()
editor.hide()
editor.fields = [{
element: editor.element,
load: (field, annotation) ->
$(field).find('textarea').val(annotation.text || '')
submit: (field, annotation) ->
annotation.text = $(field).find('textarea').val()
}]
@unsaved_drafts.push editor
editor
# Override things not used in the angular version.
_setupDynamicStyle: -> this
_setupViewer: -> this
_setupEditor: -> this
createAnnotation: ->
annotation = super
......@@ -314,7 +286,7 @@ class Hypothesis extends Annotator
configurable: true
enumerable: false
writable: true
value: window.btoa (JSON.stringify annotation)
value: window.btoa (JSON.stringify annotation + Math.random())
annotation
......@@ -344,13 +316,12 @@ class Hypothesis extends Annotator
# added.
if annotation.thread
annotation.ranges = []
@provider.setupAnnotation
id: annotation.id
ranges: annotation.ranges
else
@provider.setupAnnotation
id: annotation.id
ranges: annotation.ranges
showViewer: (annotations=[]) =>
return unless this._canCloseUnsaved()
@element.injector().invoke [
'$location', '$rootScope',
($location, $rootScope) ->
......@@ -360,7 +331,6 @@ class Hypothesis extends Annotator
]
showEditor: (annotation) =>
return unless this._canCloseUnsaved()
@element.injector().invoke [
'$location',
($location) ->
......@@ -384,31 +354,34 @@ class Hypothesis extends Annotator
@element.find('#toolbar').removeClass('shown')
.find('.tri').attr('draggable', false)
_canCloseUnsaved: ->
# See if there's an unsaved/uncancelled reply
can_close = true
open_editors = 0
for editor in @unsaved_drafts
unsaved_text = editor.element.find(':input:first').attr 'value'
if unsaved_text? and unsaved_text.toString().length > 0
open_editors += 1
if open_editors > 0
if open_editors > 1
ctext = "You have #{open_editors} unsaved replies."
else
ctext = "You have an unsaved reply."
ctext = ctext + " Do you really want to close the view?"
can_close = confirm ctext
if can_close then @unsaved_drafts = []
can_close
threadId: (annotation) ->
if annotation?.thread?
annotation.thread + '/' + annotation.id
class DraftProvider
drafts: []
$get: -> this
add: (draft) -> @drafts.push draft unless this.contains draft
remove: (draft) -> @drafts = (d for d in @drafts when d isnt draft)
contains: (draft) -> (@drafts.indexOf draft) != -1
discard: ->
count = (d for d in @drafts when d.text?.length).length
text =
switch count
when 0 then null
when 1
"""You have an unsaved reply.
Do you really want to discard this draft?"""
else
"""You have #{count} unsaved replies.
Do you really want to discard these drafts?"""
if count == 0 or confirm text
@drafts = []
true
else
annotation.id
false
class FlashProvider
......@@ -450,6 +423,7 @@ class FlashProvider
angular.module('h.services', [])
.provider('drafts', DraftProvider)
.provider('flash', FlashProvider)
.service('annotator', Hypothesis)
.value('threading', mail.messageThread())
/**
* @license AngularJS v1.0.4
* (c) 2010-2012 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {
'use strict';
var directive = {};
directive.dropdownToggle =
['$document', '$location', '$window',
function ($document, $location, $window) {
var openElement = null, close;
return {
restrict: 'C',
link: function(scope, element, attrs) {
scope.$watch(function dropdownTogglePathWatch(){return $location.path();}, function dropdownTogglePathWatchAction() {
close && close();
});
element.parent().bind('click', function(event) {
close && close();
});
element.bind('click', function(event) {
event.preventDefault();
event.stopPropagation();
var iWasOpen = false;
if (openElement) {
iWasOpen = openElement === element;
close();
}
if (!iWasOpen){
element.parent().addClass('open');
openElement = element;
close = function (event) {
event && event.preventDefault();
event && event.stopPropagation();
$document.unbind('click', close);
element.parent().removeClass('open');
close = null;
openElement = null;
}
$document.bind('click', close);
}
});
}
};
}];
directive.tabbable = function() {
return {
restrict: 'C',
compile: function(element) {
var navTabs = angular.element('<ul class="nav nav-tabs"></ul>'),
tabContent = angular.element('<div class="tab-content"></div>');
tabContent.append(element.contents());
element.append(navTabs).append(tabContent);
},
controller: ['$scope', '$element', function($scope, $element) {
var navTabs = $element.contents().eq(0),
ngModel = $element.controller('ngModel') || {},
tabs = [],
selectedTab;
ngModel.$render = function() {
var $viewValue = this.$viewValue;
if (selectedTab ? (selectedTab.value != $viewValue) : $viewValue) {
if(selectedTab) {
selectedTab.paneElement.removeClass('active');
selectedTab.tabElement.removeClass('active');
selectedTab = null;
}
if($viewValue) {
for(var i = 0, ii = tabs.length; i < ii; i++) {
if ($viewValue == tabs[i].value) {
selectedTab = tabs[i];
break;
}
}
if (selectedTab) {
selectedTab.paneElement.addClass('active');
selectedTab.tabElement.addClass('active');
}
}
}
};
this.addPane = function(element, attr) {
var li = angular.element('<li><a href></a></li>'),
a = li.find('a'),
tab = {
paneElement: element,
paneAttrs: attr,
tabElement: li
};
tabs.push(tab);
attr.$observe('value', update)();
attr.$observe('title', function(){ update(); a.text(tab.title); })();
function update() {
tab.title = attr.title;
tab.value = attr.value || attr.title;
if (!ngModel.$setViewValue && (!ngModel.$viewValue || tab == selectedTab)) {
// we are not part of angular
ngModel.$viewValue = tab.value;
}
ngModel.$render();
}
navTabs.append(li);
li.bind('click', function(event) {
event.preventDefault();
event.stopPropagation();
if (ngModel.$setViewValue) {
$scope.$apply(function() {
ngModel.$setViewValue(tab.value);
ngModel.$render();
});
} else {
// we are not part of angular
ngModel.$viewValue = tab.value;
ngModel.$render();
}
});
return function() {
tab.tabElement.remove();
for(var i = 0, ii = tabs.length; i < ii; i++ ) {
if (tab == tabs[i]) {
tabs.splice(i, 1);
}
}
};
}
}]
};
};
directive.tabPane = function() {
return {
require: '^tabbable',
restrict: 'C',
link: function(scope, element, attrs, tabsCtrl) {
element.bind('$remove', tabsCtrl.addPane(element, attrs));
}
};
};
angular.module('bootstrap', []).directive(directive);
})(window, window.angular);
/*
AngularJS v1.0.3
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(n,j){'use strict';j.module("bootstrap",[]).directive({dropdownToggle:["$document","$location","$window",function(h,e){var d=null,a;return{restrict:"C",link:function(g,b){g.$watch(function(){return e.path()},function(){a&&a()});b.parent().bind("click",function(){a&&a()});b.bind("click",function(i){i.preventDefault();i.stopPropagation();i=!1;d&&(i=d===b,a());i||(b.parent().addClass("open"),d=b,a=function(c){c&&c.preventDefault();c&&c.stopPropagation();h.unbind("click",a);b.parent().removeClass("open");
d=a=null},h.bind("click",a))})}}}],tabbable:function(){return{restrict:"C",compile:function(h){var e=j.element('<ul class="nav nav-tabs"></ul>'),d=j.element('<div class="tab-content"></div>');d.append(h.contents());h.append(e).append(d)},controller:["$scope","$element",function(h,e){var d=e.contents().eq(0),a=e.controller("ngModel")||{},g=[],b;a.$render=function(){var a=this.$viewValue;if(b?b.value!=a:a)if(b&&(b.paneElement.removeClass("active"),b.tabElement.removeClass("active"),b=null),a){for(var c=
0,d=g.length;c<d;c++)if(a==g[c].value){b=g[c];break}b&&(b.paneElement.addClass("active"),b.tabElement.addClass("active"))}};this.addPane=function(e,c){function l(){f.title=c.title;f.value=c.value||c.title;if(!a.$setViewValue&&(!a.$viewValue||f==b))a.$viewValue=f.value;a.$render()}var k=j.element("<li><a href></a></li>"),m=k.find("a"),f={paneElement:e,paneAttrs:c,tabElement:k};g.push(f);c.$observe("value",l)();c.$observe("title",function(){l();m.text(f.title)})();d.append(k);k.bind("click",function(b){b.preventDefault();
b.stopPropagation();a.$setViewValue?h.$apply(function(){a.$setViewValue(f.value);a.$render()}):(a.$viewValue=f.value,a.$render())});return function(){f.tabElement.remove();for(var a=0,b=g.length;a<b;a++)f==g[a]&&g.splice(a,1)}}}]}},tabPane:function(){return{require:"^tabbable",restrict:"C",link:function(h,e,d,a){e.bind("$remove",a.addPane(e,d))}}}})})(window,window.angular);
This diff is collapsed.
/*
AngularJS v1.0.3
(c) 2010-2012 Google, Inc. http://angularjs.org
License: MIT
*/
(function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b<a.length;b++)d[a[b]]=!0;return d}function z(a,d){function b(a,b,c,h){b=g.lowercase(b);if(m[b])for(;f.last()&&n[f.last()];)e("",f.last());o[b]&&f.last()==b&&e("",b);(h=p[b]||!!h)||f.push(b);var j={};c.replace(A,function(a,b,d,e,c){j[b]=k(d||e||c||"")});d.start&&d.start(b,j,h)}function e(a,b){var e=0,c;if(b=g.lowercase(b))for(e=f.length-1;e>=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length=
e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+
f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(/</g,"&lt;");return l.innerText||l.textContent||""}function t(a){return a.replace(/&/g,"&amp;").replace(F,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]==
!0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b("</"),b(a),b(">"));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^</,B=/^<\s*\//,D=/<\!--(.*?)--\>/g,
E=/<!\[CDATA\[(.*?)]]\>/g,H=/^((ftp|https?):\/\/|mailto:|#)/,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),
q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[];
z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index,
h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular);
This diff is collapsed.
This diff is collapsed.
/*
** Annotator 1.2.6-dev-c4fcdfe
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-01-29 11:20:24Z
*/
(function() {
var base64Decode, base64UrlDecode, createDateFromISO8601, parseToken,
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
createDateFromISO8601 = function(string) {
var d, date, offset, regexp, time, _ref;
regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" + "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
d = string.match(new RegExp(regexp));
offset = 0;
date = new Date(d[1], 0, 1);
if (d[3]) date.setMonth(d[3] - 1);
if (d[5]) date.setDate(d[5]);
if (d[7]) date.setHours(d[7]);
if (d[8]) date.setMinutes(d[8]);
if (d[10]) date.setSeconds(d[10]);
if (d[12]) date.setMilliseconds(Number("0." + d[12]) * 1000);
if (d[14]) {
offset = (Number(d[16]) * 60) + Number(d[17]);
offset *= (_ref = d[15] === '-') != null ? _ref : {
1: -1
};
}
offset -= date.getTimezoneOffset();
time = Number(date) + (offset * 60 * 1000);
date.setTime(Number(time));
return date;
};
base64Decode = function(data) {
var ac, b64, bits, dec, h1, h2, h3, h4, i, o1, o2, o3, tmp_arr;
if (typeof atob !== "undefined" && atob !== null) {
return atob(data);
} else {
b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
i = 0;
ac = 0;
dec = "";
tmp_arr = [];
if (!data) return data;
data += '';
while (i < data.length) {
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits >> 16 & 0xff;
o2 = bits >> 8 & 0xff;
o3 = bits & 0xff;
if (h3 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1);
} else if (h4 === 64) {
tmp_arr[ac++] = String.fromCharCode(o1, o2);
} else {
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
}
return tmp_arr.join('');
}
};
base64UrlDecode = function(data) {
var i, m, _ref;
m = data.length % 4;
if (m !== 0) {
for (i = 0, _ref = 4 - m; 0 <= _ref ? i < _ref : i > _ref; 0 <= _ref ? i++ : i--) {
data += '=';
}
}
data = data.replace(/-/g, '+');
data = data.replace(/_/g, '/');
return base64Decode(data);
};
parseToken = function(token) {
var head, payload, sig, _ref;
_ref = token.split('.'), head = _ref[0], payload = _ref[1], sig = _ref[2];
return JSON.parse(base64UrlDecode(payload));
};
Annotator.Plugin.Auth = (function(_super) {
__extends(Auth, _super);
Auth.prototype.options = {
token: null,
tokenUrl: '/auth/token',
autoFetch: true
};
function Auth(element, options) {
Auth.__super__.constructor.apply(this, arguments);
this.waitingForToken = [];
if (this.options.token) {
this.setToken(this.options.token);
} else {
this.requestToken();
}
}
Auth.prototype.requestToken = function() {
var _this = this;
this.requestInProgress = true;
return $.ajax({
url: this.options.tokenUrl,
dataType: 'text',
xhrFields: {
withCredentials: true
}
}).done(function(data, status, xhr) {
return _this.setToken(data);
}).fail(function(xhr, status, err) {
var msg;
msg = Annotator._t("Couldn't get auth token:");
console.error("" + msg + " " + err, xhr);
return Annotator.showNotification("" + msg + " " + xhr.responseText, Annotator.Notification.ERROR);
}).always(function() {
return _this.requestInProgress = false;
});
};
Auth.prototype.setToken = function(token) {
var _results,
_this = this;
this.token = token;
this._unsafeToken = parseToken(token);
if (this.haveValidToken()) {
if (this.options.autoFetch) {
this.refreshTimeout = setTimeout((function() {
return _this.requestToken();
}), (this.timeToExpiry() - 2) * 1000);
}
this.updateHeaders();
_results = [];
while (this.waitingForToken.length > 0) {
_results.push(this.waitingForToken.pop()(this._unsafeToken));
}
return _results;
} else {
console.warn(Annotator._t("Didn't get a valid token."));
if (this.options.autoFetch) {
console.warn(Annotator._t("Getting a new token in 10s."));
return setTimeout((function() {
return _this.requestToken();
}), 10 * 1000);
}
}
};
Auth.prototype.haveValidToken = function() {
var allFields;
allFields = this._unsafeToken && this._unsafeToken.issuedAt && this._unsafeToken.ttl && this._unsafeToken.consumerKey;
return allFields && this.timeToExpiry() > 0;
};
Auth.prototype.timeToExpiry = function() {
var expiry, issue, now, timeToExpiry;
now = new Date().getTime() / 1000;
issue = createDateFromISO8601(this._unsafeToken.issuedAt).getTime() / 1000;
expiry = issue + this._unsafeToken.ttl;
timeToExpiry = expiry - now;
if (timeToExpiry > 0) {
return timeToExpiry;
} else {
return 0;
}
};
Auth.prototype.updateHeaders = function() {
var current;
current = this.element.data('annotator:headers');
return this.element.data('annotator:headers', $.extend(current, {
'x-annotator-auth-token': this.token
}));
};
Auth.prototype.withToken = function(callback) {
if (!(callback != null)) return;
if (this.haveValidToken()) {
return callback(this._unsafeToken);
} else {
this.waitingForToken.push(callback);
if (!this.requestInProgress) return this.requestToken();
}
};
return Auth;
})(Annotator.Plugin);
}).call(this);
/*
** Annotator v1.2.5
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2012-06-22 12:25:29Z
*/((function(){var a,b,c,d,e=Object.prototype.hasOwnProperty,f=function(a,b){function d(){this.constructor=a}for(var c in b)e.call(b,c)&&(a[c]=b[c]);return d.prototype=b.prototype,a.prototype=new d,a.__super__=b.prototype,a};c=function(a){var b,c,d,e,f,g;return e="([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?",b=a.match(new RegExp(e)),d=0,c=new Date(b[1],0,1),b[3]&&c.setMonth(b[3]-1),b[5]&&c.setDate(b[5]),b[7]&&c.setHours(b[7]),b[8]&&c.setMinutes(b[8]),b[10]&&c.setSeconds(b[10]),b[12]&&c.setMilliseconds(Number("0."+b[12])*1e3),b[14]&&(d=Number(b[16])*60+Number(b[17]),d*=(g=b[15]==="-")!=null?g:{1:-1}),d-=c.getTimezoneOffset(),f=Number(c)+d*60*1e3,c.setTime(Number(f)),c},a=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;if(typeof atob!="undefined"&&atob!==null)return atob(a);c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",j=0,b=0,e="",n=[];if(!a)return a;a+="";while(j<a.length)f=c.indexOf(a.charAt(j++)),g=c.indexOf(a.charAt(j++)),h=c.indexOf(a.charAt(j++)),i=c.indexOf(a.charAt(j++)),d=f<<18|g<<12|h<<6|i,k=d>>16&255,l=d>>8&255,m=d&255,h===64?n[b++]=String.fromCharCode(k):i===64?n[b++]=String.fromCharCode(k,l):n[b++]=String.fromCharCode(k,l,m);return n.join("")},b=function(b){var c,d,e;d=b.length%4;if(d!==0)for(c=0,e=4-d;0<=e?c<e:c>e;0<=e?c++:c--)b+="=";return b=b.replace(/-/g,"+"),b=b.replace(/_/g,"/"),a(b)},d=function(a){var c,d,e,f;return f=a.split("."),c=f[0],d=f[1],e=f[2],JSON.parse(b(d))},Annotator.Plugin.Auth=function(a){function b(a,c){b.__super__.constructor.apply(this,arguments),this.waitingForToken=[],this.options.token?this.setToken(this.options.token):this.requestToken()}return f(b,a),b.prototype.options={token:null,tokenUrl:"/auth/token",autoFetch:!0},b.prototype.requestToken=function(){var a=this;return this.requestInProgress=!0,$.ajax({url:this.options.tokenUrl,dataType:"text",xhrFields:{withCredentials:!0}}).done(function(b,c,d){return a.setToken(b)}).fail(function(a,b,c){var d;return d=Annotator._t("Couldn't get auth token:"),console.error(""+d+" "+c,a),Annotator.showNotification(""+d+" "+a.responseText,Annotator.Notification.ERROR)}).always(function(){return a.requestInProgress=!1})},b.prototype.setToken=function(a){var b,c=this;this.token=a,this._unsafeToken=d(a);if(this.haveValidToken()){this.options.autoFetch&&(this.refreshTimeout=setTimeout(function(){return c.requestToken()},(this.timeToExpiry()-2)*1e3)),this.updateHeaders(),b=[];while(this.waitingForToken.length>0)b.push(this.waitingForToken.pop()(this._unsafeToken));return b}console.warn(Annotator._t("Didn't get a valid token."));if(this.options.autoFetch)return console.warn(Annotator._t("Getting a new token in 10s.")),setTimeout(function(){return c.requestToken()},1e4)},b.prototype.haveValidToken=function(){var a;return a=this._unsafeToken&&this._unsafeToken.issuedAt&&this._unsafeToken.ttl&&this._unsafeToken.consumerKey,a&&this.timeToExpiry()>0},b.prototype.timeToExpiry=function(){var a,b,d,e;return d=(new Date).getTime()/1e3,b=c(this._unsafeToken.issuedAt).getTime()/1e3,a=b+this._unsafeToken.ttl,e=a-d,e>0?e:0},b.prototype.updateHeaders=function(){var a;return a=this.element.data("annotator:headers"),this.element.data("annotator:headers",$.extend(a,{"x-annotator-auth-token":this.token}))},b.prototype.withToken=function(a){if(a==null)return;if(this.haveValidToken())return a(this._unsafeToken);this.waitingForToken.push(a);if(!this.requestInProgress)return this.requestToken()},b}(Annotator.Plugin)})).call(this);
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
/*
** Annotator 1.2.6-dev-c4fcdfe
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-01-29 11:20:26Z
*/
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
Annotator.Plugin.Permissions = (function(_super) {
__extends(Permissions, _super);
Permissions.prototype.events = {
'beforeAnnotationCreated': 'addFieldsToAnnotation'
};
Permissions.prototype.options = {
showViewPermissionsCheckbox: true,
showEditPermissionsCheckbox: true,
userId: function(user) {
return user;
},
userString: function(user) {
return user;
},
userAuthorize: function(action, annotation, user) {
var token, tokens, _i, _len;
if (annotation.permissions) {
tokens = annotation.permissions[action] || [];
if (tokens.length === 0) return true;
for (_i = 0, _len = tokens.length; _i < _len; _i++) {
token = tokens[_i];
if (this.userId(user) === token) return true;
}
return false;
} else if (annotation.user) {
return user && this.userId(user) === this.userId(annotation.user);
}
return true;
},
user: '',
permissions: {
'read': [],
'update': [],
'delete': [],
'admin': []
}
};
function Permissions(element, options) {
this._setAuthFromToken = __bind(this._setAuthFromToken, this);
this.updateViewer = __bind(this.updateViewer, this);
this.updateAnnotationPermissions = __bind(this.updateAnnotationPermissions, this);
this.updatePermissionsField = __bind(this.updatePermissionsField, this);
this.addFieldsToAnnotation = __bind(this.addFieldsToAnnotation, this); Permissions.__super__.constructor.apply(this, arguments);
if (this.options.user) {
this.setUser(this.options.user);
delete this.options.user;
}
}
Permissions.prototype.pluginInit = function() {
var createCallback, self,
_this = this;
if (!Annotator.supported()) return;
self = this;
createCallback = function(method, type) {
return function(field, annotation) {
return self[method].call(self, type, field, annotation);
};
};
if (!this.user && this.annotator.plugins.Auth) {
this.annotator.plugins.Auth.withToken(this._setAuthFromToken);
}
if (this.options.showViewPermissionsCheckbox === true) {
this.annotator.editor.addField({
type: 'checkbox',
label: Annotator._t('Allow anyone to <strong>view</strong> this annotation'),
load: createCallback('updatePermissionsField', 'read'),
submit: createCallback('updateAnnotationPermissions', 'read')
});
}
if (this.options.showEditPermissionsCheckbox === true) {
this.annotator.editor.addField({
type: 'checkbox',
label: Annotator._t('Allow anyone to <strong>edit</strong> this annotation'),
load: createCallback('updatePermissionsField', 'update'),
submit: createCallback('updateAnnotationPermissions', 'update')
});
}
this.annotator.viewer.addField({
load: this.updateViewer
});
if (this.annotator.plugins.Filter) {
return this.annotator.plugins.Filter.addFilter({
label: Annotator._t('User'),
property: 'user',
isFiltered: function(input, user) {
var keyword, _i, _len, _ref;
user = _this.options.userString(user);
if (!(input && user)) return false;
_ref = input.split(/\s*/);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
keyword = _ref[_i];
if (user.indexOf(keyword) === -1) return false;
}
return true;
}
});
}
};
Permissions.prototype.setUser = function(user) {
return this.user = user;
};
Permissions.prototype.addFieldsToAnnotation = function(annotation) {
if (annotation) {
annotation.permissions = this.options.permissions;
if (this.user) return annotation.user = this.user;
}
};
Permissions.prototype.authorize = function(action, annotation, user) {
if (user === void 0) user = this.user;
if (this.options.userAuthorize) {
return this.options.userAuthorize.call(this.options, action, annotation, user);
} else {
return true;
}
};
Permissions.prototype.updatePermissionsField = function(action, field, annotation) {
var input;
field = $(field).show();
input = field.find('input').removeAttr('disabled');
if (!this.authorize('admin', annotation)) field.hide();
if (this.authorize(action, annotation || {}, null)) {
return input.attr('checked', 'checked');
} else {
return input.removeAttr('checked');
}
};
Permissions.prototype.updateAnnotationPermissions = function(type, field, annotation) {
var dataKey;
if (!annotation.permissions) {
annotation.permissions = this.options.permissions;
}
dataKey = type + '-permissions';
if ($(field).find('input').is(':checked')) {
return annotation.permissions[type] = [];
} else {
return annotation.permissions[type] = [this.user];
}
};
Permissions.prototype.updateViewer = function(field, annotation, controls) {
var user, username;
field = $(field);
username = this.options.userString(annotation.user);
if (annotation.user && username && typeof username === 'string') {
user = Annotator.$.escape(this.options.userString(annotation.user));
field.html(user).addClass('annotator-user');
} else {
field.remove();
}
if (controls) {
if (!this.authorize('update', annotation)) controls.hideEdit();
if (!this.authorize('delete', annotation)) return controls.hideDelete();
}
};
Permissions.prototype._setAuthFromToken = function(token) {
return this.setUser(token.userId);
};
return Permissions;
})(Annotator.Plugin);
}).call(this);
/*
** Annotator v1.2.5
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2012-06-22 12:25:33Z
*/((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a};Annotator.Plugin.Permissions=function(b){function d(b,c){this._setAuthFromToken=a(this._setAuthFromToken,this),this.updateViewer=a(this.updateViewer,this),this.updateAnnotationPermissions=a(this.updateAnnotationPermissions,this),this.updatePermissionsField=a(this.updatePermissionsField,this),this.addFieldsToAnnotation=a(this.addFieldsToAnnotation,this),d.__super__.constructor.apply(this,arguments),this.options.user&&(this.setUser(this.options.user),delete this.options.user)}return c(d,b),d.prototype.events={beforeAnnotationCreated:"addFieldsToAnnotation"},d.prototype.options={showViewPermissionsCheckbox:!0,showEditPermissionsCheckbox:!0,userId:function(a){return a},userString:function(a){return a},userAuthorize:function(a,b,c){var d,e,f,g;if(b.permissions){e=b.permissions[a]||[];if(e.length===0)return!0;for(f=0,g=e.length;f<g;f++){d=e[f];if(this.userId(c)===d)return!0}return!1}return b.user?c&&this.userId(c)===this.userId(b.user):!0},user:"",permissions:{read:[],update:[],"delete":[],admin:[]}},d.prototype.pluginInit=function(){var a,b,c=this;if(!Annotator.supported())return;b=this,a=function(a,c){return function(d,e){return b[a].call(b,c,d,e)}},!this.user&&this.annotator.plugins.Auth&&this.annotator.plugins.Auth.withToken(this._setAuthFromToken),this.options.showViewPermissionsCheckbox===!0&&this.annotator.editor.addField({type:"checkbox",label:Annotator._t("Allow anyone to <strong>view</strong> this annotation"),load:a("updatePermissionsField","read"),submit:a("updateAnnotationPermissions","read")}),this.options.showEditPermissionsCheckbox===!0&&this.annotator.editor.addField({type:"checkbox",label:Annotator._t("Allow anyone to <strong>edit</strong> this annotation"),load:a("updatePermissionsField","update"),submit:a("updateAnnotationPermissions","update")}),this.annotator.viewer.addField({load:this.updateViewer});if(this.annotator.plugins.Filter)return this.annotator.plugins.Filter.addFilter({label:Annotator._t("User"),property:"user",isFiltered:function(a,b){var d,e,f,g;b=c.options.userString(b);if(!a||!b)return!1;g=a.split(/\s*/);for(e=0,f=g.length;e<f;e++){d=g[e];if(b.indexOf(d)===-1)return!1}return!0}})},d.prototype.setUser=function(a){return this.user=a},d.prototype.addFieldsToAnnotation=function(a){if(a){a.permissions=this.options.permissions;if(this.user)return a.user=this.user}},d.prototype.authorize=function(a,b,c){return c===void 0&&(c=this.user),this.options.userAuthorize?this.options.userAuthorize.call(this.options,a,b,c):!0},d.prototype.updatePermissionsField=function(a,b,c){var d;return b=$(b).show(),d=b.find("input").removeAttr("disabled"),this.authorize("admin",c)||b.hide(),this.authorize(a,c||{},null)?d.attr("checked","checked"):d.removeAttr("checked")},d.prototype.updateAnnotationPermissions=function(a,b,c){var d;return c.permissions||(c.permissions=this.options.permissions),d=a+"-permissions",$(b).find("input").is(":checked")?c.permissions[a]=[]:c.permissions[a]=[this.user]},d.prototype.updateViewer=function(a,b,c){var d,e;a=$(a),e=this.options.userString(b.user),b.user&&e&&typeof e=="string"?(d=Annotator.$.escape(this.options.userString(b.user)),a.html(d).addClass("annotator-user")):a.remove(),this.authorize("update",b)||c.hideEdit();if(!this.authorize("delete",b))return c.hideDelete()},d.prototype._setAuthFromToken=function(a){return this.setUser(a.userId)},d}(Annotator.Plugin)})).call(this);
\ No newline at end of file
/*
** Annotator 1.2.6-dev-c4fcdfe
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2013-01-29 11:20:27Z
*/
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
__hasProp = Object.prototype.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; },
__indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
Annotator.Plugin.Store = (function(_super) {
__extends(Store, _super);
Store.prototype.events = {
'annotationCreated': 'annotationCreated',
'annotationDeleted': 'annotationDeleted',
'annotationUpdated': 'annotationUpdated'
};
Store.prototype.options = {
prefix: '/store',
autoFetch: true,
annotationData: {},
loadFromSearch: false,
urls: {
create: '/annotations',
read: '/annotations/:id',
update: '/annotations/:id',
destroy: '/annotations/:id',
search: '/search'
}
};
function Store(element, options) {
this._onError = __bind(this._onError, this);
this._onLoadAnnotationsFromSearch = __bind(this._onLoadAnnotationsFromSearch, this);
this._onLoadAnnotations = __bind(this._onLoadAnnotations, this);
this._getAnnotations = __bind(this._getAnnotations, this); Store.__super__.constructor.apply(this, arguments);
this.annotations = [];
}
Store.prototype.pluginInit = function() {
if (!Annotator.supported()) return;
if (this.annotator.plugins.Auth) {
return this.annotator.plugins.Auth.withToken(this._getAnnotations);
} else {
return this._getAnnotations();
}
};
Store.prototype._getAnnotations = function() {
if (this.options.loadFromSearch) {
return this.loadAnnotationsFromSearch(this.options.loadFromSearch);
} else {
return this.loadAnnotations();
}
};
Store.prototype.annotationCreated = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) < 0) {
this.registerAnnotation(annotation);
return this._apiRequest('create', annotation, function(data) {
if (!(data.id != null)) {
console.warn(Annotator._t("Warning: No ID returned from server for annotation "), annotation);
}
return _this.updateAnnotation(annotation, data);
});
} else {
return this.updateAnnotation(annotation, {});
}
};
Store.prototype.annotationUpdated = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) >= 0) {
return this._apiRequest('update', annotation, (function(data) {
return _this.updateAnnotation(annotation, data);
}));
}
};
Store.prototype.annotationDeleted = function(annotation) {
var _this = this;
if (__indexOf.call(this.annotations, annotation) >= 0) {
return this._apiRequest('destroy', annotation, (function() {
return _this.unregisterAnnotation(annotation);
}));
}
};
Store.prototype.registerAnnotation = function(annotation) {
return this.annotations.push(annotation);
};
Store.prototype.unregisterAnnotation = function(annotation) {
return this.annotations.splice(this.annotations.indexOf(annotation), 1);
};
Store.prototype.updateAnnotation = function(annotation, data) {
if (__indexOf.call(this.annotations, annotation) < 0) {
console.error(Annotator._t("Trying to update unregistered annotation!"));
} else {
$.extend(annotation, data);
}
return $(annotation.highlights).data('annotation', annotation);
};
Store.prototype.loadAnnotations = function() {
return this._apiRequest('read', null, this._onLoadAnnotations);
};
Store.prototype._onLoadAnnotations = function(data) {
if (data == null) data = [];
this.annotations = data;
return this.annotator.loadAnnotations(data.slice());
};
Store.prototype.loadAnnotationsFromSearch = function(searchOptions) {
return this._apiRequest('search', searchOptions, this._onLoadAnnotationsFromSearch);
};
Store.prototype._onLoadAnnotationsFromSearch = function(data) {
if (data == null) data = {};
return this._onLoadAnnotations(data.rows || []);
};
Store.prototype.dumpAnnotations = function() {
var ann, _i, _len, _ref, _results;
_ref = this.annotations;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
ann = _ref[_i];
_results.push(JSON.parse(this._dataFor(ann)));
}
return _results;
};
Store.prototype._apiRequest = function(action, obj, onSuccess) {
var id, options, request, url;
id = obj && obj.id;
url = this._urlFor(action, id);
options = this._apiRequestOptions(action, obj, onSuccess);
request = $.ajax(url, options);
request._id = id;
request._action = action;
return request;
};
Store.prototype._apiRequestOptions = function(action, obj, onSuccess) {
var opts;
opts = {
type: this._methodFor(action),
headers: this.element.data('annotator:headers'),
dataType: "json",
success: onSuccess || function() {},
error: this._onError
};
if (action === "search") {
opts = $.extend(opts, {
data: obj
});
} else {
opts = $.extend(opts, {
data: obj && this._dataFor(obj),
contentType: "application/json; charset=utf-8"
});
}
return opts;
};
Store.prototype._urlFor = function(action, id) {
var replaceWith, url;
replaceWith = id != null ? '/' + id : '';
url = this.options.prefix != null ? this.options.prefix : '';
url += this.options.urls[action];
url = url.replace(/\/:id/, replaceWith);
return url;
};
Store.prototype._methodFor = function(action) {
var table;
table = {
'create': 'POST',
'read': 'GET',
'update': 'PUT',
'destroy': 'DELETE',
'search': 'GET'
};
return table[action];
};
Store.prototype._dataFor = function(annotation) {
var data, highlights;
highlights = annotation.highlights;
delete annotation.highlights;
$.extend(annotation, this.options.annotationData);
data = JSON.stringify(annotation);
if (highlights) annotation.highlights = highlights;
return data;
};
Store.prototype._onError = function(xhr) {
var action, message;
action = xhr._action;
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" this annotation");
if (xhr._action === 'search') {
message = Annotator._t("Sorry we could not search the store for annotations");
} else if (xhr._action === 'read' && !xhr._id) {
message = Annotator._t("Sorry we could not ") + action + Annotator._t(" the annotations from the store");
}
switch (xhr.status) {
case 401:
message = Annotator._t("Sorry you are not allowed to ") + action + Annotator._t(" this annotation");
break;
case 404:
message = Annotator._t("Sorry we could not connect to the annotations store");
break;
case 500:
message = Annotator._t("Sorry something went wrong with the annotation store");
}
Annotator.showNotification(message, Annotator.Notification.ERROR);
return console.error(Annotator._t("API request failed:") + (" '" + xhr.status + "'"));
};
return Store;
})(Annotator.Plugin);
}).call(this);
/*
** Annotator v1.2.5
** https://github.com/okfn/annotator/
**
** Copyright 2012 Aron Carroll, Rufus Pollock, and Nick Stenning.
** Dual licensed under the MIT and GPLv3 licenses.
** https://github.com/okfn/annotator/blob/master/LICENSE
**
** Built at: 2012-06-22 12:25:35Z
*/((function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b=Object.prototype.hasOwnProperty,c=function(a,c){function e(){this.constructor=a}for(var d in c)b.call(c,d)&&(a[d]=c[d]);return e.prototype=c.prototype,a.prototype=new e,a.__super__=c.prototype,a},d=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};Annotator.Plugin.Store=function(b){function e(b,c){this._onError=a(this._onError,this),this._onLoadAnnotationsFromSearch=a(this._onLoadAnnotationsFromSearch,this),this._onLoadAnnotations=a(this._onLoadAnnotations,this),this._getAnnotations=a(this._getAnnotations,this),e.__super__.constructor.apply(this,arguments),this.annotations=[]}return c(e,b),e.prototype.events={annotationCreated:"annotationCreated",annotationDeleted:"annotationDeleted",annotationUpdated:"annotationUpdated"},e.prototype.options={prefix:"/store",autoFetch:!0,annotationData:{},loadFromSearch:!1,urls:{create:"/annotations",read:"/annotations/:id",update:"/annotations/:id",destroy:"/annotations/:id",search:"/search"}},e.prototype.pluginInit=function(){if(!Annotator.supported())return;return this.annotator.plugins.Auth?this.annotator.plugins.Auth.withToken(this._getAnnotations):this._getAnnotations()},e.prototype._getAnnotations=function(){return this.options.loadFromSearch?this.loadAnnotationsFromSearch(this.options.loadFromSearch):this.loadAnnotations()},e.prototype.annotationCreated=function(a){var b=this;return d.call(this.annotations,a)<0?(this.registerAnnotation(a),this._apiRequest("create",a,function(c){return c.id==null&&console.warn(Annotator._t("Warning: No ID returned from server for annotation "),a),b.updateAnnotation(a,c)})):this.updateAnnotation(a,{})},e.prototype.annotationUpdated=function(a){var b=this;if(d.call(this.annotations,a)>=0)return this._apiRequest("update",a,function(c){return b.updateAnnotation(a,c)})},e.prototype.annotationDeleted=function(a){var b=this;if(d.call(this.annotations,a)>=0)return this._apiRequest("destroy",a,function(){return b.unregisterAnnotation(a)})},e.prototype.registerAnnotation=function(a){return this.annotations.push(a)},e.prototype.unregisterAnnotation=function(a){return this.annotations.splice(this.annotations.indexOf(a),1)},e.prototype.updateAnnotation=function(a,b){return d.call(this.annotations,a)<0?console.error(Annotator._t("Trying to update unregistered annotation!")):$.extend(a,b),$(a.highlights).data("annotation",a)},e.prototype.loadAnnotations=function(){return this._apiRequest("read",null,this._onLoadAnnotations)},e.prototype._onLoadAnnotations=function(a){return a==null&&(a=[]),this.annotations=a,this.annotator.loadAnnotations(a.slice())},e.prototype.loadAnnotationsFromSearch=function(a){return this._apiRequest("search",a,this._onLoadAnnotationsFromSearch)},e.prototype._onLoadAnnotationsFromSearch=function(a){return a==null&&(a={}),this._onLoadAnnotations(a.rows||[])},e.prototype.dumpAnnotations=function(){var a,b,c,d,e;d=this.annotations,e=[];for(b=0,c=d.length;b<c;b++)a=d[b],e.push(JSON.parse(this._dataFor(a)));return e},e.prototype._apiRequest=function(a,b,c){var d,e,f,g;return d=b&&b.id,g=this._urlFor(a,d),e=this._apiRequestOptions(a,b,c),f=$.ajax(g,e),f._id=d,f._action=a,f},e.prototype._apiRequestOptions=function(a,b,c){var d;return d={type:this._methodFor(a),headers:this.element.data("annotator:headers"),dataType:"json",success:c||function(){},error:this._onError},a==="search"?d=$.extend(d,{data:b}):d=$.extend(d,{data:b&&this._dataFor(b),contentType:"application/json; charset=utf-8"}),d},e.prototype._urlFor=function(a,b){var c,d;return c=b!=null?"/"+b:"",d=this.options.prefix||"/",d+=this.options.urls[a],d=d.replace(/\/:id/,c),d},e.prototype._methodFor=function(a){var b;return b={create:"POST",read:"GET",update:"PUT",destroy:"DELETE",search:"GET"},b[a]},e.prototype._dataFor=function(a){var b,c;return c=a.highlights,delete a.highlights,$.extend(a,this.options.annotationData),b=JSON.stringify(a),c&&(a.highlights=c),b},e.prototype._onError=function(a){var b,c;b=a._action,c=Annotator._t("Sorry we could not ")+b+Annotator._t(" this annotation"),a._action==="search"?c=Annotator._t("Sorry we could not search the store for annotations"):a._action==="read"&&!a._id&&(c=Annotator._t("Sorry we could not ")+b+Annotator._t(" the annotations from the store"));switch(a.status){case 401:c=Annotator._t("Sorry you are not allowed to ")+b+Annotator._t(" this annotation");break;case 404:c=Annotator._t("Sorry we could not connect to the annotations store");break;case 500:c=Annotator._t("Sorry something went wrong with the annotation store")}return Annotator.showNotification(c,Annotator.Notification.ERROR),console.error(Annotator._t("API request failed:")+(" '"+a.status+"'"))},e}(Annotator.Plugin)})).call(this);
\ No newline at end of file
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.6
*
* Requires: 1.2.2+
*/
(function($) {
var types = ['DOMMouseScroll', 'mousewheel'];
if ($.event.fixHooks) {
for ( var i=types.length; i; ) {
$.event.fixHooks[ types[--i] ] = $.event.mouseHooks;
}
}
$.event.special.mousewheel = {
setup: function() {
if ( this.addEventListener ) {
for ( var i=types.length; i; ) {
this.addEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i=types.length; i; ) {
this.removeEventListener( types[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
},
unmousewheel: function(fn) {
return this.unbind("mousewheel", fn);
}
});
function handler(event) {
var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0;
event = $.event.fix(orgEvent);
event.type = "mousewheel";
// Old school scrollwheel delta
if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; }
if ( orgEvent.detail ) { delta = -orgEvent.detail/3; }
// New school multidimensional scroll (touchpads) deltas
deltaY = delta;
// Gecko
if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
deltaY = 0;
deltaX = -1*delta;
}
// Webkit
if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; }
if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; }
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
})(jQuery);
/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
* Licensed under the MIT License (LICENSE.txt).
*
* Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
* Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
* Thanks to: Seamus Leahy for adding deltaX and deltaY
*
* Version: 3.0.6
*
* Requires: 1.2.2+
*/(function(a){function d(b){var c=b||window.event,d=[].slice.call(arguments,1),e=0,f=!0,g=0,h=0;return b=a.event.fix(c),b.type="mousewheel",c.wheelDelta&&(e=c.wheelDelta/120),c.detail&&(e=-c.detail/3),h=e,c.axis!==undefined&&c.axis===c.HORIZONTAL_AXIS&&(h=0,g=-1*e),c.wheelDeltaY!==undefined&&(h=c.wheelDeltaY/120),c.wheelDeltaX!==undefined&&(g=-1*c.wheelDeltaX/120),d.unshift(b,e,g,h),(a.event.dispatch||a.event.handle).apply(this,d)}var b=["DOMMouseScroll","mousewheel"];if(a.event.fixHooks)for(var c=b.length;c;)a.event.fixHooks[b[--c]]=a.event.mouseHooks;a.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=b.length;a;)this.addEventListener(b[--a],d,!1);else this.onmousewheel=d},teardown:function(){if(this.removeEventListener)for(var a=b.length;a;)this.removeEventListener(b[--a],d,!1);else this.onmousewheel=null}},a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
\ No newline at end of file
This diff is collapsed.
// example usage:
// thread = mail.messageThread().thread(messages.map(
// function(message) {
// return mail.message(message.subject, message.messageId, message.references);
// }
// ));
// conversation = thread.getConversation(messageId);
(function() {
function message(subject, id, references) {
return function(subject, id, references) {
return {
subject: subject,
id: id,
references: references
}
}(subject, id, references);
}
function messageContainer(message) {
return function(message) {
var children = [];
function getConversation(id) {
var child = this.getSpecificChild(id);
var flattened = [];
if(child) flattened = child.flattenChildren();
if(child.message) flattened.unshift(child.message);
return flattened;
}
function flattenChildren() {
var messages = [];
_.each(this.children, function(child) {
if (child.message) messages.push(child.message);
var nextChildren = child.flattenChildren();
if (nextChildren) {
_.each(nextChildren, function(nextChild) {
messages.push(nextChild);
})
}
});
if (messages.length > 0) return messages;
}
function getSpecificChild(id) {
var instance = this;
if (instance.message && instance.message.id == id) return instance;
var specificChild = null;
_.each(instance.children, function(child) {
var found = child.getSpecificChild(id);
if (found) {
specificChild = found;
return;
}
})
return specificChild;
}
function threadParent() {
if (!this.message) return this;
var next = this.parent;
if (!next) return this;
var top = next;
while (next) {
next = next.parent;
if (next) {
if (!next.message) return top;
top = next;
}
}
return top;
}
function addChild(child) {
if(child.parent) child.parent.removeChild(child);
this.children.push(child);
child.parent = this;
}
function removeChild(child) {
this.children = _.without(this.children, child);
delete child.parent;
}
function hasDescendant(container) {
if (this === container) return true;
if (this.children.length < 1) return false;
var descendantPresent = false;
_.each(this.children, function(child) {
if(child.hasDescendant(container)) descendantPresent = true;
})
return descendantPresent;
}
return {
message: message,
children: children,
flattenChildren: flattenChildren,
getConversation: getConversation,
getSpecificChild: getSpecificChild,
threadParent: threadParent,
addChild: addChild,
removeChild: removeChild,
hasDescendant: hasDescendant
}
}(message);
}
function messageThread() {
return function() {
var idTable = {};
function thread(messages) {
idTable = this.createIdTable(messages);
var root = messageContainer();
_.each(_.keys(idTable), function(id) {
var container = idTable[id];
if (!_.include(_.keys(container), "parent")) root.addChild(container);
})
delete idTable;
pruneEmpties(root);
return root;
}
function pruneEmpties(parent) {
for(var i = parent.children.length - 1; i >= 0; i--) {
var container = parent.children[i];
pruneEmpties(container);
if (!container.message && container.children.length === 0) {
parent.removeChild(container);
} else if (!container.message && container.children.length > 0) {
if (!parent.parent && container.children.length === 1) {
promoteChildren(parent, container)
} else if (!parent.parent && container.children.length > 1) {
// do nothing
} else {
promoteChildren(parent, container)
}
}
}
}
function promoteChildren(parent, container) {
for(var i = container.children.length - 1; i >= 0; i--) {
var child = container.children[i];
parent.addChild(child);
}
parent.removeChild(container);
}
function createIdTable(messages) {
idTable = {};
_.map(messages, function(message) {
var parentContainer = getContainer(message.id);
parentContainer.message = message;
var prev = null;
var references = message.references || [];
if (typeof(references) == 'string') {
references = [references]
}
_.each(references, function(reference) {
var container = getContainer(reference);
if (prev && !_.include(_.keys(container), "parent") && !container.hasDescendant(prev)) {
prev.addChild(container);
}
prev = container;
})
if (prev && !parentContainer.hasDescendant(prev)) {
prev.addChild(parentContainer);
}
})
return idTable;
}
function getContainer(id) {
if (_.include(_.keys(idTable), id)) {
return idTable[id];
} else {
return createContainer(id);
}
}
function createContainer(id) {
var container = mail.messageContainer();
idTable[id] = container;
return container;
}
function groupBySubject(root) {
var subjectTable = {};
_.each(root.children, function(container) {
if(!container.message) {
var c = container.children[0];
} else {
var c = container;
}
if (c && c.message) {
var message = c.message;
} else {
return;
}
var subject = helpers.normalizeSubject(message.subject);
if (subject.length === 0) return;
var existing = subjectTable[subject];
if (!existing) {
subjectTable[subject] = c;
} else if (
(typeof(existing.message) !== "undefined") && (
(typeof(c.message) === "undefined") ||
(helpers.isReplyOrForward(existing.message.subject)) &&
(!helpers.isReplyOrForward(message.subject))
)
) {
subjectTable[subject] = c;
}
})
for(var i = root.children.length - 1; i >= 0; i--) {
var container = root.children[i];
if (container.message) {
var subject = container.message.subject;
} else {
var subject = container.children[0].message.subject;
}
subject = helpers.normalizeSubject(subject);
var c = subjectTable[subject];
if (!c || c === container) continue;
if (
(typeof(c.message) === "undefined") &&
(typeof(container.message) === "undefined")
) {
_.each(container.children, function(ctr) {
c.addChild(ctr);
})
container.parent.removeChild(container);
} else if (
(typeof(c.message) === "undefined") &&
(typeof(container.message) !== "undefined")
) {
c.addChild(container);
} else if (
(!helpers.isReplyOrForward(c.message.subject)) &&
(helpers.isReplyOrForward(container.message.subject))
) {
c.addChild(container);
} else {
var newContainer = mail.messageContainer();
newContainer.addChild(c);
newContainer.addChild(container);
subjectTable[subject] = newContainer;
}
}
return subjectTable;
}
return {
getContainer: getContainer,
createContainer: createContainer,
createIdTable: createIdTable,
promoteChildren: promoteChildren,
pruneEmpties: pruneEmpties,
groupBySubject: groupBySubject,
thread: thread,
idTable: idTable
}
}();
}
var helpers = {
isReplyOrForward: function(subject) {
var pattern = /^(Re|Fwd)/i;
var match = subject.match(pattern);
return match ? true : false;
},
normalizeSubject: function(subject) {
var pattern = /((Re|Fwd)(\[[\d+]\])?:(\s)?)*([\w]*)/i;
var match = subject.match(pattern);
return match ? match[5] : false;
},
normalizeMessageId: function(messageId) {
var pattern = /<([^<>]+)>/;
var match = messageId.match(pattern);
return match ? match[1] : null;
},
parseReferences: function(references) {
if (!references) return null;
var pattern = /<[^<>]+>/g;
return _.map(references.match(pattern), function(match) {
return match.match(/[^<>]+/)[0];
})
}
}
var mail = this.mail = {
message: message,
messageContainer: messageContainer,
messageThread: messageThread,
helpers: helpers
};
if (typeof module !== 'undefined' && module.exports) {
_ = require('underscore');
module.exports = mail;
}
})();
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
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