<commit_before>define([
           'dojo/_base/declare',
           'dojo/_base/array',
           'dojo/_base/lang',
           'JBrowse/Util/FastPromise',
           'JBrowse/View/FeatureGlyph',
           './_FeatureLabelMixin'
       ],
       function(
           declare,
           array,
           lang,
           FastPromise,
           FeatureGlyph,
           FeatureLabelMixin
       ) {

return declare([ FeatureGlyph, FeatureLabelMixin ], {

    constructor: function() {
        this._embeddedImagePromises = {};
    },

    _defaultConfig: function() {
        return this._mergeConfigs(
            this.inherited(arguments),
            {
                style: {
                    maxDescriptionLength: 70,

                    color: 'goldenrod',
                    mouseovercolor: 'rgba(0,0,0,0.3)',
                    border_color: null,
                    border_width: 0.5,
                    height: 11,
                    marginBottom: 2,

                    strandArrow: true,

                    label: 'name, id',
                    textFont: 'normal 12px Univers,Helvetica,Arial,sans-serif',
                    textColor:  'black',
                    text2Color: 'blue',
                    text2Font: 'normal 12px Univers,Helvetica,Arial,sans-serif',

                    description: 'note, description'
                }
            });
    },

    _getFeatureHeight: function( viewArgs, feature ) {
        var h = this.getStyle( feature, 'height');

        if( viewArgs.displayMode == 'compact' )
            h = 0.45 * h;

        return h;
    },

    _getFeatureRectangle: function( viewArgs, feature ) {
        var block = viewArgs.block;
        var fRect = {
            l: block.bpToX( feature.get('start') ),
            h: this._getFeatureHeight(viewArgs, feature),
            viewInfo: viewArgs,
            f: feature,
            glyph: this
        };

        fRect.w = block.bpToX( feature.get('end') ) - fRect.l;

        // save the original rect in `rect` as the dimensions
        // we'll use for the rectangle itself
        fRect.rect = { l: fRect.l, h: fRect.h, w: Math.max( fRect.w, 2 ), t: 0 };
        fRect.w = fRect.rect.w; // in case it was increased
        if( viewArgs.displayMode != 'compact' )
            fRect.h += this.getStyle( feature, 'marginBottom' ) || 0
;
        // if we are showing strand arrowheads, expand the frect a little
        if( this.getStyle( feature, 'strandArrow') ) {
            var strand = fRect.strandArrow = feature.get('strand');

            fRect.w += 9;
            fRect.rect.w += 9;
            if( strand == -1 ) {
                fRect.l -= 9;
                fRect.rect.l -= 9;
            }
        }

        // no labels or descriptions if displayMode is collapsed, so stop here
        if( viewArgs.displayMode == "collapsed")
            return fRect;

        this._expandRectangleWithLabels( viewArgs, feature, fRect );
        this._addMasksToRect( viewArgs, feature, fRect );

        return fRect;
    },

    layoutFeature: function( viewArgs, layout, feature ) {
        var rect = this.inherited( arguments );
        if( ! rect ) return rect;

        // need to set the top of the inner rect
        rect.rect.t = rect.t;

        return rect;
    },

    // given an under-construction feature layout rectangle, expand it
    // to accomodate a label and/or a description
    _expandRectangleWithLabels: function( viewArgs, feature, fRect ) {
        // maybe get the feature's name, and update the layout box
        // accordingly
        if( viewArgs.showLabels ) {
            var label = this.makeFeatureLabel( feature, fRect );
            if( label ) {
                fRect.h += label.h;
                fRect.w = Math.max( label.w, fRect.w );
                fRect.label = label;
                label.yOffset = fRect.rect.h + label.h;
            }
        }

        // maybe get the feature's description if available, and
        // update the layout box accordingly
        if( viewArgs.showDescriptions ) {
            var description = this.makeFeatureDescriptionLabel( feature, fRect );
            if( description ) {
                fRect.description = description;
                fRect.h += description.h;
                fRect.w = Math.max( description.w, fRect.w );
                description.yOffset = fRect.h-(this.getStyle( feature, 'marginBottom' ) || 0);
            }
        }
    },

    _imgData: {
         plus_arrow: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAATUlEQVQIW2NkwATGQKFYIG4A4g8gacb///+7AWlBmNq+vj6V4uLiJiD/FRBXA/F8xu7u7kcVFRWyMEVATQz//v0Dcf9CxaYRZxIxbgIARiAhmifVe8UAAAAASUVORK5CYII=",
         minus_arrow: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAASklEQVQIW2NkQAABILMBiBcD8VkkcQZGIAeEE4G4FYjFent764qKiu4gKXoPUjAJiLOggsxMTEwMjIwgYQjo6Oh4TLRJME043QQA+W8UD/sdk9IAAAAASUVORK5CYII="
    },

    /**
     * Returns a promise for an Image object for the image with the
     * given name.  Image data comes from a data URL embedded in this
     * source code.
     */
    getEmbeddedImage: function( name ) {
        return (this._embeddedImagePromises[name] || function() {
                    var p = new FastPromise();
                    var data = this._imgData[ name ];
                    if( ! data ) {
                        p.resolve( null );
                    }
                    else {
                        var i = new Image();
                        var thisB = this;
                        i.onload = function() {
                            p.resolve( this );
                        };
                        i.src = data;
                    }
                    return this._embeddedImagePromises[name] = p;
                }.call(this));
    },

    renderFeature: function( context, fRect ) {
        if( this.track.displayMode != 'collapsed' )
            context.clearRect( Math.floor(fRect.l), fRect.t, Math.ceil(fRect.w-Math.floor(fRect.l)+fRect.l), fRect.h );

        this.renderBox( context, fRect.viewInfo, fRect.f, fRect.t, fRect.rect.h, fRect.f );
        this.renderLabel( context, fRect );
        this.renderDescription( context, fRect );
        this.renderArrowhead( context, fRect );
    },

    // top and height are in px
    renderBox: function( context, viewInfo, feature, top, overallHeight, parentFeature, style ) {
        var left  = viewInfo.block.bpToX( feature.get('start') );
        var width = viewInfo.block.bpToX( feature.get('end') ) - left;
        //left = Math.round( left );
        //width = Math.round( width );

        style = style || lang.hitch( this, 'getStyle' );

        var height = style( feature, 'height' );
        if( ! height )
            return;
        if( height != overallHeight )
            top += Math.round( (overallHeight - height)/2 );

        // background
        var bgcolor = style( feature, 'color' );
        if( bgcolor ) {
            context.fillStyle = bgcolor;
            context.fillRect( left, top, Math.max(1,width), height );
        }
        else {
            context.clearRect( left, top, Math.max(1,width), height );
        }

        // foreground border
        var borderColor, lineWidth;
        if( (borderColor = style( feature, 'border_color' )) && ( lineWidth = style( feature, 'border_width')) ) {
            if( width > 3 ) {
                context.lineWidth = lineWidth;
                context.strokeStyle = borderColor;

                // need to stroke a smaller rectangle to remain within
                // the bounds of the feature's overall height and
                // width, because of the way stroking is done in
                // canvas.  thus the +0.5 and -1 business.
                context.strokeRect( left+lineWidth/2, top+lineWidth/2, width-lineWidth, height-lineWidth );
            }
            else {
                context.globalAlpha = lineWidth*2/width;
                context.fillStyle = borderColor;
                context.fillRect( left, top, Math.max(1,width), height );
                context.globalAlpha = 1;
            }
        }
    },

    // feature label
    renderLabel: function( context, fRect ) {
        if( fRect.label ) {
            context.font = fRect.label.font;
            context.fillStyle = fRect.label.fill;
            context.textBaseline = fRect.label.baseline;
            context.fillText( fRect.label.text,
                              fRect.l+(fRect.label.xOffset||0),
                              fRect.t+(fRect.label.yOffset||0)
                            );
        }
    },

    // feature description
    renderDescription: function( context, fRect ) {
        if( fRect.description ) {
            context.font = fRect.description.font;
            context.fillStyle = fRect.description.fill;
            context.textBaseline = fRect.description.baseline;
            context.fillText(
                fRect.description.text,
                fRect.l+(fRect.description.xOffset||0),
                fRect.t + (fRect.description.yOffset||0)
            );
        }
    },

    // strand arrowhead
    renderArrowhead: function( context, fRect ) {
        if( fRect.strandArrow ) {
            if( fRect.strandArrow == 1 && fRect.rect.l+fRect.rect.w <= context.canvas.width ) {
                this.getEmbeddedImage( 'plus_arrow' )
                    .then( function( img ) {
                               context.imageSmoothingEnabled = false;
                               context.drawImage( img, fRect.rect.l + fRect.rect.w - 9, fRect.t + (fRect.rect.h-img.height)/2 );
                           });
            }
            else if( fRect.strandArrow == -1 && fRect.rect.l >= 0 ) {
                this.getEmbeddedImage( 'minus_arrow' )
                    .then( function( img ) {
                               context.imageSmoothingEnabled = false;
                               context.drawImage( img, fRect.rect.l, fRect.t + (fRect.rect.h-img.height)/2 );
                           });
            }
        }
    }

});
});<commit_msg>don't include strand arrows as part of the fRect.rect<commit_after>define([
           'dojo/_base/declare',
           'dojo/_base/array',
           'dojo/_base/lang',
           'JBrowse/Util/FastPromise',
           'JBrowse/View/FeatureGlyph',
           './_FeatureLabelMixin'
       ],
       function(
           declare,
           array,
           lang,
           FastPromise,
           FeatureGlyph,
           FeatureLabelMixin
       ) {

return declare([ FeatureGlyph, FeatureLabelMixin ], {

    constructor: function() {
        this._embeddedImagePromises = {};
    },

    _defaultConfig: function() {
        return this._mergeConfigs(
            this.inherited(arguments),
            {
                style: {
                    maxDescriptionLength: 70,

                    color: 'goldenrod',
                    mouseovercolor: 'rgba(0,0,0,0.3)',
                    border_color: null,
                    border_width: 0.5,
                    height: 11,
                    marginBottom: 2,

                    strandArrow: true,

                    label: 'name, id',
                    textFont: 'normal 12px Univers,Helvetica,Arial,sans-serif',
                    textColor:  'black',
                    text2Color: 'blue',
                    text2Font: 'normal 12px Univers,Helvetica,Arial,sans-serif',

                    description: 'note, description'
                }
            });
    },

    _getFeatureHeight: function( viewArgs, feature ) {
        var h = this.getStyle( feature, 'height');

        if( viewArgs.displayMode == 'compact' )
            h = 0.45 * h;

        return h;
    },

    _getFeatureRectangle: function( viewArgs, feature ) {
        var block = viewArgs.block;
        var fRect = {
            l: block.bpToX( feature.get('start') ),
            h: this._getFeatureHeight(viewArgs, feature),
            viewInfo: viewArgs,
            f: feature,
            glyph: this
        };

        fRect.w = block.bpToX( feature.get('end') ) - fRect.l;

        // save the original rect in `rect` as the dimensions
        // we'll use for the rectangle itself
        fRect.rect = { l: fRect.l, h: fRect.h, w: Math.max( fRect.w, 2 ), t: 0 };
        fRect.w = fRect.rect.w; // in case it was increased
        if( viewArgs.displayMode != 'compact' )
            fRect.h += this.getStyle( feature, 'marginBottom' ) || 0
;
        // if we are showing strand arrowheads, expand the frect a little
        if( this.getStyle( feature, 'strandArrow') ) {
            var strand = fRect.strandArrow = feature.get('strand');

            fRect.w += 9;
            if( strand == -1 ) {
                fRect.l -= 9;
            }
        }

        // no labels or descriptions if displayMode is collapsed, so stop here
        if( viewArgs.displayMode == "collapsed")
            return fRect;

        this._expandRectangleWithLabels( viewArgs, feature, fRect );
        this._addMasksToRect( viewArgs, feature, fRect );

        return fRect;
    },

    layoutFeature: function( viewArgs, layout, feature ) {
        var rect = this.inherited( arguments );
        if( ! rect ) return rect;

        // need to set the top of the inner rect
        rect.rect.t = rect.t;

        return rect;
    },

    // given an under-construction feature layout rectangle, expand it
    // to accomodate a label and/or a description
    _expandRectangleWithLabels: function( viewArgs, feature, fRect ) {
        // maybe get the feature's name, and update the layout box
        // accordingly
        if( viewArgs.showLabels ) {
            var label = this.makeFeatureLabel( feature, fRect );
            if( label ) {
                fRect.h += label.h;
                fRect.w = Math.max( label.w, fRect.w );
                fRect.label = label;
                label.yOffset = fRect.rect.h + label.h;
            }
        }

        // maybe get the feature's description if available, and
        // update the layout box accordingly
        if( viewArgs.showDescriptions ) {
            var description = this.makeFeatureDescriptionLabel( feature, fRect );
            if( description ) {
                fRect.description = description;
                fRect.h += description.h;
                fRect.w = Math.max( description.w, fRect.w );
                description.yOffset = fRect.h-(this.getStyle( feature, 'marginBottom' ) || 0);
            }
        }
    },

    _imgData: {
         plus_arrow: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAATUlEQVQIW2NkwATGQKFYIG4A4g8gacb///+7AWlBmNq+vj6V4uLiJiD/FRBXA/F8xu7u7kcVFRWyMEVATQz//v0Dcf9CxaYRZxIxbgIARiAhmifVe8UAAAAASUVORK5CYII=",
         minus_arrow: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAASklEQVQIW2NkQAABILMBiBcD8VkkcQZGIAeEE4G4FYjFent764qKiu4gKXoPUjAJiLOggsxMTEwMjIwgYQjo6Oh4TLRJME043QQA+W8UD/sdk9IAAAAASUVORK5CYII="
    },

    /**
     * Returns a promise for an Image object for the image with the
     * given name.  Image data comes from a data URL embedded in this
     * source code.
     */
    getEmbeddedImage: function( name ) {
        return (this._embeddedImagePromises[name] || function() {
                    var p = new FastPromise();
                    var data = this._imgData[ name ];
                    if( ! data ) {
                        p.resolve( null );
                    }
                    else {
                        var i = new Image();
                        var thisB = this;
                        i.onload = function() {
                            p.resolve( this );
                        };
                        i.src = data;
                    }
                    return this._embeddedImagePromises[name] = p;
                }.call(this));
    },

    renderFeature: function( context, fRect ) {
        if( this.track.displayMode != 'collapsed' )
            context.clearRect( Math.floor(fRect.l), fRect.t, Math.ceil(fRect.w-Math.floor(fRect.l)+fRect.l), fRect.h );

        this.renderBox( context, fRect.viewInfo, fRect.f, fRect.t, fRect.rect.h, fRect.f );
        this.renderLabel( context, fRect );
        this.renderDescription( context, fRect );
        this.renderArrowhead( context, fRect );
    },

    // top and height are in px
    renderBox: function( context, viewInfo, feature, top, overallHeight, parentFeature, style ) {
        var left  = viewInfo.block.bpToX( feature.get('start') );
        var width = viewInfo.block.bpToX( feature.get('end') ) - left;
        //left = Math.round( left );
        //width = Math.round( width );

        style = style || lang.hitch( this, 'getStyle' );

        var height = style( feature, 'height' );
        if( ! height )
            return;
        if( height != overallHeight )
            top += Math.round( (overallHeight - height)/2 );

        // background
        var bgcolor = style( feature, 'color' );
        if( bgcolor ) {
            context.fillStyle = bgcolor;
            context.fillRect( left, top, Math.max(1,width), height );
        }
        else {
            context.clearRect( left, top, Math.max(1,width), height );
        }

        // foreground border
        var borderColor, lineWidth;
        if( (borderColor = style( feature, 'border_color' )) && ( lineWidth = style( feature, 'border_width')) ) {
            if( width > 3 ) {
                context.lineWidth = lineWidth;
                context.strokeStyle = borderColor;

                // need to stroke a smaller rectangle to remain within
                // the bounds of the feature's overall height and
                // width, because of the way stroking is done in
                // canvas.  thus the +0.5 and -1 business.
                context.strokeRect( left+lineWidth/2, top+lineWidth/2, width-lineWidth, height-lineWidth );
            }
            else {
                context.globalAlpha = lineWidth*2/width;
                context.fillStyle = borderColor;
                context.fillRect( left, top, Math.max(1,width), height );
                context.globalAlpha = 1;
            }
        }
    },

    // feature label
    renderLabel: function( context, fRect ) {
        if( fRect.label ) {
            context.font = fRect.label.font;
            context.fillStyle = fRect.label.fill;
            context.textBaseline = fRect.label.baseline;
            context.fillText( fRect.label.text,
                              fRect.l+(fRect.label.xOffset||0),
                              fRect.t+(fRect.label.yOffset||0)
                            );
        }
    },

    // feature description
    renderDescription: function( context, fRect ) {
        if( fRect.description ) {
            context.font = fRect.description.font;
            context.fillStyle = fRect.description.fill;
            context.textBaseline = fRect.description.baseline;
            context.fillText(
                fRect.description.text,
                fRect.l+(fRect.description.xOffset||0),
                fRect.t + (fRect.description.yOffset||0)
            );
        }
    },

    // strand arrowhead
    renderArrowhead: function( context, fRect ) {
        if( fRect.strandArrow ) {
            if( fRect.strandArrow == 1 && fRect.rect.l+fRect.rect.w <= context.canvas.width ) {
                this.getEmbeddedImage( 'plus_arrow' )
                    .then( function( img ) {
                               context.imageSmoothingEnabled = false;
                               context.drawImage( img, fRect.rect.l + fRect.rect.w, fRect.t + (fRect.rect.h-img.height)/2 );
                           });
            }
            else if( fRect.strandArrow == -1 && fRect.rect.l >= 0 ) {
                this.getEmbeddedImage( 'minus_arrow' )
                    .then( function( img ) {
                               context.imageSmoothingEnabled = false;
                               context.drawImage( img, fRect.rect.l-9, fRect.t + (fRect.rect.h-img.height)/2 );
                           });
            }
        }
    }

});
});<|endoftext|>


