function setupNavigation() {
    var anchors = document.getElementsByTagName("a");
    var sub_navigation_bars = document.getElementsByClassName("sub-navigation");

    for (var i = 0; i < anchors.length; i++) {
        if (anchors[i].parentElement.id == "main-navigation" && anchors[i].innerHTML != "Home") {
            anchors[i].onmouseover = function(e) {
                for (var j = 0; j < sub_navigation_bars.length; j++) {
                    if (sub_navigation_bars[j].getAttribute("parent") == this.innerHTML) {
                        sub_navigation_bars[j].className = "sub-navigation";
                    } else {
                        sub_navigation_bars[j].className = "sub-navigation hidden";
                    }
                }
            };
        }
    }

    document.getElementById("navigation").onmouseleave = function(e) {
        var selected_anchors = document.getElementsByClassName("selected");
        var sub_navigation_bars = document.getElementsByClassName("sub-navigation");

        for (var i = 0; i < selected_anchors.length; i++) {
            if (selected_anchors[i].parentElement.id == "main-navigation") {
                for (var j = 0; j < sub_navigation_bars.length; j++) {
                    if (sub_navigation_bars[j].getAttribute("parent") == selected_anchors[i].innerHTML) {
                        sub_navigation_bars[j].className = "sub-navigation";
                    } else {
                        sub_navigation_bars[j].className = "sub-navigation hidden";
                    }
                }

                break;
            }
        }
    };
}

setupNavigation();

function setupNavigation() {
    if (document.getElementById("navigation") == null) {
        return;
    }

    var anchors = document.getElementsByTagName("a");
    var sub_navigation_bars = document.getElementsByClassName("sub-navigation");

    for (var i = 0; i < anchors.length; i++) {
        if (anchors[i].parentElement.id == "main-navigation" && anchors[i].innerHTML != "Home") {
            anchors[i].onmouseover = function(e) {
                for (var j = 0; j < sub_navigation_bars.length; j++) {
                    if (sub_navigation_bars[j].getAttribute("parent") == this.innerHTML) {
                        sub_navigation_bars[j].className = "sub-navigation";
                    } else {
                        sub_navigation_bars[j].className = "sub-navigation hidden";
                    }
                }
            };
        }
    }

    document.getElementById("navigation").onmouseleave = function(e) {
        var selected_anchors = document.getElementsByClassName("selected");
        var sub_navigation_bars = document.getElementsByClassName("sub-navigation");

        for (var i = 0; i < selected_anchors.length; i++) {
            if (selected_anchors[i].parentElement.id == "main-navigation") {
                for (var j = 0; j < sub_navigation_bars.length; j++) {
                    if (sub_navigation_bars[j].getAttribute("parent") == selected_anchors[i].innerHTML) {
                        sub_navigation_bars[j].className = "sub-navigation";
                    } else {
                        sub_navigation_bars[j].className = "sub-navigation hidden";
                    }
                }

                break;
            }
        }
    };
}

setupNavigation();

Make shapes reappear when navigating back to the homepage


const mainWindows = WindowManager.getAll('main');
const mainWindow = mainWindows[0];

let mini = false;
Emitter.on('mini', (event, state) => {
  mini = state.state;
});

const _save = () => {
  if (!mini) {
    Settings.set('position', mainWindow.getPosition());
    Settings.set('size', mainWindow.getSize());
  } else {
    const dimension = Math.max(...mainWindow.getSize());
    mainWindow.setSize(dimension, dimension);
    Emitter.sendToGooglePlayMusic('set:zoom', dimension / 310);
  }
};

mainWindow.on('move', _save);
mainWindow.on('resize', _save);

Emitter.on('eq:change', (event, details) => {
  const eq = Settings.get('eq', [1, 1, 1, 1, 1, 1]);
  eq[details.index] = details.value;
  Settings.set('eq', eq);
});

Emitter.on('audiooutput:set', (event, name) => {
  Settings.set('audiooutput', name);
});

Emitter.on('welcomed', (event, version) => {
  Settings.set('welcomed', version);
});

const mainWindows = WindowManager.getAll('main');
const mainWindow = mainWindows[0];

let mini = false;
Emitter.on('mini', (event, state) => {
  mini = state.state;
});

let resizeTimer;

const _save = () => {
  if (!mini) {
    Settings.set('position', mainWindow.getPosition());
    Settings.set('size', mainWindow.getSize());
  } else {
    const dimension = Math.max(...mainWindow.getSize());
    if (resizeTimer) clearTimeout(resizeTimer);
    resizeTimer = setTimeout(() => mainWindow.setSize(dimension, dimension), 100);
    Emitter.sendToGooglePlayMusic('set:zoom', dimension / 310);
  }
};

mainWindow.on('move', _save);
mainWindow.on('resize', _save);
mainWindow.on('maximize', (ev) => {
  if (mini) {
    mainWindow.unmaximize();
    ev.preventDefault();
    return false;
  }
});

Emitter.on('eq:change', (event, details) => {
  const eq = Settings.get('eq', [1, 1, 1, 1, 1, 1]);
  eq[details.index] = details.value;
  Settings.set('eq', eq);
});

Emitter.on('audiooutput:set', (event, name) => {
  Settings.set('audiooutput', name);
});

Emitter.on('welcomed', (event, version) => {
  Settings.set('welcomed', version);
});

Fix mini player resizing bugs


Template.dashboardLayout.rendered = function () {
  // Get reference to page body
  var body = $("body");

  // Make sure page fills viewport by triggering resize event
  body.trigger("resize");

  // Add skin-blue sidebar-mini classes to body
  body.addClass(function () {
    var bodyClasses = "skin-blue-light sidebar-mini";

    return bodyClasses;
  });
};

Template.dashboardLayout.rendered = function () {
  // Get reference to page body
  var body = $("body");

  // Make sure page fills viewport by triggering resize event
  body.trigger("resize");

  // Add skin-blue sidebar-mini classes to body
  body.addClass(function () {
    var bodyClasses = "layout-top-nav skin-blue-light sidebar-mini";

    return bodyClasses;
  });
};

Add layout-top-nav class to remove sidebar


import {store} from "./store/store";
import Photos from "./components/Photos.vue";
import Theater from "./components/Theater.vue";

export {
    store,
    Photos,
    Theater
};

Add semicolon at the end of the file


'use strict';

var _ = require('underscore'),
    events = require('./events'),
    qualitySelectorFactory = require('./components/QualitySelector'),
    sourceInterceptorFactory = require('./middleware/SourceInterceptor');

module.exports = function(videojs) {
   videojs = videojs || window.videojs;

   qualitySelectorFactory(videojs);
   sourceInterceptorFactory(videojs);

   videojs.hook('setup', function(player) {
      // Add handler to switch sources when the user requests a change
      player.on(events.QUALITY_SELECTED, function(event, newSource) {
         var sources = player.currentSources(),
             currentTime = player.currentTime(),
             isPaused = player.paused(),
             selectedSource;

         sources = _.map(sources, _.partial(_.omit, _, 'selected'));
         selectedSource = _.findWhere(sources, { src: newSource.src });
         // Note: `_.findWhere` returns a reference to an object. Thus the
         // following updates the original object in `sources`.
         selectedSource.selected = true;

         player.src(sources);

         player.one('loadeddata', function() {
            player.currentTime(currentTime);
            if (!isPaused) {
               player.play();
            }
         });
      });
   });
};

module.exports.EVENTS = events;

'use strict';

var _ = require('underscore'),
    events = require('./events'),
    qualitySelectorFactory = require('./components/QualitySelector'),
    sourceInterceptorFactory = require('./middleware/SourceInterceptor');

module.exports = function(videojs) {
   videojs = videojs || window.videojs;

   qualitySelectorFactory(videojs);
   sourceInterceptorFactory(videojs);

   videojs.hook('setup', function(player) {
      // Add handler to switch sources when the user requests a change
      player.on(events.QUALITY_SELECTED, function(event, newSource) {
         var sources = player.currentSources(),
             currentTime = player.currentTime(),
             isPaused = player.paused(),
             selectedSource;

         // Clear out any previously selected sources (see: #11)
         _.each(sources, function(source) {
            source.selected = false;
         });

         selectedSource = _.findWhere(sources, { src: newSource.src });
         // Note: `_.findWhere` returns a reference to an object. Thus the
         // following updates the original object in `sources`.
         selectedSource.selected = true;

         player.src(sources);

         player.one('loadeddata', function() {
            player.currentTime(currentTime);
            if (!isPaused) {
               player.play();
            }
         });
      });
   });
};

module.exports.EVENTS = events;

Use `selected = false`, over `_.omit` to deselect a source


module.exports = (db, Sequelize) => {
  const Post = db.define('posts', {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    body: Sequelize.TEXT,
    tags: Sequelize.STRING,
    title: Sequelize.STRING,
    type: Sequelize.STRING,
    address: Sequelize.STRING,
    city: Sequelize.STRING,
    state: Sequelize.STRING,
    zip: Sequelize.STRING,
  });

  return Post;
};

module.exports = (db, Sequelize) => {
  const Post = db.define('posts', {
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    body: Sequelize.TEXT,
    tags: Sequelize.STRING,
    title: Sequelize.STRING,
    type: Sequelize.STRING,
    address: Sequelize.STRING,
    city: Sequelize.STRING,
    state: Sequelize.STRING,
    zip: Sequelize.STRING,
    isDeleted: Sequelize.BOOLEAN,
  });

  return Post;
};

Add 'isDeleted' field to Post schema


'use strict';

/*
 * Module for handling and saving various different user preferences
 */

angular.module('settings')
.factory('settingsFactory', function ($localStorage, configFactory) {
    return {
        setInitialSettingsIfApplicable: function () {
            if ($localStorage.settings) return;

            $localStorage.settings = configFactory.getValue('initialUserSettings');
        },

        setSettings: function (settingOpt) {
            var keys   = _.keys(settingOpt);
            var values = _.values(settingOpt);
            var i = 0;

            for (i = 0; i < values.length; i++) {
                $localStorage.settings[keys[i]] = values[i];
            }
        },

        getSetting: function (settingKey) {
            return $localStorage.settings[settingKey];
        },
        getAllSettings: function () {
            return $localStorage.settings;
        }
    };
});

'use strict';

/*
 * Module for handling and saving various different user preferences
 */

angular.module('settings')
.factory('settingsFactory', function ($localStorage, configFactory) {
    return {
        setInitialSettingsIfApplicable: function () {
            var initialSettings = configFactory.getValue('initialUserSettings');

            if (!$localStorage.settings) {
                $localStorage.settings = {};
            }

            $localStorage.settings = _.defaults($localStorage.settings, initialSettings);
        },

        setSettings: function (settingOpt) {
            var keys   = _.keys(settingOpt);
            var values = _.values(settingOpt);
            var i = 0;

            for (i = 0; i < values.length; i++) {
                $localStorage.settings[keys[i]] = values[i];
            }
        },

        getSetting: function (settingKey) {
            return $localStorage.settings[settingKey];
        },
        getAllSettings: function () {
            return $localStorage.settings;
        }
    };
});

Add incremental default settings merging


/**
 * Define the `View` class as a jQuery plugin.
 */

var $ = require('jquery')
var View = require('./src/View')
var Template = require('./src/Template')

$.fn.view = function (options) {
  if (!this.length) return
  var view = new View(this, options)
  this.data('view', view)
  return view
}

