var Fs = require("fs")
  , ArcAssembler = require("./lib")
  ;

const OUTPUT_FILE = __dirname + "/out"
    , INPUT_FILE = __dirname + "/Test.asm"
    ;

var outputStream = Fs.createWriteStream(OUTPUT_FILE);

Fs.readFile(INPUT_FILE, "utf-8", function (err, lines) {
    var result = ArcAssembler.compile(lines);

    result.raw.forEach(function (c) {
        console.log(c.code.match(/.{1,4}/g).join(" ") + " << Line " + c.line);
    });

    console.log("---- Full machine code ----");
    for (var i = 0; i < result.mCode.length; i += 32) {
        console.log(result.mCode.slice(i, i + 32).join("").match(/.{1,4}/g).join(" "));
    }

    outputStream.write("#!/usr/bin/env arc-int");
    outputStream.write(new Buffer(result.mCode));
    outputStream.end();
    Fs.chmodSync(OUTPUT_FILE, 0755);
});

// Dependencies
var Fs = require("fs")
  , ArcAssembler = require("../lib")
  , Argv = require("yargs")
     .alias("s", "source")
     .alias("o", "output")
     .argv
  , Path = require("path")
  ;

// Constants
const INPUT_FILE = Path.resolve(process.cwd() + Argv.source)
    , OUTPUT_FILE = Path.resolve(process.cwd() + Argv.output)
    ;

// Create the write stream
var outputStream = Fs.createWriteStream(OUTPUT_FILE);

// Read the input file content
Fs.readFile(INPUT_FILE, "utf-8", function (err, content) {
    if (err) throw err;

    // Compile the input
    var result = ArcAssembler.compile(content);

    // Show some output
    result.raw.forEach(function (c) {
        console.log(c.code.match(/.{1,4}/g).join(" ") + " << Line " + c.line);
    });

    // Write things in the output stream
    outputStream.write("#!/usr/bin/env arc-int");
    outputStream.write(new Buffer(result.mCode));
    outputStream.end();

    // Make the file executable
    Fs.chmodSync(OUTPUT_FILE, 0755);
});

Use yargs to parse the command line args


import fetch from 'fetch';
import Route from '@ember/routing/route';
import ResetScrollPositionMixin from 'frontend/mixins/reset-scroll-position';

export default Route.extend(ResetScrollPositionMixin, {
  model() {
    const host = this.store.adapterFor('summarized-statistic').get('host');
    return fetch(`${host}/chart_data/similarities.json`).then(function(response) {
        return response.json();
      });
    }
  }
);

Add fetch polyfill for IE


export default function lazyLoader (images: NodeList<HTMLElement>): void {
  const callback = function(entries, observer) {
    for (let entry of entries) {
      if (entry.isIntersecting && !entry.target.src) {
        let target = entry.target
        target.setAttribute('src', target.getAttribute('data-src'))
      }
    }
  }
  const options: IntersectionObserverInit = {
    root: null,
    rootMargin: '200px'
  }
  const observer: IntersectionObserver = new IntersectionObserver(callback, options)

  images.forEach(el => observer.observe(el))
}

export default function lazyLoader (images: NodeList<HTMLElement>): void {
  const callback = function(entries, observer) {
    for (let entry of entries) {
      if (entry.isIntersecting && !entry.target.src) {
        let target = entry.target
        target.setAttribute('src', target.getAttribute('data-src'))
        target.addEventListener('load', () => {
          target.removeAttribute('style')
        })
      }
    }
  }
  const options: IntersectionObserverInit = {
    root: null,
    rootMargin: '200px'
  }
  const observer: IntersectionObserver = new IntersectionObserver(callback, options)

  images.forEach(el => observer.observe(el))
}

Remove style after image loaded


var express = require('express');
var app     = express();
var http    = require('http');
var mongoose= require('mongoose');

app.set('port', process.env.PORT || 3000);
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);

if ('development' === app.get('env')){
  app.use(express.errorHandler());
}
else if('test' === app.get('env')){
  app.use(express.errorHandler());
  database = 'test';
}

require('./routes');

http.createServer(app).listen(app.get('port'), function(){
  console.log('Listening on ' + app.get('port'));
});

exports.app = app;

var express = require('express');
var app     = express();
var http    = require('http');
var mongoose= require('mongoose');

app.set('port', process.env.PORT || 3000);
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);

if ('development' === app.get('env')){
  app.use(express.errorHandler());
}
else if('test' === app.get('env')){
  app.use(express.errorHandler());
  database = 'test';
}

require('./models/task');
require('./routes');

http.createServer(app).listen(app.get('port'), function(){
  console.log('Listening on ' + app.get('port'));
});

exports.app = app;

Fix previous error. Add require('./models/task')


/* See license.txt for terms of usage */

"use strict";

const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
const { loadSheet, removeSheet } = require("sdk/stylesheet/utils");
const { Class } = require("sdk/core/heritage");
const { BaseOverlay } = require("../chrome/base-overlay.js");

/**
 * @overlay This object represents an overlay that is responsible
 * for customizing the 'Performance' panel.
 */
const PerformanceOverlay = Class(
/** @lends PerformanceOverlay */
{
  extends: BaseOverlay,

  overlayId: "performance",

  // Initialization

  initialize: function(options) {
    BaseOverlay.prototype.initialize.apply(this, arguments);
  },

  onReady: function(options) {
    BaseOverlay.prototype.onReady.apply(this, arguments);
  },

  destroy: function() {
  },

  onApplyTheme: function(win, oldTheme) {
    loadSheet(win, "chrome://firebug/skin/performance.css", "author");
  },

  onUnapplyTheme: function(win, newTheme) {
    removeSheet(win, "chrome://firebug/skin/performance.css", "author");
  },

  // Options

  /**
   * The performance panel uses the original popup menu already
   * populated with all options since its XUL structure is
   * wired with the JS logic. See: devtools/client/performance/performance.xul
   */
  getOptionsMenuPopup: function() {
    let doc = this.getPanelDocument();
    return doc.getElementById("performance-options-menupopup");
  },
});

// Exports from this module
exports.PerformanceOverlay = PerformanceOverlay;

/* See license.txt for terms of usage */

"use strict";

// Add-on SDK
const { loadSheet, removeSheet } = require("sdk/stylesheet/utils");
const { Class } = require("sdk/core/heritage");

// Firebug.SDK
const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
const { PanelOverlay } = require("firebug.sdk/lib/panel-overlay.js");

/**
 * @overlay This object represents an overlay that is responsible
 * for customizing the 'Performance' panel.
 */
const PerformanceOverlay = Class(
/** @lends PerformanceOverlay */
{
  extends: PanelOverlay,

  overlayId: "performance",

  onApplyTheme: function(win, oldTheme) {
    loadSheet(win, "chrome://firebug/skin/performance.css", "author");
  },

  onUnapplyTheme: function(win, newTheme) {
    removeSheet(win, "chrome://firebug/skin/performance.css", "author");
  },

  // Options

  /**
   * The performance panel uses the original popup menu already
   * populated with all options since its XUL structure is
   * wired with the JS logic. See: devtools/client/performance/performance.xul
   */
  getOptionsMenuPopup: function() {
    let doc = this.getPanelDocument();
    return doc.getElementById("performance-options-menupopup");
  },
});

// Exports from this module
exports.PerformanceOverlay = PerformanceOverlay;

Use proper base object for Performance panel overlay


var fs = require('fs'),
    http = require('http'),
    https = require('https'),
    httpProxy = require('http-proxy')

environment = process.env.NODE_ENV || 'production'
config      = require('./config.' + environment)

var options = {
    https: {
        key: fs.readFileSync(config.ssl.key, 'utf8'),
        cert: fs.readFileSync(config.ssl.cert, 'utf8'),
        ca: config.ssl.ca ? fs.readFileSync(config.ssl.ca, 'utf8') : null,
        ciphers: 'ECDHE-RSA-AES256-SHA:AES256-SHA:RC4-SHA:RC4:HIGH:'
            + '!MD5:!aNULL:!EDH:!AESGCM',
        honorCipherOrder: true
    },
    enable: {
        xforward: true // enables X-Forwarded-For
    }
}

var proxy = new httpProxy.HttpProxy({
    target: {
        host: 'localhost',
        port: config.app.port
    },
    enable: { xforward: true }
})

var listenOn = config.listen || [ 80, 443 ]

listenOn.forEach(function(port) {

    httpProxy.createServer(options, function (req, res) {
        proxy.proxyRequest(req, res)
    }).listen(port).on('upgrade', function (req, socket, head) {
        proxy.proxyWebSocketRequest(req, socket, head);
    })
})

console.log("Ready to proxy requests on port(s) " + listenOn.join(', ') + "...")

var fs = require('fs'),
    http = require('http'),
    https = require('https'),
    httpProxy = require('http-proxy')

environment = process.env.NODE_ENV || 'production'
config      = require('./config.' + environment)

var options = {
    https: {
        key: fs.readFileSync(config.ssl.key, 'utf8'),
        cert: fs.readFileSync(config.ssl.cert, 'utf8'),
        ca: config.ssl.ca ? fs.readFileSync(config.ssl.ca, 'utf8') : null,
        ciphers: 'ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:'
            + 'HIGH:!MD5:!aNULL:!EDH',
        honorCipherOrder: true
    },
    enable: {
        xforward: true // enables X-Forwarded-For
    }
}

var proxy = new httpProxy.HttpProxy({
    target: {
        host: 'localhost',
        port: config.app.port
    },
    enable: { xforward: true }
})

var listenOn = config.listen || [ 80, 443 ]

listenOn.forEach(function(port) {

    httpProxy.createServer(options, function (req, res) {
        proxy.proxyRequest(req, res)
    }).listen(port).on('upgrade', function (req, socket, head) {
        proxy.proxyWebSocketRequest(req, socket, head);
    })
})

console.log("Ready to proxy requests on port(s) " + listenOn.join(', ') + "...")

Update the cipher list to pass ssllabs BEAST tests


var assign = require('object-assign');
var baseRules = require('./base');
var rules = assign(baseRules, {
  'jsx-quotes': [2, 'prefer-single'],
  'react/jsx-no-bind': 1,
  'react/sort-comp': 0, // enable when https://github.com/yannickcr/eslint-plugin-react/issues/128 is fixed
});

module.exports = {
  extends: 'airbnb',
  rules: rules
};

var assign = require('object-assign');
var baseRules = require('./base');
var rules = assign(baseRules, {
  'jsx-quotes': [2, 'prefer-single'],
  'react/jsx-curly-spacing': [2, 'never'],
  'react/jsx-no-bind': 1,
  'react/sort-comp': 0, // enable when https://github.com/yannickcr/eslint-plugin-react/issues/128 is fixed
});

module.exports = {
  extends: 'airbnb',
  rules: rules
};

Enforce no curly spacing in jsx


export default {
  authToken: process.env.AUTH_TOKEN || 'secret',
  env: process.env.NODE_ENV,
  host: process.env.HOST || 'localhost',
  github: {
    apiUrl: 'https://api.github.com',
    username: process.env.GITHUB_USERNAME || 'username'
  },
  jobs: {
    frequency: {
      githubActivity: process.env.JOBS_FREQUENCY_GITHUB || '00 */1 * * * *' // '00 00 03 * * *'
    }
  },
  mongo: {
    url: process.env.MONGO_URL || 'mongodb://localhost:27017/FranVarney'
  },
  port: process.env.PORT || 8000
}