<commit_before>/*!
  * $script.js Async loader & dependency manager
  * https://github.com/ded/script.js
  * (c) Dustin Diaz, Jacob Thornton 2011
  * License: MIT
  */

// Commented this line and created my own withou cacheing. Good for dev environment

// !function(a,b){typeof define=="function"?define(b):typeof module!="undefined"?module.exports=b():this[a]=b()}("$script",function(){function s(a,b,c){for(c=0,j=a.length;c<j;++c)if(!b(a[c]))return m;return 1}function t(a,b){s(a,function(a){return!b(a)})}function u(a,b,c){function o(a){return a.call?a():f[a]}function p(){if(!--m){f[l]=1,j&&j();for(var a in h)s(a.split("|"),o)&&!t(h[a],o)&&(h[a]=[])}}a=a[n]?a:[a];var e=b&&b.call,j=e?b:c,l=e?a.join(""):b,m=a.length;return setTimeout(function(){t(a,function(a){if(k[a])return l&&(g[l]=1),k[a]==2&&p();k[a]=1,l&&(g[l]=1),v(!d.test(a)&&i?i+a+".js":a,p)})},0),u}function v(a,d){var e=b.createElement("script"),f=m;e.onload=e.onerror=e[r]=function(){if(e[p]&&!/^c|loade/.test(e[p])||f)return;e.onload=e[r]=null,f=1,k[a]=2,d()},e.async=1,e.src=a,c.insertBefore(e,c.firstChild)}var a=this,b=document,c=b.getElementsByTagName("head")[0],d=/^https?:\/\//,e=a.$script,f={},g={},h={},i,k={},l="string",m=!1,n="push",o="DOMContentLoaded",p="readyState",q="addEventListener",r="onreadystatechange";return!b[p]&&b[q]&&(b[q](o,function w(){b.removeEventListener(o,w,m),b[p]="complete"},m),b[p]="loading"),u.get=v,u.order=function(a,b,c){(function d(e){e=a.shift(),a.length?u(e,d):u(e,b,c)})()},u.path=function(a){i=a},u.ready=function(a,b,c){a=a[n]?a:[a];var d=[];return!t(a,function(a){f[a]||d[n](a)})&&s(a,function(a){return f[a]})?b():!function(a){h[a]=h[a]||[],h[a][n](b),c&&c(d)}(a.join("|")),u},u.noConflict=function(){return a.$script=e,this},u})

// Commented the line above and created my own withou cacheing. Good for dev environment