$.fn.view.Template = Template

/**
 * Expose `View`.
 */

module.exports = View

/**
 * Define the `View` class as a jQuery plugin.
 */

var $ = require('jquery')
var View = require('./src/View')
var Template = require('./src/Template')

$.fn.view = function (options) {
  if (!this.length) return

  if (this.length > 1) {
    var views = []

    this.each(function () {
      views.push($.fn.view.call($(this), options))
    })

    return views
  }

  var view = new View(this, options)
  this.data('view', view)
  return view
}

$.fn.view.Template = Template

/**
 * Expose `View`.
 */

module.exports = View

Add support for multiple elements


import moment from "moment";

function initLocale(name, config) {
  try {
    moment.locale(name, config);
  } catch (error) {
    throw new Error(
      "Locale prop is not in the correct format. \n Locale has to be in form of object, with keys of name and config. " +
        error.message
    );
  }
}

function initMoment(date, locale) {
  if (locale && locale.name) {
    return moment(date).startOf("day").locale(locale.name);
  } else {
    return moment(date).startOf("day");
  }
}

function getStartOfWeek(moment, isoStartDay = 1) {
  let result = moment.clone().isoWeekday(isoStartDay);
  if (moment.isoWeekday() < isoStartDay) {
    return result.subtract(1, "week");
  } else {
    return result.isoWeekday(isoStartDay);
  }
}

function getSelectedDayOfWeek(selectedDate, isoStartDay) {
  let result = selectedDate.isoWeekday() - isoStartDay;
  if (result < 0) {
    result += 7;
  }
  return result;
}

export default { getSelectedDayOfWeek, getStartOfWeek, initLocale, initMoment };

import moment from "moment";

function initLocale(name, config) {
  try {
    moment.locale(name, config);
  } catch (error) {
    throw new Error(
      "Locale prop is not in the correct format. \n Locale has to be in form of object, with keys of name and config. " +
        error.message
    );
  }
}

function initMoment(date, locale) {
  if (locale && locale.name) {
    return moment(date).startOf("day").locale(locale.name);
  } else {
    return moment(date).startOf("day");
  }
}

function getStartOfWeek(moment, isoStartDay = 1) {
  let result = moment.clone().isoWeekday(isoStartDay);
  if (moment.isoWeekday() < isoStartDay) {
    return result.subtract(1, "week");
  } else {
    return result;
  }
}

function getSelectedDayOfWeek(selectedDate, isoStartDay) {
  let result = selectedDate.isoWeekday() - isoStartDay;
  if (result < 0) {
    result += 7;
  }
  return result;
}

export default { getSelectedDayOfWeek, getStartOfWeek, initLocale, initMoment };

Remove unnecessary call to isoWeekday


import Combokeys from 'combokeys'
import hookGlobalBind from 'combokeys/plugins/global-bind'

const combo = new Combokeys(document.documentElement)
hookGlobalBind(combo)

import {remote} from '../electron'

import createQueue from '../sagas/queue'

import {
  focusSearch,
  showNextTab,
  showPreviousTab,
  closeTab
} from '../actions'

const queue = createQueue('shortcuts')

export default function * shortcutsSaga () {
  yield* queue.exhaust()
}

function openDevTools () {
  const win = remote.getCurrentWindow()
  win.webContents.openDevTools({detach: true})
}

// dev shortcuts
combo.bindGlobal(['shift+f12', 'ctrl+shift+c'], openDevTools)
combo.bindGlobal(['shift+f5', 'shift+command+r'], () => window.location.reload())

// user shortcuts
combo.bindGlobal(['ctrl+f', 'command+f'], () => {
  queue.dispatch(focusSearch())
})

combo.bindGlobal(['ctrl+tab', 'ctrl+pagedown'], () => {
  queue.dispatch(showNextTab())
})

combo.bindGlobal(['ctrl+shift+tab', 'ctrl+pageup'], () => {
  queue.dispatch(showPreviousTab())
})

combo.bindGlobal(['ctrl+w'], () => {
  queue.dispatch(closeTab())
})

import Combokeys from 'combokeys'
import hookGlobalBind from 'combokeys/plugins/global-bind'

const combo = new Combokeys(document.documentElement)
hookGlobalBind(combo)

import {remote} from '../electron'

import createQueue from '../sagas/queue'

import {
  focusSearch,
  showNextTab,
  showPreviousTab,
  closeTab
} from '../actions'

const queue = createQueue('shortcuts')

export default function * shortcutsSaga () {
  yield* queue.exhaust()
}

function openDevTools () {
  const win = remote.getCurrentWindow()
  win.webContents.openDevTools({detach: true})
}

// dev shortcuts
combo.bindGlobal(['shift+f12', 'ctrl+shift+c', 'command+shift+c'], openDevTools)
combo.bindGlobal(['shift+f5', 'shift+command+r'], () => window.location.reload())

// user shortcuts
combo.bindGlobal(['ctrl+f', 'command+f'], () => {
  queue.dispatch(focusSearch())
})

combo.bindGlobal(['ctrl+tab', 'ctrl+pagedown'], () => {
  queue.dispatch(showNextTab())
})

combo.bindGlobal(['ctrl+shift+tab', 'ctrl+pageup'], () => {
  queue.dispatch(showPreviousTab())
})

combo.bindGlobal(['ctrl+w'], () => {
  queue.dispatch(closeTab())
})

Add 'command+shift+c' to open dev tools


Session.setDefault('searchBy', 'Navn');
Session.setDefault('searchQuery', '');

Template.Search.events({
  'click .dropdown-menu li': function(event, tmpl) {
    var chosenAttribute = $(event.currentTarget).text();
    Session.set('searchBy', chosenAttribute);
    Session.set('query', {});
    Session.set('searchQuery', '');
  },
  'keyup [type="text"]': function(event, template) {
    //escape regex
    var value = event.target.value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
    var field = attToField(Session.get('searchBy'));
    var query = {};
    //if number, use equals operator
    if(!isNaN(parseInt(value))) {
      query[field] = parseInt(value);
    }
    //if string, use contains operator with regex
    else {
      query[field] = { $regex: value, $options: 'i' };
    }
    //this triggers iron router to load the report subscription again
    Session.set('query', query);
    Session.set('searchQuery', value);
  }
});

Session.setDefault('searchBy', 'Navn');
Session.setDefault('searchQuery', '');

Template.Search.events({
  'click .dropdown-menu li': function(event, tmpl) {
    var chosenAttribute = $(event.currentTarget).text();
    Session.set('searchBy', chosenAttribute);
    Session.set('query', {});
    Session.set('searchQuery', '');
  },
  'keyup [type="text"]': function(event, template) {
    //escape regex
    var value = event.target.value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
    var field = attToField(Session.get('searchBy'));
    var query = {};
    //if number, use equals operator
    if(!isNaN(parseInt(value))) {
      query[field] = parseInt(value);
    }
    //if string, use contains operator with regex
    else {
      query[field] = { $regex: value, $options: 'i' };
    }
    //if the searchbox is empty we have to reset the query
    if(value == null || value == "")
      query = {};
    //this triggers iron router to load the report subscription again
    Session.set('query', query);
    Session.set('searchQuery', value);
  }
});

Fix for not showing all reports on backslash


var express = require("express");
var logfmt = require("logfmt");
var app = express();

app.use(logfmt.requestLogger());

app.get('/', function(req, res) {
  res.send('Hello World!');
});

var port = Number(process.env.PORT || 5000);
app.listen(port, function() {
  console.log("Listening on " + port);
});

var express = require("express");
var logfmt = require("logfmt");
var app = express();

app.use(logfmt.requestLogger());

app.use(express.static('dist'));

app.get('/', function(req, res) {
  res.send('Hello World!');
});

var port = Number(process.env.PORT || 5000);
app.listen(port, function() {
  console.log("Listening on " + port);
});

Allow static assets to be served.


Template.courseSidebarSection.helpers({
    'lessons': function () {
        // Get lesson IDs array
        var lessonIDs = this.lessonIDs;

        // Get lessons from database
        var lessons = Lessons.find({_id: {$in: lessonIDs}});

        return lessons;
    }
});

Template.courseSidebarSection.events({
    'click .editable-submit': function (event, template) {
        // Get the value of the inline editor
        var newName = template.find('input').value;

        // update the lesson in database
        Lessons.update(this._id, {$set: {'name': newName}});
    }
});

Update lesson title in database.


var gulp = require('gulp')
var join = require('path').join
var express = require('express')
var WebpackDevServer = require('webpack-dev-server')
var webpack = require('./support/webpack')
var PORT = process.env.PORT || (process.env.PORT = 3000)
var DEV_PORT = process.env.DEV_PORT || (process.env.DEV_PORT = 8080)

gulp.task('server', [
  'clean',
  'copy'
], function (done) {
  // Compilation watch tasks.
  gulp.watch('assets/**/*', ['copy:assets'])

  var server = new WebpackDevServer(webpack({
    devPort: DEV_PORT,
    production: false
  }), {
    contentBase: join(__dirname, '../build'),
    hot: true,
    filename: 'bundle.js',
    publicPath: '/js/',
    watchDelay: 300,
    stats: {
      colors: true,
      hash: false,
      chunks: false
    }
  })

  var app = require('../')

  app.listen(PORT, function (err) {
    return err ? done(err) : server.listen(DEV_PORT, '0.0.0.0', done)
  })
})

var gulp = require('gulp')
var join = require('path').join
var express = require('express')
var WebpackDevServer = require('webpack-dev-server')
var webpack = require('./support/webpack')
var PORT = process.env.PORT || (process.env.PORT = 3000)
var DEV_PORT = process.env.DEV_PORT || (process.env.DEV_PORT = 8080)

gulp.task('server', [
  'clean',
  'copy'
], function (done) {
  // Compilation watch tasks.
  gulp.watch('assets/**/*', ['copy:assets'])

  var server = new WebpackDevServer(webpack({
    devPort: DEV_PORT,
    production: false
  }), {
    contentBase: join(__dirname, '../build'),
    hot: true,
    filename: 'bundle.js',
    publicPath: '/js/',
    stats: {
      colors: true,
      hash: false,
      chunks: false
    }
  })

  var app = require('../')

  app.listen(PORT, function (err) {
    return err ? done(err) : server.listen(DEV_PORT, '0.0.0.0', done)
  })
})

Remove deprecated watch delay from web pack