export default {
  authToken: process.env.AUTH_TOKEN || 'secret',
  env: process.env.NODE_ENV,
  host: process.env.HOST || 'localhost',
  github: {
    apiUrl: 'https://api.github.com',
    username: process.env.GITHUB_USERNAME || 'username'
  },
  jobs: {
    frequency: {
      githubActivity: process.env.JOBS_FREQUENCY_GITHUB || '00 */1 * * * *',
      updateCache: process.env.JOBS_FREQUENCY_CACHE || '00 */2 * * * *'
    }
  },
  mongo: {
    url: process.env.MONGO_URL || 'mongodb://localhost:27017/FranVarney'
  },
  port: process.env.PORT || 8000
}

Add update cache frequency option


/**
 * Escapes a HTML string.
 * @param {String} html The HTML string to escape.
 * @returns {String} The escaped string.
 */
function escapeHTML(html) {
    return html.replace(/[&<>"]/g, function (tag) {
		var charsToReplace = {
            '&': '&',
            '<': '<',
            '>': '>',
            '"': '"'
        };

		return charsToReplace[tag] || tag;
	});
}

Replace double quotes with "


'use strict'

const Model = require('trails-model')

/**
 * @module Project
 * @description Workspace group by users
 */
module.exports = class Project extends Model {

  static config () {
  }

  static schema () {
    return {
      name: {
        type: 'string'
      },
      status: {
        type: 'string'
      },
      user: {
        model: 'User'
      }
    }
  }
}

'use strict'

const Model = require('trails-model')

/**
 * @module Project
 * @description Workspace group by users
 */
module.exports = class Project extends Model {

  static config () {
  }

  static schema () {
    return {
      name: {
        type: 'string'
      },
      status: {
        type: 'string'
      },
      user: {
        model: 'User'
      },
      latitude: {
        type: 'float'
      },
      longitude: {
        type: 'float'
      },
      zoom: {
        type: 'integer'
      }
    }
  }
}

Add position and zoom in project


'use strict';

/* Controllers */

angular.module('myApp.controllers', [])
  .controller('HomeController', ['$scope', function($scope) {
  }])
  .controller('AlbumsController', ['$scope', function($scope) {
  }])
  .controller('ArtistsController', ['$scope', function($scope) {
  }])
  .controller('SongsController', ['$scope', function($scope) {
  }])
  .controller('FavoritesController', ['$scope', function($scope) {
  }]);

'use strict';

/* Controllers */

angular.module('myApp.controllers', [])
  .controller('HomeController', ['$scope', function($scope) {
  }])
  .controller('AlbumsController', ['$scope', function($scope) {
    $scope.albums = [];
  }])
  .controller('ArtistsController', ['$scope', function($scope) {
    $scope.artists = [];
  }])
  .controller('SongsController', ['$scope', function($scope) {
    $scope.songs = [];
  }])
  .controller('FavoritesController', ['$scope', function($scope) {
    $scope.favorites = [];
  }]);

Create containers to hold music data


import instances from '../../tempInstances';
import { addTestNamePrefixes } from '../../utils';
import _ from 'lodash';

export default addTestNamePrefixes({
  tags: ['planUsage'],
  beforeEach: (client) => {
    const { account_key: accountKey } = instances.account;

    client
      .loginUsingLocalStorage(accountKey)
      .setResolution(client);
  },
  afterEach: (client, done) => client.end(done),
  'User views Plan Usage': (client) => {
    const planUsagePage = client.page.planUsagePage();
    const randomInstance = _.sample(instances).instanceName;
    const usageChartsSelector = planUsagePage.elements.usageCharts.selector;

    planUsagePage
      .navigate()
      .waitForElementPresent('@planUsageTitle')
      .waitForElementPresent('@usageWithCurrentPlanText')
      .assert.containsText('@usageWithCurrentPlanText', 'Usage with your current plan:')
      .waitForElementVisible('@apiCallsText')
      .assert.containsText('@apiCallsText', 'API calls')
      .assert.containsText('@scriptSecondsText', 'Script seconds')
      .selectDropdownValue('@instancePlanDropdown', randomInstance)
      .assert.containsText('@instancePlanDropdown', randomInstance)
      .waitForElementVisible('@usageCharts')
      .assertSelectedCount('xpath', usageChartsSelector, 3);
  }
});

import instances from '../../tempInstances';
import { addTestNamePrefixes } from '../../utils';
import _ from 'lodash';

export default addTestNamePrefixes({
  tags: ['planUsage'],
  beforeEach: (client) => {
    const { account_key: accountKey } = instances.account;

    client
      .loginUsingLocalStorage(accountKey)
      .setResolution(client);
  },
  afterEach: (client, done) => client.end(done),
  'User views Plan Usage': (client) => {
    const planUsagePage = client.page.planUsagePage();
    const instancesArray = _.filter(instances, 'instanceName');
    const randomInstance = _.sample(instancesArray).instanceName;
    const usageChartsSelector = planUsagePage.elements.usageCharts.selector;

    planUsagePage
      .navigate()
      .waitForElementPresent('@planUsageTitle')
      .waitForElementPresent('@usageWithCurrentPlanText')
      .assert.containsText('@usageWithCurrentPlanText', 'Usage with your current plan:')
      .waitForElementVisible('@apiCallsText')
      .assert.containsText('@apiCallsText', 'API calls')
      .assert.containsText('@scriptSecondsText', 'Script seconds')
      .selectDropdownValue('@instancePlanDropdown', randomInstance)
      .assert.containsText('@instancePlanDropdown', randomInstance)
      .waitForElementVisible('@usageCharts')
      .assertSelectedCount('xpath', usageChartsSelector, 3);
  }
});

Fix randomly failing plan usage e2e tests


import localforage from 'localforage';
import Vue from 'vue'
import Vuex from 'vuex'
import SpotifyWebApi from 'spotify-web-api-js'

import App from './App.vue'

Vue.use(Vuex);

var store = new Vuex.Store({
  state: {
    loggedIn: false
  },
  mutations: {
    LOGGED_IN (state) {
      state.loggedIn = true;
    },
    LOGGED_OUT (state) {
      state.loggedIn = false;
    }
  },
  // learned_that::actions recieve a context object exposing same methods on store instance
  actions: {
    logOut(context) {
      localforage.clear()
      .then(() => {
        context.commit('LOGGED_OUT');
      })
      .catch((err) => console.error(err));
    }
  }
})

var spotify = new SpotifyWebApi();

Object.defineProperty(Vue.prototype, '$spotify', {value: spotify});

localforage.config({
  name: 'sortzzi'
});

window.vm = new Vue({
  el: '#app',
  store,
  render: h => h(App)
});

import localforage from 'localforage';
import Vue from 'vue'
import Vuex from 'vuex'
import SpotifyWebApi from 'spotify-web-api-js'

import App from './App.vue'

Vue.use(Vuex);

var store = new Vuex.Store({
  state: {
    loggedIn: false,
    trackCart: []
  },
  mutations: {
    LOGGED_IN (state) {
      state.loggedIn = true;
    },
    LOGGED_OUT (state) {
      state.loggedIn = false;
    },
    ADD_TO_TRACK_CART (state, payload) {
      if(!state.trackCart.includes(payload) && payload != undefined)
        state.trackCart.push(payload);
    },
    REMOVE_FROM_TRACK_CART(state, payload){
      if(state.trackCart.includes(payload))
        state.trackCart = state.trackCart.filter(item => item != payload);
    },
  },
  // learned_that::actions recieve a context object exposing same methods on store instance
  actions: {
    logOut(context) {
      localforage.clear()
      .then(() => {
        context.commit('LOGGED_OUT');
      })
      .catch((err) => console.error(err));
    }
  }
})

var spotify = new SpotifyWebApi();

Object.defineProperty(Vue.prototype, '$spotify', {value: spotify});

localforage.config({
  name: 'sortzzi'
});

window.vm = new Vue({
  el: '#app',
  store,
  render: h => h(App)
});

Add simple track cart state management to Vuex store.


window.ReflectionVariable = require('./ReflectionVariable');
window.ReflectionAssignedFunctionExpression = require('./ReflectionAssignedFunctionExpression');
window.ReflectionFunctionExpression = require('./ReflectionFunctionExpression');
window.ReflectionFunctionDeclaration = require('./ReflectionFunctionDeclaration');
window.ReflectionFunction = require('./ReflectionFunction');
window.ReflectionScopeAbstract = require('./ReflectionScopeAbstract');
window.ReflectionFile = require('./ReflectionFile');
window.Reflection = require('./Reflection');

global.ReflectionVariable = require('./ReflectionVariable');
global.ReflectionAssignedFunctionExpression = require('./ReflectionAssignedFunctionExpression');
global.ReflectionFunctionExpression = require('./ReflectionFunctionExpression');
global.ReflectionFunctionDeclaration = require('./ReflectionFunctionDeclaration');
global.ReflectionFunction = require('./ReflectionFunction');
global.ReflectionScopeAbstract = require('./ReflectionScopeAbstract');
global.ReflectionFile = require('./ReflectionFile');
global.Reflection = require('./Reflection');

Replace the uses of window with global


export default {
    /**
     * Naive polyfill for Object.assign()
     * Why I did it? Because even modular lodash adds too much code in final build
     * @param {Object} target
     * @return {Object}
     */
    assign(target) {
        for (let i = 1; i < arguments.length; i++) {
            let obj = Object(arguments[i]),
                keys = Object.keys(obj);

            for (let j = 0; j < keys.length; j++) {
                let key = keys[j];

                if (!obj.hasOwnProperty(key)) {
                    continue;
                }

                target[key] = obj[key];
            }
        }

        return target;
    },

    /**
     * Returns URL with query string
     * @param {String} url
     * @param {Object} params
     * @return {String}
     */
    joinParams(url, params = null) {
        if (!params) {
            return url;
        }

        let queryString = Object.keys(params).reduce((query, key) => {
            let value = params[key];

            if (value) {
                value = value.toString();
                value = encodeURIComponent(value);
                key = encodeURIComponent(key);
                query.push(`${key}=${value}`);
            }

            return query;
        }, []).join('&');

        if (!queryString) {
            return url;
        }

        // Trim trailing "?"
        url = url.replace(/\?+$/, '');

        return url +
            (url.indexOf('?') === -1 ? '?' : '&') +
            queryString;
    }
};

export default {
    /**
     * Naive polyfill for Object.assign()
     * Why I did it? Because even modular lodash adds too much code in final build
     * @param {Object} target
     * @return {Object}
     */
    assign(target) {
        for (let i = 1; i < arguments.length; i++) {
            let obj = Object(arguments[i]),
                keys = Object.keys(obj);

            for (let j = 0; j < keys.length; j++) {
                let key = keys[j];

                if (!obj.hasOwnProperty(key)) {
                    continue;
                }

                target[key] = obj[key];
            }
        }

        return target;
    },

    /**
     * Returns URL with query string
     * @param {String} url
     * @param {Object} params
     * @return {String}
     */
    joinParams(url, params = null) {
        if (!params) {
            return url;
        }

        let queryString = Object.keys(params)
            .filter((key) => key && params[key])
            .map((key) => {
                let value = params[key].toString();

                key = encodeURIComponent(key);
                value = encodeURIComponent(value);

                return `${key}=${value}`;
            })
            .join('&');

        if (!queryString) {
            return url;
        }

        // Trim trailing "?"
        url = url.replace(/\?+$/, '');

        return url +
            (url.indexOf('?') === -1 ? '?' : '&') +
            queryString;
    }
};

Correct implementation of joinParams() helper


import SocketIO from 'socket.io';
import { createClient } from 'redis';

import { redisUrl } from './config';

const io = SocketIO();

io.use((socket, next) => {
  return next();
});