!function(a,b){typeof define=="function"?define(b):typeof module!="undefined"?module.exports=b():this[a]=b()}("$script",function(){function s(a,b,c){for(c=0,j=a.length;c<j;++c)if(!b(a[c]))return m;return 1}function t(a,b){s(a,function(a){return!b(a)})}function u(a,b,c){function o(a){return a.call?a():f[a]}function p(){if(!--m){f[l]=1,j&&j();for(var a in h)s(a.split("|"),o)&&!t(h[a],o)&&(h[a]=[])}}a=a[n]?a:[a];var e=b&&b.call,j=e?b:c,l=e?a.join(""):b,m=a.length;return setTimeout(function(){t(a,function(a){if(k[a])return l&&(g[l]=1),k[a]==2&&p();k[a]=1,l&&(g[l]=1),v(!d.test(a)&&i?i+a+".js":a,p)})},0),u}function v(a,d){var e=b.createElement("script"),f=m;e.onload=e.onerror=e[r]=function(){e.onload=e[r]=null,k[a]=2,d()},e.async=1,e.src=a,c.insertBefore(e,c.firstChild)}var a=this,b=document,c=b.getElementsByTagName("head")[0],d=/^https?:\/\//,e=a.$script,f={},g={},h={},i,k={},l="string",m=!1,n="push",o="DOMContentLoaded",p="readyState",q="addEventListener",r="onreadystatechange";return!b[p]&&b[q]&&(b[q](o,function w(){b.removeEventListener(o,w,m),b[p]="complete"},m),b[p]="loading"),u.get=v,u.order=function(a,b,c){(function d(e){e=a.shift(),a.length?u(e,d):u(e,b,c)})()},u.path=function(a){i=a},u.ready=function(a,b,c){a=a[n]?a:[a];var d=[];return!t(a,function(a){f[a]||d[n](a)})&&s(a,function(a){return f[a]})?b():!function(a){h[a]=h[a]||[],h[a][n](b),c&&c(d)}(a.join("|")),u},u.noConflict=function(){return a.$script=e,this},u})

/*
 * Catarse JS loader
 * Should be the only file included from views
 */
var CATARSE_LOADER = window.CATARSE_LOADER = {

  initial: ['jquery-1.7.1.min', 'underscore-min'],

  intermediate: ['backbone-min', 'mustache', 'jquery-ui-1.8.6.custom.min', 'jquery.numeric', 'jquery.maxlength', 'jquery.timers-1.2', 'timedKeyup', 'waypoints.min', 'jquery.scrollto', 'jquery.jeditable.mini', 'jquery.maskedinput-1.2.2.min', 'jquery.cpf', 'twitter', 'jquery_ujs'],

  final: ['jquery.ui.datepicker-pt-BR', 'on_the_spot', 'app/catarse', 'bootstrap'],

  catarse: {

    initial: [
      'app/router'
    ],

    intermediate: [
      'app/views/layouts/application',
      'app/models/project',
      'app/models/backer',
      'app/models/user',
      'app/models/user_back',
      'app/models/user_project',
      'app/collections/paginated',
      'app/views/model',
      'app/views/paginated'
    ],

    final: [
      'app/collections/projects',
      'app/collections/backers',
      'app/collections/user_backs',
      'app/collections/user_projects'
    ]
  },

  scriptURI: function(path){
    return '/javascripts/' + path + '.js?' + new Date().getTime();
  },

  scriptURIs: function(paths){
    var result = new Array()
    var len = paths.length
    for(var i=0; i<len; i++) {
      result[i] = this.scriptURI(paths[i])
    }
    return result
  },

  load: function(what, name){
    if(typeof what == "object") {
      $script(this.scriptURIs(what), name)
    } else if(typeof what == "string") {
      $script(this.scriptURI(what), name)
    }
  },

  init: function(){
    this.load(this.initial, 'initial')
    $script.ready('initial', function(){
      CATARSE_LOADER.load(CATARSE_LOADER.intermediate, 'intermediate')
      $script.ready('intermediate', function(){
        CATARSE_LOADER.load(CATARSE_LOADER.final, 'final')
        $script.ready('final', function(){
          CATARSE_LOADER.load(CATARSE_LOADER.catarse.initial, 'catarse.initial')
          $script.ready('catarse.initial', function(){
            CATARSE_LOADER.load(CATARSE_LOADER.catarse.intermediate, 'catarse.intermediate')
            $script.ready('catarse.intermediate', function(){
              CATARSE_LOADER.load(CATARSE_LOADER.catarse.final, 'catarse.final')
              $script.ready('catarse.final', function(){
                $(document).ready(CATARSE_LOADER.loadAction);
              })
            })
          })
        })
      })
    })
  },

  exec: function(namespace, controller, action) {
    if ( namespace && namespace[controller] && typeof namespace[controller][action] == "function" ) {
      namespace[controller][action]();
      return true;
    } else {
      return false;
    }
  },

  expandNamespace: function(object, list) {
    var name = list.shift()
    if (object[name])
      return CATARSE_LOADER.expandNamespace(object[name], list)
    else
      return object
  },

  viewName: function() {
    var len = CATARSE_LOADER.namespace.list.push(CATARSE_LOADER.controller, CATARSE_LOADER.action, "View")
    var result = ""
    for(var i=0; i<len; i++) {
      result = result + CATARSE_LOADER.namespace.list[i].charAt(0).toUpperCase() + CATARSE_LOADER.namespace.list[i].slice(1)
    }
    return result
  },

  execAction: function() {
    CATARSE_LOADER.exec(CATARSE, "common", "init");
    CATARSE_LOADER.exec(CATARSE_LOADER.namespace.object, CATARSE_LOADER.controller, "init");
    if ( !CATARSE_LOADER.exec(CATARSE_LOADER.namespace.object, CATARSE_LOADER.controller, CATARSE_LOADER.action) ) {
      var View = CATARSE[CATARSE_LOADER.viewName()]
      if (View) {
        if(!CATARSE_LOADER.namespace.object[CATARSE_LOADER.controller])
          CATARSE_LOADER.namespace.object[CATARSE_LOADER.controller] = {}
        CATARSE_LOADER.namespace.object[CATARSE_LOADER.controller][CATARSE_LOADER.action] = new View({el: $("body") })
      }
    }
    CATARSE_LOADER.exec(CATARSE, "common", "finish");
  },

  loadAction: function(){
    var body = $(document.body)
    var namespace_text = ""

    CATARSE_LOADER.namespace = {}
    if(body.data("namespace") != null) {
      namespace_text = body.data("namespace");
    }
    CATARSE_LOADER.namespace.text = namespace_text
    CATARSE_LOADER.namespace.list = CATARSE_LOADER.namespace.text.split("_")
    CATARSE_LOADER.namespace.folder = CATARSE_LOADER.namespace.list.join("/")
    if (CATARSE_LOADER.namespace.folder.length > 0)
      CATARSE_LOADER.namespace.folder = CATARSE_LOADER.namespace.folder + "/"
    CATARSE_LOADER.namespace.object = CATARSE_LOADER.expandNamespace(CATARSE, CATARSE_LOADER.namespace.list)

    CATARSE_LOADER.controller = body.data("controller")
    CATARSE_LOADER.action = body.data("action")

    CATARSE_LOADER.load("app/views/" + CATARSE_LOADER.namespace.folder + CATARSE_LOADER.controller + "/" + CATARSE_LOADER.action, 'action')

    $script.ready('action', function() {
      if (CATARSE.loader.dependencies) {
        $script.ready('dependencies', function() {
          CATARSE_LOADER.execAction()
        })
      } else {
        CATARSE_LOADER.execAction()
      }
    })

  }

}

CATARSE_LOADER.init()
<commit_msg>deleted the loader.js<commit_after><|endoftext|>


<commit_before>/*******************************************************************************
**
** This file is part of BetterPonymotes.
** Copyright (c) 2012-2015 Typhos.
**
** This program is free software: you can redistribute it and/or modify it
** under the terms of the GNU Affero General Public License as published by
** the Free Software Foundation, either version 3 of the License, or (at your
** option) any later version.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
** FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
** for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*******************************************************************************/

"use strict";

// public functions:
// - initialize_prefs() from userscript
// - manage_prefs() from backends
// cm.force_update() from backends

var default_prefs = {
    "enableNSFW": false,
    "enableExtraCSS": true,
    "showUnknownEmotes": true,
    "hideDisabledEmotes": false,
    "stealthMode": false,
    "showAltText": true,
    "enableGlobalEmotes": false,
    "enableGlobalSearch": false,
    "clickToggleSFW": true,
    "searchLimit": 250,
    "maxEmoteSize": 0,
    "enabledSubreddits2": {}, // subreddit name -> 0/1 enabled
    "disabledEmotes": [],
    "whitelistedEmotes": [],
    "customCSSSubreddits": {}, // subreddit name -> timestamp
    "blacklistedSubreddits": {}, // subreddit name -> true

    "searchBoxInfo": [600, 25, 620, 450],
    "lastSearchQuery": "sr:mylittlepony",
    "globalIconPos": [16, 16]
    };
//Platform is not defined here so we duplicate our 'is this discord' logic
if(window.process) {
//We are always global in discord, so these prefs actually
//function as enable/disable functionality.
    default_prefs.enableGlobalEmotes = true;
    default_prefs.enableGlobalSearch = true;
//Discord-specific preferences
    default_prefs.disableDisruptiveEmotes = true;
    default_prefs.disableEmotesInCodeBlocks = false;
}

function initialize_prefs(prefs, sr_name2id) {
    for(var key in default_prefs) {
        if(prefs[key] === undefined) {
            prefs[key] = default_prefs[key];
        }
    }

    // Migration from previous schema
    if(prefs.enabledSubreddits !== undefined) {
        for(var old_sr in prefs.enabledSubreddits) {
          // r_mlp -> r/mlp
          if(old_sr === "bpmextras") {
              continue;
          }
          var new_sr = old_sr.replace("_", "/");
          var value = Number(prefs.enabledSubreddits[old_sr])
          prefs.enabledSubreddits2[new_sr] = value;
        }
        delete prefs.enabledSubreddits;
    }

    // New subreddits
    for(var sr in sr_name2id) {
        if(prefs.enabledSubreddits2[sr] === undefined) {
            prefs.enabledSubreddits2[sr] = 1;
        }
    }
    // Removed subreddits
    for(var sr in prefs.enabledSubreddits2) {
        // Cast to int while we're at it- I think I screwed this up before
        prefs.enabledSubreddits2[sr] = +prefs.enabledSubreddits2[sr];

        if(sr_name2id[sr] === undefined) {
            console.log("Deleting " + sr);
            delete prefs.enabledSubreddits2[sr];
        }
    }
}

function sanitize_emote(s) {
    return s.toLowerCase().replace("!", "_excl_").replace(":", "_colon_").replace("#", "_hash_").replace("/", "_slash_");
}

//                  a    :suffix          [href |   ="( /emote name )"] :suffix         (through '}')
var block_regexp = /a\s*(:[a-zA-Z\-()]+)?\[href[|^]?="(\/[\w:!#\/]+)"\](:[a-zA-Z\-()]+)?[^}]*}/g;
var emote_regexp = /a\s*(:[a-zA-Z\-()]+)?\[href[|^]?="(\/[\w:!#\/]+)"\](:[a-zA-Z\-()]+)?/;