function loadHeatmap(url) {
  var deferred = $.Deferred();

  $.getJSON(url, function(json) {
    $('#stations').removeClass('hidden');
    $('#loading').addClass('hidden');

    json.forEach(function(data) {
      count = Math.ceil(data.load / 10)
      $('#'+ data.direction +' .'+ data.station + ' a').append('

')
      $('#'+ data.direction +' .'+ data.station +' #indicator').append('');
    })
    deferred.resolve();
  });

  return deferred.promise();
}

function getColor(value){
    value = value / 100
    var hue=((1-value)*120).toString(10);
    return ["hsla(",hue,",100%,50%,0.8)"].join("");
}

Use a scale of colours from green to red


import issues from './issues'
import watch from './watch'
import webhooks from './webhooks'

const displayHelp = (bot, message) => bot.reply(message, `
  Here are some of the things I can do:
  - **list open tickets** — list open issues assigned to you or someone else. For \
     example, to list your own issues, try \`list my open issues\`. To list issues \
     for someone else try \`list open issues for George\`.
  - **create tickets** — I can create a new task, story, or bug for you. You \
     must specify the project, type of ticket and summary. For example: \
     \`create new TEST task "Add more features"\`.
  - **ticket status** — I can find the status of an existing ticket. You can \
     ask, for example, \`what is the status of TEST-12?\`.
  - **comment on a ticket** - To comment on an issue, you can tell me which ticket \
     and your comment: \`comment on TEST-12 "These features are important"\`
  - **help** — display this message

  I'll also post notifications to ${process.env.JIRA_WEBHOOK_ROOM} \
  when issues are created, assigned, or their status changes.
`)

export const handleJoin = (bot, message) =>
  bot.reply(message, 'This trusty JIRA bot is here to help.')

export default {
  displayHelp,
  handleJoin,
  issues,
  watch,
  webhooks
}

import issues from './issues'
import watch from './watch'
import webhooks from './webhooks'

const displayHelp = (bot, message) => bot.reply(message, `
  Here are some of the things I can do:
  - **list open tickets** — list open issues assigned to you or someone else. For \
    example, to list your own issues, try \`list my open issues\`. To list issues \
    for someone else try \`list open issues for George\`.
  - **create tickets** — I can create a new task, story, or bug for you. You \
    must specify the project, type of ticket and summary. For example: \
    \`create new TEST task "Add more features"\`.
  - **ticket status** — I can find the status of an existing ticket. You can \
    ask, for example, \`what is the status of TEST-12?\`.
  - **comment on a ticket** - To comment on an issue, you can tell me which ticket \
    and your comment: \`comment on TEST-12 "These features are important"\`
  - **watch a ticket** - I can notify you of changes to a particular ticket. \
    To receive notifications about "TEST-12", for example, you can say \
    \`start watching TEST-12\`. To stop receiving notifications, you can tell \
    me to stop watching a ticket: \`stop watching TEST-12\`. To see a list of \
    tickets I'm watching you can use: \`list watched tickets\`.
  - **help** — display this message
`)

export const handleJoin = (bot, message) =>
  bot.reply(message, 'This trusty JIRA bot is here to help.')

export default {
  displayHelp,
  handleJoin,
  issues,
  watch,
  webhooks
}

Update help message with watch commands


// setup canvas

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;

// function to generate random number

function random(min,max) {
  const num = Math.floor(Math.random()*(max-min)) + min;
  return num;
}

// setup canvas

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;

// function to generate random number

function random(min, max) {
  const num = Math.floor(Math.random() * (max - min + 1)) + min;
  return num;
}

Bring bouncing balls JS in line with tutorial text


/* global console */

var EditorControllerMixin = Ember.Mixin.create({
    //## Computed post properties
    isPublished: Ember.computed.equal('status', 'published'),
    isDraft: Ember.computed.equal('status', 'draft'),
    /**
     * By default, a post will not change its publish state.
     * Only with a user-set value (via setSaveType action)
     * can the post's status change.
     */
    willPublish: function (key, val) {
        if (val) {
            return val;
        }
        return this.get('isPublished');
    }.property('isPublished'),

    actions: {
        save: function () {
            var status = this.get('willPublish') ? 'published' : 'draft',
                self = this;

            this.set('status', status);
            this.get('model').save().then(function () {
                console.log('saved');
                self.notifications.showSuccess('Post status saved as <strong>' +
                    self.get('status') + '</strong>.');
            }, this.notifications.showErrors);
        },

        setSaveType: function (newType) {
            if (newType === 'publish') {
                this.set('willPublish', true);
            } else if (newType === 'draft') {
                this.set('willPublish', false);
            } else {
                console.warn('Received invalid save type; ignoring.');
            }
        }
    }
});

export default EditorControllerMixin;

/* global console */

var EditorControllerMixin = Ember.Mixin.create({
    //## Computed post properties
    isPublished: Ember.computed.equal('status', 'published'),
    isDraft: Ember.computed.equal('status', 'draft'),
    /**
     * By default, a post will not change its publish state.
     * Only with a user-set value (via setSaveType action)
     * can the post's status change.
     */
    willPublish: function (key, value) {
        if (arguments.length > 1) {
            return value;
        }
        return this.get('isPublished');
    }.property('isPublished'),

    actions: {
        save: function () {
            var status = this.get('willPublish') ? 'published' : 'draft',
                self = this;

            this.set('status', status);
            this.get('model').save().then(function () {
                console.log('saved');
                self.notifications.showSuccess('Post status saved as <strong>' +
                    self.get('status') + '</strong>.');
            }, this.notifications.showErrors);
        },

        setSaveType: function (newType) {
            if (newType === 'publish') {
                this.set('willPublish', true);
            } else if (newType === 'draft') {
                this.set('willPublish', false);
            } else {
                console.warn('Received invalid save type; ignoring.');
            }
        }
    }
});

export default EditorControllerMixin;

Fix Editor Save Button not allowing unpublish


var webpack = require('webpack');
var config = require('./webpack.config.js');

config.entry = [
  'webpack-dev-server/client?http://localhost:8888',
  'webpack/hot/only-dev-server'
].concat(config.entry);

config.plugins = [
  new webpack.NoErrorsPlugin()
];

config.module.loaders = [
  {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loaders: ['react-hot', 'babel?optional[]=es7.classProperties'],
  },
  {
    test: /\.css$/,
    loader: 'style-loader!css-loader'
  },
  {
    test: /\.scss$/,
    loaders: ['style', 'css', 'resolve-url', 'sass'],
    exclude: /node_modules/
  }
].concat(config.module.loaders);

config.devServer = {
  contentBase: "./public",
  noInfo: true, //  --no-info option
  historyApiFallback: true,
  hot: true,
  inline: true,
  port: 8888,
  watchOptions: {
    poll: true
  }
}

module.exports = config;

var webpack = require('webpack');
var config = require('./webpack.config.js');

config.entry = [
  'webpack-dev-server/client?http://localhost:8888',
  'webpack/hot/only-dev-server'
].concat(config.entry);

config.plugins = [
  new webpack.NoErrorsPlugin()
];

config.module.loaders = [
  {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loaders: ['react-hot', 'babel?optional[]=es7.classProperties'],
  },
  {
    test: /\.css$/,
    loader: 'style-loader!css-loader'
  },
  {
    test: /\.scss$/,
    loaders: ['style', 'css', 'resolve-url', 'sass'],
    exclude: /node_modules/
  }
].concat(config.module.loaders);

config.devServer = {
  contentBase: "./public",
  noInfo: true, //  --no-info option
  historyApiFallback: true,
  hot: true,
  host: '0.0.0.0',
  inline: true,
  port: 8888,
  watchOptions: {
    poll: true
  }
}

module.exports = config;

Make dev server accessible on lan


import {compose, createStore, applyMiddleware} from 'redux'
import {devTools, persistState} from 'redux-devtools'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '../reducers'

const finalCreateStore = compose(
  applyMiddleware(thunkMiddleware),
  // Provides support for DevTools
  devTools(),
  // Lets you write ?debug_session=<name> in address bar to persist debug sessions
  persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore)

export default finalCreateStore(rootReducer, {})

import {compose, createStore, applyMiddleware} from 'redux'
import {devTools, persistState} from 'redux-devtools'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '../reducers'
import {logger} from '../middleware'

const finalCreateStore = compose(
  applyMiddleware(thunkMiddleware),
  applyMiddleware(logger),
  // Provides support for DevTools
  devTools(),
  // Lets you write ?debug_session=<name> in address bar to persist debug sessions
  persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
)(createStore)

export default finalCreateStore(rootReducer, {})

Add logger middleware for console-logging dispatching


import { expect } from 'chai';
import { Deck } from '../model/Deck.js';

describe('Card Deck', () => {
    it('Should have a length of 52', () => {
        let deck = new Deck;
        expect(deck._cards.length).to.equal(52);
    });

    it('Should not contain any duplicates', () => {
        let deck = new Deck;
        expect(deck._cards.length).to.equal(new Set(deck._cards).size);
    });

});

import { expect } from 'chai';
import { Deck } from '../model/Deck.js';

describe('Card Deck', () => {
    it('Should have a length of 52', () => {
        let deck = new Deck;
        expect(deck._cards.length).to.equal(52);
    });

    it('Should not contain any duplicates', () => {
        let deck = new Deck;
        let flatValues = deck._cards.map(card => `${card.suit}${card.rank}`); // flatten to strings so we can compare using Set
        expect(deck._cards.length).to.equal(new Set(flatValues).size);
    });

});

Fix up the duplication check (wasn't working when trying to compare objects, as all objects were being treated as unique by Set)


import { capitalize, getRandomString } from '../../lib/helpers/string';

describe('string', () => {
    describe('getRandomString', () => {
        // We mock the random generator so the test itself...
        it('should generate a random string of length 16', () => {
            const result = getRandomString(16);
            expect(result).toEqual('ABCDEFGHIJKLMNOP');
        });
    });

    describe('capitalize', () => {
        it('should return empty string for non-string arguments', () => {
            const cases = [0, [], {}];
            const expected = ['', '', ''];
            expect(cases.map(capitalize)).toEqual(expected);
        });
        it('should capitalize strings as expected', () => {
            const cases = ['', 'n', 'A', 'NY', 'name', 'once upon a time...'];
            const expected = ['', 'N', 'A', 'NY', 'Name', 'Once upon a time...'];
            expect(cases.map(capitalize)).toEqual(expected);
        });
    });
});

import { capitalize, getRandomString, getInitial } from '../../lib/helpers/string';

describe('string', () => {
    describe('getRandomString', () => {
        // We mock the random generator so the test itself...
        it('should generate a random string of length 16', () => {
            const result = getRandomString(16);
            expect(result).toEqual('ABCDEFGHIJKLMNOP');
        });
    });

    describe('capitalize', () => {
        it('should return empty string for non-string arguments', () => {
            const cases = [0, [], {}];
            const expected = ['', '', ''];
            expect(cases.map(capitalize)).toEqual(expected);
        });
        it('should capitalize strings as expected', () => {
            const cases = ['', 'n', 'A', 'NY', 'name', 'once upon a time...'];
            const expected = ['', 'N', 'A', 'NY', 'Name', 'Once upon a time...'];
            expect(cases.map(capitalize)).toEqual(expected);
        });
    });

    describe('getInitial', () => {
        it('should handle empty parameter', () => {
            expect(getInitial()).toEqual('');
        });

        it('should handle uniq word', () => {
            expect(getInitial('熊猫')).toEqual('熊');
        });

        it('should return 2 first initials and capitalize it', () => {
            expect(getInitial('Lorem ipsum dolor sit amet')).toEqual('LI');
        });

        it('should handle emoji', () => {
            expect(getInitial('🐼')).toEqual('🐼');
        });
    });
});

Add test for getInitial function


module.exports = {
  port: 8001,
  //cdns: ['bootstrap', 'cdnjs', 'google', 'jsdelivr', 'jquery'],
  //cdns: ['bootstrap-cdn', 'jsdelivr', 'cdnjs', 'google'],
  cdns: ['bootstrap-cdn', 'jsdelivr', 'cdnjs'],
  //cdns: ['bootstrap','google'],
  db: 'db',
  cdnCollections: [
    {name: 'bootstrap-cdn', aliases: ['bootstrap']},
    {name: 'jsdelivr', aliases: ['jsdelivr']},
    {name: 'cdnjs', aliases: ['cdnjs']},
    //{name: 'google', aliases: ['google']}
  ],
  etagsCollection: 'etagsCollection',
  syncUrl: 'http://localhost:8000/data/',
  //syncUrl: 'http://jsdelivrapi-sync.aws.af.cm/data/',
  tasks: {
    sync: {minute: 5}
  },
  maxcdn: {
    alias: 'replace this',
    key: 'replace this',
    secret: 'replace this',
    zoneId: 0 // replace with some zone id
  },
  github: '' // replace with github token
};

module.exports = {
  port: 8001,
  //cdns: ['bootstrap', 'cdnjs', 'google', 'jsdelivr', 'jquery'],
  cdns: ['bootstrap-cdn', 'jsdelivr', 'cdnjs', 'google'],
  //cdns: ['bootstrap-cdn', 'jsdelivr', 'cdnjs'],
  //cdns: ['bootstrap','google'],
  db: 'db',
  cdnCollections: [
    {name: 'bootstrap-cdn', aliases: ['bootstrap']},
    {name: 'jsdelivr', aliases: ['jsdelivr']},
    {name: 'cdnjs', aliases: ['cdnjs']},
    {name: 'google', aliases: ['google']}
  ],
  etagsCollection: 'etagsCollection',
  syncUrl: 'http://localhost:8000/data/',
  //syncUrl: 'http://jsdelivrapi-sync.aws.af.cm/data/',
  tasks: {
    sync: {minute: 5}
  },
  maxcdn: {
    alias: 'replace this',
    key: 'replace this',
    secret: 'replace this',
    zoneId: 0 // replace with some zone id
  },
  github: '' // replace with github token
};

Bring google syncs back online


'use strict';

// modules > 3rd party
const _ = require('lodash');
const p = require('path');

// modules > gulp:utilities
const gulp = require('gulp');

const TASK_NAME = 'watch';
const { watch, rollup } = require('../config');

gulp.task(TASK_NAME, () => {
  _.forIn(watch, (value, key) => {
    if (value) gulp.watch(value, gulp.series(key));
  });

  _.forEach(rollup.entries, (entry) => {
    gulp.watch(`${p.resolve(rollup.src)}/**/*.{js,jsx}`, gulp.series(`rollup:${Array.isArray(entry) ? entry[0] : entry}`));
  });
});

'use strict';

// modules > 3rd party
const _ = require('lodash');
const p = require('path');

// modules > gulp:utilities
const gulp = require('gulp');

const TASK_NAME = 'watch';
const { watch, rollup } = require('../config');

gulp.task(TASK_NAME, () => {
  _.forIn(watch, (value, key) => {
    if (value) gulp.watch(value, gulp.series(key));
  });

  _.forEach(rollup.entries, (entry) => {
    const filename = Array.isArray(entry) ? entry[0] : entry;
    const dir = p.dirname(p.join(rollup.src, filename));
    gulp.watch(`${p.resolve(dir)}/**/*.{js,jsx}`, gulp.series(`rollup:${filename}`));
  });
});

Watch rollup bundles seperate dirs


Template.top.show = function(){
  return Session.equals('state', 'list');
};

Template.top.posts = function(){
  var posts = Posts.find({}, {sort: {score: -1}});
  return posts;
};

Template.top.rendered = function(){
};

Template.top.show = function(){
  return Session.equals('state', 'list');
};

Template.top.posts = function(){
  var posts = Posts.find({}, {sort: {submitted: -1}});
  return posts;
};

Template.top.rendered = function(){
};

Order posts by submitted as we render them.


import forge from 'node-forge';

const forgePki = forge.pki;
const forgeRsa = forge.pki.rsa;

function generateRsa(cb) {
  return forgeRsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
    if (err) {
      return cb(err);
    }

    return cb(null, {
      private: forgePki.privateKeyToPem(keypair.privateKey),
      public: forgePki.publicKeyToPem(keypair.publicKey)
    });
  });
}