io.of('/live-chatroom')
  .use((socket, next) => {
    return next();
  })
  .on('connection', (socket) => {
    const redis = createClient(redisUrl);
    let currentRoom = '';

    socket.on('subscribe', (id) => {
      currentRoom = `live:${id}:comments`;
      socket.join(currentRoom);

      redis.subscribe(`${currentRoom}:latest`);

      redis.exists('live', id, (err, result) => {
        if (result < 2) {
          redis.lpush('live', id);
        }
      });

      socket.emit('subscribed');
    });

    redis.on('message', (channel, message) => {
      socket.emit('comment', message);
    });

    socket.on('unsubscribe', (id) => {
      socket.leave(id);
      socket.emit('unsubscribed');
    });

    socket.on('disconnect', () => {
      redis.unsubscribe(`${currentRoom}:latest`);
      redis.quit();
    })
  });

export default io;

import SocketIO from 'socket.io';
import { createClient } from 'redis';

import { redisUrl } from './config';

const io = SocketIO();

io.use((socket, next) => {
  return next();
});

io.of('/live-chatroom')
  .use((socket, next) => {
    return next();
  })
  .on('connection', (socket) => {
    const redisSubscriber = createClient(redisUrl);
    let currentRoom = '';

    socket.on('subscribe', (id) => {
      currentRoom = `live:${id}:comments`;
      socket.join(currentRoom);

      redisSubscriber.subscribe(`${currentRoom}:latest`);

      const redis = createClient(redisUrl);
      redis.exists('live', id, (err, result) => {
        if (result < 2) {
          redis.lpush('live', id);
        }
      });
      redis.quit();

      socket.emit('subscribed');
    });

    redisSubscriber.on('message', (channel, message) => {
      socket.emit('comment', message);
    });

    socket.on('unsubscribe', (id) => {
      socket.leave(id);
      socket.emit('unsubscribed');
    });

    socket.on('disconnect', () => {
      redisSubscriber.unsubscribe(`${currentRoom}:latest`);
      redisSubscriber.quit();
    })
  });

export default io;

Fix redis error => If redis client subscribed a key, it cannot run another commands.


/*
 * grunt-dredd
 * https://github.com/mfgea/grunt-dredd
 *
 * Copyright (c) 2014 Matias Gea
 * Licensed under the MIT license.
 */

'use strict';

var Dredd = require('dredd');

module.exports = function(grunt) {

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerTask('dredd', 'Grunt wrapper for Dredd API Blueprint tester', function() {

    var done = grunt.task.current.async();

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      server: 'http://localhost:3000',
      src: './api/api.apib'
    });

    var dreddConfiguration = {
      'blueprintPath': options.src,
      'server': options.server,
      'options': options
    };

    var dreddInstance = new Dredd(dreddConfiguration);

    dreddInstance.run(function(error, stats){
      if (error) {
        if (error.message) {
          grunt.log.error(error.message);
        }
        if (error.stack) {
          grunt.log.error(error.stack);
        }
        done(false);
      }
      if (stats.failures + stats.errors > 0) {
        done(false);
      } else {
        done(true);
      }
    });

    grunt.log.write('Dredd test run at ' + dreddConfiguration.options.server + ', using ' + options.src);

  });

};

/*
 * grunt-dredd
 * https://github.com/mfgea/grunt-dredd
 *
 * Copyright (c) 2014 Matias Gea
 * Licensed under the MIT license.
 */

'use strict';

var Dredd = require('dredd');

module.exports = function(grunt) {

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerTask('dredd', 'Grunt wrapper for Dredd API Blueprint tester', function() {

    var done = grunt.task.current.async();

    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options({
      server: 'http://localhost:3000',
      src: './api/api.apib'
    });

    var dreddConfiguration = {
      'blueprintPath': options.src,
      'server': options.server,
      'options': options
    };

    var dreddInstance = new Dredd(dreddConfiguration);

    dreddInstance.run(function(error, stats){
      if (error) {
        if (error.message) {
          grunt.log.error(error.message);
        }
        if (error.stack) {
          grunt.log.error(error.stack);
        }
        done(false);
      }
      if (stats.failures + stats.errors > 0) {
        done(false);
      } else {
        done(true);
      }
    });

    grunt.log.write('Dredd test run at ' + dreddConfiguration.options.server + ', using ' + options.src + '\n');

  });

};

Add new line character after output


module.exports = function(grunt) {
  return grunt.registerMultiTask('mkdir', 'Make directories.', function() {
    var options;
    options = this.options({
      mode: null,
      create: []
    });
    grunt.verbose.writeflags(options, 'Options');
    return options.create.forEach(function(filepath) {
      var e;
      grunt.log.write('Creating "' + filepath + '"...');
      try {
        filepath = grunt.template.process(filepath);
        grunt.file.mkdir(filepath, options.mode);
        return grunt.log.ok();
      } catch (_error) {
        e = _error;
        grunt.log.error();
        grunt.verbose.error(e);
        return grunt.fail.warn('Mkdir operation failed.');
      }
    });
  });
};

module.exports = function(grunt) {
  return grunt.registerMultiTask('mkdir', 'Make directories.', function() {
    var options;
    options = this.options({
      mode: null,
      create: []
    });
    grunt.verbose.writeflags(options, 'Options');
    return options.create.forEach(function(filepath) {
      grunt.log.write('Creating "' + filepath + '"...');
      try {
        filepath = grunt.template.process(filepath);
        grunt.file.mkdir(filepath, options.mode);
        return grunt.log.ok();
      } catch (e) {
        grunt.log.error();
        grunt.verbose.error(e);
        return grunt.fail.warn('Mkdir operation failed.');
      }
    });
  });
};

Replace e removed during build by _error


expect =require('chai').expect;
fs = require('fs');
PNG = require('png-js');
QrCode = require('../dist/index.js');

it('should work with basic image', function(done) {

    c = fs.readFileSync(__dirname + '/image.png');
    p = new PNG(c);

    p.decode(function(data) {

        qr = new QrCode();
        qr.callback = function(result) {

            expect(result).to.equal('Test');
            done();
        }
        qr.decode(p, data);
    });
});

it('should work with imageData format', function(done) {

    c = fs.readFileSync(__dirname + '/image.png');
    p = new PNG(c);

    p.decode(function(data) {

        qr = new QrCode();
        qr.callback = function(result) {

            expect(result).to.equal('Test');
            done();
        }
        qr.decode({
            height: p.height,
            width: p.width,
            data: data
        });
    });
});

var expect = require('chai').expect;
var fs = require('fs');
var PNG = require('png-js');
var QrCode = require('../dist/index.js');

it('should work with basic image', function(done) {

    var c = fs.readFileSync(__dirname + '/image.png');
    var p = new PNG(c);

    p.decode(function(data) {

        var qr = new QrCode();
        qr.callback = function(result) {

            expect(result).to.equal('Test');
            done();
        }
        qr.decode(p, data);
    });
});

it('should work with imageData format', function(done) {

    var c = fs.readFileSync(__dirname + '/image.png');
    var p = new PNG(c);

    p.decode(function(data) {

        var qr = new QrCode();
        qr.callback = function(result) {

            expect(result).to.equal('Test');
            done();
        }
        qr.decode({
            height: p.height,
            width: p.width,
            data: data
        });
    });
});

Use local variables instead of globals


import React from 'react';
import icons from '../weather_icons/WeatherIcons';

const TenDay = ({ tenDayForecast }) => {
  if (!tenDayForecast) {
    return null;
  }
  const forecastArray = tenDayForecast.simpleforecast.forecastday;
  const tenDayDataLoop = forecastArray.map((day, i) => {
    return (

{day.date.weekday_short}

        <div className={`ten-day-icon ${icons[day.icon]}`} tabIndex="0" alt="daily weather icon" aria-label="daily weather icon">

{day.low.fahrenheit}°

{day.high.fahrenheit}°

    );
  });

  return (

      {tenDayDataLoop}

  );
};

export default TenDay;

Create line break for icons div.


import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
    this.audioLoader = new AudioLoader();
  }

  integrate(self) {
    this.addAudioListener = function (cameraModule) {
      this.native.add(self.positionalAudio);
      cameraModule.camera.native.add(self.audioListener);
    };

    this.playAudio = function (path) {
      const sound = self.positionalAudio;

      self.audioLoader.load(path, buffer => {
        sound.setBuffer(buffer);
        sound.setRefDistance(50);
        sound.play();
      });
    };
  }
}

import {
  PositionalAudio,
  AudioListener,
  AudioLoader
} from 'three';

export default class PositionalAudioModule {
  constructor(params = {}) {
    this.params = Object.assign({
      loop: true
    }, params);

    this.audioListener = new AudioListener();
    this.audioLoader = new AudioLoader();

    this.positionalAudio = new PositionalAudio(this.audioListener);
    this.positionalAudio.setLoop(this.params.loop);
  }

  addListener(object) {
    object.native.add(this.audioListener);
  };

  playAudio(path) {
    const sound = this.positionalAudio;

    this.audioLoader.load(path, buffer => {
      sound.setBuffer(buffer);
      sound.setRefDistance(50);
      sound.play();
    });
  };

  }
}

Move methods to module scope


define(function (require) {
	var documentKeyboardHandler = require("utilities/documentKeyboardHandler");

	var keyboardShortcuts = function (eventBus) {
		documentKeyboardHandler(function (event, isDown, isRepeated) {
			// spacebar
			if (isDown && event.which === 32) {
				if (!isRepeated)
					eventBus.trigger("togglePlayback");

				return false;
			}

			// shift
			if (event.which === 16) {
				if (!isRepeated) {
					eventBus.trigger("setGridViewState", {
						state: isDown ? "play" : "addOrRemove"
					});
				}

				return false;
			}

			// number keys 1 to 9
			if (isDown && event.which >= 49 && event.which <= 57) {
				if (!isRepeated)
					eventBus.trigger("selectTrack", {
						trackIndex: event.which - 49
					});

				return false;
			}
		});
	};

	return keyboardShortcuts;
});

define(function (require) {
	var $ = require("jquery");
	var documentKeyboardHandler = require("utilities/documentKeyboardHandler");

	var keyboardShortcuts = function (eventBus) {
		documentKeyboardHandler(function (event, isDown, isRepeated) {
			// spacebar
			if (isDown && event.which === 32) {
				if (!isRepeated)
					eventBus.trigger("togglePlayback");

				return false;
			}

			// shift
			if (event.which === 16) {
				if (!isRepeated) {
					eventBus.trigger("setGridViewState", {
						state: isDown ? "play" : "addOrRemove"
					});
				}

				return false;
			}

			// number keys 1 to 9
			if (isDown && event.which >= 49 && event.which <= 57) {
				if ($(event.target).is("input"))
					return;

				if (!isRepeated)
					eventBus.trigger("selectTrack", {
						trackIndex: event.which - 49
					});

				return false;
			}
		});
	};

	return keyboardShortcuts;
});

Disable keyboard shortcut if target element is an input element.


( function() {
	'use strict';

	describe( 'vui', function() {

		var node;

		beforeEach( function () {
			jasmine.addMatchers( d2l.jasmine.matchers );
			node = document.body.appendChild( document.createElement( 'div' ) );
		} );

		afterEach( function() {
			document.body.removeChild( node );
		} );

		describe( 'more/less', function() {

			beforeEach( function () {
			} );

			it( 'todo', function() {
				expect( true ).toBe( true );
			} );

		} );

	} );

} )();