function strip_subreddit_css(data) {
    // Strip comments
    data = data.replace(/\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//g, "");
    // Strip PONYSCRIPT-IGNORE blocks
    data = data.replace(/START-PONYSCRIPT-IGNORE[^{]*{[^}]*}[\s\S]*?END-PONYSCRIPT-IGNORE[^{]*{[^}]*}/g, "");
    // Strip !important tags
    data = data.replace(/\s*!important/gi, "");
    // Strip leading spaces and newlines
    data = data.replace(/^\s*|\n/gm, "");

    block_regexp.lastIndex = 0;

    // Locate all emotes and emote CSS
    var emote_text = "";
    var emotes = [];
    var block_match, emote_match;
    while((block_match = block_regexp.exec(data)) !== null) {
        var css_chunk = block_match[0];
        while((emote_match = emote_regexp.exec(css_chunk)) !== null) {
            var emote = emote_match[2].toLowerCase();
            if(emotes.indexOf(emote) < 0) {
                emotes.push(emote);
            }

            // Sanitize class names and convert to .bpm-cmote structure
            var class_name = (".bpm-cmote-" + sanitize_emote(emote.slice(1)) +
                              (emote_match[1] ? emote_match[1] : "") +
                              (emote_match[3] ? emote_match[3] : ""));
            css_chunk = css_chunk.replace(emote_regexp, class_name);
        }
        emote_text += css_chunk + "\n";
    }

    return [emotes, emote_text];
}

// Essentially a list compare, because JS sucks
function subreddits_changed(old, n) {
    if(old.length !== n.length) {
        return true;
    }

    // Copy
    var common = old.slice(0);

    // Remove all elements in n
    for(var i = 0; i < n.length; i++) {
        var idx = common.indexOf(n[i]);
        if(idx < 0) {
            return true;
        }
        common.splice(idx, 1);
    }

    // Should be empty?
    return common.length;
}

function manage_prefs(sr_name2id, hooks) {
    var prefs = hooks.read_json("prefs");

    var manager = {
        get: function() {
            return prefs;
        },

        db_json: hooks.read_json,
        db_set_json: hooks.write_json,
        db_key: hooks.read_value,
        db_set_key: hooks.write_value,

        set_pref: function(key, value) {
            if(prefs[key] === undefined) {
                console.log("BPM: ERROR: Attempt to write to nonexistent pref key " + key);
                return;
            }
            prefs[key] = value;
            this._sync();
            this.cm.after_pref_write();
        },

        // Wait 2.5s between hitting Reddit
        dl_queue: new TaskQueue(hooks.set_timeout, hooks.download_file, 2500),

        _sync: function() {
            hooks.write_json("prefs", prefs);
        }
    };

    initialize_prefs(prefs, sr_name2id);
    manager._sync();
    manager.cm = new css_manager(manager);
    manager.cm.after_pref_write();

    return manager;
}

function TaskQueue(set_timeout, callback, delay) {
    this.set_timeout = set_timeout;
    this.callback = callback;
    this.delay = delay;
    this.last_run = 0;
    this.queue = [];
    this.running = false;
}

TaskQueue.prototype = {
    add: function() {
        this.queue.push(Array.prototype.slice.call(arguments));

        // The task chain will keep itself running so long as there are items
        // in the queue, so don't worry about that
        if(!this.running) {
            this.running = true;
            // Wait until we're allowed to run things again
            var wait = (this.last_run + this.delay) - Date.now();
            if(wait > 0) {
                this.set_timeout(this.run_next.bind(this), wait);
            } else {
                // May as well just call it directly
                this.run_next();
            }
        }
    },

    run_next: function() {
        // Set up args list: first is always done(), rest is as passed
        var args = this.queue.shift();

        args.unshift(function() {
            this.last_run = Date.now();
            // If the queue isn't empty yet, keep running
            if(this.queue.length) {
                this.set_timeout(this.run_next.bind(this), this.delay);
            } else {
                this.running = false;
            }
        }.bind(this));

        // Run task
        this.callback.apply(undefined, args);
    }
};

// Once a week. TODO: Make configurable, within certain tolerances.
var DOWNLOAD_INTERVAL = 1000 * 60 * 60 * 24 * 7;

function css_manager(pref_manager) {
    this.pm = pref_manager;
    this.cached_subreddits = [];
    this.css_cache = null;
    this.emote_cache = null;

    // Done from pref_manager
    //this.check_cache();
}

css_manager.prototype = {
    force_update: function(subreddit) {
        this.download_update(subreddit);
    },

    rebuild_cache: function() {
        var prefs = this.pm.get();
        var emote_lists = this.pm.db_json("customcss_emotes");
        this.cached_subreddits = [];

        this.css_cache = "";
        this.emote_cache = {};

        for(var subreddit in prefs.customCSSSubreddits) {
            var key = "csscache_" + subreddit.toLowerCase();
            if(this.pm.db_key(key) !== undefined) {
                this.css_cache += this.pm.db_key(key);
                this.cached_subreddits.push(subreddit);
            }

            if(emote_lists[subreddit] !== undefined) {
                var emote_names = emote_lists[subreddit];
                for(var i = 0; i < emote_names.length; i++) {
                    this.emote_cache[emote_names[i]] = subreddit;
                }
            }
        }
    },

    check_cache: function() {
        var now = Date.now();
        var prefs = this.pm.get();

        if(this.pm.db_json("customcss_emotes") === undefined) {
            this.pm.db_set_json("customcss_emotes", {});

            // Upgrade path: clear old CSS caches, as they're invalid
            for(var subreddit in prefs.customCSSSubreddits) {
                prefs.customCSSSubreddits[subreddit] = 0;
            }
        }

        for(var subreddit in prefs.customCSSSubreddits) {
            var last_dl_time = prefs.customCSSSubreddits[subreddit];
            if(last_dl_time === undefined || last_dl_time + DOWNLOAD_INTERVAL < now) {
                this.download_update(subreddit);
            }
        }
        // TODO: Remove stale CSS caches
    },

    download_update: function(subreddit) {
        // TODO: space these requests out somewhat
        var prefs = this.pm.get();
        console.log("BPM: Downloading updated CSS file for r/" + subreddit);
        var key = "csscache_" + subreddit.toLowerCase();
        // Chrome doesn't permit setting User-Agent (because it sucks), but this
        // should help a little bit
        var url = "https://www.reddit.com/r/" + subreddit + "/stylesheet.css?__ua=BetterPonymotes";
        this.pm.dl_queue.add(url, function(css) {
            var tmp = strip_subreddit_css(css);
            var extracted_emotes = tmp[0];
            var stripped_css = tmp[1];

            var custom_emotes = this.pm.db_json("customcss_emotes");
            custom_emotes[subreddit] = extracted_emotes;
            this.pm.db_set_json("customcss_emotes", custom_emotes);
            this.pm.db_set_key(key, stripped_css);

            prefs.customCSSSubreddits[subreddit] = Date.now();
            this.pm._sync();
            this.rebuild_cache();
        }.bind(this));
    },

    after_pref_write: function() {
        this.check_cache();

        var tmp = []; // ...
        for(var sr in this.pm.get().customCSSSubreddits) {
            tmp.push(sr);
        }
        // Always true for first run (compare [] to any other list)
        var changed = subreddits_changed(this.cached_subreddits, tmp);

        if(this.css_cache === null || this.emote_cache === null || changed) {
            this.rebuild_cache();
        }
    }
};

// Firefox
if(typeof(exports) !== "undefined") {
    exports.manage_prefs = manage_prefs;
}
<commit_msg>Add search button location setting<commit_after>/*******************************************************************************
**
** This file is part of BetterPonymotes.
** Copyright (c) 2012-2015 Typhos.
**
** This program is free software: you can redistribute it and/or modify it
** under the terms of the GNU Affero General Public License as published by
** the Free Software Foundation, either version 3 of the License, or (at your
** option) any later version.
**
** This program is distributed in the hope that it will be useful, but WITHOUT
** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
** FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
** for more details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program.  If not, see <http://www.gnu.org/licenses/>.
**
*******************************************************************************/

"use strict";

// public functions:
// - initialize_prefs() from userscript
// - manage_prefs() from backends
// cm.force_update() from backends

var default_prefs = {
    "enableNSFW": false,
    "enableExtraCSS": true,
    "showUnknownEmotes": true,
    "hideDisabledEmotes": false,
    "stealthMode": false,
    "showAltText": true,
    "enableGlobalEmotes": false,
    "enableGlobalSearch": false,
    "clickToggleSFW": true,
    "searchLimit": 250,
    "maxEmoteSize": 0,
    "enabledSubreddits2": {}, // subreddit name -> 0/1 enabled
    "disabledEmotes": [],
    "whitelistedEmotes": [],
    "customCSSSubreddits": {}, // subreddit name -> timestamp
    "blacklistedSubreddits": {}, // subreddit name -> true

    "searchBoxInfo": [600, 25, 620, 450],
    "lastSearchQuery": "sr:mylittlepony",
    "globalIconPos": [16, 16]
    };
//Platform is not defined here so we duplicate our 'is this discord' logic
if(window.process) {
//We are always global in discord, so these prefs actually
//function as enable/disable functionality.
    default_prefs.enableGlobalEmotes = true;
    default_prefs.enableGlobalSearch = true;
//Discord-specific preferences
    default_prefs.disableDisruptiveEmotes = true;
    default_prefs.disableEmotesInCodeBlocks = false;
    default_prefs.searchButtonTopRight = false;
}

function initialize_prefs(prefs, sr_name2id) {
    for(var key in default_prefs) {
        if(prefs[key] === undefined) {
            prefs[key] = default_prefs[key];
        }
    }

    // Migration from previous schema
    if(prefs.enabledSubreddits !== undefined) {
        for(var old_sr in prefs.enabledSubreddits) {
          // r_mlp -> r/mlp
          if(old_sr === "bpmextras") {
              continue;
          }
          var new_sr = old_sr.replace("_", "/");
          var value = Number(prefs.enabledSubreddits[old_sr])
          prefs.enabledSubreddits2[new_sr] = value;
        }
        delete prefs.enabledSubreddits;
    }

    // New subreddits
    for(var sr in sr_name2id) {
        if(prefs.enabledSubreddits2[sr] === undefined) {
            prefs.enabledSubreddits2[sr] = 1;
        }
    }
    // Removed subreddits
    for(var sr in prefs.enabledSubreddits2) {
        // Cast to int while we're at it- I think I screwed this up before
        prefs.enabledSubreddits2[sr] = +prefs.enabledSubreddits2[sr];

        if(sr_name2id[sr] === undefined) {
            console.log("Deleting " + sr);
            delete prefs.enabledSubreddits2[sr];
        }
    }
}

function sanitize_emote(s) {
    return s.toLowerCase().replace("!", "_excl_").replace(":", "_colon_").replace("#", "_hash_").replace("/", "_slash_");
}

//                  a    :suffix          [href |   ="( /emote name )"] :suffix         (through '}')
var block_regexp = /a\s*(:[a-zA-Z\-()]+)?\[href[|^]?="(\/[\w:!#\/]+)"\](:[a-zA-Z\-()]+)?[^}]*}/g;
var emote_regexp = /a\s*(:[a-zA-Z\-()]+)?\[href[|^]?="(\/[\w:!#\/]+)"\](:[a-zA-Z\-()]+)?/;