function generateSignature(content, keypair, cb) {
  const md = forge.md.sha1.create();
  const privateKey = forgePki.privateKeyfromPem(keypair.private);
  md.update(content, 'utf8');
  const signature = privateKey.sign(md);
  return cb(null, signature, content);
}

export { generateRsa as default, generateSignature };

import forge from 'node-forge';

const forgePki = forge.pki;
const forgeRsa = forge.pki.rsa;

function generateRsa(cb) {
  return forgeRsa.generateKeyPair({ bits: 2048, workers: -1 }, (err, keypair) => {
    if (err) {
      return cb(err);
    }

    return cb(null, {
      private: forgePki.privateKeyToPem(keypair.privateKey),
      public: forgePki.publicKeyToPem(keypair.publicKey)
    });
  });
}

function generateSignature(content, keypair, cb) {
  const md = forge.md.sha1.create();
  const privateKey = forgePki.privateKeyFromPem(keypair.private);
  md.update(content, 'utf8');
  const signature = privateKey.sign(md);
  return cb(null, signature, content);
}

function validateSignature(signature, data, keypair, cb) {
  const publicKey = forgePki.publicKeyFromPem(keypair.public);
  const verified = publicKey.verify(data, signature);
  return cb(null, verified);
}

function encryptData(keypair, data, method = 'RSAES-PKCS1-V1_5', cb) {
  if (typeof method === 'function') {
    cb = method;  // eslint-disable-line no-param-reassign
  }
  const publicKey = forgePki.publicKeyFromPem(keypair.public);
  const encrypted = publicKey.encrypt(data, method);
  return cb(null, encrypted);
}

function decryptData(keypair, data, method = 'RSAES-PKCS1-V1_5', cb) {
  if (typeof method === 'function') {
    cb = method;  // eslint-disable-line no-param-reassign
  }
  const privateKey = forgePki.privateKeyFromPem(keypair.private);
  const decrypted = privateKey.decrypt(data, method);
  return cb(null, decrypted);
}

export { generateRsa as default, generateSignature, validateSignature, encryptData, decryptData };

Add some more encryption helpers


define('app/views/user_menu', ['app/views/templated', 'md5'],
    /**
     *  User Menu View
     *
     *  @returns Class
     */
    function (TemplatedView) {

        'user strict';

        return TemplatedView.extend({

            /**
             *  Properties
             */

            isNotCore: !IS_CORE,
            accountUrl: URL_PREFIX + '/account',
            gravatarURL: EMAIL && ('https://www.gravatar.com/avatar/' + md5(EMAIL) + '?d=' +
                  encodeURIComponent('https://mist.io/resources/images/sprite-images/user.png') +'&s=36'),

            /**
             *
             *  Actions
             *
             */

            actions: {

                meClicked: function(){
                    $('#user-menu-popup').popup('open');
                },

                loginClicked: function() {
                    $('#user-menu-popup').popup('close');
                    Ember.run.later(function() {
                        Mist.loginController.open();
                    }, 300);
                },

                logoutClicked: function() {
                    Mist.loginController.logout();
                }
            }
        });
    }
);

define('app/views/user_menu', ['app/views/templated', 'md5'],
    /**
     *  User Menu View
     *
     *  @returns Class
     */
    function (TemplatedView) {

        'user strict';

        return TemplatedView.extend({

            /**
             *  Properties
             */

            isNotCore: !IS_CORE,
            accountUrl: URL_PREFIX + '/account',
            //TODO: change the logo_splash.png to user.png
            gravatarURL: EMAIL && ('https://www.gravatar.com/avatar/' + md5(EMAIL) + '?d=' +
                  encodeURIComponent('https://mist.io/resources/images/user.png') +'&s=36'),

            /**
             *
             *  Actions
             *
             */

            actions: {

                meClicked: function(){
                    $('#user-menu-popup').popup('open');
                },

                loginClicked: function() {
                    $('#user-menu-popup').popup('close');
                    Ember.run.later(function() {
                        Mist.loginController.open();
                    }, 300);
                },

                logoutClicked: function() {
                    Mist.loginController.logout();
                }
            }
        });
    }
);

Revert "Fix url for default user image"


var command = {
  command: "help",
  description: "Display information about a given command",
  userHelp: {
    usage: "truffle help <command>",
    parameters: [],
  },
  builder: {},
  run: function (options, callback) {
    var commands = require("./index");
    if (options._.length === 0) {
      this.displayHelpInformation("help");
      return;
    }
    var selectedCommand = options._[0];

    if (commands[selectedCommand]) {
      this.displayHelpInformation(selectedCommand);
    } else {
      console.log(`\n  Cannot find the given command '${selectedCommand}'`);
      console.log("  Please ensure your command is one of the following: ");
      Object.keys(commands).sort().forEach((command) => console.log(`      ${command}`))
      console.log("");
    }
  },
  displayHelpInformation: function (selectedCommand) {
    var commands = require("./index");
    var commandHelp = commands[selectedCommand].userHelp;
    console.log(`\n  COMMAND NAME: ${commands[selectedCommand].command}`);
    console.log(`  DESCRIPTION:  ${commands[selectedCommand].description}`);
    console.log(`  USAGE:        ${commandHelp.usage}`);

    if (commandHelp.parameters.length > 0) {
      console.log(`  PARAMETERS: `);
      commandHelp.parameters.forEach((parameter) => console.log(`                ${parameter.parameter}: ${parameter.description}`));
    }
    console.log("");
  }
}

module.exports = command;

var command = {
  command: "help",
  description: "Display information about a given command",
  userHelp: {
    usage: "truffle help <command>",
    parameters: [],
  },
  builder: {},
  run: function (options, callback) {
    var commands = require("./index");
    if (options._.length === 0) {
      this.displayHelpInformation("help");
      return;
    }
    var selectedCommand = options._[0];

    if (commands[selectedCommand]) {
      this.displayHelpInformation(selectedCommand);
    } else {
      console.log(`\n  Cannot find the given command '${selectedCommand}'`);
      console.log("  Please ensure your command is one of the following: ");
      Object.keys(commands).sort().forEach((command) => console.log(`      ${command}`));
      console.log("");
    }
  },
  displayHelpInformation: function (selectedCommand) {
    var commands = require("./index");
    var commandHelp = commands[selectedCommand].userHelp;
    console.log(`\n  COMMAND NAME: ${commands[selectedCommand].command}`);
    console.log(`  DESCRIPTION:  ${commands[selectedCommand].description}`);
    console.log(`  USAGE:        ${commandHelp.usage}`);

    if (commandHelp.parameters.length > 0) {
      console.log(`  PARAMETERS: `);
      commandHelp.parameters.forEach((parameter) => {
        console.log(`                ${parameter.parameter}: ${parameter.description}`);
      });
    }
    console.log("");
  }
}

module.exports = command;

Add semicolon and rewrite line to be more readable


jest.dontMock('../MountPoint');
jest.dontMock('../MountService');
/* eslint-disable no-unused-vars */
const React = require('react');
/* eslint-enable no-unused-vars */
const MountService = require('../MountService');