( function() {
	'use strict';

	describe( 'vui.moreLess', function() {

		var node, $container1;

		beforeEach( function () {

			jasmine.addMatchers( d2l.jasmine.matchers );

			node = document.body.appendChild( document.createElement( 'div' ) );

			$container1 = $( "

some content

" )
				.appendTo( node );

		} );

		afterEach( function() {
			document.body.removeChild( node );
		} );

		describe( 'create', function() {

			it( 'binds the more-less container element using widget method', function() {
				$container1.vui_moreless();
				expect( $container1.data( 'vui-vui_moreless' ) ).toBeDefined();
			} );

			it( 'constructs a link that the user can click to toggle the state of more/less widget', function() {
				$container1.vui_moreless();
				expect( $( '.vui-moreless-link' ).length ).toBe( 1 );
			} );

			it( 'applies custom more title to more link if specified', function() {
				$container1.attr( 'data-moreless-moretitle', '+More' );
				$container1.vui_moreless();
				expect( $( '.vui-moreless-link' )[0].innerText ).toBe( '+More' );
			} );

		} );

		describe( 'destroy', function() {

			beforeEach( function () {
				$container1.vui_moreless();
			} );

			it( 'unbinds container from widget when destroy is called', function() {
				$container1.vui_moreless( 'destroy' );
				expect( $container1.data( 'vui-vui_moreless' ) )
					.not.toBeDefined();
			} );

		} );

	} );

} )();

Add basic widget create/destroy tests.


/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

'use strict';

import type {DangerouslyImpreciseStyle} from './StyleSheet';

const OUTER_PROPS = Object.assign(Object.create(null), {
  margin: true,
  marginHorizontal: true,
  marginVertical: true,
  marginBottom: true,
  marginTop: true,
  marginLeft: true,
  marginRight: true,
  flex: true,
  flexGrow: true,
  flexShrink: true,
  flexBasis: true,
  alignSelf: true,
  height: true,
  minHeight: true,
  maxHeight: true,
  width: true,
  minWidth: true,
  maxWidth: true,
  position: true,
  left: true,
  right: true,
  bottom: true,
  top: true,
  transform: true,
});

function splitLayoutProps(
  props: ?DangerouslyImpreciseStyle,
): {
  outer: DangerouslyImpreciseStyle,
  inner: DangerouslyImpreciseStyle,
  ...
} {
  const inner = {};
  const outer = {};
  if (props) {
    Object.keys(props).forEach(k => {
      const value: $ElementType<DangerouslyImpreciseStyle, typeof k> = props[k];
      if (OUTER_PROPS[k]) {
        outer[k] = value;
      } else {
        inner[k] = value;
      }
    });
  }
  return {outer, inner};
}

module.exports = splitLayoutProps;

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

'use strict';

import type {DangerouslyImpreciseStyle} from './StyleSheet';

const OUTER_PROPS = Object.assign(Object.create(null), {
  margin: true,
  marginHorizontal: true,
  marginVertical: true,
  marginBottom: true,
  marginTop: true,
  marginLeft: true,
  marginRight: true,
  flex: true,
  flexGrow: true,
  flexShrink: true,
  flexBasis: true,
  alignSelf: true,
  height: true,
  minHeight: true,
  maxHeight: true,
  width: true,
  minWidth: true,
  maxWidth: true,
  position: true,
  left: true,
  right: true,
  bottom: true,
  top: true,
  transform: true,
});

function splitLayoutProps(
  props: ?DangerouslyImpreciseStyle,
): {
  outer: DangerouslyImpreciseStyle,
  inner: DangerouslyImpreciseStyle,
  ...
} {
  const inner = {};
  const outer = {};
  if (props) {
    Object.keys(props).forEach((k: string) => {
      const value = props[k];
      if (OUTER_PROPS[k]) {
        outer[k] = value;
      } else {
        inner[k] = value;
      }
    });
  }
  return {outer, inner};
}

module.exports = splitLayoutProps;

Add type annotations to prevent issues with planned Flow changes


import React, { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';

import Cursor from './Cursor';

class Cursors extends Component {
  render() {
    return (
      <div>
        {this.props.displayedUsers.map(user => (
          <Cursor
            username={user.get('username')}
            color={user.get('color')}
            x={user.get('x')}
            y={user.get('y')}
          />
        ))}
      </div>
    );
  }
}

Cursors.propTypes = {
  displayedUsers: ImmutablePropTypes.orderedMap.isRequired
};

export default Cursors;

import React, { Component } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';

import Cursor from './Cursor';

class Cursors extends Component {
  render() {
    return (
      <div>
        {this.props.displayedUsers.valueSeq().toArray().map(user => (
          <Cursor
            key={user.get('username')}
            username={user.get('username')}
            color={user.get('color')}
            x={user.get('x')}
            y={user.get('y')}
          />
        ))}
      </div>
    );
  }
}

Cursors.propTypes = {
  displayedUsers: ImmutablePropTypes.orderedMap.isRequired
};

export default Cursors;

Adjust Cursors render method map to iterate through a normal array


import React, {Component, PropTypes} from 'react';
import {IndexLink, Link} from 'react-router';
import {connect} from 'react-redux';

@connect(
  state => ({
    loggedIn: state.auth.loggedIn
  })
)
export default class Header extends Component {
  static propTypes = {
    loggedIn: PropTypes.bool
  }

  render() {
    let links = [
      {
        to: '/trends',
        title: 'Github trends'
      },
      {
        to: '/about',
        title: 'About'
      }
    ];

    if (this.props.loggedIn) {
      links.push({
        to: '/profile',
        title: 'Profile'
      });
      links.push({
        to: '/logout',
        title: 'Logout'
      });
    } else {
      links.push({
        to: '/login',
        title: 'Login'
      });
    }

    return (

Redux universal boilerplate

          <IndexLink to="/" className="item" activeClassName="active">Homepage</IndexLink>
          {
            links.map(function(link, i) {
              return <Link to={link.to} key={i} className="item" activeClassName="active">{link.title}</Link>;
            })
          }

    );
  }
}

import React, {Component, PropTypes} from 'react';
import {IndexLink, Link} from 'react-router';
import {connect} from 'react-redux';

@connect(
  state => ({
    loggedIn: state.auth.loggedIn
  })
)
export default class Header extends Component {
  static propTypes = {
    loggedIn: PropTypes.bool
  }

  render() {
    let links = [
      {
        to: '/trends',
        title: 'Github trends'
      },
      {
        to: '/about',
        title: 'About'
      },
      {
        to: '/404',
        title: 'Non-exists page'
      }
    ];

    if (this.props.loggedIn) {
      links.push({
        to: '/profile',
        title: 'Profile'
      });
      links.push({
        to: '/logout',
        title: 'Logout'
      });
    } else {
      links.push({
        to: '/login',
        title: 'Login'
      });
    }

    return (

Redux universal boilerplate

          <IndexLink to="/" className="item" activeClassName="active">Homepage</IndexLink>
          {
            links.map(function(link, i) {
              return <Link to={link.to} key={i} className="item" activeClassName="active">{link.title}</Link>;
            })
          }

    );
  }
}

Add link to 404 example page


'use strict';

function setElementsValueByName(name, value) {
  var elements = document.getElementsByName(name);
  for(var i = 0; i < elements.length; i++) {
    elements[i].value = value;
  }
}

function serializeForms() {
  var fields = [];
  for(var i = 0; i < document.forms.length; i++) {
    var elements = document.forms[i].elements;
    for(var j = 0; j < elements.length; j++) {
      var el = elements[j];
      if(el.clientHeight < 1 || el.clientWidth < 1) {
        continue;
      }
      fields.push({name: el.name, value: el.value});
    }
  }
  return fields;
}

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
  switch(message.type) {
  case "SET_FORM":
    message.data.fields.forEach(function(field) {
      setElementsValueByName(field.name, field.value);
    });
    break;
  case "GET_FORM":
    sendResponse(serializeForms());
    break;
  }
});

'use strict';

function setElementsValueByName(name, value) {
  var elements = document.getElementsByName(name);
  for(var i = 0; i < elements.length; i++) {
    elements[i].value = value;
  }
}

function serializeForms() {
  var fields = [];
  for(var i = 0; i < document.forms.length; i++) {
    var elements = document.forms[i].elements;
    for(var j = 0; j < elements.length; j++) {
      var el = elements[j];
      if(!el.name || el.clientHeight < 1 || el.clientWidth < 1) {
        continue;
      }
      fields.push({name: el.name, value: el.value});
    }
  }
  return fields;
}

chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
  switch(message.type) {
  case "SET_FORM":
    message.data.fields.forEach(function(field) {
      setElementsValueByName(field.name, field.value);
    });
    break;
  case "GET_FORM":
    sendResponse(serializeForms());
    break;
  }
});

Stop serialising fields with no name


const chai = require('chai')
chai.should()

const fs = require('fs')
const testFolder = fs.readdirSync('./test')
const ignoreFiles = [ '__tests-setup.js', 'mocha.opts' ]
const testFiles = testFolder.filter(function (f) {
  return ignoreFiles.indexOf(f) === -1
})

export default function runTests (getTestSubject) {

  return function () {

    testFiles.forEach(function (f) {
      var name = f.split('.')[0]
      var test = require('../test/' + f)
      var fn = getTestSubject(name)
      describe(name, test(fn))
    })

  }

}

const chai = require('chai')
chai.should()

const fs = require('fs')
const testFolder = fs.readdirSync('./test')
const ignoreFiles = [ '__tests-setup.js', 'mocha.opts' ]
const testFiles = testFolder.filter(function (f) {
  return ignoreFiles.indexOf(f) === -1
})

function shouldBeCurried (name, fn) {
  return name !== 'curry' && typeof fn === 'function'
}

export default function runTests (getTestSubject) {

  return function () {

    testFiles.forEach(function (f) {
      var name = f.split('.')[0]
      var test = require('../test/' + f)
      var fn = getTestSubject(name)
      describe(name, function () {
        if (shouldBeCurried(name, fn)) {
          it('is curried', function () {
            fn().should.equal(fn())
            fn.should.equal(fn())
          })
        }
        test(fn)()
      })
    })

  }

}

Make sure all functions are curried


// This file is ES5; it's loaded before Babel.
require('babel/register')({
  extensions: ['.desktop.js', '.es6', '.es', '.jsx', '.js']
})

const menubar = require('menubar')
const ipc = require('ipc')
const Window = require('./window')

const mb = menubar({
  index: `file://${__dirname}/../renderer/launcher.html`,
  width: 200, height: 250,
  preloadWindow: true,
  icon: 'Icon.png'
})

const trackerWindow = new Window('tracker', {
  width: 500, height: 300,
  resizable: false,
  fullscreen: false,
  frame: false
})

const mainWindow = new Window('index', {
  width: 1600, height: 1200, openDevTools: true
})

mb.on('ready', () => {
  require('../../react-native/react/native/notifications').init()
})

ipc.on('showMain', () => { mainWindow.show() })
ipc.on('showTracker', () => { trackerWindow.show() })

// This file is ES5; it's loaded before Babel.
require('babel/register')({
  extensions: ['.desktop.js', '.es6', '.es', '.jsx', '.js']
})

const menubar = require('menubar')
const ipc = require('ipc')
const Window = require('./window')

const mb = menubar({
  index: `file://${__dirname}/../renderer/launcher.html`,
  width: 200, height: 250,
  preloadWindow: true,
  icon: 'Icon.png'
})

const trackerWindow = new Window('tracker', {
  width: 500, height: 300,
  resizable: false,
  fullscreen: false,
  frame: false
})

const mainWindow = new Window('index', {
  width: 1600, height: 1200, openDevTools: true
})

mb.on('ready', () => {
  require('../../react-native/react/native/notifications').init()
})

// Work around an OS X bug that leaves a gap in the status bar if you exit
// without removing your status bar icon.
if (process.platform === 'darwin') {
  mb.app.on('quit', () => { mb.tray.destroy() })
}

ipc.on('showMain', () => { mainWindow.show() })
ipc.on('showTracker', () => { trackerWindow.show() })

Fix gap left in status bar on OS X after quit


const chordRegex = require('../src/chord-regex')
const tape = require('tape')