function strip_subreddit_css(data) {
    // Strip comments
    data = data.replace(/\/\*[^*]*\*+(?:[^\/][^*]*\*+)*\//g, "");
    // Strip PONYSCRIPT-IGNORE blocks
    data = data.replace(/START-PONYSCRIPT-IGNORE[^{]*{[^}]*}[\s\S]*?END-PONYSCRIPT-IGNORE[^{]*{[^}]*}/g, "");
    // Strip !important tags
    data = data.replace(/\s*!important/gi, "");
    // Strip leading spaces and newlines
    data = data.replace(/^\s*|\n/gm, "");

    block_regexp.lastIndex = 0;

    // Locate all emotes and emote CSS
    var emote_text = "";
    var emotes = [];
    var block_match, emote_match;
    while((block_match = block_regexp.exec(data)) !== null) {
        var css_chunk = block_match[0];
        while((emote_match = emote_regexp.exec(css_chunk)) !== null) {
            var emote = emote_match[2].toLowerCase();
            if(emotes.indexOf(emote) < 0) {
                emotes.push(emote);
            }

            // Sanitize class names and convert to .bpm-cmote structure
            var class_name = (".bpm-cmote-" + sanitize_emote(emote.slice(1)) +
                              (emote_match[1] ? emote_match[1] : "") +
                              (emote_match[3] ? emote_match[3] : ""));
            css_chunk = css_chunk.replace(emote_regexp, class_name);
        }
        emote_text += css_chunk + "\n";
    }

    return [emotes, emote_text];
}

// Essentially a list compare, because JS sucks
function subreddits_changed(old, n) {
    if(old.length !== n.length) {
        return true;
    }

    // Copy
    var common = old.slice(0);

    // Remove all elements in n
    for(var i = 0; i < n.length; i++) {
        var idx = common.indexOf(n[i]);
        if(idx < 0) {
            return true;
        }
        common.splice(idx, 1);
    }

    // Should be empty?
    return common.length;
}

function manage_prefs(sr_name2id, hooks) {
    var prefs = hooks.read_json("prefs");

    var manager = {
        get: function() {
            return prefs;
        },

        db_json: hooks.read_json,
        db_set_json: hooks.write_json,
        db_key: hooks.read_value,
        db_set_key: hooks.write_value,

        set_pref: function(key, value) {
            if(prefs[key] === undefined) {
                console.log("BPM: ERROR: Attempt to write to nonexistent pref key " + key);
                return;
            }
            prefs[key] = value;
            this._sync();
            this.cm.after_pref_write();
        },

        // Wait 2.5s between hitting Reddit
        dl_queue: new TaskQueue(hooks.set_timeout, hooks.download_file, 2500),

        _sync: function() {
            hooks.write_json("prefs", prefs);
        }
    };

    initialize_prefs(prefs, sr_name2id);
    manager._sync();
    manager.cm = new css_manager(manager);
    manager.cm.after_pref_write();

    return manager;
}

function TaskQueue(set_timeout, callback, delay) {
    this.set_timeout = set_timeout;
    this.callback = callback;
    this.delay = delay;
    this.last_run = 0;
    this.queue = [];
    this.running = false;
}

TaskQueue.prototype = {
    add: function() {
        this.queue.push(Array.prototype.slice.call(arguments));

        // The task chain will keep itself running so long as there are items
        // in the queue, so don't worry about that
        if(!this.running) {
            this.running = true;
            // Wait until we're allowed to run things again
            var wait = (this.last_run + this.delay) - Date.now();
            if(wait > 0) {
                this.set_timeout(this.run_next.bind(this), wait);
            } else {
                // May as well just call it directly
                this.run_next();
            }
        }
    },

    run_next: function() {
        // Set up args list: first is always done(), rest is as passed
        var args = this.queue.shift();

        args.unshift(function() {
            this.last_run = Date.now();
            // If the queue isn't empty yet, keep running
            if(this.queue.length) {
                this.set_timeout(this.run_next.bind(this), this.delay);
            } else {
                this.running = false;
            }
        }.bind(this));

        // Run task
        this.callback.apply(undefined, args);
    }
};

// Once a week. TODO: Make configurable, within certain tolerances.
var DOWNLOAD_INTERVAL = 1000 * 60 * 60 * 24 * 7;

function css_manager(pref_manager) {
    this.pm = pref_manager;
    this.cached_subreddits = [];
    this.css_cache = null;
    this.emote_cache = null;

    // Done from pref_manager
    //this.check_cache();
}

css_manager.prototype = {
    force_update: function(subreddit) {
        this.download_update(subreddit);
    },

    rebuild_cache: function() {
        var prefs = this.pm.get();
        var emote_lists = this.pm.db_json("customcss_emotes");
        this.cached_subreddits = [];

        this.css_cache = "";
        this.emote_cache = {};

        for(var subreddit in prefs.customCSSSubreddits) {
            var key = "csscache_" + subreddit.toLowerCase();
            if(this.pm.db_key(key) !== undefined) {
                this.css_cache += this.pm.db_key(key);
                this.cached_subreddits.push(subreddit);
            }

            if(emote_lists[subreddit] !== undefined) {
                var emote_names = emote_lists[subreddit];
                for(var i = 0; i < emote_names.length; i++) {
                    this.emote_cache[emote_names[i]] = subreddit;
                }
            }
        }
    },

    check_cache: function() {
        var now = Date.now();
        var prefs = this.pm.get();

        if(this.pm.db_json("customcss_emotes") === undefined) {
            this.pm.db_set_json("customcss_emotes", {});

            // Upgrade path: clear old CSS caches, as they're invalid
            for(var subreddit in prefs.customCSSSubreddits) {
                prefs.customCSSSubreddits[subreddit] = 0;
            }
        }

        for(var subreddit in prefs.customCSSSubreddits) {
            var last_dl_time = prefs.customCSSSubreddits[subreddit];
            if(last_dl_time === undefined || last_dl_time + DOWNLOAD_INTERVAL < now) {
                this.download_update(subreddit);
            }
        }
        // TODO: Remove stale CSS caches
    },

    download_update: function(subreddit) {
        // TODO: space these requests out somewhat
        var prefs = this.pm.get();
        console.log("BPM: Downloading updated CSS file for r/" + subreddit);
        var key = "csscache_" + subreddit.toLowerCase();
        // Chrome doesn't permit setting User-Agent (because it sucks), but this
        // should help a little bit
        var url = "https://www.reddit.com/r/" + subreddit + "/stylesheet.css?__ua=BetterPonymotes";
        this.pm.dl_queue.add(url, function(css) {
            var tmp = strip_subreddit_css(css);
            var extracted_emotes = tmp[0];
            var stripped_css = tmp[1];

            var custom_emotes = this.pm.db_json("customcss_emotes");
            custom_emotes[subreddit] = extracted_emotes;
            this.pm.db_set_json("customcss_emotes", custom_emotes);
            this.pm.db_set_key(key, stripped_css);

            prefs.customCSSSubreddits[subreddit] = Date.now();
            this.pm._sync();
            this.rebuild_cache();
        }.bind(this));
    },

    after_pref_write: function() {
        this.check_cache();

        var tmp = []; // ...
        for(var sr in this.pm.get().customCSSSubreddits) {
            tmp.push(sr);
        }
        // Always true for first run (compare [] to any other list)
        var changed = subreddits_changed(this.cached_subreddits, tmp);

        if(this.css_cache === null || this.emote_cache === null || changed) {
            this.rebuild_cache();
        }
    }
};

// Firefox
if(typeof(exports) !== "undefined") {
    exports.manage_prefs = manage_prefs;
}
<|endoftext|>


<commit_before>define( ['dojo/_base/declare',
         'dojo/_base/array',
         'JBrowse/Util',
         'JBrowse/View/Track/HTMLFeatures'
        ],
        function( declare, array, Util, HTMLFeatures ) {

return declare( HTMLFeatures,
/**
 * @lends JBrowse.View.Track.Alignments
 */
{

    _defaultConfig: function() {
        return Util.deepUpdate(
            dojo.clone( this.inherited(arguments) ),
            {
                maxFeatureScreenDensity: 1.5,
                layoutPitchY: 4,
                showBaseMismatches: true,
                style: {
                    _defaultLabelScale: 50,
                    className: 'alignment',
                    arrowheadClass: 'arrowhead'
                }
            }
        );
    },

    renderFeature: function(feature, uniqueId, block, scale, containerStart, containerEnd, destBlock ) {
        var featDiv = this.inherited( arguments );

        var displayStart = Math.max( feature.get('start'), containerStart );
        var displayEnd = Math.min( feature.get('end'), containerEnd );
        this._drawMismatches( feature, featDiv, scale, displayStart, displayEnd );

        // if this feature is part of a multi-segment read, and not
        // all of its segments are aligned, add missing_mate to its
        // class
        if( feature.get('multi_segment_template') && !feature.get('multi_segment_all_aligned') )
            featDiv.className += ' missing_mate';

        return featDiv;
    },

    /**
     * draw base-mismatches on the feature
     */
    _drawMismatches: function( feature, featDiv, scale, displayStart, displayEnd ) {
        var featLength = displayEnd - displayStart;
        // recall: scale is pixels/basepair
        if ( featLength*scale > 1 ) {
            var mismatches = this._getMismatches( feature );
            var charSize = this.getCharacterMeasurements();
            var drawChars = scale >= charSize.w;

            array.forEach( mismatches, function( mismatch ) {
                var start = feature.get('start') + mismatch.start;
                var end = start + mismatch.bases.length;

                // if the feature has been truncated to where it doesn't cover
                // this mismatch anymore, just skip this subfeature
                if ( end <= displayStart || start >= displayEnd )
                    return;

                if( drawChars ) {
                    array.forEach( mismatch.bases, function( base, i ) {
                        dojo.create('span', {
                                        className: mismatch.type+' base base_'+(base == '*' ? 'deletion' : base.toLowerCase()),
                                        style: {
                                            position: 'absolute',
                                            left: 100 * (start + i - displayStart)/featLength + '%',
                                            width: scale + 'px'
                                        },
                                        innerHTML: base
                                    }, featDiv );
                    });
                } else {
                    var base = mismatch.bases[0];
                    dojo.create('span',  {
                        className: mismatch.type + ' base_'+base.toLowerCase(),
                        style: {
                            position: 'absolute',
                            left: 100 * (start - displayStart)/featLength + '%',
                            width: (100 * (end - start)/featLength) + "%"
                        },
                        innerHTML: ''
                    }, featDiv );
                }
            }, this );
        }
    },

    _getMismatches: function( feature ) {
        var mismatches = [];
        // parse the MD tag if it has one
        if( feature.get('MD') ) {
            mismatches.push.apply( mismatches, this._mdToMismatches( feature, feature.get('MD') ) );
        }
        // parse the CIGAR tag if it has one
        if( feature.get('cigar') ) {
            mismatches.push.apply( mismatches, this._cigarToMismatches( feature, feature.get('cigar') ) );
        }

        return mismatches;
    },

    _parseCigar: function( cigar ) {
        return array.map( cigar.match(/\d+\D/g), function( op ) {
           return [ op.match(/\D/)[0].toUpperCase(), parseInt( op ) ];
        });
    },

    _cigarToMismatches: function( feature, cigarstring ) {
        var ops = this._parseCigar( cigarstring );
        var currOffset = 0;
        var mismatches = [];
        array.forEach( ops, function( oprec ) {
           var op  = oprec[0].toUpperCase();
           if( !op )
               return;
           var len = oprec[1];
           // if( op == 'M' || op == '=' || op == 'E' ) {
           //     // nothing
           // }
           if( op == 'I' )
               mismatches.push( { start: currOffset, type: 'insertion', bases: ''+len } );
           else if( op == 'D' )
               mismatches.push( { start: currOffset, type: 'deletion',  bases: new Array(len+1).join('*') } );
           else if( op == 'N' )
               mismatches.push( { start: currOffset, type: 'skip',      bases: new Array(len+1).join('N') } );
           else if( op == 'X' )
               mismatches.push( { start: currOffset, type: 'mismatch',  bases: new Array(len+1).join('X') } );

           currOffset += len;
        });
        return mismatches;
    },

    /**
     * parse a SAM MD tag to find mismatching bases of the template versus the reference
     * @returns {Array[Object]} array of mismatches and their positions
     * @private
     */
    _mdToMismatches: function( feature, mdstring ) {
        var mismatchRecords = [];
        var curr = { start: 0, bases: '', type: 'mismatch' };
        var seq = feature.get('seq');
        var nextRecord = function() {
              mismatchRecords.push( curr );
              curr = { start: curr.start + curr.bases.length, bases: '', type: 'mismatch'};
        };
        array.forEach( mdstring.match(/(\d+|\^[a-z]+|[a-z])/ig), function( token ) {
          if( token.match(/^\d/) ) { // matching bases
              curr.start += parseInt( token );
          }
          else if( token.match(/^\^/) ) { // insertion in the template
              var i = token.length-1;
              while( i-- ) {
                  curr.bases += '*';
              }
              curr.type = 'deletion';
              nextRecord();
          }
          else if( token.match(/^[a-z]/i) ) { // mismatch
              curr.bases = seq ? seq.substr( curr.start, token.length ) : new Array( token.length+1 ).join('X');
              nextRecord();
          }
        });
        return mismatchRecords;
    },

    // stub out subfeature rendering, this track doesn't render subfeatures
    renderSubfeature: function() {},

    /**
     * @returns {Object} containing h and w,
     *      in pixels, of the characters being used for sequences
     */
    getCharacterMeasurements: function() {
        if( !this._measurements )
            this._measurements = this._measureSequenceCharacterSize( this.div );
        return this._measurements;
    },

    /**
     * Conducts a test with DOM elements to measure sequence text width
     * and height.
     */
    _measureSequenceCharacterSize: function( containerElement ) {
        var widthTest = dojo.create('div', {
                                        innerHTML: 'A'
                                            +'C'
                                            +'T'
                                            +'G'
                                            +'N',
                                        style: {
                                            visibility: 'hidden',
                                            position: 'absolute',
                                            left: '0px'
                                        }
                                    }, containerElement );
        var result = {
            w:  widthTest.clientWidth / 5,
            h: widthTest.clientHeight
        };
        containerElement.removeChild(widthTest);
        return result;
    },

    /**
     * Make a default feature detail page for the given feature.
     * @returns {HTMLElement} feature detail page HTML
     */

    defaultFeatureDetail: function( /** JBrowse.Track */ track, /** Object */ f, /** HTMLElement */ div ) {
        var fmt = dojo.hitch( this, function( name, value ) {
            name = Util.ucFirst( name.replace(/_/g,' ') );
            return this._fmtDetailField(name, value);
        });
        var container = dojo.create('div', {
            className: 'detail feature-detail feature-detail-'+track.name,
            innerHTML: ''
        });
        container.innerHTML += fmt( 'Name', f.get('name') );
        container.innerHTML += fmt( 'Type', f.get('type') );
        container.innerHTML += fmt( 'Score', f.get('score') );
        container.innerHTML += fmt( 'Description', f.get('note') );
        container.innerHTML += fmt(
            'Position',
            Util.assembleLocString({ start: f.get('start'),
                                     end: f.get('end'),
                                     ref: this.refSeq.name })
            + ({'1':' (+)', '-1': ' (-)', 0: ' (no strand)' }[f.get('strand')] || '')
        );

        var alignment = '
'+f.get('seq')+'
';
        if( f.get('seq') ) {
            container.innerHTML += fmt('Sequence and Quality', this._renderSeqQual( f ) );
        }

        var additionalTags = array.filter(
            f.tags(), function(t) {
                return ! {name:1,score:1,start:1,end:1,strand:1,note:1,subfeatures:1,type:1}[t.toLowerCase()];
            }
        ).sort();

        dojo.forEach( additionalTags, function(t) {
            container.innerHTML += fmt( t, f.get(t) );
        });

        return container;
    },

    _renderSeqQual: function( feature ) {

        var seq  = feature.get('seq'),
            qual = feature.get('qual') || '';
        if( !seq )
            return '';

        qual = qual.split(/\s+/);
        var fieldWidth = (''+Math.max.apply( Math, qual )).length;

        // pad the sequence with spaces
        var seqPadding = ' ';
        while( seqPadding.length < fieldWidth-1 ) {
            seqPadding += ' ';
        }
        var paddedSeq = array.map( seq, function(s) {
            return s + seqPadding;
        });

        var tableRowHTML = function(fields, class_) {
            class_ = class_ ? ' class="'+class_+'"' : '';
            return '
'+array.map( fields, function(f) { return ''+f+'	'; }).join('')+'
';
        };
        // insert newlines
        var rendered = '';
        var lineFields = Math.round(60/fieldWidth);
        while( paddedSeq.length ) {
            var line = paddedSeq.slice(0,Math.min( paddedSeq.length, lineFields ) );
            paddedSeq = paddedSeq.slice(lineFields);
            rendered += tableRowHTML( line, 'seq' );
            if( qual.length ) {
                line = qual.slice(0, Math.min( qual.length, lineFields ));
                qual = qual.slice(lineFields);
                rendered += tableRowHTML( line, 'qual' );
            }
        }
        return '
'+rendered+'
';
    }
});
});<commit_msg>more work onthe way Alignments tracks draw mismatches<commit_after>define( ['dojo/_base/declare',
         'dojo/_base/array',
         'JBrowse/Util',
         'JBrowse/View/Track/HTMLFeatures'
        ],
        function( declare, array, Util, HTMLFeatures ) {

return declare( HTMLFeatures,
/**
 * @lends JBrowse.View.Track.Alignments
 */
{

    _defaultConfig: function() {
        return Util.deepUpdate(
            dojo.clone( this.inherited(arguments) ),
            {
                maxFeatureScreenDensity: 1.5,
                layoutPitchY: 4,
                showBaseMismatches: true,
                style: {
                    _defaultLabelScale: 50,
                    className: 'alignment',
                    arrowheadClass: 'arrowhead'
                }
            }
        );
    },

    renderFeature: function(feature, uniqueId, block, scale, containerStart, containerEnd, destBlock ) {
        var featDiv = this.inherited( arguments );

        var displayStart = Math.max( feature.get('start'), containerStart );
        var displayEnd = Math.min( feature.get('end'), containerEnd );
        this._drawMismatches( feature, featDiv, scale, displayStart, displayEnd );

        // if this feature is part of a multi-segment read, and not
        // all of its segments are aligned, add missing_mate to its
        // class
        if( feature.get('multi_segment_template') && !feature.get('multi_segment_all_aligned') )
            featDiv.className += ' missing_mate';

        return featDiv;
    },

    /**
     * draw base-mismatches on the feature
     */
    _drawMismatches: function( feature, featDiv, scale, displayStart, displayEnd ) {
        var featLength = displayEnd - displayStart;
        // recall: scale is pixels/basepair
        if ( featLength*scale > 1 ) {
            var mismatches = this._getMismatches( feature );
            var charSize = this.getCharacterMeasurements();
            var drawChars = scale >= charSize.w;

            array.forEach( mismatches, function( mismatch ) {
                var start = feature.get('start') + mismatch.start;
                var end = start + mismatch.length;

                // if the feature has been truncated to where it doesn't cover
                // this mismatch anymore, just skip this subfeature
                if ( end <= displayStart || start >= displayEnd )
                    return;

                var base = mismatch.base;
                var overall = dojo.create('span',  {
                    className: mismatch.type + ' base_'+base.toLowerCase(),
                    style: {
                        position: 'absolute',
                        left: 100 * (start - displayStart)/featLength + '%',
                        width: (100 * (end - start)/featLength) + "%"
                    }
                }, featDiv );
                if( drawChars ) {
                    for( var i = 0; i<mismatch.length; i++ ) {
                        dojo.create('span',{
                                        className: 'base',
                                        style: { width: scale+'px' },
                                        innerHTML: base
                                    }, overall );
                    }
                }
            }, this );
        }
    },

    _getMismatches: function( feature ) {
        var mismatches = [];
        // parse the MD tag if it has one
        if( feature.get('MD') ) {
            mismatches.push.apply( mismatches, this._mdToMismatches( feature, feature.get('MD') ) );
        }
        // parse the CIGAR tag if it has one
        if( feature.get('cigar') ) {
            mismatches.push.apply( mismatches, this._cigarToMismatches( feature, feature.get('cigar') ) );
        }

        return mismatches;
    },

    _parseCigar: function( cigar ) {
        return array.map( cigar.match(/\d+\D/g), function( op ) {
           return [ op.match(/\D/)[0].toUpperCase(), parseInt( op ) ];
        });
    },

    _cigarToMismatches: function( feature, cigarstring ) {
        var ops = this._parseCigar( cigarstring );
        var currOffset = 0;
        var mismatches = [];
        array.forEach( ops, function( oprec ) {
           var op  = oprec[0].toUpperCase();
           if( !op )
               return;
           var len = oprec[1];
           // if( op == 'M' || op == '=' || op == 'E' ) {
           //     // nothing
           // }
           if( op == 'I' )
               mismatches.push( { start: currOffset, type: 'insertion', base: ''+len, length: 1 });
           else if( op == 'D' )
               mismatches.push( { start: currOffset, type: 'deletion',  base: '*', length: len  });
           else if( op == 'N' )
               mismatches.push( { start: currOffset, type: 'skip',      base: 'N', length: len  });
           else if( op == 'X' )
               mismatches.push( { start: currOffset, type: 'mismatch',  base: 'X', length: len  });

           currOffset += len;
        });
        return mismatches;
    },

    /**
     * parse a SAM MD tag to find mismatching bases of the template versus the reference
     * @returns {Array[Object]} array of mismatches and their positions
     * @private
     */
    _mdToMismatches: function( feature, mdstring ) {
        var mismatchRecords = [];
        var curr = { start: 0, base: '', length: 0, type: 'mismatch' };
        var seq = feature.get('seq');
        var nextRecord = function() {
              mismatchRecords.push( curr );
              curr = { start: curr.start + curr.length, length: 0, base: '', type: 'mismatch'};
        };
        array.forEach( mdstring.match(/(\d+|\^[a-z]+|[a-z])/ig), function( token ) {
          if( token.match(/^\d/) ) { // matching bases
              curr.start += parseInt( token );
          }
          else if( token.match(/^\^/) ) { // insertion in the template
              curr.length = token.length-1;
              curr.base   = '*';
              curr.type   = 'deletion';
              nextRecord();
          }
          else if( token.match(/^[a-z]/i) ) { // mismatch
              for( var i = 0; i<token.length; i++ ) {
                  curr.length = 1;
                  curr.base = seq ? seq.substr( curr.start, 1 ) : 'X';
                  nextRecord();
              }
          }
        });
        return mismatchRecords;
    },

    // stub out subfeature rendering, this track doesn't render subfeatures
    renderSubfeature: function() {},

    /**
     * @returns {Object} containing h and w,
     *      in pixels, of the characters being used for sequences
     */
    getCharacterMeasurements: function() {
        if( !this._measurements )
            this._measurements = this._measureSequenceCharacterSize( this.div );
        return this._measurements;
    },

    /**
     * Conducts a test with DOM elements to measure sequence text width
     * and height.
     */
    _measureSequenceCharacterSize: function( containerElement ) {
        var widthTest = dojo.create('div', {
                                        innerHTML: 'A'
                                            +'C'
                                            +'T'
                                            +'G'
                                            +'N',
                                        style: {
                                            visibility: 'hidden',
                                            position: 'absolute',
                                            left: '0px'
                                        }
                                    }, containerElement );
        var result = {
            w:  widthTest.clientWidth / 5,
            h: widthTest.clientHeight
        };
        containerElement.removeChild(widthTest);
        return result;
    },

    /**
     * Make a default feature detail page for the given feature.
     * @returns {HTMLElement} feature detail page HTML
     */

    defaultFeatureDetail: function( /** JBrowse.Track */ track, /** Object */ f, /** HTMLElement */ div ) {
        var fmt = dojo.hitch( this, function( name, value ) {
            name = Util.ucFirst( name.replace(/_/g,' ') );
            return this._fmtDetailField(name, value);
        });
        var container = dojo.create('div', {
            className: 'detail feature-detail feature-detail-'+track.name,
            innerHTML: ''
        });
        container.innerHTML += fmt( 'Name', f.get('name') );
        container.innerHTML += fmt( 'Type', f.get('type') );
        container.innerHTML += fmt( 'Score', f.get('score') );
        container.innerHTML += fmt( 'Description', f.get('note') );
        container.innerHTML += fmt(
            'Position',
            Util.assembleLocString({ start: f.get('start'),
                                     end: f.get('end'),
                                     ref: this.refSeq.name })
            + ({'1':' (+)', '-1': ' (-)', 0: ' (no strand)' }[f.get('strand')] || '')
        );

        var alignment = '
'+f.get('seq')+'
';
        if( f.get('seq') ) {
            container.innerHTML += fmt('Sequence and Quality', this._renderSeqQual( f ) );
        }

        var additionalTags = array.filter(
            f.tags(), function(t) {
                return ! {name:1,score:1,start:1,end:1,strand:1,note:1,subfeatures:1,type:1}[t.toLowerCase()];
            }
        ).sort();

        dojo.forEach( additionalTags, function(t) {
            container.innerHTML += fmt( t, f.get(t) );
        });

        return container;
    },

    _renderSeqQual: function( feature ) {

        var seq  = feature.get('seq'),
            qual = feature.get('qual') || '';
        if( !seq )
            return '';

        qual = qual.split(/\s+/);
        var fieldWidth = (''+Math.max.apply( Math, qual )).length;

        // pad the sequence with spaces
        var seqPadding = ' ';
        while( seqPadding.length < fieldWidth-1 ) {
            seqPadding += ' ';
        }
        var paddedSeq = array.map( seq, function(s) {
            return s + seqPadding;
        });

        var tableRowHTML = function(fields, class_) {
            class_ = class_ ? ' class="'+class_+'"' : '';
            return '
'+array.map( fields, function(f) { return ''+f+'	'; }).join('')+'
';
        };
        // insert newlines
        var rendered = '';
        var lineFields = Math.round(60/fieldWidth);
        while( paddedSeq.length ) {
            var line = paddedSeq.slice(0,Math.min( paddedSeq.length, lineFields ) );
            paddedSeq = paddedSeq.slice(lineFields);
            rendered += tableRowHTML( line, 'seq' );
            if( qual.length ) {
                line = qual.slice(0, Math.min( qual.length, lineFields ));
                qual = qual.slice(lineFields);
                rendered += tableRowHTML( line, 'qual' );
            }
        }
        return '
'+rendered+'
';
    }
});
});<|endoftext|>


<commit_before>'use strict';

var MAX_INTERVAL_BETWEEN = 1000;
var WORDS_IN_PHRASE = 3;

var directives = angular.module('skim.directives', []);

directives.directive('skimReader',['$http', '$location', '$interval', function($http, $location, $interval){
  function link(scope, element, attrs) {
    // Initial variables.
    var intervalBetween = MAX_INTERVAL_BETWEEN,
        optimizedTextArray = [],
        currentIndex = 0,
        phraseCount = 1,
        keyword = "",
        timer,
        isTimerRunning = false;

    scope.userWantsToSkim = false;

    // Interface variables.
    scope.startSkimming = function(){
      scope.userWantsToSkim = true;
      startTimer();
    };

    scope.stopSkimming = function(){
      scope.userWantsToSkim = false;
      stopTimer();
    };

    // Updates intervalBetween if wpm changes changes
    scope.$watch(attrs.wpm, function(value) {
      updateIntervalBetweenWithWPM(value);
    });

    // Prepare timer.
    element.on('$destroy', function() {
      console.log("SkimReader destroyed");
      stopTimer();
    });

    // Get the optimized text.
    var optimizedDocumentURL = $location.absUrl() + '/optimized.json';
    $http.get(optimizedDocumentURL).success(function(data, status){
      optimizedTextArray = data;
    });

    var punctuationPauseInterval = function(){
      return intervalBetween * 1.5;
    };

    var phrasePauseInterval = function(){
      return intervalBetween / 2;
    };

    function updateIntervalBetweenWithWPM(wpm) {
      intervalBetween = Math.min(1000 / (wpm/60), MAX_INTERVAL_BETWEEN);
      restartTimer();
    }

    function progressKeyword() {
      if (currentIndex < optimizedTextArray.length) {
        var wordHash = optimizedTextArray[currentIndex];
        var word = wordHash.word;
        keyword = formatKeyword(word, wordHash.optimum_index);
        currentIndex += 1;
        phraseCount += 1;
        element.html(keyword);

        // If last character is a punctuation, pause timer.
        if (isLastCharacterPunctuation(word)) {
          phraseCount = 0;
          pauseTimer(punctuationPauseInterval);
        } else if (phraseCount >= WORDS_IN_PHRASE) {
          phraseCount = 0;
          pauseTimer(phrasePauseInterval);
        }
      } else {
        stopTimer();
      }
    }

    function formatKeyword(word, keyIndex) {
      var keywordMarkup = "";

      // One letter word, close the first half span.
      if (word.length < 2) {
        keywordMarkup += "";
      }
      for(var i = 0; i < word.length; i++ ) {
        if (i == keyIndex) {
          keywordMarkup += ""+word[i] + "";
        }
        else if (i == keyIndex - 1) {
          keywordMarkup += word[i] + "";
        }
        else if (i == keyIndex + 1) {
          keywordMarkup += "" + word[i];
        }
        else {
          keywordMarkup += word[i];
        }
      }
      // Close the last span.
      keywordMarkup += "";
      return keywordMarkup;
    }

    function startTimer(){
      if (!isTimerRunning && scope.userWantsToSkim) {
        isTimerRunning = true;
        timer = $interval(function() {
          progressKeyword()
        }, intervalBetween);
        console.log("Timer started");
      }
    }

    function stopTimer(){
      if (isTimerRunning) {
        isTimerRunning = false;
        $interval.cancel(timer);
        timer = undefined;
        console.log("Timer Stopped");
      }
    }

    function restartTimer(){
      stopTimer();
      startTimer();
    }

    function pauseTimer(delay) {
      stopTimer();
      if (delay) {
        console.log("Paused for: " + delay() + "s");
        $interval(function() {
          startTimer();
        }, delay(), 1);
      }
    }

    function isLastCharacterPunctuation (word){
      var punctutations = ".,:;?!)";
      var lastCharacter = word[word.length - 1];
      return punctutations.indexOf(lastCharacter) != -1;
    }
  }

  return {
    link: link
  };
}]);
<commit_msg>Removes console.logs for production.<commit_after>'use strict';

var MAX_INTERVAL_BETWEEN = 1000;
var WORDS_IN_PHRASE = 3;

var directives = angular.module('skim.directives', []);

directives.directive('skimReader',['$http', '$location', '$interval', function($http, $location, $interval){
  function link(scope, element, attrs) {
    // Initial variables.
    var intervalBetween = MAX_INTERVAL_BETWEEN,
        optimizedTextArray = [],
        currentIndex = 0,
        phraseCount = 1,
        keyword = "",
        timer,
        isTimerRunning = false;

    scope.userWantsToSkim = false;

    // Interface variables.
    scope.startSkimming = function(){
      scope.userWantsToSkim = true;
      startTimer();
    };

    scope.stopSkimming = function(){
      scope.userWantsToSkim = false;
      stopTimer();
    };

    // Updates intervalBetween if wpm changes changes
    scope.$watch(attrs.wpm, function(value) {
      updateIntervalBetweenWithWPM(value);
    });

    // Prepare timer.
    element.on('$destroy', function() {
      stopTimer();
    });

    // Get the optimized text.
    var optimizedDocumentURL = $location.absUrl() + '/optimized.json';
    $http.get(optimizedDocumentURL).success(function(data, status){
      optimizedTextArray = data;
    });

    var punctuationPauseInterval = function(){
      return intervalBetween * 1.5;
    };

    var phrasePauseInterval = function(){
      return intervalBetween / 2;
    };

    function updateIntervalBetweenWithWPM(wpm) {
      intervalBetween = Math.min(1000 / (wpm/60), MAX_INTERVAL_BETWEEN);
      restartTimer();
    }

    function progressKeyword() {
      if (currentIndex < optimizedTextArray.length) {
        var wordHash = optimizedTextArray[currentIndex];
        var word = wordHash.word;
        keyword = formatKeyword(word, wordHash.optimum_index);
        currentIndex += 1;
        phraseCount += 1;
        element.html(keyword);

        // If last character is a punctuation, pause timer.
        if (isLastCharacterPunctuation(word)) {
          phraseCount = 0;
          pauseTimer(punctuationPauseInterval);
        } else if (phraseCount >= WORDS_IN_PHRASE) {
          phraseCount = 0;
          pauseTimer(phrasePauseInterval);
        }
      } else {
        stopTimer();
      }
    }

    function formatKeyword(word, keyIndex) {
      var keywordMarkup = "";

      // One letter word, close the first half span.
      if (word.length < 2) {
        keywordMarkup += "";
      }
      for(var i = 0; i < word.length; i++ ) {
        if (i == keyIndex) {
          keywordMarkup += ""+word[i] + "";
        }
        else if (i == keyIndex - 1) {
          keywordMarkup += word[i] + "";
        }
        else if (i == keyIndex + 1) {
          keywordMarkup += "" + word[i];
        }
        else {
          keywordMarkup += word[i];
        }
      }
      // Close the last span.
      keywordMarkup += "";
      return keywordMarkup;
    }

    function startTimer(){
      if (!isTimerRunning && scope.userWantsToSkim) {
        isTimerRunning = true;
        timer = $interval(function() {
          progressKeyword()
        }, intervalBetween);
      }
    }

    function stopTimer(){
      if (isTimerRunning) {
        isTimerRunning = false;
        $interval.cancel(timer);
        timer = undefined;
      }
    }

    function restartTimer(){
      stopTimer();
      startTimer();
    }

    function pauseTimer(delay) {
      stopTimer();
      if (delay) {
        $interval(function() {
          startTimer();
        }, delay(), 1);
      }
    }

    function isLastCharacterPunctuation (word){
      var punctutations = ".,:;?!)";
      var lastCharacter = word[word.length - 1];
      return punctutations.indexOf(lastCharacter) != -1;
    }
  }

  return {
    link: link
  };
}]);
<|endoftext|>


<commit_before>// based on http://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side

$(function() {

  $('table.table > thead > tr > th:nth-last-child(1)').append('')
  $('span.export-csv').on('click', function() {
    var csvFile = '';
    var table = $(this).closest('table');
    table.find('thead > tr > th:not(:last-child)').each(function(idx, th) {
      csvFile += '"' + $(th).text() + '",';
    });
    csvFile += '\n';
    table.find('tbody > tr').each(function(ids, tr) {

      $(tr).find('td:not(:last-child)').each(function(idx, td) {
        csvFile += '"' + $(td).text().replace(/\s+/g,',') + '",';
      });
      csvFile += '\n';
    });

    var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob, filename);
    } else {
        var link = document.createElement("a");
        if (link.download !== undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", 'export.csv');
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
  });
});
<commit_msg>better export of tags/skills<commit_after>// based on http://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side

$(function() {

  $('table.table > thead > tr > th:nth-last-child(1)').append('')
  $('span.export-csv').on('click', function() {
    var csvFile = '';
    var table = $(this).closest('table');
    table.find('thead > tr > th:not(:last-child)').each(function(idx, th) {
      csvFile += '"' + $(th).text() + '",';
    });
    csvFile += '\n';
    table.find('tbody > tr').each(function(ids, tr) {

      $(tr).find('td:not(:last-child)').each(function(idx, td) {
        jtd = $(td)
        if (jtd.find('span.label').length > 0) {
          csvFile += '"';
          var comma = '';
          jtd.find('span.label').each(function(idx, sp) {
            csvFile += comma + $(sp).text().replace(/s+/g, ' ');
            comma = ',';
            });
          csvFile += '",';
        }
        else {
          csvFile += '"' + jtd.text().replace(/[\n\r]/g,'').replace(/\s+/g,' ') + '",';
        }
      });
      csvFile += '\n';
    });

    var blob = new Blob([csvFile], { type: 'text/csv;charset=utf-8;' });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob, filename);
    } else {
        var link = document.createElement("a");
        if (link.download !== undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", 'export.csv');
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
  });
});
<|endoftext|>


<commit_before>import _ from "lodash";
import $ from "jquery";

export type ToastType = {
  remove: () => void,
};

function hashCode(s) {
  return s.split("").reduce((a, b) => {
    a = (a << 5) - a + b.charCodeAt(0);
    return a & a;
  }, 0);
}

function alertWithTimeout($this: JQuery, timeout = 3000) {
  let timerId = -1;

  $this.hover(
    () => clearTimeout(timerId),
    () => {
      timerId = setTimeout(() => $this.alert("close"), timeout);
    },
  );
  $(window).one("mousemove", () => $this.mouseout());
}

const getToasts = (type, message) => $(`.alert-${type}[data-id='${hashCode(message)}']`);

const shouldDisplayToast = (type, message, sticky) =>
  // Don't show duplicate sticky toasts
  !sticky || getToasts(type, message).length === 0;

const Toast = {
  message(type, message, sticky = false, optTimeout): ToastType {
    let messages;
    if (_.isArray(type) && message == null) {
      messages = type;
      const toasts = messages.map(singleMessage => {
        if (singleMessage.success != null) {
          this.success(singleMessage.success);
        }
        if (singleMessage.error != null) {
          this.error(singleMessage.error);
        }
      });
      return {
        remove: () => toasts.foreach(toast => toast.remove()),
      };
    } else if (_.isArray(message)) {
      messages = message;
      return messages.map(singleMessage => this.message(type, singleMessage, sticky));
    } else if (shouldDisplayToast(type, message, sticky)) {
      let displayMessage;
      if (message.match(/<html[^>]*>/)) {
        displayMessage = `<iframe src='data:text/html;charset=utf-8,${escape(message)}'></iframe>`;
      } else {
        displayMessage = message;
      }
      const $messageElement = $("<div>", {
        class: `alert alert-${type} fade in`,
        "data-id": hashCode(message),
      }).html(displayMessage);
      const $closeButton = $("<button>", {
        type: "button",
        class: "close",
        "data-dismiss": "alert",
      }).html("×");
      $messageElement.prepend($closeButton);
      if (sticky) {
        $messageElement.alert();
      } else {
        const timeout = optTimeout || (type === "danger" ? 6000 : 3000);
        alertWithTimeout($messageElement, timeout);
      }
      $("#alert-container").append($messageElement);

      if (type === "danger") {
        this.highlight($messageElement);
      }

      return {
        remove() {
          return $closeButton.click();
        },
      };
    } else {
      return {};
    }
  },

  info(message, sticky) {
    return this.message("info", message, sticky);
  },

  warning(message, sticky) {
    return this.message("warning", message, sticky);
  },

  success(message = "Success :-)", sticky) {
    return this.message("success", message, sticky);
  },

  error(message = "Error :-/", sticky) {
    return this.message("danger", message, sticky);
  },

  highlight(target) {
    target.addClass("alert-wiggle");
  },

  delete(type, message) {
    getToasts(type, message).alert("close");
  },
};

export default Toast;
<commit_msg>readd alert<commit_after>import _ from "lodash";
import $ from "jquery";

export type ToastType = {
  remove: () => void,
};

function hashCode(s) {
  return s.split("").reduce((a, b) => {
    a = (a << 5) - a + b.charCodeAt(0);
    return a & a;
  }, 0);
}

function alertWithTimeout($this: JQuery, timeout = 3000) {
  $this.alert();
  let timerId = -1;

  $this.hover(
    () => clearTimeout(timerId),
    () => {
      timerId = setTimeout(() => $this.alert("close"), timeout);
    },
  );
  $(window).one("mousemove", () => $this.mouseout());
}

const getToasts = (type, message) => $(`.alert-${type}[data-id='${hashCode(message)}']`);

const shouldDisplayToast = (type, message, sticky) =>
  // Don't show duplicate sticky toasts
  !sticky || getToasts(type, message).length === 0;

const Toast = {
  message(type, message, sticky = false, optTimeout): ToastType {
    let messages;
    if (_.isArray(type) && message == null) {
      messages = type;
      const toasts = messages.map(singleMessage => {
        if (singleMessage.success != null) {
          this.success(singleMessage.success);
        }
        if (singleMessage.error != null) {
          this.error(singleMessage.error);
        }
      });
      return {
        remove: () => toasts.foreach(toast => toast.remove()),
      };
    } else if (_.isArray(message)) {
      messages = message;
      return messages.map(singleMessage => this.message(type, singleMessage, sticky));
    } else if (shouldDisplayToast(type, message, sticky)) {
      let displayMessage;
      if (message.match(/<html[^>]*>/)) {
        displayMessage = `<iframe src='data:text/html;charset=utf-8,${escape(message)}'></iframe>`;
      } else {
        displayMessage = message;
      }
      const $messageElement = $("<div>", {
        class: `alert alert-${type} fade in`,
        "data-id": hashCode(message),
      }).html(displayMessage);
      const $closeButton = $("<button>", {
        type: "button",
        class: "close",
        "data-dismiss": "alert",
      }).html("×");
      $messageElement.prepend($closeButton);
      if (sticky) {
        $messageElement.alert();
      } else {
        const timeout = optTimeout || (type === "danger" ? 6000 : 3000);
        alertWithTimeout($messageElement, timeout);
      }
      $("#alert-container").append($messageElement);

      if (type === "danger") {
        this.highlight($messageElement);
      }

      return {
        remove() {
          return $closeButton.click();
        },
      };
    } else {
      return {};
    }
  },

  info(message, sticky) {
    return this.message("info", message, sticky);
  },

  warning(message, sticky) {
    return this.message("warning", message, sticky);
  },

  success(message = "Success :-)", sticky) {
    return this.message("success", message, sticky);
  },

  error(message = "Error :-/", sticky) {
    return this.message("danger", message, sticky);
  },

  highlight(target) {
    target.addClass("alert-wiggle");
  },

  delete(type, message) {
    getToasts(type, message).alert("close");
  },
};

export default Toast;
<|endoftext|>


<commit_before><commit_msg>Fixes menu removing items<commit_after><|endoftext|>


<commit_before>function Navigation($shreds) {
  $('.rm-cat').on('click', function (ev) {
    var id = $(this).data('id');
    Assert(id, "id not found");
    $.ajax('/i/categories/' + id + '.json', {
      type: 'DELETE'
    }).done(function () {
      location.reload();
    });
  });

  $('.mark-as-read').on('click', function (ev) {
    var id = $(this).data('id');
    Assert(id, 'id not found: ' + id);
    $.ajax('/i/feeds/' + id + '/mark_as_read.json', {
      type: 'PATCH'
    }).done(function () {
      location.reload();
    });
  });

  $shreds.on('shreds:create', function (ev, data) {
    if (data.error) {
      $shreds.trigger('shreds:notification:error', data.error);
    } else {
      location.reload();
    }
    $shreds.trigger('shreds:subscription:spinner:stop');
  });
}
<commit_msg>Also log id number here.<commit_after>function Navigation($shreds) {
  $('.rm-cat').on('click', function (ev) {
    var id = $(this).data('id');
    Assert(id, "id not found: " + id);
    $.ajax('/i/categories/' + id + '.json', {
      type: 'DELETE'
    }).done(function () {
      location.reload();
    });
  });

  $('.mark-as-read').on('click', function (ev) {
    var id = $(this).data('id');
    Assert(id, 'id not found: ' + id);
    $.ajax('/i/feeds/' + id + '/mark_as_read.json', {
      type: 'PATCH'
    }).done(function () {
      location.reload();
    });
  });

  $shreds.on('shreds:create', function (ev, data) {
    if (data.error) {
      $shreds.trigger('shreds:notification:error', data.error);
    } else {
      location.reload();
    }
    $shreds.trigger('shreds:subscription:spinner:stop');
  });
}
<|endoftext|>


<commit_before>$(function(){
  $('.campaign-infolist .previous_page').hide()

  $('.thumbnails').isotope({
    // options
    itemSelector : '.item',
    layoutMode   : 'fitRows'
    layoutMode: 'fitRows',
    itemSelector : '.item'
  });

$('.campaign-infolist .next_page')
  .addClass('btn_readmore')
  .click(function(e){

    var btn = $(this);
    btn.addClass('disabled');
    var page = btn.attr('href').split('?')[1].split('=')[1];

    e.preventDefault();
    $.ajax({
      url: '/campaigns?page=' + page,
      type: 'get',
      dataType: 'script',
      success: function(data) {
        $('.thumbnails').isotope( 'reloadItems' ).isotope();
        btn.removeClass('disabled');
        page++;
        btn.attr('href', next_link = btn.attr('href').split('=')[0] + '=' + page);
        if (data.trim() == "$('.thumbnails').append('');") {
          $('.alert-pagination-end').show().slideDown('slow');
          btn.hide();
        }
      }
    });

  })
});
<commit_msg>terminando donate<commit_after>$(function(){
  $('.campaign-infolist .previous_page').hide()

  $('.thumbnails').isotope({
    // options
    itemSelector : '.item',
    layoutMode   : 'fitRows'
  });

$('.campaign-infolist .next_page')
  .addClass('btn_readmore')
  .click(function(e){

    var btn = $(this);
    btn.addClass('disabled');
    var page = btn.attr('href').split('?')[1].split('=')[1];

    e.preventDefault();
    $.ajax({
      url: '/campaigns?page=' + page,
      type: 'get',
      dataType: 'script',
      success: function(data) {
        $('.thumbnails').isotope( 'reloadItems' ).isotope();
        btn.removeClass('disabled');
        page++;
        btn.attr('href', next_link = btn.attr('href').split('=')[0] + '=' + page);
        if (data.trim() == "$('.thumbnails').append('');") {
          $('.alert-pagination-end').show().slideDown('slow');
          btn.hide();
        }
      }
    });

  })
});
<|endoftext|>


<commit_before>class StudySet {
  constructor(title, description, owner, flashCards = []){
    this.title = title;
    this.description = description;
    this.ownerId = owner["id"];
    this.ownerImage = owner["image"]
    this.ownerEmail = owner["email"];
    this.flashCards = flashCards;
  }

  flashCardCount() {
    return this.flashCards.length
  }
}

function transformStudySets(studySets) {
  sets = [];
  studySets.forEach(function(set){
    sets.push(new StudySet(set["title"], set["description"], set["owner"], set["flash_cards"]))
  });
  return sets;
}

var getSearch = function() {
  var value = $("#search").val();
  $.get("/study_sets.json?search=" + value, function(sets) {
    if(sets.length === 0) {
      $(".col-md-4:not(:last)").html("");
      $("#searchResults").html('
Sorry, no study sets were found!

Try again or go back
');
    } else {
      var str = { studySets: transformStudySets(sets)};
      var source = $("#studySet-template").html();
      var template = Handlebars.compile(source);
      $(".col-md-4:not(:last)").html("");
      $("#searchResults").html(template(str));
      $("#searchResults").append("
Back
")
    }
  });
}

var searchListener = function() {
  $("[value=Search]").click(function(event){
    event.preventDefault();
    getSearch();
  });
}

var studyModeListener = function() {
  $("#studyMode").click(function(event){
    console.log("button pressed");
    debugger
  });
}

var cardFlipListener = function() {
  $('.flip').click(function(){
      $(this).find('.card').toggleClass('flipped')
      return false;
  });
}

var attachListeners = function(){
  searchListener();
  studyModeListener();
  cardFlipListener();
}

$(function(){
  Handlebars.registerHelper('flashCardCount', function() {
    return this.flashCardCount();
  });

  attachListeners();
})
<commit_msg>Add logic code to studyModeListener()<commit_after>class StudySet {
  constructor(title, description, owner, flashCards = []){
    this.title = title;
    this.description = description;
    this.ownerId = owner["id"];
    this.ownerImage = owner["image"]
    this.ownerEmail = owner["email"];
    this.flashCards = flashCards;
  }

  flashCardCount() {
    return this.flashCards.length
  }
}

function transformStudySets(studySets) {
  sets = [];
  studySets.forEach(function(set){
    sets.push(new StudySet(set["title"], set["description"], set["owner"], set["flash_cards"]))
  });
  return sets;
}

var getSearch = function() {
  var value = $("#search").val();
  $.get("/study_sets.json?search=" + value, function(sets) {
    if(sets.length === 0) {
      $(".col-md-4:not(:last)").html("");
      $("#searchResults").html('
Sorry, no study sets were found!

Try again or go back
');
    } else {
      var str = { studySets: transformStudySets(sets)};
      var source = $("#studySet-template").html();
      var template = Handlebars.compile(source);
      $(".col-md-4:not(:last)").html("");
      $("#searchResults").html(template(str));
      $("#searchResults").append("
Back
")
    }
  });
}

var searchListener = function() {
  $("[value=Search]").click(function(event){
    event.preventDefault();
    getSearch();
  });
}

var studyModeListener = function() {
  $("#studyMode").click(function(event){
    console.log("button pressed");
    var ownerId = $(this).data("ownerId");
    var id = $(this).data("id");
    var url = "/users/" + ownerId + "/study_sets/" + id + "/study_mode";
    $.get(url, function(data){
      console.log(data);
    });
  });
}

var cardFlipListener = function() {
  $('.flip').click(function(){
      $(this).find('.card').toggleClass('flipped')
      return false;
  });
}

var attachListeners = function(){
  searchListener();
  studyModeListener();
  cardFlipListener();
}

$(function(){
  Handlebars.registerHelper('flashCardCount', function() {
    return this.flashCardCount();
  });

  attachListeners();
})
<|endoftext|>