$(document).ready(function () {
    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {

        localStorage.setItem('activeTab', $(this).attr('href'));
        alert($(this).attr('href'));
        $('#userTree').jstree({
            'core': {
                'data': {
                    'url': '/Authorisation/GetTree?' + $(this).attr('href'),
                    'data': function (node) {
                        return { 'id': node.id };
                    }
                }
            }
        });
    });

    var activeTab = localStorage.getItem('activeTab');
    if (activeTab) {
        $('[href="' + activeTab + '"]').tab('show');
        alert("test 2");
    }
});

$(document).ready(function () {
    $('#userTree').jstree({
        'core': {
            'data': {
                'url': '/Authorisation/GetTree?tab=' + $(this).attr('href'),
                'data': function (node) {
                    return { 'id': node.id };
                }
            }
        }
    });

    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
        localStorage.setItem('activeTab', $(this).attr('href'));

        $('#userTree').jstree(true).settings.core.url = '/Authorisation/GetTree?tab=' + $(this).attr('href');
        $('#userTree').jstree(true).refresh();
    });

    var activeTab = localStorage.getItem('activeTab');
    if (activeTab) {
        $('[href="' + activeTab + '"]').tab('show');
    }
});

Refresh after tab selection changed.


import { Meteor } from 'meteor/meteor';
import Expressions from './measures-expressions.js';

const Measures = {};

Measures.Expressions = Expressions;

Measures.collection = new Meteor.Collection('Measures');

Measures.add = () => {
  Measures.collection.insert({
    name: 'unnamend',
    expressions: [],
  });
};

Measures.setName = (elementId, name) => {
  Measures.collection.update(elementId, { $set: { name } });
};

export default Measures;

import { Meteor } from 'meteor/meteor';
import Expressions from './measures-expressions.js';

const Measures = {};

Measures.Expressions = Expressions;

Measures.collection = new Meteor.Collection('Measures');

Measures.add = () => {
  return Measures.collection.insert({
    name: 'unnamend',
    expressions: [],
  });
};

Measures.setName = (elementId, name) => {
  Measures.collection.update(elementId, { $set: { name } });
};

export default Measures;

Make Measure.add return new measureId


import Ember from 'ember';
import $ from 'jquery';
export default Ember.Mixin.create({

  lockBackground (obj) {
    const focusableElementQuery = 'select:not([disabled]), button:not([disabled]), [tabindex="0"], input:not([disabled]), a[href]';
    const element = document.getElementById(obj.elementId);
    const backgroundActiveEl = document.activeElement;
    const focusableElements = element.querySelectorAll(focusableElementQuery);
    const firstEl = focusableElements[0];
    const lastEl = focusableElements[focusableElements.length - 1];

    // Focus first element in modal
    firstEl.focus();
    $(element).keydown(event => {
      // If Esc pressed
      if (event.keyCode === 27) {
        backgroundActiveEl.focus();
        obj.callback.call(this);
        return;
      }

      // Trap Tab key while modal open
      this.trapTabKey(event, firstEl, lastEl);
    });
  },

  trapTabKey (event, ...params) {
    const [ firstEl, lastEl ] = params;
    if (event.keyCode === 9) {
      if (event.shiftKey) {
        if (document.activeElement === firstEl) {
          event.preventDefault();
          return lastEl.focus();
        }
      } else {
        if (document.activeElement === lastEl) {
          event.preventDefault();
          return firstEl.focus();
        }
      }
    }
  }
});

import Ember from 'ember';
import $ from 'jquery';

export default Ember.Mixin.create({
  focusableElementQuery: 'select:not([disabled]), button:not([disabled]), [tabindex="0"], input:not([disabled]), a[href]',
  lockBackground (obj) {
    const element = document.getElementById(obj.elementId);
    this.set('trapElementID', obj.elementId);
    this.set('focusableElements', element.querySelectorAll(this.get('focusableElementQuery')));
    let backgroundActiveEl = document.activeElement;

    // Focus first element in modal
    firstEl.focus();
    $(element).keydown(event => {
      // If Esc pressed
      if (event.keyCode === 27) {
        backgroundActiveEl.focus();
        $(`#${obj.elementId}`).off('keydown');
        obj.callback.call(this);
        return;
      }

      // Trap Tab key while modal open
      this.trapTabKey(event);

      event.stopPropagation();
    });
  },

  trapTabKey (event) {
    if (event.keyCode === 9) {
      if (event.shiftKey) {
        if (document.activeElement === this.get('focusableElements')[0]) {
          event.preventDefault();
          return this.get('focusableElements')[this.get('focusableElements').length - 1].focus();
        }
      } else {
        if (document.activeElement === this.get('focusableElements')[this.get('focusableElements').length - 1]) {
          event.preventDefault();
          return this.get('focusableElements')[0].focus();
        }
      }
    }
  },

  updateRegisteredKeys () {
    const element = document.getElementById(this.get('trapElementID'));
    return this.set('focusableElements', element.querySelectorAll(this.get('focusableElementQuery')));
  }
});

Add method that updates all focusable elements when called


$(document).ready(function() {
  $(function(){
    $('[rel="tooltip"]').tooltip();
  });

  $(function(){
    $('#myTabs a').click(function (e) {
    	e.preventDefault();

    	var url = $(this).attr("data-url");
    	var href = this.hash;
    	var pane = $(this);

    	// ajax load from data-url
    	$(href).load(url,function(result){
    	    pane.tab('show');
    	});
    });

    // load first tab content
    $('#all').load($('.active a').attr("data-url"),function(result){
      $('.active a').tab('show');
    });
  });

});

$(document).ready(function() {
  $(function(){
    $('[rel="tooltip"]').tooltip();

    $('#myTabs a').click(function (e) {
    	e.preventDefault();

    	var url = $(this).attr("data-url");
    	var href = this.hash;
    	var pane = $(this);

    	// ajax load from data-url
    	$(href).load(url,function(result){
    	    pane.tab('show');
    	});
    });

    // load first tab content
    $('#all').load($('.active a').attr("data-url"),function(result){
    });
  });

});

Fix JS error on homepage load


import React, { Component } from 'react';

class TableRow extends Component {
  static displayName = 'TableRow';

  shouldComponentUpdate(nextProps) {
    const isNewTermDiff = nextProps.term !== this.props.term;
    const isSelectedDiff = nextProps.isSelected !== this.props.isSelected;

    return isSelectedDiff || isNewTermDiff;
  }

  render() {
    const { id, children, onClick, style } = this.props;

    return (
      <tr id={id} onClick={onClick} style={style}>
        {children}
      </tr>
    );
  }
}

export default TableRow;

import React, { Component } from 'react';

class TableRow extends Component {
  static displayName = 'TableRow';

  shouldComponentUpdate(nextProps) {
    const isTermDefined = nextProps.term !== undefined
    const isNewTermDiff = nextProps.term !== this.props.term;
    const isSelectedDiff = nextProps.isSelected !== this.props.isSelected;

    return isTermDefined || isSelectedDiff || isNewTermDiff;
  }

  render() {
    const { id, children, onClick, style } = this.props;

    return (
      <tr id={id} onClick={onClick} style={style}>
        {children}
      </tr>
    );
  }
}

export default TableRow;

Fix search query filtering issues


jQuery(document).ready(function($){
  $('notices_primary').infinitescroll({
    debug: true,
    infiniteScroll  : false,
    nextSelector    : 'body#public li.nav_next a,'+
                      'body#all li.nav_next a,'+
                      'body#showstream li.nav_next a,'+
                      'body#replies li.nav_next a,'+
                      'body#showfavorites li.nav_next a,'+
                      'body#showgroup li.nav_next a,'+
                      'body#favorited li.nav_next a',
    loadingImg      : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif',
    text            : "Loading the next set of posts...",
    donetext        : "Congratulations, you\'ve reached the end of the Internet.",
    navSelector     : "div.pagination",
    contentSelector : "#notices_primary ol.notices",
    itemSelector    : "#notices_primary ol.notices li"
    },function(){
        NoticeAttachments();
        NoticeReply();
        NoticeFavors();
    });
});

jQuery(document).ready(function($){
  $('notices_primary').infinitescroll({
    debug: false,
    infiniteScroll  : false,
    nextSelector    : 'body#public li.nav_next a,'+
                      'body#all li.nav_next a,'+
                      'body#showstream li.nav_next a,'+
                      'body#replies li.nav_next a,'+
                      'body#showfavorites li.nav_next a,'+
                      'body#showgroup li.nav_next a,'+
                      'body#favorited li.nav_next a',
    loadingImg      : $('address .url')[0].href+'plugins/InfiniteScroll/ajax-loader.gif',
    text            : "Loading the next set of posts...",
    donetext        : "Congratulations, you\'ve reached the end of the Internet.",
    navSelector     : "div.pagination",
    contentSelector : "#notices_primary ol.notices",
    itemSelector    : "#notices_primary ol.notices li"
    },function(){
        NoticeAttachments();
        NoticeReply();
        NoticeFavors();
    });
});

Disable debug mode (so there are no messages written to the javascript console)


// @flow
import {
  assertThat
} from 'hamjest';
import {
  buildFunctionSpy, wasNotCalled, callCountWas,
} from 'hamjest-spy';

type Filename = string;
type KataifyableFile = {
  sourceFilename: Filename;
  destinationFilename: Filename;
};
type KataifyableFileList = Array<KataifyableFile>;

const kataify = (kataifyableList, deps) => {
  if (kataifyableList.length) {
    return deps.readFile()
      .then(deps.writeFile)
    ;
  }
};

describe('Kataify files', () => {
  const buildDeps = () => {
    return {
      readFile: buildFunctionSpy({ returnValue: Promise.resolve() }),
      writeFile: buildFunctionSpy({ returnValue: Promise.resolve() }),
    };
  };

  it('WHEN no files given, dont read any file', async () => {
    const deps = buildDeps();
    await kataify([], deps);
    assertThat(deps.readFile, wasNotCalled());
  });
  it('WHEN one kataifyable file given, read once and write once', async () => {
    const deps = buildDeps();
    const oneFile = {
      sourceFileName: '/src/file.js',
      destinationFilename: '/dest/file.js',
    };
    await kataify([oneFile], deps);
    assertThat(deps.readFile, callCountWas(1));
    assertThat(deps.writeFile, callCountWas(1));
  });
});

// needs to filter files
// build the KataifyableList from the src+dest dir

// @flow
import {
  assertThat
} from 'hamjest';
import {
  buildFunctionSpy, wasNotCalled, callCountWas, wasCalledWith,
} from 'hamjest-spy';

type Filename = string;
type KataifyableFile = {
  sourceFilename: Filename;
  destinationFilename: Filename;
};
type KataifyableFileList = Array<KataifyableFile>;

const kataify = (kataifyableList, deps) => {
  if (kataifyableList.length) {
    return deps.readFile()
      .then(deps.writeFile)
    ;
  }
};

describe('Kataify files', () => {
  const buildDeps = () => {
    return {
      readFile: buildFunctionSpy({ returnValue: Promise.resolve() }),
      writeFile: buildFunctionSpy({ returnValue: Promise.resolve() }),
    };
  };

  it('WHEN no files given, dont read any file', async () => {
    const deps = buildDeps();
    await kataify([], deps);
    assertThat(deps.readFile, wasNotCalled());
  });
  describe('WHEN one kataifyable file given', () => {
    it('AND its empty, write the same file content', async () => {
      const originalContent = '';
      const deps = {
        readFile: buildFunctionSpy({ returnValue: Promise.resolve(originalContent) }),
        writeFile: buildFunctionSpy({ returnValue: Promise.resolve() }),
      };
      const oneFile = {
        sourceFileName: '/src/file.js',
        destinationFilename: '/dest/file.js',
      };
      await kataify([oneFile], deps);
      assertThat(deps.writeFile, wasCalledWith(originalContent));
    });
  });
});