describe('MountService', function () {

  xdescribe('hooks', function () {

    beforeEach(function () {
      this.registerAction = jasmine.createSpy('registerAction');
      this.listenerAction = jasmine.createSpy('listenerAction');
      MountService.registerComponent('foo', this.registerAction, 11);
      MountService.addListener('foo', this.listenerAction, 11);
    });

    afterEach(function () {
      MountService.removeListener('foo', this.listenerAction);
      MountService.unregisterComponent('foo', this.registerAction);
    });

    it('calls registered filter when getContent is called', function () {
      MountService.getContent('foo',
bar
, {foo: 'bar'});
      expect(this.registerAction).toHaveBeenCalledWith(
bar
, {foo: 'bar'});
    });

    it('doesn\'t call filter when not registered anymore', function () {
      MountService.unregisterComponent('foo', this.registerAction);
      MountService.getContent('foo',
bar
, {foo: 'bar'});
      expect(this.registerAction).not.toHaveBeenCalled();
    });

    it('calls action when something registers', function () {
      MountService.registerComponent('foo', this.registerAction, 10);
      expect(this.listenerAction).toHaveBeenCalled();
    });

    it('calls action when something unregisters', function () {
      MountService.unregisterComponent('foo', this.registerAction, 10);
      expect(this.listenerAction).toHaveBeenCalled();
    });

    it('doesn\'t call action before anything registers or unregisters', function () {
      expect(this.listenerAction).not.toHaveBeenCalled();
    });

  });

});

jest.dontMock('../MountPoint');
jest.dontMock('../MountService');
/* eslint-disable no-unused-vars */
const React = require('react');
/* eslint-enable no-unused-vars */

describe('MountService', function () {});

Remove incompatible mount service test


var List = require("../");

describe('List', function() {

    describe('count', function() {

        it('should return correct count (without filter)', function() {
            var l = new List();

            l.collection.reset([
                {
                    'test': 1
                },
                {
                    'test': 2
                },
                {
                    'test': 3
                }
            ]);

            expect(l.count()).to.equal(3);
        });

        it('should return correct count (with filter)', function() {
            var l = new List({
                filter: function(model) {
                    return model.get("test") >= 2;
                }
            });

            l.collection.reset([
                {
                    'test': 1
                },
                {
                    'test': 2
                },
                {
                    'test': 3
                }
            ]);

            expect(l.count()).to.equal(2);
        });

    });

});

var $ = require("jquery");
var List = require("../");

describe('List', function() {

    describe('count', function() {

        it('should return correct count (without filter)', function() {
            var l = new List();

            l.collection.reset([
                {
                    'test': 1
                },
                {
                    'test': 2
                },
                {
                    'test': 3
                }
            ]);

            expect(l.count()).to.equal(3);
        });

        it('should return correct count (with filter)', function() {
            var l = new List({
                filter: function(model) {
                    return model.get("test") >= 2;
                }
            });

            l.collection.reset([
                {
                    'test': 1
                },
                {
                    'test': 2
                },
                {
                    'test': 3
                }
            ]);

            expect(l.count()).to.equal(2);
        });

        it('should correcly be rendered', function() {
            var Item = List.Item.extend({
                render: function() {
                    this.html("test "+this.model.get('test'));
                    return this.ready();
                }
            });

            var TestList = List.extend({
                Item: Item
            });

            var l = new TestList({

            });

            l.collection.reset([
                {
                    'test': 1
                },
                {
                    'test': 2
                },
                {
                    'test': 3
                }
            ]);

            var $container = $("
", { 'class': "test"});
            $container.appendTo($("body"));
            l.appendTo($container);

            expect($(".test").html()).to.equal("

test 1

test 2

test 3

");
        });

    });

});

Add test for list display


// $Id$

(function ($)
  {
    Drupal.behaviors.navigation = {
      attach: function (context)
        {
          var time = 0;
          var timeout;

          $('#navigation li:not(.links .links li)', context)
            .focusin(function ()
              {
                $('#navigation .links .links:not(.links .links .links)', context)
                  .not($('.links', this))
                  .hide();

                $('.links:not(.links .links .links)', this).show();
              })
            .focusout(function ()
              {
                $('.links:not(.links .links .links)', this).hide();
              })
            .hover(function ()
              {
                clearTimeout(timeout);

                $('#navigation .links .links:not(.links .links .links)', context)
                  .not($('.links', this))
                  .hide();

                var li = this;
                timeout = setTimeout(function ()
                  {
                    $('.links:not(.links .links .links)', li).show();
                  }, 256);
              }, function ()
              {
                clearTimeout(timeout);

                var li = this;
                timeout = setTimeout(function ()
                  {
                    $('.links:not(.links .links .links)', li).hide();

                    time = 0;
                  }, time);
              });

          $('#navigation .links .links:not(.links .links .links)', context).mouseenter(function ()
            {
              time = 1024;
            });
        } };
  })(jQuery);

// $Id$

(function ($)
  {
    // Hack to avoid changing the default logo but
    // still keeping a clickable header...
    $(document).ready(function() {
        $("#header").click(function(event) {
          window.location = "/index.php/";
        });
    });

    Drupal.behaviors.navigation = {
      attach: function (context)
        {
          var time = 0;
          var timeout;

          $('#navigation li:not(.links .links li)', context)
            .focusin(function ()
              {
                $('#navigation .links .links:not(.links .links .links)', context)
                  .not($('.links', this))
                  .hide();

                $('.links:not(.links .links .links)', this).show();
              })
            .focusout(function ()
              {
                $('.links:not(.links .links .links)', this).hide();
              })
            .hover(function ()
              {
                clearTimeout(timeout);

                $('#navigation .links .links:not(.links .links .links)', context)
                  .not($('.links', this))
                  .hide();

                var li = this;
                timeout = setTimeout(function ()
                  {
                    $('.links:not(.links .links .links)', li).show();
                  }, 256);
              }, function ()
              {
                clearTimeout(timeout);

                var li = this;
                timeout = setTimeout(function ()
                  {
                    $('.links:not(.links .links .links)', li).hide();

                    time = 0;
                  }, time);
              });

          $('#navigation .links .links:not(.links .links .links)', context).mouseenter(function ()
            {
              time = 1024;
            });
        } };
  })(jQuery);

Add a hack to make clicking the header take you home


const functions = require('firebase-functions');
const cors = require('cors')({ origin: true });

const BASE = functions.config().legionassult.base;

const base = parseInt(+new Date(BASE) / 1000, 10);
const calcAssultTime = require('./utils/calcAssultTime')(base);

module.exports = function legionAssultTime() {
  return functions.https.onRequest((req, res) => {
    if (req.method !== 'GET') {
      res.status(403).send('Forbidden!');
    }

    cors(req, res, () => {
      /**
       * api parameters
       *
       * start - starting time (any Date object formate)
       * count - number of assult times returned
       * callback - jsonp support callback function name
       */

      const startFrom = (
        req.query.start
        ? +new Date(req.query.start)
        : +new Date()
      ) / 1000;

      const count = Number(req.query.count || 5);

      const assults = calcAssultTime(startFrom, count);
      res.status(200).jsonp({
        start: startFrom,
        count,
        assults
      });
    });
  });
};

const functions = require('firebase-functions');
const cors = require('cors');

const BASE = functions.config().legionassult.base;

const base = parseInt(+new Date(BASE) / 1000, 10);
const calcAssultTime = require('./utils/calcAssultTime')(base);

const corsOptions = {
  origin: true,
  methods: 'GET',
  exposedHeaders: 'Date'
};

module.exports = function legionAssultTime() {
  return functions.https.onRequest((req, res) => {
    if (req.method !== 'GET') {
      res.status(403).send('Forbidden!');
    }

    (cors(corsOptions))(req, res, () => {
      /**
       * api parameters
       *
       * start - starting time (any Date object formate)
       * count - number of assult times returned
       * callback - jsonp support callback function name
       */

      const startFrom = (
        req.query.start
        ? +new Date(req.query.start)
        : +new Date()
      ) / 1000;

      const count = Number(req.query.count || 5);

      const assults = calcAssultTime(startFrom, count);

      res
      .status(200)
      .set({
        'Cache-Control': 'public, max-age=3600'
      })
      .jsonp({
        start: startFrom,
        count,
        assults
      });
    });
  });
};

Fix assult api caching in sw


import * as React from "react";
import ReactTable from "react-table";
import ReactTableStyles from "../css/react-table";
import withFixedColumns from "react-table-hoc-fixed-columns";
const ReactTableFixedColumns = withFixedColumns(ReactTable);

export const DataResourceTransformGrid = ({
  data: { data, schema },
  height
}) => {
  const tableColumns = schema.fields.map((f, i) => ({
    Header: f.name,
    accessor: f.name,
    fixed: i === 0 && "left"
  }));

  return (
    <div style={{ width: "calc(100vw - 150px)" }}>
      <ReactTableFixedColumns
        data={data}
        columns={tableColumns}
        style={{
          height: `${height}px`
        }}
        className="-striped -highlight"
      />
      <style jsx>{ReactTableStyles}</style>
    </div>
  );
};

import * as React from "react";
import ReactTable from "react-table";
import ReactTableStyles from "../css/react-table";
import withFixedColumns from "react-table-hoc-fixed-columns";
const ReactTableFixedColumns = withFixedColumns(ReactTable);

export const DataResourceTransformGrid = ({
  data: { data, schema },
  height
}) => {
  const tableColumns = schema.fields.map((f, i) => ({
    Header: f.name,
    accessor: f.name,
    fixed: i === 0 && "left"
  }));

  return (
    <div style={{ width: "calc(100vw - 150px)" }}>
      <ReactTable
        data={data}
        columns={tableColumns}
        style={{
          height: `${height}px`
        }}
        className="-striped -highlight"
      />
      <style jsx>{ReactTableStyles}</style>
    </div>
  );
};

Disable ReactTableFixedColumns til prefixed uniqid


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs
//= require jquery.ui.all
//= require jquery-ui-sliderAccess
//= require jquery-ui-timepicker-addon
//= require underscore
//= require gmaps/google
//= require bootstrap
//= require forms
//= require static_pages
//= require omniauth_callbacks

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
//= require jquery
//= require jquery_ujs
//= require underscore
//= require gmaps/google
//= require forms
//= require static_pages
//= require omniauth_callbacks

Remove unneeded JS from public site


/**

Potential server side rendering strategy

1. ask flux dispatcher to start tracking new store requests
2. start the render - this is syncronis so any store request that happen during render are of concern
3. ask flux dispatcher to stop tracking and notify when all tracked request have been resolved
4. repeat set 1-3 till no more requests are being produced (cap max number of renders)
5. once no more requests are being produced take the render output and return page

Some modules may have data dependency causing a new module to be rendered.
These in turn may have more data dependencies.
Hence the need for step 4.

*/

require('node-jsx').install({harmony: true});

var fs = require("fs");
var React = require("react");
var App = require("../../client/app/app");
var router = require('express').Router({caseSensitive: true, strict: true});

//only read on startup
var template = fs.readFileSync(__dirname + "/../../client/app.html", {encoding:'utf8'});

function renderToHtml(route, callback){
  //render the app
  var body = React.renderComponentToString( App() );

  //merge body into template
  var html = template.replace(/<\/body>/, body + "</body>");

  process.nextTick(function(){
    callback(null, html);
  });
}

//wildcard route to pass to react client app
router.get('*', function(req, res) {
  renderToHtml(req.url, function(err, html){
    res.send(html);
  });
});

module.exports = router;

/**

Potential server side rendering strategy

1. ask flux dispatcher to start tracking new store requests
2. start the render - this is syncronis so any store request that happen during render are of concern
3. ask flux dispatcher to stop tracking and notify when all tracked request have been resolved
4. repeat set 1-3 till no more requests are being produced (cap max number of renders)
5. once no more requests are being produced take the render output and return page

Some modules may have data dependency causing a new module to be rendered.
These in turn may have more data dependencies.
Hence the need for step 4.

*/

require('node-jsx').install({harmony: true});

var fs = require("fs");
var React = require("react");
var App = require("../../client/app/components/app");
var router = require('express').Router({caseSensitive: true, strict: true});

//only read on startup
var template = fs.readFileSync(__dirname + "/../../client/app.html", {encoding:'utf8'});

function renderToHtml(route, callback){
  //render the app
  var body = React.renderComponentToString( App() );

  //merge body into template
  var html = template.replace(/<\/body>/, body + "</body>");

  process.nextTick(function(){
    callback(null, html);
  });
}

//wildcard route to pass to react client app
router.get('*', function(req, res) {
  renderToHtml(req.url, function(err, html){
    res.send(html);
  });
});

module.exports = router;

Fix path to enable the app to start


// @flow

import * as React from 'react';
import { Animated, StyleSheet, KeyboardAvoidingView } from 'react-native';

import SearchForm from './SearchForm';

type Props = {
  onSend: ({
    from: string,
    to: string,
    date: Date,
  }) => void,
};

export default class SearchHeader extends React.PureComponent<Props, {}> {
  state = {
    height: new Animated.Value(50),
    expanded: false,
  };

  render = () => (
    <KeyboardAvoidingView style={styles.container} behavior="padding">
      {/* TODO: this.state.expanded */}
      <SearchForm
        onSend={(from, to, date) =>
          this.props.onSend({
            from,
            to,
            date,
          })}
      />
    </KeyboardAvoidingView>
  );
}

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    zIndex: 1,
    bottom: 20,
    right: 20,
    margin: 10,
    backgroundColor: 'white',
    shadowColor: 'black',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    padding: 10,
  },
});

