Commit 801650f7 authored by Randall Leeds's avatar Randall Leeds

Merge branch 'angular-refactor' into develop

closes #219
closes #182
closes #162
closes #150
closes #104
parents f464c034 2eaf16d3
......@@ -23,19 +23,6 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
//HIDDEN TABS////////////////////////////////
.nav a[data-target="#activate-tab"],
.nav a[data-target="#forgot-tab"] {
display: none;
}
.nav .active a[data-target="#activate-tab"],
.nav .active a[data-target="#forgot-tab"] {
display: initial;
}
//ANNOTATOR STYLES////////////////////////////////
.annotator-hide {
display: none;
......@@ -53,21 +40,46 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
}
.sliding-panels > li {
@include smallshadow(-2px);
@include transition(transform .4s);
background: url('../images/noise_1.png');
margin-left: 1em;
overflow: scroll;
-webkit-overflow-scrolling: touch;
padding: 1em;
position: absolute;
height: 100%;
width: 100%;
&:first-child {
margin-left: 0;
@include box-shadow(none);
}
&.collapsed {
@include translateX(100%);
}
&.squished {
padding-left: .5em;
}
}
//SIDEBAR LAYOUT////////////////////////////////
#gutter {
background: url('../images/noise_1.png');
background-attachment: fixed;
height: 100%;
margin-left: $heatmap-width + 17px;
padding-top: 2.5em; // toolbar height + top margin
position: relative;
}
#wrapper {
background: url('../images/noise_1.png');
height: 100%;
overflow: auto;
padding: 3.75em 1em 1em 1em;
-webkit-overflow-scrolling: touch;
position: relative;
}
......@@ -169,15 +181,14 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
@include transform-origin(50%, 0);
z-index: 4;
background: url('../images/noise_1.png');
background-attachment: fixed;
margin-top: 2.5em;
padding-top: 1em;
position: absolute;
width: 100%;
.close {
margin-right: .5em;
margin-top: .25em;
position: absolute;
right: 1em;
top: 1em;
}
&.collapsed {
......@@ -201,6 +212,13 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
background-color: #f2dede;
border-color: #F5A1A0;
}
footer {
bottom: 1em;
right: 1em;
position: absolute;
text-align: right;
}
}
......@@ -212,15 +230,15 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
border: 1px solid $grayLighter;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
left: 1px;
line-height: $baseLineHeight;
height: 2em;
left: 7px;
line-height: 1.4em; // 2em (height) - .6em (padding)
margin-top: .5em;
padding: .3em;
position: absolute;
text-align: right;
width: 100%;
right: 0;
z-index: 5;
left: 7px;
& > div {
display: inline-block;
......@@ -232,31 +250,52 @@ svg { -webkit-tap-highlight-color: rgba(255, 255, 255, 0); }
}
.tri {
@include icon("tri-left.png");
@include fonticon("\e00b", left);
color: rgba( 200, 200, 200, 0.3);
text-shadow:
1px .8px 1.5px #fff,
0 0 0 #000;
float: left;
width: $baseLineHeight;
height: $baseLineHeight;
cursor: w-resize;
line-height: $baseLineHeight * .9;
font-size: $baseLineHeight * .9;
&:before {
vertical-align: 0;
}
&:hover {
color: rgba( 235, 235, 230, 0.1);
}
}
&.shown {
.tri {
@include icon("tri-right.png");
margin-right: .7em;
@include fonticon("\e010", left);
color: rgba( 200, 200, 200, 0.3);
text-shadow:
1px .8px 1.5px #fff,
0 0 0 #000;
float: left;
line-height: $baseLineHeight * .9;
font-size: $baseLineHeight * .9;
cursor: ew-resize;
&:before {
vertical-align: 0;
}
&:hover {
color: rgba( 235, 235, 230, 0.1);
}
}
}
}
.tinyman {
padding: 0 .3em;
padding: 0 .5em;
.avatar {
border-radius: 2px;
}
.dropdown-toggle {
@include linkbutton_right;
background-image: url("../images/dropdown_1.png");
@include fonticon("\002f", right, 0em);
.provider {
color: $grayLight;
display: none;
......
......@@ -52,6 +52,18 @@ $headingsFontFamily: inherit !default;
$headingsFontWeight: bold !default;
$headingsColor: inherit !default;
//ICON FONT IMPORT
@font-face {
font-family: 'icomoon';
src:url('../images/icomoon.eot');
src:url('../images/icomoon.eot?#iefix') format('embedded-opentype'),
url('../images/icomoon.svg#icomoon') format('svg'),
url('../images/icomoon.woff') format('woff'),
url('../images/icomoon.ttf') format('truetype');
font-weight: normal;
font-style: normal;
}
//STANCE COLORS
$positive: #3aab39;
$negative: #d11c2b;
......@@ -148,7 +160,9 @@ $em: 14 / 1em !default;
}
}
//ICON IMAGES
//ICON IMAGES////////////////////////////////
@mixin icon_resets {
background-repeat: no-repeat;
background-position: center;
......@@ -161,7 +175,8 @@ $em: 14 / 1em !default;
}
//ANNOTATOR TEXT STYLES
//ANNOTATOR TEXT STYLES////////////////////////////////
@mixin primarytext {
font-weight: bold;
font-size: 1.3em;
......@@ -176,7 +191,9 @@ $em: 14 / 1em !default;
font-size: .8em;
}
//LINKBUTTONS
//LINKBUTTONS////////////////////////////////
@mixin linkbutton {
text-decoration: none;
cursor: pointer;
......@@ -197,3 +214,80 @@ $em: 14 / 1em !default;
padding-left: 0;
padding-right: 1.3em;
}
//FONTICON////////////////////////////////
@mixin fonticon($char, $iconside, $offset: 0) {
text-decoration: none;
cursor: pointer;
color: $gray;
&:hover {
color: black;
opacity: 1;
}
@if $iconside == left {
&:before {
content: $char;
vertical-align: -.1em;
font-family: 'icomoon';
font-style: normal;
margin-right: $offset;
speak: none;
font-weight: normal;
}
}
@if $iconside == right {
&:after {
content: $char;
vertical-align: -.1em;
font-family: 'icomoon';
font-style: normal;
margin-left: $offset;
speak: none;
font-weight: normal;
}
}
}
//ICON CLASSES////////////////////////////////
.flag-icon {
@include fonticon("\28", left);
}
.fave-icon {
@include fonticon("\e006", left);
&.checked:before {
content: "\e005";
}
}
.reply-icon {
@include fonticon("\e004", left);
}
.share-icon {
@include fonticon("\25", left);
}
.down-icon {
@include fonticon("\e007", left);
}
.clipboard-icon {
@include fonticon("\33", left);
}
.check-icon {
@include fonticon("\35", left);
}
.x-icon {
@include fonticon("\36", left);
}
.vis-icon {
@include fonticon("\34", left);
}
......@@ -22,10 +22,14 @@ body {
}
p {
margin: 1em 0;
margin: 0 0 2em 0;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
&:last-child {
margin-bottom: 0;
}
}
em { font-style: italic; }
......@@ -124,8 +128,8 @@ label {
//CLOSER////////////////////////////////
.close {
@extend .x-icon;
float: right;
@include icon("delete_1.png");
width: 1em;
height: 1em;
opacity: .2;
......@@ -179,7 +183,6 @@ label {
}
a {
&.write {
@include linkbutton;
background-image: url("../images/pen_1.png");
......@@ -207,7 +210,6 @@ a {
}
//EXCERPT////////////////////////////////
.excerpt {
position: relative;
......@@ -232,8 +234,8 @@ blockquote {
&:before {
color: rgba(150, 150, 150, .5);
content: "\201C";
font-family: "PT Serif";
content: "\23";
font-family: "icomoon";
font-size: 6em;
position: absolute;
top: -.2em;
......@@ -242,8 +244,8 @@ blockquote {
&:after {
color: rgba(150, 150, 150, .5);
content: "\201E";
font-family: "PT Serif";
content: "\24";
font-family: "icomoon";
font-size: 5em;
position: absolute;
right: -.15em;
......@@ -269,8 +271,8 @@ blockquote {
@include smallshadow;
background: $white;
border: solid 1px $grayLighter;
border-top: 0;
left: 0;
top: 2em;
float: left;
opacity: 0;
pointer-events: none;
......@@ -349,10 +351,6 @@ blockquote {
&.active {
display: inherit !important;
}
footer > ul {
text-align: right;
}
}
......@@ -372,6 +370,16 @@ blockquote {
//ANNOTATION////////////////////////////////
//This is for everything that is formatted as an annotation.
.annotation {
position: relative;
div.body {
@include force-wrap;
}
textarea.body {
min-height: 8em;
width: 100%;
}
.user {
font-weight: bold;
......@@ -380,94 +388,136 @@ blockquote {
}
.time {
float: right;
display: inline-block;
line-height: 2em;
@include tertiarytext;
}
.body {
@include force-wrap;
}
.bottombar {
@include pie-clearfix;
margin: .25em 0;
.btn {
margin-right: .5em;
}
}
}
.tip {
@include tertiarytext;
float: right;
}
}
//DETAIL////////////////////////////////
//This is specific to the detail view.
.detail {
//THREADING////////////////////////////////
//Threaded discussion specific
.thread {
position: relative;
.paper > .threadexp {
display: none;
& > ul {
@include single-transition(max-height, .4s, cubic-bezier(1, 0, 1, 0));
max-height: 65536px;
overflow: hidden;
padding-left: $thread-padding;
margin-left: -$thread-padding;
& > * {
border-left: 1px dotted $grayLight;
position: relative;
}
}
.annotator-controls {
@include single-transition(opacity, .25s, ease-in-out);
opacity: 0;
.thread {
height: 100%;
padding-left: $thread-padding;
}
.threadexp {
height: $threadexp-width;
width: $threadexp-width;
position: absolute;
right: 0;
z-index: 1;
top: .8em;
left: -($threadexp-width / 2);
outline: 1px dotted #aaa;
@include icon("minus_1.png");
}
.replycount {
.reply-count {
@include tertiarytext;
}
.bottombar {
margin-bottom: .25em;
.annotation {
padding-top: .35em;
&.squished {
padding-left: 0;
}
}
//Threaded discussion specific
.annotator-listing {
border-left: 1px dotted $grayLight;
//These are all the changes needed to collapse thread objects.
.collapsed {
& > .annotation {
.body {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
white-space: nowrap;
font-style: italic;
margin-top: 0;
margin-bottom: 0;
}
.threadexp {
height: $threadexp-width;
width: $threadexp-width;
position: absolute;
top: .8em;
left: -($threadexp-width / 2);
outline: 1px dotted #aaa;
@include icon("minus_1.png");
.user {
display: run-in;
margin-right: .25em;
}
}
& > .detail, .writer {
padding-left: $thread-padding;
padding-top: .35em;
&.squished {
padding-left: 0;
}
& > ul {
@include single-transition(max-height, .4s, cubic-bezier(0, 1, 0, 1));
max-height: 0;
}
}
&.hover {
& > .annotator-controls {
opacity: 1;
& > .threadexp {
background-image: url("../images/plus_1.png");
}
}
}
//These are all the changes needed to collapse thread objects.
&.collapsed {
& > .body {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
white-space: nowrap;
font-style: italic;
margin-top: 0;
margin-bottom: 0;
//MAGICONTROLS////////////////////////////////
.magicontrols {
background-color: $bodyBackground;
float: right;
& > * {
@include transition(
opacity 0.1s ease-in-out .25s,
font-size .1s ease-in-out .25s,
margin .1s ease-in-out .25s,
opacity .1s ease-in-out .25s
);
@include tertiarytext;
float: left;
margin-left: .5em;
}
.show {
font-size: 0;
opacity: 0;
}
.detail:hover & {
& > * {
font-size: 1em;
}
& > .thread { overflow: hidden; }
& > .user {
display: run-in;
margin-right: .25em;
.show {
opacity: 1;
}
& > .threadexp {
background-image: url("../images/plus_1.png");
.time {
opacity: 0;
text-size: 0;
}
}
}
......@@ -490,25 +540,9 @@ blockquote {
@include smallshadow(2px, 1px, .1);
bottom: 0px;
}
}
//EDITOR////////////////////////////////
//This is an input element to make annotations.
.writer {
position: relative;
padding-left: 1em;
textarea {
width: 100%;
min-height: 8em;
}
.bottombar {
margin-top: .25em;
.btn {
margin-right: .5em;
}
// Things not shown in the summary view
.annotator-controls, .magicontrols .show, .bottombar, {
display: none;
}
}
......@@ -12,7 +12,7 @@
height: 40px;
margin-left: -20px;
margin-top: -50px;
padding: 5px;
padding: 0;
position: absolute;
border: 4px solid $border;
border-radius: 7px;
......@@ -38,7 +38,7 @@
button {
background: url("../images/pen_1.png");
background-size: 95% 95%;
background-size: 65%;
background-position: center;
background-repeat: no-repeat;
border: none;
......
This diff is collapsed.
angular.module 'h', [
imports = [
'bootstrap'
'deform'
'h.controllers'
'h.directives'
'h.filters'
'h.services'
]
configure = ($routeProvider) ->
$routeProvider.when '/editor',
controller: 'EditorController'
templateUrl: 'editor.html'
$routeProvider.when '/viewer',
controller: 'ViewerController'
reloadOnSearch: false
templateUrl: 'viewer.html'
configure.$inject = ['$routeProvider']
angular.module('h', imports, configure)
This diff is collapsed.
......@@ -49,6 +49,9 @@ deformDirective = ->
# Link function
(scope, iElement, iAttrs, controller) ->
if scope.addForm?
name = iAttrs.name or iAttrs.ngModel or iAttrs.id
scope.addForm iElement, name
deform.processCallbacks()
restrict: 'C'
require: 'form'
......
navTabsDirective = (deform) ->
link: (scope, iElement, iAttrs, controller) ->
iElement.find('a')
annotation = ['$filter', ($filter) ->
compile: (tElement, tAttrs, transclude) ->
# Adjust the ngModel directive to use the isolate scope binding.
# The expression will be bound in the isolate as '$modelValue'.
if tAttrs.ngModel
tAttrs.$set '$modelValue', tAttrs.ngModel, false
tAttrs.$set 'ngModel', '$modelValue', false
post: (scope, iElement, iAttrs, controller) ->
return unless controller
# Publish the controller
scope.model = controller
# Focus the first form element when showing a tab pane
.on 'shown', (e) ->
target = $(e.target).data('target')
deform.focusFirstInput(target)
# Format the annotation for display
controller.$formatters.push (value) ->
return unless angular.isObject value
angular.extend {}, value,
text: ($filter 'converter') (value.text or '')
# Always show the first pane to start
.first().tab('show')
# Update the annotation with text from the view
controller.$parsers.push (value) ->
if value.text != scope.$modelValue.text
angular.extend scope.$modelValue, text: value.text
else
scope.$modelValue
scope.$watch 'editText', (newValue) ->
if scope.form.$valid
controller.$viewValue.text = scope.editText
controller.$setViewValue controller.$viewValue
#scope.previewText = ($filter 'converter') scope.editText
else
scope.previewText = ''
controller: 'AnnotationController'
priority: 100 # Must run before ngModel
require: '?ngModel'
restrict: 'C'
navTabsDirective.$inject = ['deform']
scope:
$modelValue: '='
templateUrl: 'annotation.html'
]
recursive = ['$compile', '$timeout', ($compile, $timeout) ->
compile: (tElement, tAttrs, transclude) ->
placeholder = angular.element '<!-- recursive -->'
attachQueue = []
tick = false
template = tElement.contents().clone()
tElement.html ''
transclude = $compile template, (scope, cloneAttachFn) ->
clone = placeholder.clone()
cloneAttachFn clone
$timeout ->
transclude scope, (el, scope) -> attachQueue.push [clone, el]
unless tick
tick = true
requestAnimationFrame ->
tick = false
for [clone, el] in attachQueue
clone.replaceWith el
attachQueue = []
clone
post: (scope, iElement, iAttrs, controller) ->
transclude scope, (contents) -> iElement.append contents
restrict: 'A'
terminal: true
]
tabReveal = ['$parse', ($parse) ->
compile: (tElement, tAttrs, transclude) ->
panes = []
hiddenPanesGet = $parse tAttrs.tabReveal
pre: (scope, iElement, iAttrs, [ngModel, tabbable] = controller) ->
# Hijack the tabbable controller's addPane so that the visibility of the
# secret ones can be managed. This avoids traversing the DOM to find
# the tab panes.
addPane = tabbable.addPane
tabbable.addPane = (element, attr) =>
removePane = addPane.call tabbable, element, attr
panes.push
element: element
attr: attr
=>
for i in [0..panes.length]
if panes[i].element is element
panes.splice i, 1
break
removePane()
post: (scope, iElement, iAttrs, [ngModel, tabbable] = controller) ->
tabs = angular.element(iElement.children()[0].childNodes)
render = angular.bind ngModel, ngModel.$render
ngModel.$render = ->
render()
hiddenPanes = hiddenPanesGet scope
return unless angular.isArray hiddenPanes
for i in [0..panes.length-1]
pane = panes[i]
value = pane.attr.value || pane.attr.title
if value == ngModel.$viewValue
deform.focusFirstInput pane.element
pane.element.css 'display', ''
angular.element(tabs[i]).css 'display', ''
else if value in hiddenPanes
pane.element.css 'display', 'none'
angular.element(tabs[i]).css 'display', 'none'
require: ['ngModel', 'tabbable']
]
thread = ->
link: (scope, iElement, iAttrs, controller) ->
scope.collapsed = false
restrict: 'C'
scope: true
angular.module('h.directives', ['deform'])
.directive('navTabs', navTabsDirective)
angular.module('h.directives', ['ngSanitize', 'deform'])
.directive('annotation', annotation)
.directive('recursive', recursive)
.directive('tabReveal', tabReveal)
.directive('thread', thread)
class Converter extends Markdown.Converter
constructor: ->
super
this.hooks.chain "postConversion", (text) ->
text.replace /<a href=/, "<a target=\"_blank\" href="
fuzzyTime = (date) ->
return '' if not date
delta = Math.round((+new Date - new Date(date)) / 1000)
minute = 60
hour = minute * 60
day = hour * 24
week = day * 7
month = day * 30
if (delta < 30)
fuzzy = 'moments ago.'
else if (delta < minute)
fuzzy = delta + ' seconds ago.'
else if (delta < 2 * minute)
fuzzy = 'a minute ago.'
else if (delta < hour)
fuzzy = Math.floor(delta / minute) + ' minutes ago.'
else if (Math.floor(delta / hour) == 1)
fuzzy = '1 hour ago.'
else if (delta < day)
fuzzy = Math.floor(delta / hour) + ' hours ago.'
else if (delta < day * 2)
fuzzy = 'yesterday'
else if (delta < month)
fuzzy = Math.round(delta / day) + ' days ago'
else
fuzzy = new Date(date).toLocaleDateString()
userName = (user) ->
(user?.match /^acct:([^@]+)/)?[1]
angular.module('h.filters', [])
.filter('converter', -> (new Converter()).makeHtml)
.filter('fuzzyTime', -> fuzzyTime)
.filter('userName', -> userName)
/*
* http://stackoverflow.com/a/7641812
*/
var Handlebars
Handlebars.registerHelper('fuzzyTime', function (date) {
if (!date) return ''
var delta = Math.round((+new Date - new Date(date)) / 1000)
var minute = 60,
hour = minute * 60,
day = hour * 24,
week = day * 7,
month = day * 30
var fuzzy
if (delta < 30) {
fuzzy = 'moments ago.'
} else if (delta < minute) {
fuzzy = delta + ' seconds ago.'
} else if (delta < 2 * minute) {
fuzzy = 'a minute ago.'
} else if (delta < hour) {
fuzzy = Math.floor(delta / minute) + ' minutes ago.'
} else if (Math.floor(delta / hour) == 1) {
fuzzy = '1 hour ago.'
} else if (delta < day) {
fuzzy = Math.floor(delta / hour) + ' hours ago.'
} else if (delta < day * 2) {
fuzzy = 'yesterday'
} else if (delta < month) {
fuzzy = Math.round(delta / day) + ' days ago'
} else {
fuzzy = new Date(date).toLocaleDateString()
}
return fuzzy
})
Handlebars.registerHelper('splitUser', function (user, options) {
var parts = this.user.split(/(?:acct:)|@/);
return options.fn({
name: parts[1],
provider: parts[2]
})
})
Handlebars.registerHelper('formatUser', function (user) {
return new Handlebars.SafeString(
user.replace(
/^acct:([^@]+)@(.+)$/,
'$1 <span class="provider">on $2</span>'))
})
......@@ -48,10 +48,9 @@ class Annotator.Host extends Annotator
container: @wrapper[0]
local: options.local
onReady: () ->
# Outside this closure, `this`, as it refers to the `easyXDM.Rpc`
# object, is hidden, private to the `Rpc` object. Here, exploit access
# easyXDM updates this configuration object which provides access
# to the `props` attribute to find the value of the iframe's src
# attribute to be sure it is the iframe created by easyXDM.
# attribute to find the iframe created by easyXDM.
frame = $(this.container).find('[src^="'+@props.src+'"]')
.css('visibility', 'visible')
# Export the iframe element via the private `annotator` variable,
......@@ -67,6 +66,15 @@ class Annotator.Host extends Annotator
local:
publish: (args..., k, fk) => this.publish args...
setupAnnotation: => this.setupAnnotation arguments...
deleteAnnotation: (annotation) =>
toDelete = []
@wrapper.find('.annotator-hl')
.each ->
data = $(this).data('annotation')
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: =>
......@@ -89,16 +97,23 @@ class Annotator.Host extends Annotator
highlights: $(@wrapper).find('.annotator-hl').map ->
offset: $(this).offset()
height: $(this).outerHeight(true)
data: $(this).data('annotation').hash
data: $(this).data('annotation')
.get()
offset: $(window).scrollTop()
setActiveHighlights: (hashes=[]) =>
setActiveHighlights: (ids=[]) =>
@wrapper.find('.annotator-hl')
.each ->
if $(this).data('annotation').hash in hashes
if $(this).data('annotation').id in ids
$(this).addClass('annotator-hl-active')
else
else if not $(this).hasClass('annotator-hl-temporary')
$(this).removeClass('annotator-hl-active')
getHref: =>
uri = document.location.href
if document.location.hash
uri = uri.slice 0, (-1 * location.hash.length)
$('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'
......@@ -128,12 +143,6 @@ class Annotator.Host extends Annotator
back: {}
update: {}
publish: (event, args) ->
if event in ['annotationCreated']
[annotation] = args
@consumer.publish event, [annotation.hash]
super arguments...
_setupWrapper: ->
@wrapper = @element
.on 'mouseup', =>
......@@ -143,32 +152,37 @@ class Annotator.Host extends Annotator
this
_setupDocumentEvents: ->
# CSS "position: fixed" is hell of broken on most mobile devices
# In this code, fixed is used *during* scroll on touch devices to prevent
# jerky re-positioning of the sidebar. After scroll ends, the sidebar
# is reset to "position: absolute" and positioned accordingly.
tick = false
timeout = null
touch = false
update = util.debounce =>
unless touch
@consumer.update()
return
if timeout then cancelTimeout timeout
timeout = setTimeout =>
timeout = null
@frame?.css
display: ''
height: $(window).height()
position: 'absolute'
top: $(window).scrollTop()
@consumer.update()
, 400
document.addEventListener 'touchmove', =>
@frame?.css
display: 'none'
do update
update = =>
if touch
# Defer updates on mobile until after touch events are over
if timeout then cancelTimeout timeout
timeout = setTimeout =>
timeout = null
do updateFrame
, 400
else
do updateFrame
updateFrame = =>
unless tick
tick = true
requestAnimationFrame =>
tick = false
if touch
# CSS "position: fixed" is hell of broken on most mobile devices
@frame?.css
display: ''
height: $(window).height()
position: 'absolute'
top: $(window).scrollTop()
@consumer.update()
document.addEventListener 'touchmove', update
document.addEventListener 'touchstart', =>
touch = true
@frame?.css
display: 'none'
do update
document.addEventListener 'dragover', (event) =>
if @drag.last?
......@@ -185,7 +199,7 @@ class Annotator.Host extends Annotator
@drag.tick = true
window.requestAnimationFrame this.dragRefresh
$(window).on 'resize scroll', update
$(document.body).on 'resize scroll', '*', util.debounce => @consumer.update()
$(document.body).on 'resize scroll', '*', update
super
# These methods aren't used in the iframe-hosted configuration of Annotator.
......@@ -195,6 +209,17 @@ class Annotator.Host extends Annotator
_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: =>
d = @drag.delta
@drag.delta = 0
......@@ -211,20 +236,16 @@ class Annotator.Host extends Annotator
width: "#{w}px"
showEditor: (annotation) =>
stub =
ranges: annotation.ranges
quote: annotation.quote
hash: annotation.hash
if not stub.hash
@consumer.createAnnotation (hash) =>
if not hash?
if not annotation.id?
@consumer.createAnnotation (id) =>
if id?
annotation.id = id
@consumer.showEditor annotation
else
this.deleteAnnotation annotation
@ignoreMouseup = false
else
annotation.hash = stub.hash = hash
@consumer.showEditor stub
else
@consumer.showEditor stub
@consumer.showEditor annotation
checkForStartSelection: (event) =>
# Override to prevent Annotator choking when this ties to access the
......
class Annotator.Plugin.Heatmap extends Annotator.Plugin
# prototype constants
this::BUCKET_THRESHOLD_PAD = 40
this::BUCKET_SIZE = 50
BUCKET_THRESHOLD_PAD: 40
BUCKET_SIZE: 50
# heatmap svg skeleton
html: """
......@@ -30,7 +29,7 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
index: []
constructor: (element, options) ->
super $(@html, options)
super $(@html), options
_collate: (a, b) =>
for i in [0..a.length-1]
......@@ -116,7 +115,7 @@ class Annotator.Plugin.Heatmap extends Annotator.Plugin
min = w
break if min == 0 # short-circuit optimization
# Merge them if they are closer enough
# Merge them if they are close enough
if min < threshold
# Prefer merging the successor bucket backward but not if it's last
# since the gradient must always return to 0 at the end
......
class HypothesisPermissions extends Annotator.Plugin.Permissions
pluginInit: ->
# Overridden to do nothing.
# Stops the evil DOM manipulation craziness.
Annotator.Plugin.HypothesisPermissions = HypothesisPermissions
\ No newline at end of file
This diff is collapsed.
(function () {
var output, Converter;
if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
output = exports;
Converter = require("./Markdown.Converter").Converter;
} else {
output = window.Markdown;
Converter = output.Converter;
}
output.getSanitizingConverter = function () {
var converter = new Converter();
converter.hooks.chain("postConversion", sanitizeHtml);
converter.hooks.chain("postConversion", balanceTags);
return converter;
}
function sanitizeHtml(html) {
return html.replace(/<[^>]*>?/gi, sanitizeTag);
}
// (tags that can be opened/closed) | (tags that stand alone)
var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
// <a href="url..." optional title>|</a>
var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
// <img src="url..." optional width optional height optional alt optional title
var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
function sanitizeTag(tag) {
if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
return tag;
else
return "";
}
/// <summary>
/// attempt to balance HTML tags in the html string
/// by removing any unmatched opening or closing tags
/// IMPORTANT: we *assume* HTML has *already* been
/// sanitized and is safe/sane before balancing!
///
/// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
/// </summary>
function balanceTags(html) {
if (html == "")
return "";
var re = /<\/?\w+[^>]*(\s|$|>)/g;
// convert everything to lower case; this makes
// our case insensitive comparisons easier
var tags = html.toLowerCase().match(re);
// no HTML tags present? nothing to do; exit now
var tagcount = (tags || []).length;
if (tagcount == 0)
return html;
var tagname, tag;
var ignoredtags = "<p><img><br><li><hr>";
var match;
var tagpaired = [];
var tagremove = [];
var needsRemoval = false;
// loop through matched tags in forward order
for (var ctag = 0; ctag < tagcount; ctag++) {
tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
// skip any already paired tags
// and skip tags in our ignore list; assume they're self-closed
if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
continue;
tag = tags[ctag];
match = -1;
if (!/^<\//.test(tag)) {
// this is an opening tag
// search forwards (next tags), look for closing tags
for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
match = ntag;
break;
}
}
}
if (match == -1)
needsRemoval = tagremove[ctag] = true; // mark for removal
else
tagpaired[match] = true; // mark paired
}
if (!needsRemoval)
return html;
// delete all orphaned tags from the string
var ctag = 0;
html = html.replace(re, function (match) {
var res = tagremove[ctag] ? "" : match;
ctag++;
return res;
});
return html;
}
})();
/*
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);
/*
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 source diff could not be displayed because it is too large. You can view the blob instead.
var Handlebars={};Handlebars.VERSION="1.0.beta.6";Handlebars.helpers={};Handlebars.partials={};Handlebars.registerHelper=function(b,c,a){if(a){c.not=a}this.helpers[b]=c};Handlebars.registerPartial=function(a,b){this.partials[a]=b};Handlebars.registerHelper("helperMissing",function(a){if(arguments.length===2){return undefined}else{throw new Error("Could not find property '"+a+"'")}});var toString=Object.prototype.toString,functionType="[object Function]";Handlebars.registerHelper("blockHelperMissing",function(f,d){var a=d.inverse||function(){},h=d.fn;var c="";var g=toString.call(f);if(g===functionType){f=f.call(this)}if(f===true){return h(this)}else{if(f===false||f==null){return a(this)}else{if(g==="[object Array]"){if(f.length>0){for(var e=0,b=f.length;e<b;e++){c=c+h(f[e])}}else{c=a(this)}return c}else{return h(f)}}}});Handlebars.registerHelper("each",function(f,d){var g=d.fn,a=d.inverse;var c="";if(f&&f.length>0){for(var e=0,b=f.length;e<b;e++){c=c+g(f[e])}}else{c=a(this)}return c});Handlebars.registerHelper("if",function(b,a){var c=toString.call(b);if(c===functionType){b=b.call(this)}if(!b||Handlebars.Utils.isEmpty(b)){return a.inverse(this)}else{return a.fn(this)}});Handlebars.registerHelper("unless",function(c,b){var d=b.fn,a=b.inverse;b.fn=a;b.inverse=d;return Handlebars.helpers["if"].call(this,c,b)});Handlebars.registerHelper("with",function(b,a){return a.fn(b)});Handlebars.registerHelper("log",function(a){Handlebars.log(a)});Handlebars.Exception=function(b){var a=Error.prototype.constructor.apply(this,arguments);for(var c in a){if(a.hasOwnProperty(c)){this[c]=a[c]}}this.message=a.message};Handlebars.Exception.prototype=new Error;Handlebars.SafeString=function(a){this.string=a};Handlebars.SafeString.prototype.toString=function(){return this.string.toString()};(function(){var c={"<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"};var d=/&(?!\w+;)|[<>"'`]/g;var b=/[&<>"'`]/;var a=function(e){return c[e]||"&amp;"};Handlebars.Utils={escapeExpression:function(e){if(e instanceof Handlebars.SafeString){return e.toString()}else{if(e==null||e===false){return""}}if(!b.test(e)){return e}return e.replace(d,a)},isEmpty:function(e){if(typeof e==="undefined"){return true}else{if(e===null){return true}else{if(e===false){return true}else{if(Object.prototype.toString.call(e)==="[object Array]"&&e.length===0){return true}else{return false}}}}}}})();Handlebars.VM={template:function(a){var b={escapeExpression:Handlebars.Utils.escapeExpression,invokePartial:Handlebars.VM.invokePartial,programs:[],program:function(d,e,f){var c=this.programs[d];if(f){return Handlebars.VM.program(e,f)}else{if(c){return c}else{c=this.programs[d]=Handlebars.VM.program(e);return c}}},programWithDepth:Handlebars.VM.programWithDepth,noop:Handlebars.VM.noop};return function(d,c){c=c||{};return a.call(b,Handlebars,d,c.helpers,c.partials,c.data)}},programWithDepth:function(b,d,c){var a=Array.prototype.slice.call(arguments,2);return function(f,e){e=e||{};return b.apply(this,[f,e.data||d].concat(a))}},program:function(a,b){return function(d,c){c=c||{};return a(d,c.data||b)}},noop:function(){return""},invokePartial:function(a,b,d,e,c,f){options={helpers:e,partials:c,data:f};if(a===undefined){throw new Handlebars.Exception("The partial "+b+" could not be found")}else{if(a instanceof Function){return a(d,options)}else{if(!Handlebars.compile){throw new Handlebars.Exception("The partial "+b+" could not be compiled when running in runtime-only mode")}else{c[b]=Handlebars.compile(a);return c[b](d,options)}}}}};Handlebars.template=Handlebars.VM.template;
\ No newline at end of file
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