tape('Regex against valid chords', function (test) {
  let chords = [
    'C', 'D', 'E', 'F', 'G', 'A', 'B', // white keys
    'C#', 'Eb', 'F#', 'Ab', 'G#', 'Bb', // sharps and flats
    'Db', 'D#', 'Gb', 'A#', // for those who like to use non-canon names for sharps and flats
    'Cm', 'Caug', 'Cdim', 'Csus', 'Csus2', 'Csus4', // a few more triads
    'Cm7', 'CM7', 'Cmaj7', 'CmM7', // 7ths
    'C6', 'C9', 'C11', 'C13', // few more extension chords
    'C/E', 'D/F#' // Slash chords
  ]

  test.plan(chords.length)

  chords.forEach(chord => {
    const input = '[' + chord + ']'
    if (chordRegex.exec(input) === null || chordRegex.exec(input).length === 0) {
      test.fail('Failed for input: ' + input)
    } else {
      const match = chordRegex.exec(input)[1]
      test.equal(match, chord, input + ' passed with match ' + match)
    }
  })
})

const chordRegex = require('../src/chord-regex')
const tape = require('tape')

tape('Regex against valid chords', function (test) {
  let chords = [
    'C', 'D', 'E', 'F', 'G', 'A', 'B', // white keys
    'C#', 'Eb', 'F#', 'Ab', 'G#', 'Bb', // sharps and flats
    'Db', 'D#', 'Gb', 'A#', // for those who like to use non-canon names for sharps and flats
    'Cm', 'Caug', 'Cdim', 'Csus', 'Csus2', 'Csus4', // a few more triads
    'Cm7', 'CM7', 'C7', 'CmM7', // 7ths
    'Cmin7', 'Cmaj7', 'Cminmaj7', // 7ths with alt names
    'C6', 'C9', 'C11', 'C13', // few more extension chords
    'C/E', 'D/F#' // Slash chords
  ]

  test.plan(chords.length)

  chords.forEach(chord => {
    const input = '[' + chord + ']'
    if (chordRegex.exec(input) === null || chordRegex.exec(input).length === 0) {
      test.fail('Failed for input: ' + input)
    } else {
      const match = chordRegex.exec(input)[1]
      test.equal(match, chord, input + ' passed with match ' + match)
    }
  })
})

Add 7th chords with alt names to the test


'use strict';

module.exports = function(/* environment, appConfig */) {
  return { };
};

'use strict';

module.exports = function(/* environment, appConfig */) {
  return {
    EmberENV: {
      _ENABLE_LEGACY_CONTROLLER_SUPPORT: true
    }
  };
};

Add flag to skip controller keyword assertion


var
	extend              = req('/utilities/extend'),
	ServerMessage       = req('/lib/server/message'),
	ReplyNumerics       = req('/constants/reply-numerics'),
	CommandValidator    = req('/validators/command'),
	ErrorReasons        = req('/constants/error-reasons'),
	InvalidCommandError = req('/lib/errors/invalid-command');

class NeedMoreParamsMessage extends ServerMessage {

	setAttemptedCommand(command) {
		CommandValidator.validate(command);
		this.attempted_command = command;
	}

	getAttemptedCommand() {
		CommandValidator.validate(this.attempted_command);
		return this.command;
	}

	applyParsedParams(middle_params, trailing_param) {
		this.addTargetFromString(middle_params.shift());
		this.setAttemptedCommand(middle_params.shift());
		this.setBody(trailing_param);
	}

	serializeParams() {
		var
			targets = this.serializeTargets(),
			command = this.getAttemptedCommand(),
			body    = this.getBody();

		return `${targets} ${command} :${body}`;
	}

	toError() {
		return new InvalidCommandError(
			this.getAttemptedCommand(),
			ErrorReasons.NOT_ENOUGH_PARAMETERS
		);
	}

}

extend(NeedMoreParamsMessage.prototype, {

	reply_numeric: ReplyNumerics.ERR_NEEDMOREPARAMS,
	body:          'Not enough parameters.'

});

module.exports = NeedMoreParamsMessage;

var
	extend              = req('/utilities/extend'),
	ServerMessage       = req('/lib/server/message'),
	ReplyNumerics       = req('/constants/reply-numerics'),
	CommandValidator    = req('/validators/command'),
	ErrorReasons        = req('/constants/error-reasons'),
	InvalidCommandError = req('/lib/errors/invalid-command');

class NeedMoreParamsMessage extends ServerMessage {

	setAttemptedCommand(command) {
		CommandValidator.validate(command);
		this.attempted_command = command;
	}

	getAttemptedCommand() {
		CommandValidator.validate(this.attempted_command);
		return this.attempted_command;
	}

	applyParsedParams(middle_params, trailing_param) {
		this.addTargetFromString(middle_params.shift());
		this.setAttemptedCommand(middle_params.shift());
		this.setBody(trailing_param);
	}

	serializeParams() {
		var
			targets = this.serializeTargets(),
			command = this.getAttemptedCommand(),
			body    = this.getBody();

		return `${targets} ${command} :${body}`;
	}

	toError() {
		return new InvalidCommandError(
			this.getAttemptedCommand(),
			ErrorReasons.NOT_ENOUGH_PARAMETERS
		);
	}

}

extend(NeedMoreParamsMessage.prototype, {

	reply_numeric:     ReplyNumerics.ERR_NEEDMOREPARAMS,
	body:              'Not enough parameters.',
	attempted_command: null

});

module.exports = NeedMoreParamsMessage;

Fix parsing error in ERR_NEEDMOREPARAMS


import Ember from "ember";
import ResetScroll from '../mixins/reset-scroll';
import config from '../config/environment';

export default Ember.Route.extend(ResetScroll, {
	model: function(params) {
		this.set("thread_id", params["thread_id"]);
		return this.store.find("thread", params["thread_id"]);
	},
	afterModel: function() {
		// Non-system hack
		Ember.$.ajax({
            url: config.API_LOC + "/threads/" + this.get("thread_id"),
            data: {},
            contentType: "application/json",
            type: 'PUT',
        });
	},
	titleToken: function(model) {
		return model.get("title");
	},
	title: function(tokens) {
		return "KSI: " + tokens.pop();
	},
	thread_id: undefined
});

import Ember from "ember";
import ResetScroll from '../mixins/reset-scroll';
import config from '../config/environment';

export default Ember.Route.extend(ResetScroll, {
	model: function(params) {
		return this.store.find("thread", params["thread_id"]);
	},
	afterModel: function() {
		console.log("After model called!");
		// Non-system hack
		Ember.$.ajax({
            url: config.API_LOC + "/threads/" + this.modelFor(this.routeName).get("id"),
            data: {},
            contentType: "application/json",
            type: 'PUT',
        });
	},
	titleToken: function(model) {
		return model.get("title");
	},
	title: function(tokens) {
		return "KSI: " + tokens.pop();
	}
});

Fix PUT request for marking as visite


'use strict';

const BOSHManager = require('./bosh-manager/BOSHManager');
const BOSHBindManager = require('./bosh-manager/BOSHBindManager');
const BOSHTaskpoller = require('./bosh-manager/BOSHTaskpoller');

BOSHTaskpoller.start();
const boshManager = new BOSHManager();
const bindManager = new BOSHBindManager();
boshManager.init();
bindManager.init();

'use strict';

const BOSHManager = require('./bosh-manager/BOSHManager');
const BOSHBindManager = require('./bosh-manager/BOSHBindManager');
const BOSHTaskPoller = require('./bosh-manager/BOSHTaskPoller');

BOSHTaskPoller.start();
const boshManager = new BOSHManager();
const bindManager = new BOSHBindManager();
boshManager.init();
bindManager.init();

Change case for the import


(function ($, dc) {
  "use strict";

  $(function gdprBanner() {
    if (!dc.hasItem("gdpr-accept")) {
      var gdpr = $("
").on("click", ".gdpr-close", function(e) {
        dc.setItem("gdpr-accept", "yes");
        $(document.body).removeClass("with-gdpr");
        gdpr.remove();
      }).append("").
        append("
Our site uses cookies for analytics. If you're unsure about it, take a look at our privacy policy.
");

      $(document.body).addClass("with-gdpr").find("header").append(gdpr);
      dc.setItem("gdpr-accept", "yes");
    }
  });

})(jQuery, docCookies);

Add `rel="noopener noreferrer"` to external links on GDPR banner


class BpmCounter {
  constructor() {
    this.numTaps = 0;
    this.bpm = 0;
    this.lastTappedTime = new Date();
  }

  restart() {
    this.numTaps = 0;
    this.bpm = 0;
    this.lastTappedTime = new Date();
  }

  tick() {
    this.numTaps++;

    let currentTime = new Date();

    let timeDifferenceInMS = currentTime - this.lastTappedTime;

    this.bpm = this.numTaps / (timeDifferenceInMS / 1000);
  }
}

class BpmCounter {
  constructor() {
    this.numTaps = 0;
    this.bpm = 0;
    this.startTapTime = new Date();
  }

  restart() {
    this.numTaps = 0;
    this.bpm = 0;
  }

  tick() {
    if (this.numTaps === 0) {
      this.startTapTime = new Date();
    }

    this.numTaps++;

    let currentTime = new Date();

    let timeDifferenceInMS = currentTime - this.startTapTime;

    this.bpm = this.numTaps / (timeDifferenceInMS / 1000) * 60;
  }
}

Fix bug where the BPM calculated was wrong


// @flow
module.exports = {
  env: {
    jest: true,
  },
  plugins: ['testing-library'],
  extends: ['plugin:jest/recommended', 'plugin:testing-library/react'],
  rules: {
    'react/jsx-no-bind': 0,
    // This rule isn't useful because use Flow.
    'jest/valid-title': 0,

    // Adding more errors now
    'testing-library/no-manual-cleanup': 'error',
    'testing-library/no-wait-for-empty-callback': 'error',
    'testing-library/no-wait-for-snapshot': 'error',
    'testing-library/prefer-explicit-assert': 'error',
    'testing-library/prefer-presence-queries': 'error',
    'testing-library/prefer-wait-for': 'error',
  },
};

// @flow
module.exports = {
  env: {
    jest: true,
  },
  plugins: ['jest', 'testing-library'],
  extends: ['plugin:jest/recommended', 'plugin:testing-library/react'],
  rules: {
    'react/jsx-no-bind': 0,
    // This rule isn't useful because use Flow.
    'jest/valid-title': 0,

    // Adding more errors now
    'testing-library/no-manual-cleanup': 'error',
    'testing-library/no-wait-for-empty-callback': 'error',
    'testing-library/no-wait-for-snapshot': 'error',
    'testing-library/prefer-explicit-assert': 'error',
    'testing-library/prefer-presence-queries': 'error',
    'testing-library/prefer-wait-for': 'error',
  },
};

Correct configuration of the jest plugin


import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import Home from './components/Home';
import Programs from './components/Programs';
import Program from './components/Program';
import Login from './components/Login';
import Register from './components/Register';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';

import auth from './auth';

const requireAuth = (nextState, replaceState) => {
  if (!auth.loggedIn()) {
    replaceState({ nextPathname: nextState.location.pathname }, '/login');
  }
};

ReactDOM.render(
	<Router history={browserHistory}>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="programs" component={Programs} onEnter={requireAuth} />
      <Route path="programs/:id" component={Program} />
      <Route path="login" component={Login} />
      <Route path="register" component={Register} />
    </Route>
  </Router>
  , document.getElementById('root')
);

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.js';
import Home from './components/Home';
import Programs from './components/Programs';
import Program from './components/Program';
import Login from './components/Login';
import Register from './components/Register';
import { Router, Route, IndexRoute } from 'react-router';

import auth from './auth';

import createHistory from 'history/lib/createHashHistory';

let history = createHistory({
  queryKey: false
});

const requireAuth = (nextState, replaceState) => {
  if (!auth.loggedIn()) {
    replaceState({ nextPathname: nextState.location.pathname }, '/login');
  }
};