// needs to filter files
// build the KataifyableList from the src+dest dir

Make sure the right file content gets written.


console.log(arrays.reduce(function(a, b) {
    return a.concat(b);
}));

let arrays = [[1, 2, 3], [4, 5], [6]];

let array = arrays.reduce((a, b) => a.concat(b));
console.log(array);
// → [1, 2, 3, 4, 5, 6]

Add Chapter 05, exercise 1


chrome.extension.sendMessage({}, function(response) {
  var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
      clearInterval(readyStateCheckInterval);

      var colorMapping = {
        'blocked': 'rgb(199, 37, 67)',
        'needs ': 'rgb(199, 37, 67)'
      };

      function colorLabelNodes(labels) {
        Object.keys(colorMapping).forEach(function(labelKeyword) {
          Array.prototype.filter.call(labels, function(label) {
            return label.textContent.indexOf(labelKeyword) === 0
          }).forEach(function(label) {
            label.style.color = colorMapping[labelKeyword];
          });
        });
      }

      var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          Array.prototype.forEach.call(mutation.addedNodes, function(addedNode) {
            if (typeof addedNode.getElementsByClassName !== 'undefined') {
              var previews = addedNode.getElementsByClassName('preview');
              if (previews.length === 0) { return; }

              var labels = Array.prototype.filter.call(previews, function(preview) {
                return preview.getElementsByClassName !== 'undefined';
              }).map(function(preview) {
                return Array.prototype.slice.call(preview.getElementsByClassName('label'));
              });

              colorLabelNodes(Array.prototype.concat.apply([], labels));
            }
          });
        });
      });

      // configuration of the observer:
      var config = { childList: true, subtree: true };

      observer.observe(document, config);
    }
  }, 10);
});

chrome.extension.sendMessage({}, function(response) {
  var readyStateCheckInterval = setInterval(function() {
    if (document.readyState === "complete") {
      clearInterval(readyStateCheckInterval);

      var colorMapping = {
        'blocked': 'rgb(199, 37, 67)',
        'needs ': 'rgb(199, 37, 67)'
      };

      function colorLabelNodes(labels) {
        Object.keys(colorMapping).forEach(function(labelKeyword) {
          Array.prototype.filter.call(labels, function(label) {
            return label.textContent.indexOf(labelKeyword) >= 0;
          }).forEach(function(label) {
            label.style.color = colorMapping[labelKeyword];
          });
        });
      }

      var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
          Array.prototype.forEach.call(mutation.addedNodes, function(addedNode) {
            if (typeof addedNode.getElementsByClassName !== 'undefined') {
              var previews = addedNode.getElementsByClassName('preview');
              if (previews.length === 0) { return; }

              var labels = Array.prototype.filter.call(previews, function(preview) {
                return preview.getElementsByClassName !== 'undefined';
              }).map(function(preview) {
                return Array.prototype.slice.call(preview.getElementsByClassName('label'));
              });

              colorLabelNodes(Array.prototype.concat.apply([], labels));
            }
          });
        });
      });

      // configuration of the observer:
      var config = { childList: true, subtree: true };

      observer.observe(document, config);
    }
  }, 10);
});

Use substrings rather than prefixes


var CharSelect = {
    showing: false,
    bound: false,

    $ov: null,
    $ui: null,
    $chars: null,

    bind: function () {
        if (this.bound) {
            return;
        }

        this.bound = true;

        this.$ov = $('#overlay');
        this.$ui = $('#charselect');
        this.$chars = this.$ui.find('.leaders');
        this.$chars.html('');

        var leaders = Utils.shuffleArray(Leaders.leaders.slice());

        var mkLeader = function (leader) {
            var $leader = $('<div />')
                .addClass('leader')
                .addClass('option')
                .append('<img src="assets/images/flag_' + leader.id + '.png" class="flag" />')
                .append(leader.title + ' ' + leader.name + ' of ' + leader.nation)
                .click(function() {
                    MainMenu.hide();
                    this.hide();
                    Game.start(leader.id);
                }.bind(this))
                .appendTo(this.$chars)
        }.bind(this);

        for (var i = 0; i < leaders.length; i++) {
            var leader = leaders[i];
            mkLeader(leader);
        }
    },

    show: function () {
        if (this.showing) {
            return;
        }

        this.showing = true;

        this.bind();

        this.$ov.fadeIn('fast');
        this.$ui.slideDown();
    },

    hide: function () {
        this.$ov.fadeOut('fast');
        this.$ui.slideUp();
        this.showing = false;
    }
};

var CharSelect = {
    showing: false,
    bound: false,

    $ov: null,
    $ui: null,
    $chars: null,

    bind: function () {
        if (this.bound) {
            return;
        }

        this.bound = true;

        this.$ov = $('#overlay');
        this.$ui = $('#charselect');
        this.$chars = this.$ui.find('.leaders');
        this.$chars.html('');

        var leaders = Utils.shuffleArray(Leaders.leaders.slice());

        var mkLeader = function (leader) {
            var $leader = $('<div />')
                .addClass('leader')
                .addClass('option')
                .append('<img src="assets/images/flag_' + leader.id + '.png" class="flag" />')
                .append(leader.title + ' ' + leader.name + ' of ' + leader.nation)
                .click(function() {
                    if (!this.showing) {
                        return;
                    }

                    MainMenu.hide();
                    this.hide();
                    Game.start(leader.id);
                }.bind(this))
                .appendTo(this.$chars)
        }.bind(this);

        for (var i = 0; i < leaders.length; i++) {
            var leader = leaders[i];
            mkLeader(leader);
        }
    },

    show: function () {
        if (this.showing) {
            return;
        }

        this.showing = true;

        this.bind();

        this.$ov.fadeIn('fast');
        this.$ui.slideDown();
    },

    hide: function () {
        this.$ov.fadeOut('fast');
        this.$ui.slideUp();
        this.showing = false;
    }
};

Fix issue where double clicking in char select causes game breakage.


import React, { Component } from 'react';

/* global styles for app */
import './styles/app.scss';

/* application components */
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';

export class App extends Component {
  static propTypes = {
    children: React.PropTypes.any,
  };

  render() {
    return (
      <section>
        <Header />
        {this.props.children}
        <Footer />
      </section>
    );
  }
}

import React, { Component } from 'react';

/* global styles for app */
import './styles/app.scss';

/* application components */
import { Header } from '../../components/Header';
import { Footer } from '../../components/Footer';

export class App extends Component {
  static propTypes = {
    children: React.PropTypes.any,
  };

  constructor(props) {
    super(props);

    this.socket = io();
  }

  render() {
    return (
      <section>
        <Header />
        {this.props.children && React.cloneElement(this.props.children, {
          socket: this.socket
        })}
        <Footer />
      </section>
    );
  }
}

Move socket connection into main App container, pass the connection instance as a property to children components


var Memory = require('../lib/cache').Memory
  , assert = require('assert')
  ;

describe('cache', function(){
	var cache = new Memory();
	before(function(){

	})

	describe('#get/#set', function(){
		it('should set a value', function(){
			cache.set('foo', 'bar' );
			assert.equal( cache.get('foo'), 'bar')
		})

		it('should expire keys', function( done ){
			cache.setMeta({timeout:0.5});
			cache.set('bar','baz')
			assert.equal( cache.get('bar'), 'baz')

			setTimeout( function(){
				assert.notEqual( cache.get('bar'), 'baz')
				done();
			},1000)
		})

		it('should defaine control property function', function(){
			assert.equal( cache.control.no_cache, true )
			cache.setMeta({control:{no_cache:false}});
			assert.equal( cache.control.no_cache, false)
		})
	})
})

var Memory = require('../lib/cache').Memory
  , assert = require('assert')
  ;

describe('cache', function(){
	var cache = new Memory();
	before(function(){

	})

	describe('#get/#set', function(){
		it('should set a value', function(){
			cache.set('foo', 'bar', function(){
				cache.get('foo', function(err, value ){

					assert.equal(value, 'bar' )
				})

			});
		})

		it('should expire keys', function( done ){
			cache.setMeta({timeout:0.5});
			cache.set('bar','baz')

			 cache.get('bar', function( err, value){
			 	assert.equal( value, 'baz')
			 })

			setTimeout( function(){
				cache.get('bar', function(err, value){
					assert.notEqual(value, 'baz')
					done();
				})
			},1000)
		})

		it('should defaine control property function', function(){
			assert.equal( cache.control.no_cache, true )
			cache.setMeta({control:{no_cache:false}});
			assert.equal( cache.control.no_cache, false)
		})
	})
})

Fix cache tests to account for callback style.


module.exports = {
  onPreBuild({ utils: { status: { show } } }) {
    show({ summary: 'Test' })
  }
}

const { spawn } = require('child_process')

module.exports = {
  onPreBuild({ utils: { status: { show } } }) {
    spawn('sleep', ['60'], { detached: true })
    show({ summary: 'Test' })
  }
}