// @flow

import * as React from 'react';
import {
  Animated,
  StyleSheet,
  View,
  Platform,
  StatusBar,
} from 'react-native';

import SearchForm from './SearchForm';

type Props = {
  onSend: ({
    from: string,
    to: string,
    date: Date,
  }) => void,
};

export default class SearchHeader extends React.PureComponent<Props, {}> {
  state = {
    height: new Animated.Value(50),
    expanded: false,
  };

  render = () => (
    <View style={styles.container} behavior="padding">
      {/* TODO: this.state.expanded */}
      <SearchForm
        onSend={(from, to, date) =>
          this.props.onSend({
            from,
            to,
            date,
          })}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    position: 'absolute',
    zIndex: 1,
    top: Platform.OS === 'ios' ? 40 : StatusBar.currentHeight,
    right: 10,
    left: 10,
    margin: 10,
    backgroundColor: 'white',
    shadowColor: 'black',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    padding: 10,
  },
});

Move search header to the top part of the screen


import React from 'react';
import {compose, withState} from 'recompose';

import logo from './logo.svg';
import './App.css';

const App = ({
  time,
  updateTime,
  date,
  updateDate
}) => {
  return (

Welcome to Spooky Timer

        To get started, pick a time.

        <input
          type="time"
          placeholder="time"
          value={time}
          onChange={(e) => updateTime(e.target.value)}
        />
        <input
          type="date"
          placeholder="date"
          value={date}
          onChange={(e) => updateDate(e.target.value)}
        />

        {time} + {date}

  );
}

export default compose (
  withState('time', 'updateTime', ""),
  withState('date', 'updateDate', ""),
)(App);

import React from 'react';
import {compose, withState} from 'recompose';
import Countdown from 'react-countdown-now';

import logo from './logo.svg';
import './App.css';

const App = ({
  time,
  updateTime,
  date,
  updateDate,
  going,
  go
}) => {
  return (

Welcome to Spooky Timer

        To get started, pick a time.

        <input
          type="time"
          placeholder="time"
          value={time}
          onChange={(e) => updateTime(e.target.value)}
        />
        <input
          type="date"
          placeholder="date"
          value={date}
          onChange={(e) => updateDate(e.target.value)}
        />

        {time} + {date}

        {
          !going
          ? <button
            onClick={() => go(true)}
            disabled={!Date.parse(time + " " + date)}
            >
              Spooky Countdown
            </button>
          : <Countdown date={Date.parse(time + " " + date)} />
        }

  );
}

export default compose (
  withState('time', 'updateTime', ""),
  withState('date', 'updateDate', ""),
  withState('going', 'go', false)
)(App);

Add button to activate spooky countdown


require('es6-promise').polyfill();
const helper = require('./helper');

function api(baseUrl, config, method, params) {
  let url = baseUrl;
  const auth = new Buffer(`${config.username}:${config.apiKey}`).toString('base64');
  const authHeader = `Basic ${auth}`;
  const options = { method, headers: { Authorization: authHeader } };
  if (method === 'POST') {
    options.body = new helper.FormData();
    Object.keys(params).forEach((key) => {
      options.body.append(key, params[key]);
    });
  } else if (params) {
    const generateQueryParam = key => `${key}=${params[key]}`;
    const queryParams = Object.keys(params).map(generateQueryParam);
    url = `${url}?${queryParams.join('&')}`;
  }
  return helper.fetch(url, options).then(res => res.json());
}

module.exports = api;

require('es6-promise').polyfill();
const helper = require('./helper');

function api(baseUrl, config, method, params) {
  let url = baseUrl;
  const auth = Buffer.from(`${config.username}:${config.apiKey}`).toString('base64');
  const authHeader = `Basic ${auth}`;
  const options = { method, headers: { Authorization: authHeader } };
  if (method === 'POST') {
    options.body = new helper.FormData();
    Object.keys(params).forEach((key) => {
      options.body.append(key, params[key]);
    });
  } else if (params) {
    const generateQueryParam = key => `${key}=${params[key]}`;
    const queryParams = Object.keys(params).map(generateQueryParam);
    url = `${url}?${queryParams.join('&')}`;
  }
  return helper.fetch(url, options).then(res => res.json());
}

module.exports = api;

Replace deprecated Buffer() with Buffer.from().


#!/usr/bin/env node

import yargs from 'yargs';
import path from 'path';
import fs from 'fs';
import * as pose from '.';

const opts = yargs
.usage('$ pose <action> [options]')
.help('help')
.options({
  name: {
    alias: ['n'],
    default: path.basename(process.cwd()),
    desc: 'Name of template',
  },

  entry: {
    alias: ['e'],
    default: process.cwd(),
    defaultDesc: 'cwd',
    desc: 'Entry of template',
  },

  help: {
    alias: ['h'],
  },
}).argv;
const action = opts._[0];

if (!action) {
  console.log('Type "pose help" to get started.');
  process.exit();
}

[
  opts._pose = path.join(process.env.HOME, '.pose'),
  opts._templates = path.join(opts._pose, 'templates'),
].forEach(dir => {
  fs.mkdir(dir, () => null);
});

pose[action](opts);
export { action, opts };

#!/usr/bin/env node

import yargs from 'yargs';
import path from 'path';
import fs from 'fs';
import * as pose from '.';

const opts = yargs
.usage('$ pose <action> [options]')
.help('help')
.options({
  name: {
    alias: ['n'],
    default: path.basename(process.cwd()),
    desc: 'Name of template',
  },

  entry: {
    alias: ['e'],
    default: process.cwd(),
    defaultDesc: 'cwd',
    desc: 'Entry of template',
  },

  help: {
    alias: ['h'],
  },
}).argv;
const action = opts._[0];

if (!action) {
  console.log('Type "pose help" to get started.');
  process.exit();
}

[
  opts._pose = path.join(process.env.HOME, '.pose'),
  opts._templates = path.join(opts._pose, 'templates'),
].forEach(dir => {
  fs.mkdir(dir, () => null);
});

if (typeof pose[action] !== 'function') {
  pose[action]();
} else {
  console.error(`Unknown action "${action}"`);
}
export { action, opts };

Add safer function call for CLI


'use strict';

const assert = require('assert');
const nativeImage = require('electron').nativeImage;
const path = require('path');