ReactDOM.render(
	<Router history={history}>
    <Route path="/" component={App}>
      <IndexRoute component={Home}/>
      <Route path="programs" component={Programs} onEnter={requireAuth} />
      <Route path="programs/:id" component={Program} />
      <Route path="login" component={Login} />
      <Route path="register" component={Register} />
    </Route>
  </Router>
  , document.getElementById('root')
);

Remove _k parameter from url. This is used for old browsers.


"use strict";

angular.module('arethusa.search').service('search', function(state, configurator) {
  var self = this;
  this.conf = configurator.configurationFor('search');
  this.name = this.conf.name;
  this.template = this.conf.template;

  this.queryTokens = function() {
    var tokens = self.tokenQuery.split(' ');
    var ids = arethusaUtil.inject([], tokens, function(memo, token) {
      arethusaUtil.pushAll(memo, self.strings[token]);
    });
    state.multiSelect(ids);
  };

  this.collectTokenStrings = function() {
    return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
      var str = token.string;
      if (! memo[str]) {
        memo[str] = [];
      }
      memo[str].push(id);
    });
  };

  this.init = function() {
    self.strings = self.collectTokenStrings();
  };
});

"use strict";

angular.module('arethusa.search').service('search', function(state, configurator) {
  var self = this;
  this.conf = configurator.configurationFor('search');
  this.name = this.conf.name;
  this.template = this.conf.template;
  this.queryByRegex = this.conf.regex;

  function findByRegex(str) {
    // We might need to escape some chars here, we need to try
    // this out more
    var regex = new RegExp(str, 'i');
    return arethusaUtil.inject([], self.strings, function(memo, string, ids) {
      if (string.match(regex)) {
        arethusaUtil.pushAll(memo, ids);
      }
    });
  }

  this.queryTokens = function() {
    if (self.tokenQuery === '') {
      return;
    }
    var tokens = self.tokenQuery.split(' ');
    var ids = arethusaUtil.inject([], tokens, function(memo, token) {
      var hits = self.queryByRegex ?  findByRegex(token) : self.strings[token];
      arethusaUtil.pushAll(memo, hits);
    });
    state.multiSelect(ids);
  };

  this.collectTokenStrings = function() {
    return arethusaUtil.inject({}, state.tokens, function(memo, id, token) {
      var str = token.string;
      if (! memo[str]) {
        memo[str] = [];
      }
      memo[str].push(id);
    });
  };

  this.init = function() {
    self.strings = self.collectTokenStrings();
  };
});

Enable regex searching in search plugin


(function() {

  Discourse.addInitializer(function () {

    var applySpoilers = function($post) {
      // text
      $('.spoiler:not(:has(img))', $post).removeClass('spoiler')
                                         .addClass('spoiled')
                                         .spoilText();
      // images
      $('.spoiler:has(img)', $post).removeClass('spoiler')
                                   .addClass('spoiled')
                                   .wrap("

")
                                   .spoilImage();
    };

    Em.run.next(function() {
      applySpoilers();

      Discourse.PostView.prototype.on("postViewInserted", applySpoilers);
      Discourse.ComposerView.prototype.on("previewRefreshed", applySpoilers);
      Discourse.UserStreamView.prototype.on("didInsertElement", applySpoilers);
    });

  });

})();

(function() {

  Discourse.addInitializer(function () {

    var applySpoilers = function($post) {
      // text
      $('.spoiler:not(:has(img))', $post).removeClass('spoiler')
                                         .addClass('spoiled')
                                         .spoilText();
      // images
      $('.spoiler:has(img)', $post).removeClass('spoiler')
                                   .addClass('spoiled')
                                   .wrap("

")
                                   .spoilImage();
    };

    Em.run.next(function() {
      applySpoilers();

      Discourse.PostView.prototype.on("postViewInserted", applySpoilers);
      Discourse.ComposerView.prototype.on("previewRefreshed", applySpoilers);
      Discourse.UserStreamView.prototype.on("didInsertElement", applySpoilers);
      Discourse.EmbeddedPostView.prototype.on("didInsertElement", applySpoilers);
    });

  });

})();

Apply spoilers to embedded posts as well.


import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';

class CoursesPage extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.state = {
      course: { title: '' }
    };

    // For performance, bind in the constructor. Binding in the render
    // function will create a new instance of the function, and impacts
    // performance.
    this.onTitleChange = this.onTitleChange.bind(this);
    this.onClickSave = this.onClickSave.bind(this);
  }

  onTitleChange(event) {
    const course = this.state.course;
    course.title = event.target.value;
    this.setState({ course: course });
  }

  onClickSave() {
    this.props.actions.createCourse(this.state.course);

    this.setState({
      course: { title: '' }
    });
  }

  courseRow(course, index) {
    return
{course.title}
;
  }

  render() {
    return (

Courses

        {this.props.courses.map(this.courseRow)}

Add Course

        <input
          type="text"
          onChange={this.onTitleChange}
          value={this.state.course.title}/>

        <input
          type="submit"
          value="Save"
          onClick={this.onClickSave}/>

    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses
  };
}

function matchDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  };
}

CoursesPage.propTypes = {
  actions: PropTypes.object.isRequired,
  courses: PropTypes.array.isRequired
};

export default connect(mapStateToProps, matchDispatchToProps)(CoursesPage);

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as courseActions from '../../actions/courseActions';

class CoursesPage extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  courseRow(course, index) {
    return
{course.title}
;
  }

  render() {
    return (

Courses

        {this.props.courses.map(this.courseRow)}

    );
  }
}

function mapStateToProps(state, ownProps) {
  return {
    courses: state.courses
  };
}

function matchDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(courseActions, dispatch)
  };
}

CoursesPage.propTypes = {
  actions: PropTypes.object.isRequired,
  courses: PropTypes.array.isRequired
};

export default connect(mapStateToProps, matchDispatchToProps)(CoursesPage);

Remove course management concerns from course page


L.FreeRotateAction = L.EditAction.extend({
  initialize: function(map, overlay, options) {
    options = options || {};
    options.toolbarIcon = {
      svg: true,
      html: 'crop_rotate',
      tooltip: overlay.options.translation.freeRotateImage,
      className: 'freeRotate',
    };

    L.DistortableImage.action_map.f = '_freeRotateMode';
    L.EditAction.prototype.initialize.call(this, map, overlay, options);
  },

  addHooks: function() {
    var edit = this._overlay.editing;
    edit._freeRotateMode();
  },
});

L.FreeRotateAction = L.EditAction.extend({
  initialize(map, overlay, options) {
    options = options || {};
    options.toolbarIcon = {
      svg: true,
      html: 'crop_rotate',
      tooltip: overlay.options.translation.freeRotateImage,
      className: 'freeRotate',
    };

    L.DistortableImage.action_map.f = '_freeRotateMode';
    L.EditAction.prototype.initialize.call(this, map, overlay, options);
  },

  addHooks() {
    var edit = this._overlay.editing;
    edit._freeRotateMode();
  },
});

Update method creation syntax to ES6 FreeRotationAction.js


/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.RELATIONSHIP({
  cardinality: '*:*',
  sourceModel: 'foam.nanos.auth.Group',
  targetModel: 'foam.nanos.auth.Permission',
  forwardName: 'permissions',
  inverseName: 'groups',
  junctionDAOKey: 'localGroupPermissionJunctionDAO'
});

/*
foam.RELATIONSHIP({
  cardinality: '*:*',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.auth.Group',
  forwardName: 'groups',
  inverseName: 'users',
  sourceProperty: {
    hidden: true
  },
  targetProperty: {
    hidden: true
  }
});
*/

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.theme.Theme',
  targetModel: 'foam.nanos.auth.User',
  cardinality: '1:*',
  forwardName: 'users',
  inverseName: 'personalTheme',
  sourceProperty: {
    hidden: true,
    visibility: 'HIDDEN'
  }
});

/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.RELATIONSHIP({
  cardinality: '*:*',
  sourceModel: 'foam.nanos.auth.Group',
  targetModel: 'foam.nanos.auth.Permission',
  forwardName: 'permissions',
  inverseName: 'groups',
  junctionDAOKey: 'groupPermissionJunctionDAO'
});

/*
foam.RELATIONSHIP({
  cardinality: '*:*',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.auth.Group',
  forwardName: 'groups',
  inverseName: 'users',
  sourceProperty: {
    hidden: true
  },
  targetProperty: {
    hidden: true
  }
});
*/

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.theme.Theme',
  targetModel: 'foam.nanos.auth.User',
  cardinality: '1:*',
  forwardName: 'users',
  inverseName: 'personalTheme',
  sourceProperty: {
    hidden: true,
    visibility: 'HIDDEN'
  }
});

Revert target dao key to non-local groupPermissionJunctionDAO


define([
  'client/client_bootstrap',
  'client/controllers/dashboard'
],
function (bootstrap, DashboardController) {
  describe('client bootstrap', function () {

    var config;
    beforeEach(function () {
      spyOn(DashboardController.prototype, 'render');
      config = {
        'page-type': 'dashboard'
      };
    });

    it('instantiates a controller from config data', function () {
      var controller = bootstrap(config);

      expect(controller instanceof DashboardController).toBe(true);
      expect(controller.render).toHaveBeenCalled();
    });

    it('executes page preprocessors', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      bootstrap(config);
      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });
  });
});

define([
  'client/client_bootstrap',
  'client/controllers/dashboard'
],
function (bootstrap, DashboardController) {
  describe('client bootstrap', function () {

    var config;
    beforeEach(function () {
      spyOn(DashboardController.prototype, 'render');
      config = {
        'page-type': 'dashboard'
      };
      $('body').removeClass('ready loaded');
    });

    it('instantiates a controller from config data', function () {
      var controller = bootstrap(config);

      expect(controller instanceof DashboardController).toBe(true);
      expect(controller.render).toHaveBeenCalled();
    });

    it('executes page preprocessors on controller ready', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      var controller = bootstrap(config);

      expect(bootstrap.preprocessors[0]).not.toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).not.toHaveBeenCalled();
      controller.trigger('ready');
      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });

    it('executes page preprocessors immediately if no controller defined', function () {
      var originalPreprocessors = bootstrap.preprocessors;
      bootstrap.preprocessors = [
        jasmine.createSpy(),
        jasmine.createSpy()
      ];

      bootstrap({
        'page-type': 'no-contoller'
      });

      expect(bootstrap.preprocessors[0]).toHaveBeenCalled();
      expect(bootstrap.preprocessors[1]).toHaveBeenCalled();

      bootstrap.preprocessors = originalPreprocessors;
    });

    it('adds a ready class to the body on controller ready', function () {
      var controller = bootstrap(config);

      expect($('body').hasClass('ready')).toBe(false);
      controller.trigger('ready');
      expect($('body').hasClass('ready')).toBe(true);
    });

    it('adds ready class to body immediately if no controller defined', function () {
      bootstrap({
        'page-type': 'no-contoller'
      });

      expect($('body').hasClass('ready')).toBe(true);
    });
  });
});

Fix tests for client bootstrap


'use strict';

// We assume this JS is sourced at the end of any HTML, avoiding the
// need for a $(document).ready(…) call. But it really needs the
// document fully loaded to operated properly.

// TODO: put this in our own namespace, not in the window…

common_init();

start_checking_for_needed_updates();

function update_needed() {
    console.log('update needed…')
}

// Support for AJAX loaded modal window.
// Focuses on first input textbox after it loads the window.
$('[data-toggle="modal"]').click(function(e) {

    e.preventDefault();

    var url = $(this).attr('href');

    if (url.indexOf('#') == 0) {
        $(url).modal('open');

    } else {

        $.get(url, function(data) {
            $(data).modal().on("shown", function () {

                //$('input:visible:enabled:first').focus();
                //$first_input.select();

                var $first_input = $('input:visible:enabled').first();

                $first_input.focus(function() {
                    // OMG. Thanks http://stackoverflow.com/a/10576409/654755
                    this.selectionStart = this.selectionEnd = this.value.length;
                });

                $first_input.focus();

            }).on('hidden', function () {
                $(this).remove();
            });
        });
    }
});