Revert "Revert "Revert "Revert "Try child process""""


/* global describe, it */
'use strict';

describe('Load', function() {

    it('shows welcome page', function(client) {
        client
        .urlHash('/notes')
        .expect.element('#welcome--page').to.be.present.before(50000);
    });

    it('closes welcome page', function(client) {
        client
        .click('.modal-header button')
        .expect.element('#welcome--page').not.to.be.present.before(5000);
    });

});

Add UI tests for welcome page


const EXPR = /{([\s\S]+?)}/g;
const BLOCK_START = 'block:';
const BLOCK_END = '/block:';
//const BLOCK_START_EXPR = /{block:([\s\S]+?)}/g;
//const BLOCK_END_EXPR = /{\/block:([\s\S]+?)}/g;

export default class Parser {
  constructor() {}

  // Tokenizes a raw Tumblr template
  // Returns a list of tokens
  tokenize(template) {
    let tokens = [],
        lastIndex = 0,
        match, token;
    while ((match = EXPR.exec(template)) !== null) {
      if (match.index !== lastIndex) {
        tokens.push({type: 'text', value: template.slice(lastIndex, match.index)});
      }
      lastIndex = EXPR.lastIndex;

      if (match[1].startsWith(BLOCK_START)) {
        tokens.push({type: 'blockStart', value: match[1].slice(BLOCK_START.length)});
      } else if (match[1].startsWith(BLOCK_END)) {
        tokens.push({type: 'blockEnd', value: match[1].slice(BLOCK_END.length)});
      } else {
        tokens.push({type: 'var', value: match[1]});
      }
    }

    return tokens;
  }

  // Parses a raw Tumblr template
  // Returns an AST
  parse(template) {

  }
}

const EXPR = /{([\s\S]+?)}/g;
const BLOCK_START = 'block:';
const BLOCK_END = '/block:';
//const BLOCK_START_EXPR = /{block:([\s\S]+?)}/g;
//const BLOCK_END_EXPR = /{\/block:([\s\S]+?)}/g;

export default class Parser {
  constructor() {}

  // Tokenizes a raw Tumblr template
  // Returns a list of tokens
  tokenize(template) {
    let tokens = [],
        lastIndex = 0,
        match;

    while ((match = EXPR.exec(template)) !== null) {
      if (match.index !== lastIndex) {
        tokens.push({type: 'text', value: template.slice(lastIndex, match.index)});
      }
      lastIndex = EXPR.lastIndex;

      if (match[1].startsWith(BLOCK_START)) {
        tokens.push({type: 'blockStart', value: match[1].slice(BLOCK_START.length)});
      } else if (match[1].startsWith(BLOCK_END)) {
        tokens.push({type: 'blockEnd', value: match[1].slice(BLOCK_END.length)});
      } else {
        tokens.push({type: 'var', value: match[1]});
      }
    }

    return tokens;
  }

  // Parses a raw Tumblr template
  // Returns an AST
  parse(template) {

  }
}

Remove obsolete variable from tokenize


// ==UserScript==
// @name        Wanikani for Vimperator
// @namespace   rr-@sakuya.pl
// @description Improves Wanikani navigation on Vimperator
// @include     https://www.wanikani.com/review/session
// @include     https://www.wanikani.com/lesson/session
// @version     1
// @grant       none
// ==/UserScript==

function convertLinks($targetItems)
{
    $targetItems.each(function(i, item)
    {
        var $item = $(item);
        // link already added?
        if ($item.find('a').length > 0)
            return;
        $item.wrapInner($('<a/>').click(function() { $item.click(); }));
    });
}

function convertLinksInReviews()
{
    // buttons under the main input
    convertLinks($('#additional-content li'));
}

function convertLinksInLessons()
{
    $('#lesson').bind('DOMSubtreeModified', function()
    {
        // top tabs
        convertLinks($('#supplement-nav li'));

        // bottom kanji buttons
        convertLinks($('#batch-items li'));
    });
}

$(function()
{
    convertLinksInReviews();
    convertLinksInLessons();
});

// ==UserScript==
// @name        Wanikani for Vimperator
// @namespace   rr-@sakuya.pl
// @description Improves Wanikani navigation on Vimperator
// @include     https://www.wanikani.com/review/session
// @include     https://www.wanikani.com/lesson/session
// @version     1
// @grant       none
// ==/UserScript==

function convertLinks($targetItems)
{
    $targetItems.each(function(i, item)
    {
        var $item = $(item);

        // link already added?
        if ($item.find('a').length > 0)
            return;

        $item.wrapInner(
            $('<a/>').click(function(e)
                {
                    $item.click();

                    // prevent firing event listeners that Wanikani might attach
                    // at later point in time
                    e.stopPropagation();
                    e.preventDefault();
                }));
    });
}

function convertLinksInReviews()
{
    // buttons under the main input
    convertLinks($('#additional-content li'));
}

function convertLinksInLessons()
{
    $('#lesson').bind('DOMSubtreeModified', function()
    {
        // top tabs
        convertLinks($('#supplement-nav li'));

        // bottom kanji buttons
        convertLinks($('#batch-items li'));

        // big left/right arrows
        convertLinks($('#prev-btn, #next-btn'));
    });
}

$(function()
{
    convertLinksInReviews();
    convertLinksInLessons();
});

Add links to arrows in lessons


function UnauthorizedError (code, error) {
  Error.call(this, error.message);
  Error.captureStackTrace(this, this.constructor);
  this.name = "UnauthorizedError";
  this.message = error.message;
  this.code = code;
  this.status = 401;
  this.inner = error;
}

UnauthorizedError.prototype = Object.create(Error.prototype);
UnauthorizedError.prototype.constructor = UnauthorizedError;

module.exports = UnauthorizedError;

function UnauthorizedError (code, error) {
  Error.call(this, error.message);
  Error.captureStackTrace(this, this.constructor);
  this.name = "UnauthorizedError";
  this.message = error.message;
  this.code = code;
  this.statusCode = 401;
  this.inner = error;
}

UnauthorizedError.prototype = Object.create(Error.prototype);
UnauthorizedError.prototype.constructor = UnauthorizedError;

module.exports = UnauthorizedError;

Use statusCode instead of status


var keys = require("lodash/keys");
var omit = require("lodash/omit");
var through = require("through2");
var assign = require("lodash/assign");
var includes = require("lodash/includes");

module.exports = function() {
	return through.obj(function(data, enc, done) {
		try {
			done(null, filterGraph(data));
		} catch (err) {
			done(err);
		}
	});
};

var blackList = [
	"npm",
	"npm-convert",
	"npm-crawl",
	"npm-extension",
	"npm-load",
	"npm-utils",
	"semver",
	"@dev"
];

function filterGraph(data) {
	var visited = {};
	var filtered = {};
	var graph = data.graph;

	keys(graph).forEach(function visit(name) {
		// don't visit a node twice
		if (visited[name]) return;

		visited[name] = true;

		if (!includes(blackList, name)) {
			filtered[name] = graph[name];
		}
	});

	return assign({}, omit(data, "graph"), { graph: filtered });
}

var omit = require("lodash/omit");
var through = require("through2");
var assign = require("lodash/assign");
var includes = require("lodash/includes");

module.exports = function() {
	return through.obj(function(data, enc, done) {
		try {
			done(null, filterGraph(data));
		} catch (err) {
			done(err);
		}
	});
};

function filterGraph(data) {
	var graph = data.graph;

	var configMain = data.loader.configMain;
	var configNode = graph[configMain];
	var blackList = [];

	configNode.dependencies.forEach(function visit(name) {
		if (includes(blackList, name)) return;

		blackList.push(name);

		if (graph[name]) {
			(graph[name].dependencies || []).forEach(visit);
		}
	});

	return assign({}, omit(data, "graph"), { graph: omit(graph, blackList) });
}

Refactor slim graph filter logic


var chai = require('chai'),
	chordprojs = require('../');

var expect = chai.expect;

describe('Parse Directives', () => {

	it('{title:Song Title} should set song.title property to \'Song Title\'', () => {
		var song = chordprojs.parse('{title:Song Title}');
		expect(song.title).to.equal('Song Title');
	});

	it('{subtitle:Song Subtitle} should set song.subtitle property to \'Song Subtitle\'', () => {
		var song = chordprojs.parse('{subtitle:Song Subtitle}');
		expect(song.subtitle).to.equal('Song Subtitle');
	});

});

var chai = require('chai'),
	chordprojs = require('../');

var expect = chai.expect;

describe('Parse Directives', function () {

	it('{title:Song Title} should set song.title property to \'Song Title\'', function () {
		var song = chordprojs.parse('{title:Song Title}');
		expect(song.title).to.equal('Song Title');
	});

	it('{subtitle:Song Subtitle} should set song.subtitle property to \'Song Subtitle\'', function () {
		var song = chordprojs.parse('{subtitle:Song Subtitle}');
		expect(song.subtitle).to.equal('Song Subtitle');
	});

});

Remove ES6 code from tests as well for backwards compatibility.


'use strict';

angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus',
  function ($scope, $state, Authentication, Menus) {
    // Expose view variables
    $scope.$state = $state;
    $scope.authentication = Authentication;

    // Get the topbar menu
    $scope.menu = document.getElementById("side-menu");

    // Toggle the menu items
    $scope.isCollapsed = false;
    $scope.toggleCollapsibleMenu = function () {
      $scope.isCollapsed = !$scope.isCollapsed;
    };

    // Collapsing the menu after navigation
    $scope.$on('$stateChangeSuccess', function () {
      $scope.isCollapsed = false;
    });

    $scope.toggleMenu = function() {
        if ($scope.menu.style.left === "-200px") {
            $scope.menu.style.left = "0px";
        } else {
            $scope.menu.style.left = "-200px";
        }
    };

    $scope.closeMenu = function() {
      $scope.menu.style.left = "-200px";
    }
  }
]);

'use strict';

angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus',
  function ($scope, $state, Authentication, Menus) {
    // Expose view variables
    $scope.$state = $state;
    $scope.authentication = Authentication;

    // Get the topbar menu
    $scope.menu = document.getElementById('side-menu');

    // Toggle the menu items
    $scope.isCollapsed = false;
    $scope.toggleCollapsibleMenu = function () {
      $scope.isCollapsed = !$scope.isCollapsed;
    };

    // Collapsing the menu after navigation
    $scope.$on('$stateChangeSuccess', function () {
      $scope.isCollapsed = false;
    });

    $scope.toggleMenu = function() {
      if ($scope.menu.style.left === '-200px') {
        $scope.menu.style.left = '0px';
      } else {
        $scope.menu.style.left = '-200px';
      }
    };

    $scope.closeMenu = function() {
      $scope.menu.style.left = '-200px';
    };
  }
]);

Fix some grunt / linting issues


"use strict";

annotationApp.service('tokenRetriever', function($http) {
  this.getData = function(callback) {
    var request = $.ajax({
      url: './static/tokens.json',
      async: false
    });

    request.done(callback);
  };
});

"use strict";

annotationApp.service('tokenRetriever', function($http) {
  // tokens should always be loaded synchronous - the app should
  // not start anything without knowing an initial state
  this.getData = function(callback) {
    var request = $.ajax({
      url: './static/tokens.json',
      async: false
    });

    request.done(callback);
  };
});

Add comment about synchronous token retrieval


/* global angular, console, toml */

angular.module('app')
.controller('HomeCtrl', function ($location, $route, $scope, $http, Anchors, History, Wallet) {
	'use strict';

	var accountId = $route.current.params.accountId;
	if (accountId) {
		Wallet.current = Wallet.accounts[accountId];
	}

	$scope.wallet = Wallet;

	$scope.doRefresh = function () {

		Wallet.current.refresh()
		.then(function () {
			$scope.$broadcast('scroll.refreshComplete');
		});
	};
});

/* global angular, console, toml */

angular.module('app')
.controller('HomeCtrl', function ($location, $route, $scope, $http, Anchors, History, Wallet) {
	'use strict';

	var accountId = $route.current.params.accountId;
	if (accountId) {
		Wallet.current = Wallet.accounts[accountId];
	}

	$scope.wallet = Wallet;

	$scope.doRefresh = function () {

		Wallet.current.refresh()
		.then(function () {
			$scope.$broadcast('scroll.refreshComplete');
		}, function (err) {
			$scope.$broadcast('scroll.refreshComplete');
			// :TODO: Display some message about not being able to refresh
		});
	};
});

Handle rejections when refreshing doesn’t work.


describe('Landing page', function() {
  it('should have the correct title', function(done, server, client) {
    // Get the title with some Laika mumbo jumbo and a dash of jQuery:
    var title = client.evalSync(function() {
      var titleText = $('title').text();

      // Laika test functions don't end with a return call. Instead, they use
      // emit('return', anything);

      // You can pass anything, object or whatever you want.
      emit('return', titleText);
    });

    // Check if it's correct.
    title.should.equal('mds');

    // All tests end with the 'done' callback.
    done();
  });

  it('should display "Hello world!"', function(done, server, client) {
    var paragraph = client.evalSync(function() {
      emit('return', $('p').text());
    });

    paragraph.should.equal('Hello world!');
    done();
  });
});

describe('Landing page', function() {
  it('should have the correct title', function(done, server, client) {
    var title = client.evalSync(function() {
      var titleText = $('title').text();

      emit('return', titleText);
    });

    title.should.equal('mds');

    done();
  });
});

Update test suite to get it to pass


/* jshint browser: true, jquery: true */
/* global AgileSprint:false, console:false */

var AgileSprintManager = function(options) {
//  if(options.server === null || options.server === undefined) {
//    return null;
//  }

  this.widgetManager = (options.widgetManager !== null && options.widgetManager !== undefined) ? options.widgetManager : null;
  this.panelManager = (options.panelManager !== null && options.panelManager !== undefined) ? options.panelManager : null;

  this.server = options.server;
};

AgileSprintManager.prototype.processMessages = function(server, id, content) {
  var processed = false;

  switch (id) {
    case AgileSprint.MESSAGE_STATS:
      this.__receivedStats(server, content);
      processed = true;
      break;
  }

  return processed;
};

AgileSprintManager.prototype.__receivedStats = function(server, d) {
  console.log(JSON.stringify(d, null, ' '));
};

/* jshint browser: true, jquery: true */
/* global AgileSprint:false, console:false */

var AgileSprintManager = function(options) {
//  if(options.server === null || options.server === undefined) {
//    return null;
//  }

  this.widgetManager = (options.widgetManager !== null && options.widgetManager !== undefined) ? options.widgetManager : null;
  this.panelManager = (options.panelManager !== null && options.panelManager !== undefined) ? options.panelManager : null;

  this.server = options.server;
};

AgileSprintManager.prototype.processMessages = function(server, id, content) {
  var processed = false;

  switch (id) {
    case AgileSprint.MESSAGE_STATS:
      this.__receivedStats(server, content);
      processed = true;
      break;
  }

  return processed;
};

AgileSprintManager.prototype.__receivedStats = function(server, d) {
  if(this.widgetManager !== null) {
    this.widgetManager.receivedData(d);
  }
};

Call widgetManager when data received


'use strict';

angular
.module('app.controllers')
.controller('EditCtrl', function($scope, $rootScope, $stateParams, $sce, $interval, Restangular, apiDescription, formElementProvider) {
  var resourceName = $stateParams.resourceName,
      resourceId = //$stateParams.id;
      '53f54dd98d1e62ff12539dbb'; // test id
  $scope.rdesc = apiDescription.resource(resourceName);
  $scope.fep = formElementProvider;
  $scope.model = {};
  console.log($scope.rdesc);
  $interval(function() { console.log($scope.model); }, 500);
  var resource = Restangular.one(resourceName, resourceId);

  resource.get()
    .then(function(presenter) {
      // // transform schools to String
      // presenter.schools = presenter.schools.join(',');

      // // format date for input[type=date]
      // if (presenter.graduationDate) {
      //   presenter.graduationDate = presenter.graduationDate.formatForInputTypeDate(); // see js/lib/extentions.js
      // }

      // $scope.presenter = Restangular.stripRestangular(presenter);
    });

  // FAKE, but more or less like this...
  $scope.updateResource = function() {
    console.log($scope.presenter);
    // resource.put($scope.presenter); ClayReedA gets 403 Forbidden
  };
});

'use strict';

angular
.module('app.controllers')
.controller('EditCtrl', function($scope, $rootScope, $stateParams, $sce, $interval, Restangular, apiDescription, formElementProvider) {
  var resourceName = $stateParams.resourceName,
      resourceId = //$stateParams.id;
      '53f54dd98d1e62ff12539dbb'; // test id
  $scope.rdesc = apiDescription.resource(resourceName);
  $scope.fep = formElementProvider;
  $scope.model = Restangular.one(resourceName, resourceId).get().$object;
  $interval(function() { console.log($scope.model); }, 500);

  // var resource = Restangular.one(resourceName, resourceId);

  // resource.get()
  //   .then(function(live) {
  //     // // transform schools to String
  //     // presenter.schools = presenter.schools.join(',');

  //     // // format date for input[type=date]
  //     // if (presenter.graduationDate) {
  //     //   presenter.graduationDate = presenter.graduationDate.formatForInputTypeDate(); // see js/lib/extentions.js
  //     // }

  //     // $scope.presenter = Restangular.stripRestangular(presenter);

  //     $scope.model =
  //   });

  // FAKE, but more or less like this...
  // $scope.updateResource = function() {
  //   console.log($scope.presenter);
  //   // resource.put($scope.presenter); ClayReedA gets 403 Forbidden
  // };
});

Edit form now pulls live data


'use strict';

var config = require('./server/config');
var helper = require('./server/helper');
var express = require('express');
var expressLayouts = require('express-ejs-layouts');
var compression = require('compression');
var app = express();

// Middlewares configuration
var oneWeek = 604800000;
app.use(compression());
app.use(express.static('client/statics'));
app.use(express.static('client/images', { maxAge: oneWeek }));
app.use(express.static('dist', { maxAge: oneWeek }));

// Handlebars configuration
app.set('views', 'client/views');
app.set('view engine', '.ejs');
app.set('layout', 'commons/_layout');
app.use(expressLayouts);

// Starts application listening
app.listen(config.port, () => {
    helper.log('running on ' + config.port);
});

// Regsiters routes
app.use('/', require('./server/routes/manifestRoute'));
app.use('/', require('./server/routes/sitemapRoute'));
app.use('/', require('./server/routes/homeRoute'));
app.use('/', require('./server/routes/itemRoute'));

// Setup cron jobs
require('./server/cronJobs').setupCrons();

'use strict';

var config = require('./server/config');
var helper = require('./server/helper');
var express = require('express');
var expressLayouts = require('express-ejs-layouts');
var compression = require('compression');
var app = express();

// Middlewares configuration
var oneWeek = 604800000;
app.use(compression());
app.use(express.static('client/statics'));
app.use(express.static('client/images', { maxAge: oneWeek }));
app.use(express.static('dist', { maxAge: oneWeek }));

// Handlebars configuration
app.set('views', 'client/views');
app.set('view engine', '.ejs');
app.set('layout', 'commons/_layout');
app.use(expressLayouts);

// Starts application listening
app.listen(config.port, () => {
    helper.log('running on ' + config.port);
});

// Regsiters routes
app.use('/', require('./server/routes/manifestRoute'));
app.use('/', require('./server/routes/sitemapRoute'));
app.use('/', require('./server/routes/homeRoute'));
app.use('/', require('./server/routes/itemRoute'));

// Setup cron jobs
// Disable auto-update until next year
// require('./server/cronJobs').setupCrons();

Disable auto-update until next year


var resolve = require( "rollup-plugin-node-resolve" );
var cjs = require( "rollup-plugin-commonjs" );
var json = require( "rollup-plugin-json" );

module.exports = function rollupTask(){
    return {
        "options": {
            "plugins": [
                resolve(),
                cjs(),
                json()
            ],
            "format": "umd"
        },
        "files": {
            "dest": "public/js/strongbox.js",
            "src": "src/strongbox.js",
        }
    };
};

var resolve = require( "rollup-plugin-node-resolve" );
var cjs = require( "rollup-plugin-commonjs" );
var json = require( "rollup-plugin-json" );
var globals = require( "rollup-plugin-node-globals" );

module.exports = function rollupTask(){
    return {
        "options": {
            "plugins": [
                resolve(),
                cjs(),
                json(),
                globals()
            ],
            "format": "umd"
        },
        "files": {
            "dest": "public/js/strongbox.js",
            "src": "src/strongbox.js",
        }
    };
};

Add special loader since Redux is unusable in the browser as is


import DS from 'ember-data';
import JsonApiAdapter from 'ember-json-api/json-api-adapter';

import config from 'flarum/config/environment';
import AlertMessage from 'flarum/components/ui/alert-message';

export default JsonApiAdapter.extend({
  host: config.apiURL,

  ajaxError: function(jqXHR) {
    var errors = this._super(jqXHR);

    // Reparse the errors in accordance with the JSON-API spec to fit with
    // Ember Data style. Hopefully something like this will eventually be a
    // part of the JsonApiAdapter.
    if (errors instanceof DS.InvalidError) {
      var newErrors = {};
      for (var i in errors.errors) {
        var error = errors.errors[i];
        newErrors[error.path] = error.detail;
      }
      return new DS.InvalidError(newErrors);
    }

    // If it's a server error, show an alert message. The alerts controller
    // has been injected into this adapter.
    if (errors instanceof JsonApiAdapter.ServerError) {
      var message = AlertMessage.create({
        type: 'warning',
        message: errors.message
      });
      this.get('alerts').send('alert', message);
      return;
    }

    return errors;
  }
});

import DS from 'ember-data';
import JsonApiAdapter from 'ember-json-api/json-api-adapter';

import config from 'flarum/config/environment';
import AlertMessage from 'flarum/components/ui/alert-message';

export default JsonApiAdapter.extend({
  host: config.apiURL,

  ajaxError: function(jqXHR) {
    var errors = this._super(jqXHR);

    // Reparse the errors in accordance with the JSON-API spec to fit with
    // Ember Data style. Hopefully something like this will eventually be a
    // part of the JsonApiAdapter.
    if (errors instanceof DS.InvalidError) {
      var newErrors = {};
      for (var i in errors.errors) {
        var error = errors.errors[i];
        newErrors[error.path] = error.detail;
      }
      return new DS.InvalidError(newErrors);
    }

    // If it's a server error, show an alert message. The alerts controller
    // has been injected into this adapter.
    if (errors instanceof JsonApiAdapter.ServerError) {
      var message = AlertMessage.create({
        type: 'warning',
        message: 'Something went wrong: '+errors.message.errors[0].code
      });
      this.get('alerts').send('alert', message);
      return;
    }

    return errors;
  }
});

Fix up uncaught ajax error alert message


/* jshint node: true */
'use strict';

module.exports = {
  afterInstall: function() {
    this.addPackagesToProject([
      { name: 'ember-cli-paint' },
      { name: 'ember-cli-lodash' },
      { name: 'ember-moment' }
    ]);

    this.addBowerPackagesToProject([
      { name: 'moment-timezone' },
      { name: 'interact' },
      { name: 'lodash' }
    ]);
  }
};

/* jshint node: true */
'use strict';

module.exports = {
  normalizeEntityName: function() {},

  beforeInstall: function() {
    return this.addBowerPackagesToProject([
      { name: 'moment-timezone' },
      { name: 'jquery-simulate' },
      { name: 'interact' },
      { name: 'lodash' }
    ]);
  },

  afterInstall: function() {
    return this.addPackagesToProject([
      { name: 'ember-cli-paint' },
      { name: 'ember-cli-lodash' },
      { name: 'ember-moment' }
    ]);
  }
};

Normalize entity name and return promises


/* eslint new-cap: 'off' */
const { Router } = require('express');
const accounts = require('./accounts');
const oauth = require('./oauth');
const rooms = require('./rooms');

const router = Router();

// Login, logout
router.post('/api/login', accounts.login);
router.get('/api/logout', accounts.logout);

// Accounts: `/api/accounts/`
router.post('/api/accounts', accounts.signup);

// OAuth: `/api/oauth/`
router.get('/api/oauth/facebook', oauth.facebook);
router.get('/api/oauth/facebook/callback', oauth.facebookCallback);

// Private rooms: `/api/rooms/`
router.post('/api/rooms', rooms.createPrivateRoom);
router.get('/api/rooms', rooms.listPrivateRooms);

module.exports = router;

/* eslint new-cap: 'off' */
const { Router } = require('express');
const passport = require('../auth/passport');
const accounts = require('./accounts');
const oauth = require('./oauth');
const rooms = require('./rooms');

/* Authorization middleware */

// Require the user to have a valid JWT before accessing any protected resources
const requireAuth = passport.authenticate('jwt', {
  session: false
});

// Validate user's name & password. Do this before sending user a token.
const validateLogin = passport.authenticate('local', {
  session: false
});

/* Routes */

const router = Router();

// Login, logout
router.post('/api/login', validateLogin, accounts.login);
router.get('/api/logout', accounts.logout);

// Accounts: `/api/accounts/`
router.post('/api/accounts', accounts.signup);

// OAuth: `/api/oauth/`
router.get('/api/oauth/facebook', oauth.facebook);
router.get('/api/oauth/facebook/callback', oauth.facebookCallback);

// Private rooms: `/api/rooms/`
router.post('/api/rooms', rooms.createPrivateRoom);
router.get('/api/rooms', rooms.listPrivateRooms);

module.exports = router;

Use passport middleware in API


// Javasctipt file for index.html

// sub-nav toggle
// Toggle contents by clicking #sub-nav

$(function (){
  var subMenuChild = $('#sub-nav').children();
  for (i = 0; i < subMenuChild.length; i++) {
    var item = $(subMenuChild[i]);
    item.on('click', function( event ) {
      event.preventDefault();
      // Remove old context
      var selected = $('#sub-nav').children('.active').toggleClass('active');
      $(selected.data().boundTo).collapse('toggle');

      // Show selected context
      var boundContext = $(this).toggleClass('active').data().boundTo;
      $(boundContext).collapse('toggle');
    });
  }
})

// Javasctipt file for index.html

// sub-nav toggle
// Toggle contents by clicking #sub-nav

$(function (){
  var subMenuChild = $('#sub-nav').children();
  for (i = 0; i < subMenuChild.length; i++) {
    var item = $(subMenuChild[i]);
    item.on('click', function( event ) {
      event.preventDefault();
      if ( $(this).hasClass('active') ) {
        // already actived
        return;
      }
      // Remove old context
      var selected = $('#sub-nav').children('.active');
      selected.toggleClass('active');
      $(selected.data().boundTo).collapse('toggle');

      // Show selected context
      var boundContext = $(this).toggleClass('active').data().boundTo;
      $(boundContext).collapse('toggle');
    });
  }
})

Fix clicking activated sub-menu will hide context


/* eslint-disable no-underscore-dangle */

export const MiniMongo = {}

const NAME_TO_CAUSE_MINIMONGO = null

if (global.Mongo) {
  MiniMongo.Collection = class MiniMongoCollection extends global.Mongo.Collection {
    constructor(name) {
      super(NAME_TO_CAUSE_MINIMONGO)
      this._name = name   // eslint-disable-line no-underscore-dangle
      this.indexes = []
    }

    _ensureIndex(keys, options) {
      if (! this._c2 || ! this._c2._simpleSchema) {
        throw new Error("Attach a schema before adding indexes so we can check they're valid")
      }
      const schemaDoc = this._c2._simpleSchema._schema
      Object.keys(keys).forEach(k => {
        if (! (k in schemaDoc)) {
          throw new Error(`Attempt to add an index key '${k}' which is not in the schema`)
        }
      })
      this.indexes.push(keys)
      // MiniMongo would normally throw an exception but we ignore it
      // If this was ever used on the client it should throw still
    }
  }
}

/* eslint-disable no-underscore-dangle */

export const MiniMongo = {}

const NAME_TO_CAUSE_MINIMONGO = null

if (global.Mongo) {
  MiniMongo.Collection = class MiniMongoCollection extends global.Mongo.Collection {
    constructor(name) {
      super(NAME_TO_CAUSE_MINIMONGO)
      this._name = name   // eslint-disable-line no-underscore-dangle
      this.indexes = []
    }

    _ensureIndex(keys, options) {
      if (! this._c2 || ! this._c2._simpleSchema) {
        throw new Error("Attach a schema before adding indexes so we can check they're valid")
      }
      const schemaDoc = this._c2._simpleSchema._schema
      Object.keys(keys).forEach(k => {
        if (! (k in schemaDoc)) {
          throw new Error(`Attempt to add an index key '${k}' which is not in the schema`)
        }
      })
      this.indexes.push(options ? { keys, options } : keys)
      // MiniMongo would normally throw an exception but we ignore it
      // If this was ever used on the client it should throw still
    }
  }
}

Store options with Minimongo indexes if passed


var userFeed = new Instafeed({
    get: 'user',
    userId: '182831931',
    clientId: '467ede5a6b9b48ae8e03f4e2582aeeb3',
    resolution: 'low_resolution',
    sortBy: 'most-recent',
    limit: 9 * 2,
    target: 'instagram-tiles',
    template: '

'
  });
userFeed.run();

var onUserFeedError = new function() {
    document.getElementById("instagram-tiles-row").style.display = "none";
    document.getElementById("instagram-tiles-header").style.display = "none";
};

var userFeed = new Instafeed({
    get: 'user',
    userId: '182831931',
    clientId: '467ede5a6b9b48ae8e03f4e2582aeeb3',
    resolution: 'low_resolution',
    sortBy: 'most-recent',
    limit: 9 * 2,
    target: 'instagram-tiles',
    template: '

',
    error: onUserFeedError
  });
userFeed.run();

Hide instagram tiles if the API fails


var ResultScene = InformationScene.extend({
    ctor: function (game) {
        this._super(game);
        return true;
    },

    createSceneNode: function () {
        return ccs.sceneReader.createNodeWithSceneFile(res.json.resultScene);
    },

    getLovePanelMode: function () {
        return InformationScene.BAR_LOVE_PANEL_MODE;
    },

    setupPlayerPanels: function () {
        this._super();

        var rankedPlayers = this.game.getRanking();
        var maxPopularity = _.max(_.map(rankedPlayers, function (player) {
            return player.getPopularity();
        }, this));

        _.each(rankedPlayers, function (player) {
            var playerPanel = this.getPlayerPanel(player.index);
            var popularityBarName = (player.getPopularity() >= 0 ? 'PositivePopularityBar' : 'NegativePopularityBar');
            playerPanel.getChildByName(popularityBarName).setPercent(Math.abs(player.getPopularity()) / maxPopularity * 100);
        }, this);
    },
});

var ResultScene = InformationScene.extend({
    ctor: function (game) {
        this._super(game);
        return true;
    },

    createSceneNode: function () {
        return ccs.sceneReader.createNodeWithSceneFile(res.json.resultScene);
    },

    getLovePanelMode: function () {
        return InformationScene.BAR_LOVE_PANEL_MODE;
    },

    setupPlayerPanels: function () {
        this._super();

        var rankedPlayers = this.game.getRanking();
        var maxPopularity = _.max(_.map(rankedPlayers, function (player) {
            return player.getPopularity();
        }, this));

        _.each(rankedPlayers, function (player) {
            var playerPanel = this.getPlayerPanel(player.index);
            var popularityBarName = (player.getPopularity() >= 0 ? 'PositivePopularityBar' : 'NegativePopularityBar');
            var popularityBar = playerPanel.getChildByName(popularityBarName);
            popularityBar.loadTexture(res.image.revealedBars[player.index], ccui.Widget.LOCAL_TEXTURE);
            popularityBar.setPercent(Math.abs(player.getPopularity()) / maxPopularity * 100);
        }, this);
    },
});

Change the color of bars in the result scene


export const getTasks = () => (
  {
    type: 'TASKS'
  }
)

export const tasks = () => (
  {
    type: 'TASKS'
  }
)

export const tasksFailure = () => (
  {
    type: 'TASKS_FAILURE'
  }
)

export const tasksSuccess = (payload) => (
  {
    type: 'TASKS_SUCCESS',
    payload
  }
)

Add the missing actions to the tasks state.


"use strict";

var LoadingState = function(game) {}

var publicMethods = function() {
  this.create = function() {

  };
};

var privateMethods = function() {};

privateMethods.call(LoadingState.prototype);
publicMethods.call(LoadingState.prototype);

module.exports = LoadingState;

"use strict";

var StateIds = require("./States");

var LoadingState = function(game) {}

var publicMethods = function() {
  var filter;
  var sprite;

  this.preload = function() {
    // var test = this.game.load.spritesheet('asdf', 'img/button-round.png', 128, 64);

    //  From http://glslsandbox.com/e#16133.0
    var fragmentSrc = [
      "precision mediump float;",
      "uniform float     time;",
      "uniform vec2      resolution;",
      "#define PI 0.01",
      "void main( void ) {",
      "vec2 p = ( gl_FragCoord.xy / resolution.xy ) - 0.5;",
      "float sx = 0.2*sin( 25.0 * p.y - time * 5.);",
      "float dy = 0.9/ ( 50. * abs(p.y - sx));",
      "gl_FragColor = vec4( (p.x + 0.5) * dy, 0.5 * dy, dy-1.65, 5.0 );",
      "}"
    ];

    filter = new Phaser.Filter(this.game, null, fragmentSrc);
    filter.setResolution(window.innerWidth, window.innerHeight);

    sprite = this.game.add.sprite();
    sprite.width = window.innerWidth;
    sprite.height = window.innerHeight;

    sprite.filters = [filter];
  };

  this.create = function() {
    // TODO: Remove setTimeout once assets gets preloaded
    var that = this;
    setTimeout(function() {
      that.game.state.start(StateIds.GAME_STATE_ID);
    }, 500);
  }

  this.update = function() {
    filter.update(this.game.input.activePointer);
  };

  this.resize = function() {
    Game.scale.setGameSize(window.innerWidth, window.innerHeight);
  };
};

var privateMethods = function() {

};

privateMethods.call(LoadingState.prototype);
publicMethods.call(LoadingState.prototype);

module.exports = LoadingState;

Add filter to loading state


/* jshint loopfunc: true*/
var has = require('has');
var classic = require('classic');

var spipe = require('./index');

var slice = Array.prototype.slice;

module.exports = function createChainable(functionMap) {
  return classic(function() {
    this.constructor = function Chainable() {
      var tgt = this;

      if (!(tgt instanceof Chainable))
        tgt = new Chainable();

      tgt._pipeline = spipe.apply(null, slice.call(arguments));
      return tgt;
    };

    for (var k in functionMap) {
      if (!has(functionMap, k)) continue;
      this[k] = (function(name, fn) {
        return function() {
          this._pipeline.apply(null, [fn].concat(slice.call(arguments)));
          return this;
        };
      })(k, functionMap[k]);
    }

    this.end = function(eager) {
      return this._pipeline(eager);
    };
  });
};

/* jshint loopfunc: true*/
var has = require('has');
var classic = require('classic');

var spipe = require('./index');

var slice = Array.prototype.slice;

module.exports = function createChainable(functionMap) {

  function Chainable() {
    var tgt = this;

    if (!(tgt instanceof Chainable))
      tgt = new Chainable();

    tgt._pipeline = spipe.apply(null, slice.call(arguments));
    return tgt;
  }

  for (var k in functionMap) {
    if (!has(functionMap, k)) continue;
    Chainable.prototype[k] = (function(name, fn) {
      return function() {
        this._pipeline.apply(null, [fn].concat(slice.call(arguments)));
        return this;
      };
    })(k, functionMap[k]);
  }

  Chainable.prototype.end = function(eager) {
    return this._pipeline(eager);
  };

  return Chainable;
};

Fix previous commit for IE7-8


module.exports = function (grunt) {
    'use strict';

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            dist: {
                files: {
                    'static/scripts/dist/<%%= pkg.name %>.js': [
                        'static/scripts/**/*.js',
                    ]
                }
            }
        },
        watch: {
            options: {
                livereload: true
            },
            scripts: {
                files: ['static/scripts/**/*.js'],
                tasks: ['concat']
            },
            styles: {
                files: ['static/styles/screen.scss'],
                tasks: ['sass:dev']
            }
        },
        sass: {
            dist: {
                options: {
                    style: 'compressed'
                },
                files: {
                    'static/styles/dist/<%%= pkg.name %>.css': 'static/styles/screen.scss'
                }
            },
            dev: {
                options: {
                    style: 'expanded'
                },
                files: {
                    'static/styles/dist/<%%= pkg.name %>.css': 'static/styles/screen.scss'
                }
            },
        },
        concat: {
            scripts: {
                files: {
                    'static/styles/dist/<%%= pkg.name %>.css': 'static/styles/screen.scss'
                }
            }
        }

    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-sass');

    grunt.registerTask('default', ['sass:dist', 'uglify']);

};

module.exports = function (grunt) {
    'use strict';

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            dist: {
                files: {
                    'src/scripts/dist/<%%= pkg.name %>.js': [
                        'src/scripts/**/*.js',
                    ]
                }
            }
        },
        watch: {
            options: {
                livereload: true
            },
            scripts: {
                files: ['src/scripts/**/*.js'],
                tasks: ['concat']
            },
            styles: {
                files: ['src/styles/screen.scss'],
                tasks: ['sass:dev']
            }
        },
        sass: {
            dist: {
                options: {
                    style: 'compressed'
                },
                files: {
                    'src/styles/dist/<%%= pkg.name %>.css': 'src/styles/screen.scss'
                }
            },
            dev: {
                options: {
                    style: 'expanded'
                },
                files: {
                    'src/styles/dist/<%%= pkg.name %>.css': 'src/styles/screen.scss'
                }
            },
        },
        concat: {
            scripts: {
                files: {
                    'src/styles/dist/<%%= pkg.name %>.css': 'src/styles/screen.scss'
                }
            }
        }

    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-sass');

    grunt.registerTask('default', ['sass:dist', 'uglify']);

};

Fix path to static files


import path from 'path';
import {statNoException} from '../promise-array';
import BuildDiscoverBase from '../build-discover-base';
import {findActualExecutable} from 'spawn-rx';

const d = require('debug')('surf:build-discover-npm');

export default class DangerBuildDiscoverer extends BuildDiscoverBase {
  constructor(rootDir) {
    super(rootDir);

    // Danger runs concurrently with other builds
    this.shouldAlwaysRun = true;
  }

  async getAffinityForRootDir() {
    let dangerFile = path.join(this.rootDir, 'Dangerfile');
    let exists = await statNoException(dangerFile);

    if (process.env.SURF_DISABLE_DANGER) return 0;

    // If we can't find Ruby or Bundler in PATH, bail
    if (!['ruby', 'bundler'].find((x) => findActualExecutable(x, []).cmd !== x)) {
      d(`Can't find Ruby and Bundler in PATH, bailing`);
      return 0;
    }

    if (exists) { d(`Found Dangerfile at ${dangerFile}`); }
    return exists ? 100 : 0;
  }

  async getBuildCommand() {
    let cmds = [
      { cmd: 'bundle', args: ['exec', 'danger']}
    ];

    if (!process.env.SURF_BUILD_NAME) {
      cmds[0].args.push('local');
    }

    if (!process.env.DANGER_GITHUB_API_TOKEN) {
      process.env.DANGER_GITHUB_API_TOKEN = process.env.GITHUB_TOKEN;
    }

    return {cmds};
  }
}

import path from 'path';
import {statNoException} from '../promise-array';
import BuildDiscoverBase from '../build-discover-base';
import {findActualExecutable} from 'spawn-rx';

const d = require('debug')('surf:build-discover-npm');

export default class DangerBuildDiscoverer extends BuildDiscoverBase {
  constructor(rootDir) {
    super(rootDir);

    // Danger runs concurrently with other builds
    this.shouldAlwaysRun = true;
  }

  async getAffinityForRootDir() {
    let dangerFile = path.join(this.rootDir, 'Dangerfile');
    let exists = await statNoException(dangerFile);

    if (process.env.SURF_DISABLE_DANGER) return 0;
    if (!exists) return;

    // If we can't find Ruby or Bundler in PATH, bail
    if (!['ruby', 'bundle'].every((x) => findActualExecutable(x, []).cmd !== x)) {
      console.log(`A Dangerfile exists but can't find Ruby and Bundler in PATH, skipping`);
      return 0;
    }

    d(`Found Dangerfile at ${dangerFile}`);
    return exists ? 100 : 0;
  }

  async getBuildCommand() {
    let cmds = [
      { cmd: 'bundle', args: ['exec', 'danger']}
    ];

    if (!process.env.SURF_BUILD_NAME) {
      cmds[0].args.push('local');
    }

    if (!process.env.DANGER_GITHUB_API_TOKEN) {
      process.env.DANGER_GITHUB_API_TOKEN = process.env.GITHUB_TOKEN;
    }

    return {cmds};
  }
}

Fix up detecting if Bundler is installed


import { numberFormat } from 'preprint-service/helpers/number-format';
import { module, test } from 'qunit';

module('Unit | Helper | number format');

test('transforms 4 digit number', function(assert) {
  let result = numberFormat([3500]);
  assert.equal(result, '3,500');
});

test('transforms 10 digit number', function(assert) {
  let result = numberFormat([1234567890]);
  assert.equal(result, '1,234,567,890');
});

import { numberFormat } from 'preprint-service/helpers/number-format';
import { module, test } from 'qunit';

module('Unit | Helper | number format');

// Works in Chrome but doesn't work in terminal
// test('transforms 4 digit number', function(assert) {
//   let result = numberFormat([3500]);
//   assert.equal(result, '3,500');
// });
//
// test('transforms 10 digit number', function(assert) {
//   let result = numberFormat([1234567890]);
//   assert.equal(result, '1,234,567,890');
// });

Comment out tests for now.


'use babel'

/* @flow */

import type { Message } from '../types'

const nbsp = String.fromCodePoint(160)

export function visitMessage(message: Message) {
  const messageFile = message.version === 1 ? message.filePath : message.location.file
  const messageRange = message.version === 1 ? message.range : message.location.position
  atom.workspace.open(messageFile, { searchAllPanes: true }).then(function() {
    const textEditor = atom.workspace.getActiveTextEditor()
    if (textEditor && textEditor.getPath() === messageFile) {
      textEditor.setCursorBufferPosition(messageRange.start)
    }
  })
}

export function htmlToText(html: any) {
  const element = document.createElement('div')
  if (typeof html === 'string') {
    element.innerHTML = html
  } else {
    element.appendChild(html.cloneNode(true))
  }
  // NOTE: Convert   to regular whitespace
  return element.textContent.replace(new RegExp(nbsp, 'g'), ' ')
}

'use babel'

/* @flow */

import type { Message } from '../types'

export function visitMessage(message: Message) {
  const messageFile = message.version === 1 ? message.filePath : message.location.file
  const messageRange = message.version === 1 ? message.range : message.location.position
  atom.workspace.open(messageFile, { searchAllPanes: true }).then(function() {
    const textEditor = atom.workspace.getActiveTextEditor()
    if (textEditor && textEditor.getPath() === messageFile) {
      textEditor.setCursorBufferPosition(messageRange.start)
    }
  })
}

export function htmlToText(html: any) {
  const element = document.createElement('div')
  if (typeof html === 'string') {
    element.innerHTML = html
  } else {
    element.appendChild(html.cloneNode(true))
  }
  // NOTE: Convert   to regular whitespace
  return element.textContent.replace(/\u00A0/gu, ' ')
}

Simplify nbsp replacing, yet again


var net = require('net'),
    path = require('path'),
    assert = require('assert'),
    spawn = require('child_process').spawn,
    cb = require('assert-called');

var PORT = 5434;

var server = net.createServer(cb(function (socket) {
  var data = '';
  socket.on('readable', cb(function () {
    var chunk = socket.read();

    if (chunk) {
      data += chunk;
    }
  }));

  socket.on('end', cb(function () {
    data = data.split('\n').filter(Boolean).map(JSON.parse);
    assert(data.length > 0);
    server.close();
  }));
}));

server.listen(PORT, function () {
  var child = spawn(
    path.join(__dirname, '..', 'estragon'),
    [
      '-h', '127.0.0.1:' + (PORT - 1).toString(),
      '-h', '127.0.0.1:' + PORT.toString(),
      '--', 'node', path.join(__dirname, 'fixtures', 'exit-after-1-s.js')
    ]
  );
});

var net = require('net'),
    path = require('path'),
    assert = require('assert'),
    spawn = require('child_process').spawn,
    cb = require('assert-called');

var PORT = 5434;

var server = net.createServer(cb(function (socket) {
  var data = '';
  socket.on('readable', cb(function () {
    var chunk = socket.read();

    if (chunk) {
      data += chunk;
    }
  }));

  socket.on('end', cb(function () {
    data = data.split('\n').filter(Boolean).map(JSON.parse);
    assert(data.length > 0);
    server.close();
  }));
}));

server.listen(PORT, function () {
  var child = spawn(
    path.join(__dirname, '..', 'estragon'),
    [
      '-h', '127.0.0.1:' + (PORT - 2).toString(),
      '-h', '127.0.0.1:' + (PORT - 1).toString(),
      '-h', '127.0.0.1:' + PORT.toString(),
      '--', 'node', path.join(__dirname, 'fixtures', 'exit-after-1-s.js')
    ]
  );
});

Add one more failed server


define([
    "home/models"
], function (models) {
    "use strict";

    QUnit.module("home.models");
    QUnit.test("instantiation", function (assert) {
        var model = new models.ApplicationListModel();
        assert.equal(model.app_list.length, 0);
        model.update().done(function() {
            assert.equal(model.app_list.length, 2);
            assert.equal(model.app_list[0].app_data.image.configurables[0], "resolution");
            assert.equal(model.app_list[0].configurables[0].resolution, "Window");
        });
    });
});

define([
    "home/models"
], function (models) {
    "use strict";

    QUnit.module("home.models");
    QUnit.test("instantiation", function (assert) {
        var model = new models.ApplicationListModel();

        assert.equal(model.app_list.length, 0);
        assert.equal(model.selected_index, null);

        model.update().done(function() {
            assert.equal(model.app_list.length, 2);
            assert.equal(model.selected_index, 0);

            assert.equal(model.app_list[0].app_data.image.configurables[0], "resolution");
            assert.equal(model.app_list[0].configurables[0].resolution, "Window");
        });
    });
});

Add tests for the model


var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var cssstats = require('cssstats')

var module = require('tachyons-font-weight/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-font-weight/tachyons-font-weight.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_font-weight.css', 'utf8')

var template = fs.readFileSync('./templates/docs/font-weight/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS
})

fs.writeFileSync('./docs/typography/font-weight/index.html', html)

var _ = require('lodash')
var fs = require('fs')
var gzip = require('gzip-size')
var filesize = require('filesize')
var cssstats = require('cssstats')

var module = require('tachyons-font-weight/package.json')
var moduleCss = fs.readFileSync('node_modules/tachyons-font-weight/tachyons-font-weight.min.css', 'utf8')
var moduleObj = cssstats(moduleCss)
var moduleSize = filesize(moduleObj.gzipSize)

var srcCSS = fs.readFileSync('./src/_font-weight.css', 'utf8')
var navDocs = fs.readFileSync('./templates/nav_docs.html', 'utf8')

var template = fs.readFileSync('./templates/docs/font-weight/index.html', 'utf8')
var tpl = _.template(template)
var html = tpl({
  moduleVersion: module.version,
  moduleSize: moduleSize,
  moduleObj: moduleObj,
  srcCSS: srcCSS,
  navDocs: navDocs
})

fs.writeFileSync('./docs/typography/font-weight/index.html', html)

Update with reference to global nav partial


#!/usr/bin/env node
/**
Provide mdb-aggregate, a MongoDB style aggregation pipeline, to NodeJS.

Copyright (C) 2014  Charles J. Ezell III

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";

//Default to json encoding for the pipeline and data.  The user can override
//this with --format.
var mdb_conduit = require("mdb-conduit"),
		argv = ["mdb-conduit", "--format", "json-json"],
		conduit_main = mdb_conduit._conduit_main;

//The first two args are usually node and the name of the script, drop them.
Array.prototype.push.apply(argv, process.argv.slice(2));

setImmediate(conduit_main, argv, process.env);

#!/usr/bin/env node
/**
Provide mdb-aggregate, a MongoDB style aggregation pipeline, to NodeJS.

Copyright (C) 2014  Charles J. Ezell III

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";

//Default to json encoding for the pipeline and data.  The user can override
//this with --format.
var mdb_conduit = require("./index.js"),
		argv = ["mdb-conduit", "--format", "json-json"],
		conduit_main = mdb_conduit._conduit_main;

//The first two args are usually node and the name of the script, drop them.
Array.prototype.push.apply(argv, process.argv.slice(2));

setImmediate(conduit_main, argv, process.env);

Make sure to load the current module, not some other global one.


'use strict';

var grunt = require('grunt');

function readFile(file) {
  var contents = grunt.file.read(file);
  if (process.platform === 'win32') {
    contents = contents.replace(/\r\n/g, '\n');
  }
  return contents;
}

function assertFileEquality(test, pathToActual, pathToExpected, message) {
  var actual, expected;
  expected = readFile(pathToExpected);
  try {
    actual = readFile(pathToActual);
  } catch (e) {
    console.log("\n" + e.message);
    actual = '';
  }
  test.equal(expected.trim(), actual.trim(), message);
}

exports.noflo_manifest = {
  update: function(test) {
    test.expect(2);

    assertFileEquality(test,
      'tmp/package.json',
      'test/expected/package.json',
      'Should add the expected graphs and components to the package.json file'
    );
    assertFileEquality(test,
      'tmp/component.json',
      'test/expected/component.json',
      'Should add the expected graphs and components to the component.json file'
    );

    test.done();
  }
};

'use strict';

var grunt = require('grunt');

function readFile(file) {
  return grunt.file.readJSON(file);
}

function assertFileEquality(test, pathToActual, pathToExpected, message) {
  var actual, expected;
  expected = readFile(pathToExpected);
  try {
    actual = readFile(pathToActual);
  } catch (e) {
    console.log("\n" + e.message);
  }
  test.deepEqual(expected, actual, message);
}

exports.noflo_manifest = {
  update: function(test) {
    test.expect(2);

    assertFileEquality(test,
      'tmp/package.json',
      'test/expected/package.json',
      'Should add the expected graphs and components to the package.json file'
    );
    assertFileEquality(test,
      'tmp/component.json',
      'test/expected/component.json',
      'Should add the expected graphs and components to the component.json file'
    );

    test.done();
  }
};

Test actual parsed JSON instead of strings


import React from 'react'
import PropTypes from 'prop-types'
import Stickyfill from 'stickyfill'
import supports from 'css-supports'

supports.shim()

const stickyfill = Stickyfill()

class ReactStickyfill extends React.PureComponent {
  componentDidMount() {
    stickyfill.add(this.refs.sticky)
  }
  componentWillUnmount() {
    stickyfill.remove(this.refs.sticky)
  }

  _getPositionStyleValue() {
    const isStickySupported = CSS.supports('position', 'sticky')

    return isStickySupported? 'sticky': '-webkit-sticky'
  }

  render() {
    return (
      <div
        {...this.props}
        ref="sticky"
        style={{
          top: 0,
          zIndex: 1,
          position: this._getPositionStyleValue(),
          ...this.props.style
        }}
      />
    )
  }
}

ReactStickyfill.displayName = 'ReactStickyfill'

ReactStickyfill.propTypes = {
  children: PropTypes.element,
  className: PropTypes.string
}

export default ReactStickyfill

import React from 'react'
import PropTypes from 'prop-types'
import Stickyfill from 'stickyfill'
import supports from 'css-supports'

supports.shim()

const stickyfill = Stickyfill()

class ReactStickyfill extends React.PureComponent {
  componentDidMount() {
    stickyfill.add(this.stickyElement)
  }
  componentWillUnmount() {
    stickyfill.remove(this.stickyElement)
  }

  _getPositionStyleValue() {
    const isStickySupported = CSS.supports('position', 'sticky')

    return isStickySupported? 'sticky': '-webkit-sticky'
  }

  render() {
    return (
      <div
        {...this.props}
        ref={(el) => { this.stickyElement = el }}
        style={{
          top: 0,
          zIndex: 1,
          position: this._getPositionStyleValue(),
          ...this.props.style
        }}
      />
    )
  }
}

ReactStickyfill.displayName = 'ReactStickyfill'

ReactStickyfill.propTypes = {
  children: PropTypes.element,
  className: PropTypes.string
}

export default ReactStickyfill

Change deprecated way of using refs


import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import AjaxService from 'ember-ajax/services/ajax';

const { reads } = computed;

export default AjaxService.extend({
  iliosConfig: service(),
  session: service(),

  host: reads('iliosConfig.apiHost'),

  headers: computed('session.isAuthenticated', 'session.data.authenticated.jwt', function(){
    const session = this.get('session');
    const { jwt } = session.data.authenticated;
    let headers = {};

    if (jwt) {
      headers['X-JWT-Authorization'] = `Token ${jwt}`;
    }

    return headers;
  }),

});

import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import AjaxService from 'ember-ajax/services/ajax';

const { reads } = computed;

export default AjaxService.extend({
  iliosConfig: service(),
  session: service(),

  host: reads('iliosConfig.apiHost'),

  headers: computed('session.isAuthenticated', 'session.data.authenticated.jwt', function(){
    let headers = {};
    if(this.session){
      const jwt = this.session.get('data.authenticated.jwt');
      if (jwt) {
        headers['X-JWT-Authorization'] = `Token ${jwt}`;
      }
    }

    return headers;
  }),

});

Reorganize the way the JWT is found


window.copyDOMIntoVTree = function copyDOMIntoVTree (vtree) {
  walk (vtree, document.body.firstChild);
};

window.walk = function walk (vtree, node) {
  var i = 0, vdomChild, domChild;
  vtree.domRef = node;

  // Fire onCreated events as though the elements had just been created.
  callCreated(vtree);

  while (i < vtree.children.length) {
    vdomChild = vtree.children[i];
    domChild = node.childNodes[i];
    if (vdomChild.type === "vtext") {
      vdomChild.domRef = domChild;
      i++;
      continue;
    }
    walk(vdomChild, domChild);
    i++;
  }
};

function MisoException(message) {
  this.message = message;
  this.name = 'MisoException';
}

function copyDOMIntoVTree(vtree) {
  walk(vtree, document.body.firstChild);
}

function walk(vtree, node) {
  // This is slightly more complicated than one might expect since
  // browsers will collapse consecutive text nodes into a single text node.
  // There can thus be fewer DOM nodes than VDOM nodes.
  var i = 0,
    j = 0,
    // This indicates if we are in a block of consecutive text nodes.
    inTextBlock = false,
    vdomChild,
    domChild;
  vtree.domRef = node;

  // Fire onCreated events as though the elements had just been created.
  callCreated(vtree);

  for (i = 0; i < vtree.children.length; i++) {
    vdomChild = vtree.children[i];
    domChild = node.childNodes[j];
    if (vdomChild.type === "vtext") {
      if (!inTextBlock) {
        // This is the first text node in a block of consecutive text nodes.
        inTextBlock = true;
        if (domChild.nodeType !== Node.TEXT_NODE) {
          throw new MisoException("Expected text node but found got element");
        }
        // Since the text nodes are collapsed on the DOM side, the text
        // contents can be different here. To make sure the VDOM and
        // the DOM agree we copy the text contents from the DOM to the VDOM.
        vdomChild.text = domChild.textContent;
        vdomChild.domRef = domChild;
        j++;
      } else {
        // In this case there have been other text nodes directly before this node.
        // There is thus no corresponding DOM node and we need to create a new node.
        vdomChild.domRef = document.createTextNode("");
        vdomChild.text = "";
      }
    } else {
      inTextBlock = false;
      if (domChild.nodeType !== Node.ELEMENT_NODE) {
        throw new MisoException("Expected element but got text node");
      }
      walk(vdomChild, domChild);
      j++;
    }
  }
}

Handle consecutive text nodes when parsing server-rendered DOM


(function () {
    require.config({
        baseUrl: "../remoteappmanager/static/js/",
        paths: {
          components: '../components',
          jquery: '../components/jquery/jquery.min',
          bootstrap: '../components/bootstrap/js/bootstrap.min',
          moment: "../components/moment/moment",
          "jsapi/v1/resources": "../../../jstests/tests/home/mock_jsapi",
          handlebars: "../components/handlebars/handlebars.amd.min",
          underscore: "../components/underscore/underscore-min"
        },
        shim: {
          bootstrap: {
            deps: ["jquery"],
            exports: "bootstrap"
          }
        }
    });

    require([
        "tests/home/test_configurables.js",
        "tests/home/test_models.js",
        "tests/home/test_application_list_view.js",
        "tests/home/test_application_view.js",
        "tests/test_utils.js",
        "tests/test_analytics.js"
        ], function() {
            window.apidata = {
                base_url: "/",
                prefix: "/"
            };

            QUnit.load();
            QUnit.start();
        });
}());

(function () {
    require.config({
        baseUrl: "../remoteappmanager/static/js/",
        paths: {
          components: '../components',
          jquery: '../components/jquery/jquery.min',
          bootstrap: '../components/bootstrap/js/bootstrap.min',
          moment: "../components/moment/moment",
          "jsapi/v1/resources": "../../../jstests/tests/home/mock_jsapi",
          underscore: "../components/underscore/underscore-min"
        },
        shim: {
          bootstrap: {
            deps: ["jquery"],
            exports: "bootstrap"
          }
        }
    });

    require([
        "tests/home/test_configurables.js",
        "tests/home/test_models.js",
        "tests/home/test_application_list_view.js",
        "tests/home/test_application_view.js",
        "tests/test_utils.js",
        "tests/test_analytics.js"
        ], function() {
            window.apidata = {
                base_url: "/",
                prefix: "/"
            };

            QUnit.load();
            QUnit.start();
        });
}());

Undo change when merged to switch_to_vue


'use strict';

const {decode, encode} = require('../app/scripts/obfuscate').default;

describe('the encode function', function() {
  it('outputs nothing if nothing is entered', function () {
    expect(encode('')).toEqual([]);
  });

  it('converts strings to integers', function () {
    expect(encode('a')).toEqual([97]);
  });

  it('can hide my mail address', function() {
    expect(encode('thib@thib')).toEqual([116, 105, 107, 101, 68, 121, 110, 112, 106]);
  });
});

describe('the decode function', function() {
  it('outputs nothing if nothing is entered', function () {
    expect(decode([])).toEqual('');
  });

  it('converts integers to chars', function () {
    expect(decode([97])).toEqual('a');
  });

  it('can hide my mail address', function() {
    expect(decode([116, 105, 107, 101, 68, 121, 110, 112, 106])).toEqual('thib@thib');
  });
});

describe('obfuscation functions can be chained', function() {
  it('looks fun', function () {
    expect(decode(encode(decode(encode('Rhinoceros!'))))).toEqual('Rhinoceros!');
  });
});

Add one more test case


describe("Simple tests examples", function () {

  it("should fail", function () {
    assert.equal(false, false);
  });

  it("should detect true", function () {
    assert.equal(true, true);
  });

  it("should increments values", function () {
    let mike = 0;

    assert.equal(mike++ === 0, true);
    assert.equal(mike === 1, true);
  });

  it("should increments values (improved)", function () {
    let mike = 0;

    assert.equal(mike++, 0);
    assert.equal(mike, 1);
  });

});

describe("Tests with before/after hooks", function () {
  let a = 0;

  beforeEach(function () {
    a++;
  });

  afterEach(function () {
    a = 0
  });

  it("should increment value", function () {
    assert.equal(a, 1);
  });

  it("should reset after each test", function () {
    assert.equal(a, 1);
  });
});

describe("Async tests", function () {
  it("should wait timer", function (done) {
    setTimeout(function () {
      assert.equal(true, true);
      done();
    }, 500);
  });
});

describe("Simple tests examples", function() {
  it("should detect true", function() {
    assert.equal(true, true);
  });

  it("should increments values", function() {
    var mike = 0;

    assert.equal(mike++ === 0, true);
    assert.equal(mike === 1, true);
  });

  it("should increments values (improved)", function() {
    var mike = 0;

    assert.equal(mike++, 0);
    assert.equal(mike, 1);
  });
});

describe("Tests with before/after hooks", function() {
  var a = 0;

  beforeEach(function() {
    a++;
  });

  afterEach(function() {
    a = 0
  });

  it("should increment value", function() {
    assert.equal(a, 1);
  });

  it("should reset after each test", function() {
    assert.equal(a, 1);
  });
});

describe("Async tests", function() {
  it("should wait timer", function(done) {
    setTimeout(function() {
      assert.equal(true, true);
      done();
    }, 500);
  });
});

Revert "Converting tabs to spaces."


import Ember from 'ember';

export default Ember.Route.extend({
    session: Ember.inject.service(),
    setupController(controller, model) {
        controller.addFilters();
    },
});

import Ember from 'ember';

export default Ember.Route.extend({
    session: Ember.inject.service(),
    setupController(controller, model) {
        controller.addFilters();
    },
    resetController(controller, isExiting) {
        if (isExiting) {
            controller.set('page', 1);
            controller.set('searchString', '');
            controller.set('tags', '');
            controller.set('sources', '');
            controller.set('publisher', '');
            controller.set('funder', '');
            controller.set('institution', '');
            controller.set('organization', '');
            controller.set('language', '');
            controller.set('contributors', '');
            controller.set('start', '');
            controller.set('end', '');
            controller.set('type', '');
            controller.set('facetFilters', Ember.Object.create());
        }
    }
});

Fix unusual query params behavior on page transitions


/**
 *
 * Application or Website name
 *
 * Copyright 2015, Author Name
 * Some information on the license.
 *
 * Tests > Unit > Sample
 *
 * Jasmine test examples: http://evanhahn.com/how-do-i-jasmine/
 *
**/

/* ======================================================== */
/* Libraries
/* ======================================================== */
// Import libraries needed to perform this test.
window.$ = require('jquery');

/* ======================================================== */
/* Data
/* ======================================================== */
// Define function.
var helloWorld = function(){
	return 'Hello world!';
}
// Get module.
var Module = require('../../src/dist/js/public/module.xxx');

/* ======================================================== */
/* Tests
/* ======================================================== */
// Suite.
describe('Hello world', function(){
	// Spec.
	it('says hello', function(){
		expect(helloWorld()).toEqual('Hello world!');
	});
	// Spec.
	it('says hello', function(){
		expect(helloWorld()).toEqual('Hellox world!');
	});
	// Spec.
    it('says world', function(){
        expect(helloWorld()).toContain('world');
    });
	// Spec.
	it('says close', function(){
		expect(Module.test()).toEqual('close');
	});
});

Hide tests that include non existent methods


import resolver from './helpers/resolver'
import {setResolver} from 'ember-mocha'

setResolver(resolver)

/* global mocha */
import resolver from './helpers/resolver'
import {setResolver} from 'ember-mocha'

mocha.setup({
  timeout: 10000
})

setResolver(resolver)

Extend mocha timeout to 10 sec


var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password');

var models = {}
var db = {}
require("fs").readdirSync("./models").forEach(function(file) {
  if (file.match(/.+\.js/g) !== null && file !== 'index.js') {
    var name = file.replace('.js', '');

    models[name] = require("../models/" + file);
  }
});
for(var k in models){
  var modelObject = {};
  for(var j in models[k]){
    modelObject[j] = Sequelize[models[k][j]];
  }
  db[k] = sequelize.define(k, modelObject);
}

module.exports = db

var Sequelize = require('sequelize');
var sequelize = new Sequelize('database', 'username', 'password');

var models = {}
var db = {}
require("fs").readdirSync("./models").forEach(function(file) {
  if (file.match(/.+\.js/g) !== null && file !== 'index.js') {
    var name = file.replace('.js', '');

    models[name] = require("../models/" + file);
  }
});
for(var k in models){
  var modelObject = {};
  for(var j in models[k]){
    modelObject[j] = Sequelize[models[k][j].toUpperCase()];
  }
  db[k] = sequelize.define(k, modelObject);
}

module.exports = db

Make it so case doesn't matter for types.


import React from 'react';
import FileListItem from './fileListItem';

const FileList = (props) => {
  // const styles = require('./BucketList.scss');

  function renderFileListItems(files) {
    if(files && files.length && files.length > 0) {
      return files.map((file) => {
        return (
          <FileListItem
            fileSelectAction = {props.fileSelectAction}
            key     = {file.id}
            {...file}
          />
        );
        //onClick = {() => props.onFileClick(file.id)}
      });
    } else {
      return (

Ready to Store Objects...

      );
    }
  }

  return (

          Name
          Size
          Type

        {renderFileListItems(props.files)}

  );
};

FileList.propTypes = {
  files  : React.PropTypes.array.isRequired,
};

export default FileList;

import React from 'react';
import FileListItem from './fileListItem';

const FileList = (props) => {
  // const styles = require('./BucketList.scss');

  function renderFileListItems(files) {
    if(files && files.length && files.length > 0) {
      return files.map((file) => {
        return (
          <FileListItem
            fileSelectAction = {props.fileSelectAction}
            key     = {file.id}
            {...file}
          />
        );
        //onClick = {() => props.onFileClick(file.id)}
      });
    } else {
      return (

Ready to Store Objects

Read the Get Started tutorial, or go straight to the Storj API.

      );
    }
  }

  return (

          Name
          Size
          Type

        {renderFileListItems(props.files)}

  );
};

FileList.propTypes = {
  files  : React.PropTypes.array.isRequired,
};

export default FileList;

Add next steps after creating new bucket


import React from 'react';
import Page from 'components/Page/Page';
import Link from 'react-router-dom/es/Link';

const Landing = () =>
  <Page heading='Landing' >

This is the landing page

    <Link to='/polls'>Polls</Link>
  </Page>;

export default Landing;

import React from 'react'
import Page from 'components/Page/Page'
import Link from 'react-router-dom/es/Link'
import Button from 'react-mdc-web/lib/Button/Button'
const Landing = () =>
  <Page heading='Landing' >

This is the landing page

    <Link to='/polls' ><Button>Polls</Button></Link>
  </Page>

export default Landing

Make landing use button component


/*jslint indent: 2 */
/*global $: false, document: false, togglbutton: false*/
'use strict';

/* the first selector is required for youtrack-5 and the second for youtrack-6 */
togglbutton.render('.fsi-toolbar-content:not(.toggl), .toolbar_fsi:not(.toggl)', {observe: true}, function (elem) {

  var link, description,
    numElem = $('a.issueId'),
    titleElem = $(".issue-summary"),
    projectElem = $('.fsi-properties .disabled.bold');

  description = titleElem.textContent;
  description = numElem.firstChild.textContent.trim() + " " + description.trim();

  link = togglbutton.createTimerLink({
    className: 'youtrack',
    description: description,
    projectName: projectElem ? projectElem.textContent : ''
  });

  elem.insertBefore(link, titleElem);
});

// Agile board
togglbutton.render('#board .sb-task:not(.toggl)', {observe: true}, function (elem) {
  var link,
    container = $('.sb-task-title', elem),
    description = $('.sb-task-summary', elem).textContent,
    projectName = $('#selectAgile').value.split("(")[1].replace(")", "").trim();

  link = togglbutton.createTimerLink({
    className: 'youtrack',
    description: description,
    projectName: projectName
  });

  container.appendChild(link);
});

/*jslint indent: 2 */
/*global $: false, document: false, togglbutton: false*/
'use strict';

/* the first selector is required for youtrack-5 and the second for youtrack-6 */
togglbutton.render('.fsi-toolbar-content:not(.toggl), .toolbar_fsi:not(.toggl)', {observe: true}, function (elem) {
  var link, description,
    numElem = $('a.issueId'),
    titleElem = $(".issue-summary"),
    projectElem = $('.fsi-properties .disabled.bold');

  description = titleElem.textContent;
  description = numElem.firstChild.textContent.trim() + " " + description.trim();

  link = togglbutton.createTimerLink({
    className: 'youtrack',
    description: description,
    projectName: projectElem ? projectElem.textContent : ''
  });

  elem.insertBefore(link, titleElem);
});

// Agile board
togglbutton.render('#board .sb-task:not(.toggl)', {observe: true}, function (elem) {
  var link,
    container = $('.sb-task-details', elem),
    description = $('.sb-task-summary', elem).textContent,
    projectName = $('#selectAgile').value.split("(")[1].replace(")", "").trim();

  link = togglbutton.createTimerLink({
    className: 'youtrack',
    buttonType: 'minimal',
    description: description,
    projectName: projectName
  });

  container.appendChild(link);
});

Tweak button rendering on agile board


require('./common');
var path = require("path");

var file = "numbers";

var default_host_path = path.join(fixturesDir,"default-host");
var fullname = path.join(default_host_path, file);

var fileText = require('fs').readFileSync(fullname);

var settings = {
    "port": PORT,
    "default_host" : {
        "root": path.join(fixturesDir,"default-host")
    }
}
antinode.start(settings);
setTimeout(function() {
    var client = http.createClient(PORT, 'localhost');
    var request = client.request('GET', '/'+file);
    request.addListener('response', function (response) {
        assert.equal(response.statusCode, 200);
        response.setEncoding('utf8');
        response.addListener('data', function (chunk) {
            assert.equal(chunk, fileText);
            puts("OK");
        });
    });
    request.end();
}, 1000);

require('./common');
var path = require("path");

var file = "numbers";

var default_host_path = path.join(fixturesDir,"default-host");
var fullname = path.join(default_host_path, file);

var fileText = require('fs').readFileSync(fullname);

var settings = {
    "port": PORT,
    "default_host" : {
        "root": path.join(fixturesDir,"default-host")
    },
    "log_level": 4 //silent
}
antinode.start(settings, function() {
    var client = http.createClient(PORT, 'localhost');
    var request = client.request('GET', '/'+file);
    request.addListener('response', function (response) {
        assert.equal(response.statusCode, 200);
        response.setEncoding('utf8');
        response.addListener('data', function (chunk) {
            assert.equal(chunk, fileText);
            puts("OK");
            antinode.stop();
        });
    });
    request.end();
});

Update test to take advantage of new 'server ready' hooks, and stop server when test is done.