describe('nativeImage module', () => {
  describe('createFromPath(path)', () => {
    it('normalizes the path', () => {
      const nonAbsolutePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`;
      const image = nativeImage.createFromPath(nonAbsolutePath);
      assert(!image.isEmpty());
      assert.equal(image.getSize().height, 190);
      assert.equal(image.getSize().width, 538);
    });
  });
});

'use strict';

const assert = require('assert');
const nativeImage = require('electron').nativeImage;
const path = require('path');

describe('nativeImage module', () => {
  describe('createFromPath(path)', () => {
    it('returns an empty image for invalid paths', () => {
      assert(nativeImage.createFromPath('').isEmpty());
      assert(nativeImage.createFromPath('does-not-exist.png').isEmpty());
    });

    it('loads images from paths relative to the current working directory', () => {
      const imagePath = `.${path.sep}${path.join('spec', 'fixtures', 'assets', 'logo.png')}`;
      const image = nativeImage.createFromPath(imagePath);
      assert(!image.isEmpty());
      assert.equal(image.getSize().height, 190);
      assert.equal(image.getSize().width, 538);
    })

    it('loads images from paths with `.` segments', () => {
      const imagePath = `${path.join(__dirname, 'fixtures')}${path.sep}.${path.sep}${path.join('assets', 'logo.png')}`;
      const image = nativeImage.createFromPath(imagePath);
      assert(!image.isEmpty());
      assert.equal(image.getSize().height, 190);
      assert.equal(image.getSize().width, 538);
    });

    it('loads images from path with `..` segments', () => {
      const imagePath = `${path.join(__dirname, 'fixtures', 'api')}${path.sep}..${path.sep}${path.join('assets', 'logo.png')}`;
      const image = nativeImage.createFromPath(imagePath);
      assert(!image.isEmpty());
      assert.equal(image.getSize().height, 190);
      assert.equal(image.getSize().width, 538);
    });
  });
});

Add specs for more image path cases


var express = require('express');
var expressWs = require('./models/utils/express-ws');

var gameRoute = require('./routes/game_route');
var TicTacToe = require('./models/games/tictactoe');

var app = expressWs(express());

app.use('/tictactoe', gameRoute(TicTacToe));

var server = app.listen(3000, function () {
  console.log('Example app listening on port %s', server.address().port);
});

var express = require('express');
var expressWs = require('./models/utils/express-ws');

var gameRoute = require('./routes/game_route');
var TicTacToe = require('./models/games/tictactoe');

var app = expressWs(express());

app.use('/tictactoe', gameRoute(TicTacToe));

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('An internal error occurred');
});

var server = app.listen(3000, function () {
  console.log('Example app listening on port %s', server.address().port);
});

Add some error handling in Express


const tjbot = require('./tjbotlib');
const config = require('./config/configs');

const credentials = config.credentials;
const WORKSPACEID = config.conversationWorkspaceId;

const hardware = ['microphone', 'speaker'];

const configuration = {
  listen: {
    microphoneDeviceId: 'plughw:1,0',
    inactivityTimeout: 60,
    language: 'pt-BR'
  },
  wave: {
    servoPin: 7
  },
  speak: {
    language: 'pt-BR'
  },
  verboseLogging: true
};

const tj = new tjbot(hardware, configuration, credentials);

tj.listen((msg) => {

  // send to the conversation service
  tj.converse(WORKSPACEID, turn, (response) => {
    // speak the result
    tj.speak(response.description);
  });

  console.log('Message logger', msg);

  // check to see if they are talking to TJBot
  const name = msg.split(' ')[0].match(/(sara|vara|sarah)/i)[0];
  if (name) {
    // remove our name from the message
    const turn = msg.replace(/(sara|vara|sarah)/i, '');

    console.log('Turn logger', turn);
    // send to the conversation service
    tj.converse(WORKSPACEID, turn, (response) => {
      // speak the result
      tj.speak(response.description);
    });
  }
})

const tjbot = require('./tjbotlib');
const config = require('./config/configs');

const credentials = config.credentials;
const WORKSPACEID = config.conversationWorkspaceId;

const hardware = ['microphone', 'speaker'];

const configuration = {
  listen: {
    microphoneDeviceId: 'plughw:1,0',
    inactivityTimeout: 60,
    language: 'pt-BR'
  },
  wave: {
    servoPin: 7
  },
  speak: {
    language: 'pt-BR'
  },
  verboseLogging: true
};

const tj = new tjbot(hardware, configuration, credentials);

tj.listen((msg) => {

  // send to the conversation service
  tj.converse(WORKSPACEID, turn, (response) => {
    // speak the result
    tj.speak(response.description);
  });

  console.log('Message logger', msg);

  // check to see if they are talking to TJBot
  const name = msg.split(' ')[0].match(/(sara|vara|sarah|rara)/i)[0];
  if (name) {

    // send to the conversation service
    tj.converse(WORKSPACEID, msg, (response) => {
      // speak the result
      tj.speak(response.description);
    });
  }
})

Remove to send to watson


var http = require( 'http' ),
    url = require( 'url' ),
    todos = [];

var server = http.createServer( function( req, res ) {

  switch( req.method ) {

    case 'POST':
      var todo = '';
      req.setEncoding( 'utf8' );
      req.on( 'data', function( chunk ) {
        todo += chunk;
      });
      req.on( 'end', function() {
        todos.push( todo );
        res.end( 'Todo added!\n' );
      });
      break;

    case 'GET':
      todos.forEach( function( todo, index ) {
        res.write( index + ' - ' + todo + '\n' );
      });
      res.end();
      break;
  }
});

server.listen( 3000 );

var http = require( 'http' ),
    url = require( 'url' ),
    todos = [];

var server = http.createServer( function( req, res ) {

  switch( req.method ) {

    case 'POST':
      var todo = '';
      req.setEncoding( 'utf8' );
      req.on( 'data', function( chunk ) {
        todo += chunk;
      });
      req.on( 'end', function() {
        todos.push( todo );
        res.end( 'Todo added!\n' );
      });
      break;

    case 'GET':
      var body = todos.map( function( todo, index ) {
        return index + ' - ' + todo ;
      }).join( '\n' );
      // Setting Content-Length disables Node chunked encoding.
      // Less data needs to be transfered this way
      res.setHeader( 'Content-Length', Buffer.byteLength( body ) );
      res.setHeader( 'Content-Type', 'text/plain; charset="utf-8"' );
      res.end( body );
      break;
  }
});

server.listen( 3000 );

Add 'Content-Length' to the Get method to pimprove perfomance.


const port = process.env.PORT || 3000;
require("./dist/server/app").start(PORT);

const PORT = process.env.PORT || 3000;
require("./dist/server/app").start(PORT);

Fix broken variable name. Whoops


/* globals module */

var EOL = require('os').EOL;

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

  afterInstall: function() {
    return this.addBowerPackageToProject('blanket', '~1.1.5')

        // Modify tests/index.html to include the blanket options after the application
        .then(function() {
            return this.insertIntoFile(
                'tests/index.html',
                '    <script src="assets/blanket-options.js"></script>',
                { after: '<script src="assets/' + this.project.config().modulePrefix + '.js"></script>' + EOL }
            );
        }.bind(this))

        // Modify tests/index.html to include the blanket loader after the blanket options
        .then(function() {
            return this.insertIntoFile(
                'tests/index.html',
                '    <script src="assets/blanket-loader.js"></script>',
                { after: '<script src="assets/blanket-options.js"></script>' + EOL }
            );
        }.bind(this))

        // modify the blanket reporter styles so it's not covered by the ember testing container
        .then(function() {
          return this.insertIntoFile(
              'tests/index.html',
              '    <style>#blanket-main { position: relative; z-index: 99999; }</style>',
              { after: '<link rel="stylesheet" href="assets/test-support.css">' + EOL }
          );
        }.bind(this));
  }
};

/* globals module */

var EOL = require('os').EOL;

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

  afterInstall: function() {
    return this.addBowerPackageToProject('blanket', '~1.1.5', {saveDev: true})

        // Modify tests/index.html to include the blanket options after the application
        .then(function() {
            return this.insertIntoFile(
                'tests/index.html',
                '    <script src="assets/blanket-options.js"></script>',
                { after: '<script src="assets/' + this.project.config().modulePrefix + '.js"></script>' + EOL }
            );
        }.bind(this))

        // Modify tests/index.html to include the blanket loader after the blanket options
        .then(function() {
            return this.insertIntoFile(
                'tests/index.html',
                '    <script src="assets/blanket-loader.js"></script>',
                { after: '<script src="assets/blanket-options.js"></script>' + EOL }
            );
        }.bind(this))

        // modify the blanket reporter styles so it's not covered by the ember testing container
        .then(function() {
          return this.insertIntoFile(
              'tests/index.html',
              '    <style>#blanket-main { position: relative; z-index: 99999; }</style>',
              { after: '<link rel="stylesheet" href="assets/test-support.css">' + EOL }
          );
        }.bind(this));
  }
};

Move blanket into bower's devDependencies.


self.MonacoEnvironment = {
	baseUrl: 'http://robmhoward.github.io/excel/script/'
};
importScripts('http://robmhoward.github.io/excel/script/vs/base/worker/workerMain.js');

self.MonacoEnvironment = {
	baseUrl: 'https://robmhoward.github.io/excel/script/'
};
importScripts('https://robmhoward.github.io/excel/script/vs/base/worker/workerMain.js');

Update references to SSL (more)


//Bubble Sort

/*RULES
  Takes one parameter
    An array
  Sort it using bubble sort
  Return sorted array
*/

/*PSEUDOCODE
  1) Get the array.length, because you'll need this number later
  2) For loop
    2a) For each number, check the number directly to the right of it.
    2b) If num < numRight, move to the next num
    2c) Else if num > numRight, swap positions of each other.
    2d) Reach the end, then repeat the process.  Do it but this time, array.length - 1 (minus 1 more each loop) times
  3) Return bubble sorted array
*/

function bubbleSort(arr){
  var length = arr.length;

  for (var i = length; i > 0; i--){

    for (var j = 0; j < i; j++){
      var tempHolder = 0;
      if (arr[j] > arr[j + 1]){
        tempholder = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tempholder;
      }
    }

  }
return arr
}

Complete algorightm challenge with nested loop


'use strict';
const os = require('os');
const BinBuild = require('bin-build');
const log = require('logalot');
const bin = require('.');

const cpuNum = os.cpus().length;

bin.run(['-version'], err => {
	if (err) {
		log.warn(err.message);
		log.warn('mozjpeg pre-build test failed');
		log.info('compiling from source');

		const cfg = [
			'./configure --disable-shared --disable-dependency-tracking --with-jpeg8',
			`--prefix="${bin.dest()}" --bindir="${bin.dest()}" --libdir="${bin.dest()}"`
		].join(' ');

		const builder = new BinBuild()
			.src('https://github.com/mozilla/mozjpeg/releases/download/v3.1/mozjpeg-3.1-release-source.tar.gz')
			.cmd('autoreconf -fiv')
			.cmd(cfg)
			.cmd(`make -j${cpuNum}`)
			.cmd(`make install -j${cpuNum}`);

		return builder.run(err => {
			if (err) {
				log.error(err.stack);
				return;
			}

			log.success('mozjpeg built successfully');
		});
	}

	log.success('mozjpeg pre-build test passed successfully');
});

'use strict';
const os = require('os');
const BinBuild = require('bin-build');
const log = require('logalot');
const bin = require('.');

const cpuNum = os.cpus().length;

bin.run(['-version'], err => {
	if (err) {
		log.warn(err.message);
		log.warn('mozjpeg pre-build test failed');
		log.info('compiling from source');

		const cfg = [
			'./configure --disable-shared --disable-dependency-tracking --with-jpeg8',
			`--prefix="${bin.dest()}" --bindir="${bin.dest()}" --libdir="${bin.dest()}"`
		].join(' ');

		const builder = new BinBuild()
			.src('https://github.com/mozilla/mozjpeg/releases/download/v3.2/mozjpeg-3.2-release-source.tar.gz')
			.cmd('autoreconf -fiv')
			.cmd(cfg)
			.cmd(`make -j${cpuNum}`)
			.cmd(`make install -j${cpuNum}`);

		return builder.run(err => {
			if (err) {
				log.error(err.stack);
				return;
			}

			log.success('mozjpeg built successfully');
		});
	}

	log.success('mozjpeg pre-build test passed successfully');
});

Update mozjpeg URL to v3.2


const {join} = require('path');
const config = require('config').grunt;

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

    grunt.initConfig({
        package: grunt.file.readJSON('package.json'),
        ports:   config.ports,
        folders: config.folders,
        files:   config.files,
        deployed: {
            assets: config.folders.assets.split('/')[1],
            images: config.files.images.split('/')[0],
            fonts:  config.files.fonts.split('/')[0]
        }
    });
    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);
    /* -- load tasks placeholder -- */
};

'use strict';

const {join} = require('path');
const config = require('config').grunt;

module.exports = function(grunt) {
    grunt.initConfig({
        package: grunt.file.readJSON('package.json'),
        ports:   config.ports,
        folders: config.folders,
        files:   config.files,
        deployed: {
            assets: config.folders.assets.split('/')[1],
            images: config.files.images.split('/')[0],
            fonts:  config.files.fonts.split('/')[0]
        }
    });
    require('time-grunt')(grunt);
    require('load-grunt-tasks')(grunt);
    /* -- load tasks placeholder -- */
};

Use global form of 'use strict' in gruntfile template file


"use strict";

(function () {
    angular
        .module("dummy", []);

    var upgradeAdapter = new ng.upgrade.UpgradeAdapter();

    angular.element(document.body).ready(function () {
        upgradeAdapter.bootstrap(document.body, ["dummy"]);
    });

    upgradeAdapter.addProvider(ng.http.HTTP_PROVIDERS);

    var fooS = ng.core
            .Injectable()
            .Class({
                constructor: [ng.http.Http, function FooService(http) {
                    this.http = http;
                }],
                getFoo: function () {
                    return this.http.get("/src/client/app/greetings.json").map(
                        function (res) {
                            return res.json();
                        }
                    ).toPromise();
                }
            });

    upgradeAdapter.addProvider(fooS);

    angular.module("dummy")
        .factory("FooService",
            upgradeAdapter.downgradeNg2Provider(fooS));

    angular.module("dummy")
        .component("foo", {
            template: "<p>{{ foo.hello }} from mix Angular 1 and Angular 2 </p>",
            controller: function (FooService) {
                var vm = this;

                FooService.getFoo().then(function (foos) {
                    vm.hello = foos.hello;
                });
            }
        });
}());

"use strict";

(function () {
    angular
        .module("dummy", []);

    var upgradeAdapter = new ng.upgrade.UpgradeAdapter();

    angular.element(document.body).ready(function () {
        upgradeAdapter.bootstrap(document.body, ["dummy"]);
    });

    upgradeAdapter.addProvider(ng.http.HTTP_PROVIDERS);

    var fooS = ng.core
            .Injectable()
            .Class({
                constructor: [ng.http.Http, function FooService(http) {
                    this.http = http;
                }],
                getFoo: function () {
                    return this.http.get("/src/client/app/greetings.json").map(
                        function (res) {
                            return res.json();
                        }
                    ).toPromise();
                }
            });

    upgradeAdapter.addProvider(fooS);

    angular.module("dummy")
        .factory("FooService",
            upgradeAdapter.downgradeNg2Provider(fooS));

    angular.module("dummy")
        .component("foo", {
            template: "<p>{{ $ctrl.hello }} from mix Angular 1 and Angular 2 </p>",
            controller: function (FooService) {
                var vm = this;

                FooService.getFoo().then(function (foos) {
                    vm.hello = foos.hello;
                });
            }
        });
}());

Fix mix ng1-ng2 example with $ctrl.


import React from 'react'
import PropTypes from 'prop-types'
import { Map, TileLayer, GeoJSON } from 'react-leaflet'

class CenteredMap extends React.PureComponent {
  static propTypes = {
    vectors: PropTypes.object.isRequired,
    className: PropTypes.string,
    frozen: PropTypes.bool,

    lat: PropTypes.number,
    lon: PropTypes.number,
    zoom: PropTypes.number
  }

  static defaultProps = {
    frozen: false,
    lat: 47,
    lon: 1,
    zoom: 5
  }

  componentDidMount() {
    if (this.vectors) {
      this.setState({
        bounds: this.vectors.leafletElement.getBounds()
      })
    }
  }

  render() {
    const { vectors, className, frozen, lat, lon, zoom } = this.props
    const { bounds } = this.state

    return (
      <Map
        className={className}
        center={[lat, lon]}
        bounds={bounds}
        zoom={zoom}
        dragging={!frozen}
        scrollWheelZoom={false}
        doubleClickZoom={false}
        zoomControl={!frozen}
      >
        <TileLayer
          attribution='© Contributeurs OpenStreetMap'
          url='https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png'
        />

        <GeoJSON
          color='blue'
          fillOpacity={0.1}
          weight={2}
          ref={vectors => { this.vectors = vectors }}
          data={vectors}
        />
      </Map>
    )
  }
}

export default CenteredMap

import React from 'react'
import PropTypes from 'prop-types'
import { Map, TileLayer, GeoJSON } from 'react-leaflet'

class CenteredMap extends React.PureComponent {
  static propTypes = {
    vectors: PropTypes.object.isRequired,
    className: PropTypes.string,
    frozen: PropTypes.bool,

    lat: PropTypes.number,
    lon: PropTypes.number,
    zoom: PropTypes.number
  }

  static defaultProps = {
    frozen: false,
    lat: 47,
    lon: 1,
    zoom: 5
  }

  componentDidMount() {
    if (this.vectors) {
      this.setState({
        bounds: this.vectors.leafletElement.getBounds()
      })
    }
  }

  render() {
    const { vectors, className, frozen, lat, lon, zoom } = this.props
    const { bounds } = this.state

    return (
      <Map
        className={className}
        center={[lat, lon]}
        bounds={bounds}
        minZoom={zoom}
        dragging={!frozen}
        scrollWheelZoom={false}
        doubleClickZoom={false}
        zoomControl={!frozen}
      >
        <TileLayer
          attribution='© Contributeurs OpenStreetMap'
          url='https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png'
        />

        <GeoJSON
          color='blue'
          fillOpacity={0.1}
          weight={2}
          ref={vectors => { this.vectors = vectors }}
          data={vectors}
        />
      </Map>
    )
  }
}

export default CenteredMap

Use minZoom on Map instead of zoom


/**
 * @flow
 */

import fs from 'fs-extra';
import path from 'path';
import mkdirp from 'mkdirp';
import { getResolvedLocalesAsync } from './ExponentTools';

export async function writeLocalizationResourcesAsync({ supportingDirectory, context }) {
  let locales = {};
  if (context.type === 'user') {
    locales = await getResolvedLocalesAsync(context.config);
  }
  for (const [lang, localizationObj] of Object.entries(locales)) {
    const dir = path.join(supportingDirectory, `${lang}.lproj`);
    mkdirp.sync(dir);
    const strings = path.join(dir, 'InfoPlist.strings');
    const buffer = [];
    for (const [plistKey, localVersion] of Object.entries(localizationObj)) {
      buffer.push(`${plistKey} = "${localVersion}";`);
    }
    await fs.writeFile(strings, buffer.join('\n'));
  }
}

import fs from 'fs-extra';
import path from 'path';
import mkdirp from 'mkdirp';
import { getResolvedLocalesAsync } from './ExponentTools';

export async function writeLocalizationResourcesAsync({ supportingDirectory, context }) {
  let locales = {};
  if (context.type === 'user') {
    locales = await getResolvedLocalesAsync(context.config);
  } else if (context.type === 'service') {
    locales = context.config.locales !== undefined ? context.config.locales : {};
  }
  for (const [lang, localizationObj] of Object.entries(locales)) {
    const dir = path.join(supportingDirectory, `${lang}.lproj`);
    mkdirp.sync(dir);
    const strings = path.join(dir, 'InfoPlist.strings');
    const buffer = [];
    for (const [plistKey, localVersion] of Object.entries(localizationObj)) {
      buffer.push(`${plistKey} = "${localVersion}";`);
    }
    await fs.writeFile(strings, buffer.join('\n'));
  }
}

Use locales in service context


const commit = require('git-rev-sync').short();
const pkg = require('../package.json');

const version = JSON.stringify({
  commit,
  source: pkg.homepage,
  version: process.env.CIRCLE_TAG || `v${pkg.version}`
});

class VersionPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('VersionPlugin', compilation => {
      compilation.assets['version.json'] = {
        source() {
          return version;
        },
        size() {
          return version.length;
        }
      };
    });
  }
}

module.exports = VersionPlugin;

const gitRevSync = require('git-rev-sync');
const pkg = require('../package.json');

let commit = 'unknown';

try {
  commit = gitRevSync.short();
} catch (e) {
  console.warn('Error fetching current git commit: ' + e);
}

const version = JSON.stringify({
  commit,
  source: pkg.homepage,
  version: process.env.CIRCLE_TAG || `v${pkg.version}`
});

class VersionPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap('VersionPlugin', compilation => {
      compilation.assets['version.json'] = {
        source() {
          return version;
        },
        size() {
          return version.length;
        }
      };
    });
  }
}

module.exports = VersionPlugin;

Fix npm build with missing .git


const _ = require('lodash');
const exec = require('shell-utils').exec;

const android = _.includes(process.argv, '--android');
const release = _.includes(process.argv, '--release');
const skipBuild = _.includes(process.argv, '--skipBuild');

run();

function run() {
  const platform = android ? `android` : `ios`;
  const prefix = android ? `android.emu` : `ios.sim`;
  const suffix = release ? `release` : `debug`;
  const configuration = `${prefix}.${suffix}`;
  const cleanup = process.env.CI ? `--cleanup` : ``;

  if (!skipBuild) {
    exec.execSync(`detox build --configuration ${configuration}`);
  }
  exec.execSync(`detox test --configuration ${configuration} --platform ${platform} ${cleanup}`);
}

const _ = require('lodash');
const exec = require('shell-utils').exec;

const android = _.includes(process.argv, '--android');
const release = _.includes(process.argv, '--release');
const skipBuild = _.includes(process.argv, '--skipBuild');

run();

function run() {
  const platform = android ? `android` : `ios`;
  const prefix = android ? `android.emu` : `ios.sim`;
  const suffix = release ? `release` : `debug`;
  const configuration = `${prefix}.${suffix}`;
  const cleanup = process.env.CI ? `--cleanup` : ``;

  if (!skipBuild) {
    exec.execSync(`detox build --configuration ${configuration}`);
  }
  exec.execSync(`detox test --configuration ${configuration} --platform ${platform} ${cleanup} --loglevel verbose`);
}

Add loglevel verbose to e2e tests


module.exports = function(sequelize, DataTypes) {
    var User = sequelize.define('User', {
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false
        },
        experience: {
            type: DataTypes.BIGINT,
            allowNull: false,
            defaultValue: 0
        }
    });
    return User;
};

module.exports = function(sequelize, DataTypes) {
    var User = sequelize.define('User', {
        username: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true
        },
        password: {
            type: DataTypes.STRING,
            allowNull: false
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false
        },
        level: {
            type: DataTypes.BIGINT,
            allowNull: false,
            defaultValue: 1
        },
        experience: {
            type: DataTypes.BIGINT,
            allowNull: false,
            defaultValue: 0
        }
    });
    return User;
};

Add level to User model.


import { extend } from 'flarum/extend';
import LinkButton from 'flarum/components/LinkButton';
import IndexPage from 'flarum/components/IndexPage';
import DiscussionListState from 'flarum/states/DiscussionListState';

export default function addSubscriptionFilter() {
  extend(IndexPage.prototype, 'navItems', function(items) {
    if (app.session.user) {
      const params = this.stickyParams();

      params.filter = 'following';

      items.add('following', LinkButton.component({
        href: app.route('index.filter', params),
        children: app.translator.trans('flarum-subscriptions.forum.index.following_link'),
        icon: 'fas fa-star'
      }), 50);
    }
  });

  extend(IndexPage.prototype, 'config', function () {
    if (m.route() == "/following") {
      app.setTitle(app.translator.trans('flarum-subscriptions.forum.following.meta_title_text'));
    }
  });

  extend(DiscussionListState.prototype, 'requestParams', function(params) {
    if (this.params.filter === 'following') {
      params.filter.q = (params.filter.q || '') + ' is:following';
    }
  });
}

import { extend } from 'flarum/extend';
import LinkButton from 'flarum/components/LinkButton';
import IndexPage from 'flarum/components/IndexPage';
import DiscussionListState from 'flarum/states/DiscussionListState';

export default function addSubscriptionFilter() {
  extend(IndexPage.prototype, 'navItems', function(items) {
    if (app.session.user) {
      const params = app.search.stickyParams();

      params.filter = 'following';

      items.add('following', LinkButton.component({
        href: app.route('index.filter', params),
        children: app.translator.trans('flarum-subscriptions.forum.index.following_link'),
        icon: 'fas fa-star'
      }), 50);
    }
  });

  extend(IndexPage.prototype, 'config', function () {
    if (m.route() == "/following") {
      app.setTitle(app.translator.trans('flarum-subscriptions.forum.following.meta_title_text'));
    }
  });

  extend(DiscussionListState.prototype, 'requestParams', function(params) {
    if (this.params.filter === 'following') {
      params.filter.q = (params.filter.q || '') + ' is:following';
    }
  });
}

Fix extension to work with latest state changes