'use strict';

// We assume this JS is sourced at the end of any HTML, avoiding the
// need for a $(document).ready(…) call. But it really needs the
// document fully loaded to operated properly.

// TODO: put this in our own namespace, not in the window…

common_init();

start_checking_for_needed_updates();

function update_needed() {
    console.log('update needed…')
}

function handle_modal(e) {

    e.preventDefault();

    var url = $(this).attr('href');

    if (url.indexOf('#') == 0) {
        $(url).modal('open');

    } else {

        $.get(url, function(data) {

            $(data).modal().on("shown", function () {

                //$('input:visible:enabled:first').focus();
                //$first_input.select();

                var $first_input = $('input:visible:enabled').first();

                $first_input.focus(function() {
                    // OMG. Thanks http://stackoverflow.com/a/10576409/654755
                    this.selectionStart = this.selectionEnd = this.value.length;
                });

                $first_input.focus();

            }).on('hidden', function () {
                $(this).remove();
            });
        });
    }
}

// Support for AJAX loaded modal window.
// Focuses on first input textbox after it loads the window.
$('[data-toggle="modal"]').click(handle_modal);

Make an anonymous JS func now named for re-usability.


angular.module('controllers', [])

.controller('NavCtrl', function ($scope) {

})

.controller('HomeCtrl', function ($scope, json) {

  var data = json.data
  var width = 200
  var max = 5
  var resizeTimer

  $scope.resolution = 0

  var size = function (callback) {
    var number = Math.round($(window).width() / width)
    if (number > max) number = max
    $scope.resolution = number
    if (arguments.length == 1) callback()
  }

  $scope.getColumns = function() {
    return new Array($scope.resolution);
  }

  $scope.chunk = function (column, resolution) {
    var round = -1
    return data.slice(0).filter(function (x, i, a) {
      round++
      if (round >= resolution) round = 0
      if (round == column) return true
    })
  }

  size()

  $(window).on('resize', function(e) {

    clearTimeout(resizeTimer)
    resizeTimer = setTimeout(size(function () {
      $scope.$apply()
    }), 250)

  })

})

angular.module('controllers', [])

.controller('NavCtrl', function ($scope, $state) {

  $scope.state = $state.current.name

})

.controller('HomeCtrl', function ($scope, json) {

  var data = json.data
  var width = 200
  var max = 5
  var resizeTimer

  $scope.resolution = 0

  var size = function (callback) {
    var number = Math.round($(window).width() / width)
    if (number > max) number = max
    $scope.resolution = number
    if (arguments.length == 1) callback()
  }

  $scope.getColumns = function() {
    return new Array($scope.resolution);
  }

  $scope.chunk = function (column, resolution) {
    var round = -1
    return data.slice(0).filter(function (x, i, a) {
      round++
      if (round >= resolution) round = 0
      if (round == column) return true
    })
  }

  size()

  $(window).on('resize', function(e) {

    clearTimeout(resizeTimer)
    resizeTimer = setTimeout(size(function () {
      $scope.$apply()
    }), 250)

  })

})

Add scope variable for current nav tab


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

    let _ = require('lodash');
    let projectAccessCache = {};
    return {
        find: find,
        clear: clear,
        validateAccess: validateAccess
    };

    // find looks up a given project in the project cache.
    // If the projectAccessCache hasn't been loaded it loads the
    // cache.
    function *find(project_id) {
        if (! _.isEmpty(projectAccessCache)) {
            return projectAccessCache[project_id];
        }

        projectAccessCache = yield access.allByProject();
        return projectAccessCache[project_id];
    }

    // clear will clear the current project cache. This is useful
    // when project permissions have been updated or a new project
    // has been created.
    function clear() {
        projectAccessCache = {};
    }

    // validateAccess checks if the user has access to the
    // given project. This method assumes that find was called
    // first so that the projectAccessCache was preloaded. If the
    // projectAccessCache is empty then it returns false (no access).
    function validateAccess(project_id, user) {
        if (_.isEmpty(projectAccessCache)) {
            return false;
        }
        if (!(project_id in projectAccessCache)) {
            return false;
        }
        let index = _.indexOf(projectAccessCache[project_id], function(a) {
            return a.user_id == user.id;
        });

        // when index !== -1 we found the given user in the project.
        return index !== -1;
    }
};

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

    let _ = require('lodash');
    let projectAccessCache = {};
    let adminUsersCache = {};
    return {
        find: find,
        clear: clear,
        validateAccess: validateAccess
    };

    // find looks up a given project in the project cache.
    // If the projectAccessCache hasn't been loaded it loads the
    // cache.
    function *find(project_id) {
        if (_.isEmpty(adminUsersCache)) {
            let adminUsers = yield access.adminUsers();
            adminUsers.forEach(function(user) {
                adminUsersCache[user.id] = user;
            });
        }

        if (! _.isEmpty(projectAccessCache)) {
            return projectAccessCache[project_id];
        }

        projectAccessCache = yield access.allByProject();
        return projectAccessCache[project_id];
    }

    // clear will clear the current project cache. This is useful
    // when project permissions have been updated or a new project
    // has been created.
    function clear() {
        projectAccessCache = {};
    }

    // validateAccess checks if the user has access to the
    // given project. This method assumes that find was called
    // first so that the projectAccessCache was preloaded. If the
    // projectAccessCache is empty then it returns false (no access).
    function validateAccess(project_id, user) {
        if (user.id in adminUsersCache) {
            return true;
        }

        if (_.isEmpty(projectAccessCache)) {
            return false;
        }

        if (!(project_id in projectAccessCache)) {
            return false;
        }
        let index = _.indexOf(projectAccessCache[project_id], function(a) {
            return a.user_id == user.id;
        });

        // when index !== -1 we found the given user in the project.
        return index !== -1;
    }
};

Load and test for admin users.


$(document).ready(function() {

  $('#show').on('click', displayList);
  $('#hide').on('click', hideList);
  $('.say').on('click', sayText);

  $(window).bind("pageshow", function(event) {
    if(event.originalEvent.persisted) {
      window.location.reload()
    }
  });
});

var displayList = function(event){
  event.preventDefault();
  $('#display').show();
}

var hideList = function(event){
  event.preventDefault();
  $('#display').hide();
}

var sayText = function(event){
  event.preventDefault();
  var text = $(this).parent().children('.spoken_text').text();
  var msg = new SpeechSynthesisUtterance(text);
  msg.rate = 0.9;
  window.speechSynthesis.speak(msg);
  // text = encodeURIComponent(text);
  // var url = "https://translate.google.com/translate_tts?ie=UTF-8&q=" + text + "&tl=en"
  // $('audio').attr('src', url).get(0).play();
}

$(document).ready(function() {

  $('#show').on('click', displayList);
  $('#hide').on('click', hideList);
  $('.say').on('click', sayText);

  $(window).bind("pageshow", function(event) {
    if(event.originalEvent.persisted) {
      window.location.reload()
    }
  });
});

var displayList = function(event){
  event.preventDefault();
  $('#display').show();
}

var hideList = function(event){
  event.preventDefault();
  $('#display').hide();
}

var sayText = function(event){
  event.preventDefault();
  var text = $(this).parent().children('.spoken_text').text();
  var msg = new SpeechSynthesisUtterance(text);
  msg.voice = speechSynthesis.getVoices().filter(function(voice) { return voice.name == 'Bruce'; })[0];
  msg.rate = 1.1;
  window.speechSynthesis.speak(msg);
  // text = encodeURIComponent(text);
  // var url = "https://translate.google.com/translate_tts?ie=UTF-8&q=" + text + "&tl=en"
  // $('audio').attr('src', url).get(0).play();
}

Change TTS voice to sound more like yoda, voice name = bruce


import {Command} from '@grid/core/command';
import {noop} from '../utility';

export class Composite {
	static func(list, reduce = noop, memo = null) {
		return (...args) => {
			for (const f of list) {
				memo = reduce(memo, f(...args));
			}

			return memo;
		};
	}

	static command(list) {
		return new Command({
			source: 'composite',
			canExecute: (...args) => {
				return list.reduce((memo, cmd) => memo || cmd.canExecute(...args), false);
			},
			execute: (...args) => {
				return list
					.filter(cmd => cmd.canExecute(...args))
					.reduce((memo, cmd) => cmd.execute(...args) || memo, false);
			}
		});
	}

	static list(list) {
		return list.reduce((memo, xs) => memo.concat(xs), []);
	}

	static object(list, memo = {}) {
		return Object.assign(memo, ...list);
	}
}

import {Command} from '@grid/core/command';
import {noop} from '../utility';

export class Composite {
	static func(list, reduce = noop, memo = null) {
		return (...args) => {
			for (const f of list) {
				memo = reduce(memo, f(...args));
			}

			return memo;
		};
	}

	static command(list) {
		return new Command({
			source: 'composite',
			canExecute: (...args) => {
				return list.reduce((memo, cmd) => memo || cmd.canExecute(...args), false);
			},
			execute: (...args) => {
				return list
					.filter(cmd => cmd.canExecute(...args))
					.reduce((memo, cmd) => cmd.execute(...args) || memo);
			}
		});
	}

	static list(list) {
		return list.reduce((memo, xs) => memo.concat(xs), []);
	}

	static object(list, memo = {}) {
		return Object.assign(memo, ...list);
	}
}

Set command reducer memo to undefined


/* global vfs */
import {
  TextureConfigurator, ArticlePackage,
  EditorSession,
  ArticleAPI, createEditorContext,
  VfsStorageClient, TextureArchive, InMemoryDarBuffer
} from 'substance-texture'

export default function setupTestArticleSession (opts = {}) {
  let config = new TextureConfigurator()
  config.import(ArticlePackage)
  // TODO: this could be a little easier
  let manuscriptConfig = config.getConfiguration('article.manuscript')

  // load the empty archive
  let storage = new VfsStorageClient(vfs, './data/')
  let archive = new TextureArchive(storage, new InMemoryDarBuffer(), {}, config)
  // ATTENTION: in case of the VFS loading is synchronous
  // TODO: make sure that this is always the case
  let archiveId = opts.archiveId || 'blank'
  archive.load(archiveId, () => {})
  let doc = archive.getDocument('manuscript')
  if (opts.seed) {
    // clear the body
    let body = doc.get('body')
    body.set('content', [])
    opts.seed(doc)
  }
  // NOTE: this indirection is necessary because we need to pass the context to parts of the context
  let contextProvider = {}
  let editorSession = new EditorSession('test-editor', doc, manuscriptConfig, contextProvider)
  let api = new ArticleAPI(editorSession, manuscriptConfig, archive)
  let context = Object.assign(createEditorContext(manuscriptConfig, editorSession), { api })
  // ... after the context is ready we can store it into the provider
  contextProvider.context = context

  return { context, editorSession, doc, archive, api }
}

/* global vfs */
import {
  TextureConfigurator, ArticlePackage,
  EditorSession,
  ArticleAPI, createEditorContext,
  VfsStorageClient, TextureArchive, InMemoryDarBuffer
} from 'substance-texture'

export default function setupTestArticleSession (opts = {}) {
  let config = new TextureConfigurator()
  config.import(ArticlePackage)

  // load the empty archive
  let storage = new VfsStorageClient(vfs, './data/')
  let archive = new TextureArchive(storage, new InMemoryDarBuffer(), {}, config)
  // ATTENTION: in case of the VFS loading is synchronous
  // TODO: make sure that this is always the case
  let archiveId = opts.archiveId || 'blank'
  archive.load(archiveId, () => {})
  let doc = archive.getDocument('manuscript')
  if (opts.seed) {
    // clear the body
    let body = doc.get('body')
    body.set('content', [])
    opts.seed(doc)
  }
  // NOTE: this indirection is necessary because we need to pass the context to parts of the context
  let contextProvider = {}
  let editorSession = new EditorSession('test-editor', doc, config, contextProvider)
  let api = new ArticleAPI(editorSession, archive, config)
  let context = Object.assign(createEditorContext(config, editorSession), { api })
  // ... after the context is ready we can store it into the provider
  contextProvider.context = context

  return { context, editorSession, doc, archive, api }
}

Fix test helper for setting up a test session.


'use strict';

angular.module('publicApp')
  .controller('UsersCtrl', ['$scope', '$http', '$location', '$route', '$routeParams', 'UserService', function ($scope, $http, $location, $route, $routeParams, $user) {
    $scope.user = $user;
    if ($user.isLogged) {
      if (($user.id == $routeParams.id) || $user.admin) {
        $scope.user.balances = [];
        $scope.user.ripple_transactions = [];
        $scope.user.external_transaction = [];
        $scope.user.external_accounts = [];
        $scope.user.ripple_addresses = [];

        getExternalTransactions();
        getRippleTransactions();
        getBalances();
        console.log($scope.user);
      } else {
        $location.path('/users/'+$user.id);
      }
    } else {
      $location.path('/login');
    }

    function getExternalTransactions() {
      $http.get('/api/v1/users/'+$user.id+'/external_transactions').success(function(resp){
        $scope.user.external_transactions = resp.external_transactions;
      });
    }

    function getRippleTransactions() {
      $http.get('/api/v1/users/'+$user.id+'/ripple_transactions').success(function(resp){
        $scope.user.ripple_transactions = resp.ripple_transactions;
      });
    }

    function getBalances() {
      $http.get('/api/v1/users/'+$user.id+'/balances').success(function(resp){
        $scope.user.balances = resp.balances;
      });
    }

  }]);

'use strict';

angular.module('publicApp')
  .controller('UsersCtrl', ['$scope', '$http', '$location', '$route', '$routeParams', 'UserService', function ($scope, $http, $location, $route, $routeParams, $user) {
    $scope.user = $user;
    if ($user.isLogged) {
      if (($user.id == $routeParams.id) || $user.admin) {
        $scope.user.balances = [];
        $scope.user.ripple_transactions = [];
        $scope.user.external_transaction = [];
        $scope.user.external_accounts = [];
        $scope.user.ripple_addresses = [];

        getExternalTransactions();
        getRippleTransactions();
        getBalances();
        console.log($scope.user);
      } else {
        $location.path('/users/'+$user.id);
      }
    } else {
      $location.path('/login');
    }

    function getExternalTransactions() {
      $http.get('/api/v1/external_transactions').success(function(resp){
        $scope.user.external_transactions = resp.external_transactions;
      });
    }

    function getRippleTransactions() {
      $http.get('/api/v1/ripple_transactions').success(function(resp){
        $scope.user.ripple_transactions = resp.ripple_transactions;
      });
    }

    function getBalances() {
      $http.get('/api/v1/balances').success(function(resp){
        $scope.user.balances = resp.balances;
      });
    }

  }]);

Update user links in angular to get resources.


var mongoose = require('mongoose'),
    ObjectId = mongoose.Schema.Types.ObjectId,
    Product = require('./product');

var menuSectionSchema = new mongoose.Schema({
    title: String,
    content: [Product.schema]
})

menuSectionSchema.methods = {}
menuSectionSchema.statics = {
    getByName: function(name, isLean, next) {
        this.findOne({
            name: name
        })
            .lean(isLean)
            .exec(next)
    }
}

module.exports = mongoose.model('MenuSection', menuSectionSchema)

var mongoose = require('mongoose'),
    ObjectId = mongoose.Schema.Types.ObjectId,
    Place = require('./place'),
    Product = require('./product');

var menuSectionSchema = new mongoose.Schema({
    title: String,
    content: [Product.schema]
})

menuSectionSchema.methods = {
    attachToPlace: function(placeId, next) {
        var self = this

        Place.findByIdAndUpdate(placeId, {
            $push: {
                menu: self.id
            }
        }, function(err, place) {
            if (err)
                return next(err)
            return next(null, place, self)
        })
    }
}
menuSectionSchema.statics = {
    getByName: function(name, isLean, next) {
        this.findOne({
            name: name
        })
            .lean(isLean)
            .exec(next)
    }
}

module.exports = mongoose.model('MenuSection', menuSectionSchema)

Attach menu sections to place


import SkypeService from '../services/skype';

export function initialize(application) {
    application.register('service:skype', SkypeService, { initialize: true, singleton: true });
}

export default {
  name: 'skype',
  initialize
};

import SkypeService from '../services/skype';

export function initialize(application) {
    application.register('service:skype', SkypeService, { initialize: true, singleton: true });
    window.App = application;
}

export default {
  name: 'skype',
  initialize
};

Add App global object for sanity


jest.dontMock('../util');
let {toCharCodeArray, simpleHashFn}  = require('../util');

describe('toCharCodeArray', function() {
 it('returns an array of proper char codes', function() {
   expect(toCharCodeArray('abr')).toEqual([97, 98, 114]);
   expect(toCharCodeArray('Brian R. Bondy')).toEqual([ 66, 114, 105, 97, 110, 32, 82, 46, 32, 66, 111, 110, 100, 121 ]);
 });
});

describe('simpleHashFn', function() {
 it('generates a simple hash function for the specified prime', function() {
   let h = simpleHashFn(2);
   expect(h([0])).toBe(0);
 });
});

jest.dontMock('../util');
let {toCharCodeArray, simpleHashFn, setBit, isBitSet}  = require('../util');

describe('toCharCodeArray', function() {
 it('returns an array of proper char codes', function() {
   expect(toCharCodeArray('abr')).toEqual([97, 98, 114]);
   expect(toCharCodeArray('Brian R. Bondy')).toEqual([ 66, 114, 105, 97, 110, 32, 82, 46, 32, 66, 111, 110, 100, 121 ]);
 });
});

describe('simpleHashFn', function() {
 it('generates a simple hash function for the specified prime', function() {
   let h = simpleHashFn(2);
   expect(h([0])).toBe(0);
 });
});

describe('setBit and isBitSet', function() {
 it('can set and read bits properly', function() {
   let a = new Uint8Array(10);
   // First bit in a byte
   expect(isBitSet(a, 0)).toBe(false);
   setBit(a, 0);
   expect(isBitSet(a, 0)).toBe(true);

   // Last bit in a byte
   expect(isBitSet(a, 7)).toBe(false);
   setBit(a, 7);
   expect(isBitSet(a, 7)).toBe(true);
   expect(isBitSet(a, 1)).toBe(false);
   expect(isBitSet(a, 2)).toBe(false);
   expect(isBitSet(a, 3)).toBe(false);
   expect(isBitSet(a, 4)).toBe(false);
   expect(isBitSet(a, 5)).toBe(false);
   expect(isBitSet(a, 6)).toBe(false);
   expect(isBitSet(a, 0)).toBe(true);

   // Second bit in non first byte
   expect(isBitSet(a, 9)).toBe(false);
   setBit(a, 9);
   expect(isBitSet(a, 9)).toBe(true);
   expect(isBitSet(a, 1)).toBe(false);
 });
});

Add tests for isBitSet and setBit


'use babel'

export default class StatusBarItem {
  constructor () {
    this.element = document.createElement('a')
    this.element.className = 'line-ending-tile'
    this.setLineEndings(new Set())
  }

  setLineEndings (lineEndings) {
    this.lineEndings = lineEndings
    this.element.textContent = lineEndingName(lineEndings)
  }

  hasLineEnding (lineEnding) {
    return this.lineEndings.has(lineEnding)
  }

  onClick (callback) {
    this.element.addEventListener('click', callback)
  }
}

function lineEndingName (lineEndings) {
  if (lineEndings.size > 1) {
    return 'Mixed'
  } else if (lineEndings.has('\n')) {
    return 'LF'
  } else if (lineEndings.has('\r\n')) {
    return 'CRLF'
  } else {
    return ''
  }
}

'use babel'

export default class StatusBarItem {
  constructor () {
    this.element = document.createElement('a')
    this.element.className = 'line-ending-tile inline-block'
    this.setLineEndings(new Set())
  }

  setLineEndings (lineEndings) {
    this.lineEndings = lineEndings
    this.element.textContent = lineEndingName(lineEndings)
  }

  hasLineEnding (lineEnding) {
    return this.lineEndings.has(lineEnding)
  }

  onClick (callback) {
    this.element.addEventListener('click', callback)
  }
}

function lineEndingName (lineEndings) {
  if (lineEndings.size > 1) {
    return 'Mixed'
  } else if (lineEndings.has('\n')) {
    return 'LF'
  } else if (lineEndings.has('\r\n')) {
    return 'CRLF'
  } else {
    return ''
  }
}

Add 'inline-block' to status-bar item for proper margin-left


var React = require('react');
var DrawingArea = require('./drawing_area');

module.exports = React.createClass({
  // TODO chat.offsetWidth or gameplan.offsetLeft (+5 border size of
  // drawingarea)
  render: function () {
    return (

          <DrawingArea offsetX={335} offsetY={5} />

        );
  }
});

var React = require('react');
var DrawingArea = require('./drawing_area');

module.exports = React.createClass({
  getInitialState: function () {
    return {
      offset: { x: 0, y: 0 }
    };
  },

  componentDidMount: function () {
    this.setState({
      offset: {
        x: this.refs.gameplan.getDOMNode().offsetLeft + 5, // +5 border size
        y: 5 // +5 border size
      }
    });
  },

  render: function () {
    return (

          <DrawingArea offsetX={this.state.offset.x} offsetY={this.state.offset.y} />

        );
  }
});

Set offset from gameplan offset


// Removes component props that shouldn't be set on the form control.
const cleanProps = (props) => {

    const {

        // These props come from formsy-react
        // https://github.com/christianalfoni/formsy-react/blob/6b167cbfe15a72430ff475a92a70273e67a663f5/src/main.js#L441
        mapping,
        validationErrors,
        onSubmit,
        onValid,
        onValidSubmit,
        onInvalid,
        onInvalidSubmit,
        onChange,
        reset,
        preventExternalInvalidation,
        onSuccess,
        onError,

        // These come from formsy-react, too?
        validationError,
        validations,

        // These props are used by this library.
        addonAfter,
        addonBefore,
        buttonAfter,
        buttonBefore,
        elementWrapperClassName,
        help,
        label,
        options,
        labelClassName,
        layout,
        rowClassName,
        rowLabel,
        validatePristine,

        ...rest
    } = props;

    return rest;

}

export { cleanProps };

// Removes component props that shouldn't be set on the form control.
const cleanProps = (props) => {

    const {

        // These props come from formsy-react
        // https://github.com/christianalfoni/formsy-react/blob/6b167cbfe15a72430ff475a92a70273e67a663f5/src/main.js#L441
        mapping,
        validationErrors,
        onSubmit,
        onValid,
        onValidSubmit,
        onInvalid,
        onInvalidSubmit,
        onChange,
        reset,
        preventExternalInvalidation,
        onSuccess,
        onError,

        // These come from formsy-react, too?
        validationError,
        validations,

        // These props are used by this library.
        addonAfter,
        addonBefore,
        buttonAfter,
        buttonBefore,
        elementWrapperClassName,
        help,
        label,
        options,
        labelClassName,
        layout,
        rowClassName,
        rowLabel,
        validatePristine,
        validateOnSubmit,

        ...rest
    } = props;

    return rest;

}

export { cleanProps };

Update for newly-added validateOnSubmit prop.