/**
 * Gray Matter
 * Copyright (c) 2014 Jon Schlinkert, Brian Woodward, contributors.
 * Licensed under the MIT license.
 */

'use strict';

// node_modules
var YAML = require('js-yaml');
var coffee = require('coffee-script');
var toml = require('toml');

// The module to export
var parse = module.exports = {};

parse.yaml = function(src) {
  return YAML.load(src);
};

parse.json = function(src) {
  return JSON.parse(src);
};

parse.coffee = function(src, options) {
  options = options || {};
  try {
    return coffee['eval'](src, options);
  } catch (e) {
    console.warn('Could not parse coffee-script:', e);
  }
};

parse.toml = function(src) {
  try {
    return toml.parse(src.trim());
  } catch (e) {
    console.warn('Count not parse toml:' + src, e);
  }
};

/**
 * Gray Matter
 * Copyright (c) 2014 Jon Schlinkert, Brian Woodward, contributors.
 * Licensed under the MIT license.
 */

'use strict';

// node_modules
var YAML = require('js-yaml'),
  coffee,
  toml;

// The module to export
var parse = module.exports = {};

parse.yaml = function(src) {
  return YAML.load(src);
};

parse.json = function(src) {
  return JSON.parse(src);
};

parse.coffee = function(src, options) {
  options = options || {};
  if (!coffee) {
    coffee = require('coffee-script');
  }
  try {
    return coffee['eval'](src, options);
  } catch (e) {
    console.warn('Could not parse coffee-script:', e);
  }
};

parse.toml = function(src) {
  if (!toml) {
    toml = require('toml');
  }
  try {
    return toml.parse(src.trim());
  } catch (e) {
    console.warn('Count not parse toml:' + src, e);
  }
};

Fix the CoffeeScript and TOML dependency injection


// For output.filename configuration:
//
// Change "component-name" in this file to your real component name!
// DO NOT CHANGE "[name]", which denotes the entry property names that webpack automatically inserts for you!

module.exports = {
  entry: {
   dev: ['webpack/hot/dev-server', './main.js', './demo/demo.js'],
   dist: ['./main.js']
  },
  output: {
    path: './',
    filename: 'build/[name].component-name.js',
    libraryTarget: 'umd'
  },
  devtool: 'cheap-module-source-map',
  externals: [
    {
      'react': {
        root: 'React',
        commonjs2: 'react',
        commonjs: 'react',
        amd: 'react'
      }
    },
    {
      'react-dom': {
        root: 'ReactDOM',
        commonjs2: 'react-dom',
        commonjs: 'react-dom',
        amd: 'react-dom'
      }
    }
  ],
  contentBase: './demo', // for webpack dev server
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ],
    loaders: [
      {
        test: /\.scss$/,
        loader: 'style!css!sass' // sass -> css -> javascript -> inline style
      },
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          cacheDirectory: true,
          presets: ['es2015', 'react', 'stage-0']
        }
      },
      {
        test: /\.json$/,
        loader: 'json'
      }
    ]
  }
};

// For output.filename configuration:
//
// CHANGE "component-name" in this file to your real component name!
// DO NOT CHANGE "[name]", which denotes the entry property names that webpack automatically inserts for you!

module.exports = {
  entry: {
   dev: ['webpack/hot/dev-server', './demo/demo.js'],
   dist: ['./main.js']
  },
  output: {
    path: './',
    filename: 'build/[name].component-name.js',
    libraryTarget: 'umd'
  },
  devtool: 'cheap-module-source-map',
  externals: [
    {
      'react': {
        root: 'React',
        commonjs2: 'react',
        commonjs: 'react',
        amd: 'react'
      }
    },
    {
      'react-dom': {
        root: 'ReactDOM',
        commonjs2: 'react-dom',
        commonjs: 'react-dom',
        amd: 'react-dom'
      }
    }
  ],
  contentBase: './demo', // for webpack dev server
  module: {
    preLoaders: [
      {
        test: /\.js$/,
        loader: 'eslint',
        exclude: /node_modules/
      }
    ],
    loaders: [
      {
        test: /\.scss$/,
        loader: 'style!css!sass' // sass -> css -> javascript -> inline style
      },
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          cacheDirectory: true,
          presets: ['es2015', 'react', 'stage-0']
        }
      },
      {
        test: /\.json$/,
        loader: 'json'
      }
    ]
  }
};

Remove unnecessary reference from webpack dev build.


/* eslint-env node */

const webpack = require('webpack');
const debug = require('debug')('app:webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

const isDev = process.env.NODE_ENV !== 'production';
debug(`Running in ${isDev ? 'development' : 'production'} mode`);

module.exports = {
  entry: {
    main: './src/browser/main',
    'service-worker': './src/browser/service-worker.js',
  },
  output: {
    path: `${__dirname}/dist/scripts`,
    filename: '[name].js',
  },
  module: {
    rules: [{test: /\.js$/, exclude: /node_modules/, use: ['babel-loader']}],
  },
  plugins: [].concat(
    new CleanWebpackPlugin('dist'),
    new CopyWebpackPlugin([{from: 'static', to: `${__dirname}/dist`}]),
    isDev ? [] : new webpack.optimize.UglifyJsPlugin({sourceMap: true}),
    new webpack.DefinePlugin({BUILD_ID: Date.now()}),
    new webpack.BannerPlugin({banner: `Build date: ${new Date()}`})
  ),
  devtool: 'source-map',
};

/* eslint-env node */

const webpack = require('webpack');
const debug = require('debug')('app:webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

const isDev = process.env.NODE_ENV !== 'production';
debug(`Running in ${isDev ? 'development' : 'production'} mode`);

module.exports = {
  entry: {
    main: './src/browser/main',
    'service-worker': './src/browser/service-worker.js',
  },
  output: {
    path: `${__dirname}/dist`,
    filename: 'scripts/[name].js',
  },
  module: {
    rules: [{test: /\.js$/, exclude: /node_modules/, use: ['babel-loader']}],
  },
  plugins: [].concat(
    new CleanWebpackPlugin('dist'),
    new CopyWebpackPlugin([{from: 'static'}]),
    isDev ? [] : new webpack.optimize.UglifyJsPlugin({sourceMap: true}),
    new webpack.DefinePlugin({BUILD_ID: Date.now()}),
    new webpack.BannerPlugin({banner: `Build date: ${new Date()}`})
  ),
  devtool: 'source-map',
};

Use /dist as the base output dir for all plugins


// Inspired from https://github.com/markokajzer/discord-soundbot
// MIT License - Copyright (c) 2020 Marko Kajzer
const path = require('path');
const replace = require('replace-in-file');
const tsconfig = require('../tsconfig.json');
const pathAliases = tsconfig.compilerOptions.paths;

const from = Object.keys(pathAliases)
    .map(key => new RegExp(`${key.split('/*')[0]}/[^"]*`, 'g'));

const to = {};
Object.entries(pathAliases).forEach(([key, value]) => {
    const match = key.split('/*')[0];
    to[match] = value[0].split('/*')[0];
});

const options = {
    files: ['dist/**/*.@(j|t)s'],
    from,
    to: (...args) => {
        const [match, , , filename] = args;
        const [replacePattern, ...file] = match.split('/');

        const normalizedRelativePath = path.relative(
            path.join(process.cwd(), path.dirname(filename)),
            path.join(process.cwd(), 'dist', to[replacePattern], ...file)
        );

        const relativePath = normalizedRelativePath.startsWith('.')
            ? normalizedRelativePath
            : `./${normalizedRelativePath}`;

        return relativePath.replace(/\\/g, '/');
    }
};

replace.sync(options);

// Inspired from https://github.com/markokajzer/discord-soundbot
// MIT License - Copyright (c) 2020 Marko Kajzer
const path = require('path');
const replace = require('replace-in-file');
const tsconfig = require('../tsconfig.json');
const pathAliases = tsconfig.compilerOptions.paths;

const from = Object.keys(pathAliases)
    .map(key => new RegExp(`${key.split('/*')[0]}/[^'"]*`, 'g'));

const to = {};
Object.entries(pathAliases).forEach(([key, value]) => {
    const match = key.split('/*')[0];
    to[match] = value[0].split('/*')[0];
});

const options = {
    files: ['dist/**/*.@(j|t)s'],
    from,
    to: (...args) => {
        const [match, , , filename] = args;
        const [replacePattern, ...file] = match.split('/');

        const normalizedRelativePath = path.relative(
            path.join(process.cwd(), path.dirname(filename)),
            path.join(process.cwd(), 'dist', to[replacePattern], ...file)
        );

        const relativePath = normalizedRelativePath.startsWith('.')
            ? normalizedRelativePath
            : `./${normalizedRelativePath}`;

        return relativePath.replace(/\\/g, '/');
    }
};

replace.sync(options);

Fix RegExp pattern to match single quote imports


import React, { Component } from "react";
import NewNoteForm from "./NewNoteForm";

class NewNote extends Component {
  submit = values => {
    console.log("submitting new note", values);
  };

  render() {
    return (

Create a new Note

        <NewNoteForm onSubmit={this.submit} />

    );
  }
}

export default NewNote;

import React, { Component } from "react";
import NewNoteForm from "./NewNoteForm";

class NewNote extends Component {
  submit = values => {
    console.log("submitting new note", values);
  };

  render() {
    return (
      <div>
        <NewNoteForm onSubmit={this.submit} />
      </div>
    );
  }
}

export default NewNote;

Remove header from new note form


var exports = {};

exports.simplePrint = function(video) {
  return `**${video.title}**`;
};

exports.prettyPrint = function(video) {
  try {
    viewCount = video.view_count.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  } catch (e) {
    viewCount = 'unknown';
  }

  return `**${video.title}** by **${video.author}** *(${viewCount} views)* [${exports.prettyTime(video.length_seconds*1000)}]`;
};

exports.prettyPrintWithUser = function(video) {
  return exports.prettyPrint(video) + `, added by <@${video.userId}>`;
};

exports.simplify = function(video, vid) {
  return {
    vid: vid,
    title: video.title,
    author: video.author,
    view_count: video.view_count,
  };
};

exports.prettyTime = function(ms) {
  var seconds = ms / 1000;
  var actualSeconds = Math.ceil(seconds % 60);
  var paddedSeconds = String('00' + actualSeconds).slice(-2);
  var minutes = Math.round((seconds - actualSeconds) / 60);
  return `${minutes}:${paddedSeconds}`;
};

module.exports = exports;

var exports = {};

exports.simplePrint = function(video) {
  return `**${video.title}**`;
};

exports.prettyPrint = function(video) {
  try {
    viewCount = video.view_count.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  } catch (e) {
    viewCount = 'unknown';
  }

  return `**${video.title}** by **${video.author}** *(${viewCount} views)* [${exports.prettyTime(video.length_seconds*1000)}]`;
};

exports.prettyPrintWithUser = function(video) {
  return exports.prettyPrint(video) + `, added by <@${video.userId}>`;
};

exports.simplify = function(video, vid) {
  return {
    vid: vid,
    title: video.title,
    author: video.author,
    view_count: video.view_count,
    length_seconds: video.length_seconds
  };
};

exports.prettyTime = function(ms) {
  var seconds = ms / 1000;
  var actualSeconds = Math.ceil(seconds % 60);
  var paddedSeconds = String('00' + actualSeconds).slice(-2);
  var minutes = Math.round((seconds - actualSeconds) / 60);
  return `${minutes}:${paddedSeconds}`;
};

module.exports = exports;

Add the length into a simplified video


import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import Transaction from '../transaction';
import * as transactionsActionCreators from '../../../redux/transactions/action-creators';

function getTransactionsList(transactions, transactionsActions) {
  return transactions.map(t => (
    <Transaction
      key={t.id}
      transaction={t}
      deleteTransaction={transactionsActions.deleteTransaction}
      updateTransaction={transactionsActions.updateTransaction}/>
  ));
}

function getEmptyTransactions() {
  return (
There are no transactions.
);
}

export function Transactions({transactions, transactionsActions}) {
  var children;

  if (transactions && transactions.length) {
    children = getTransactionsList(transactions, transactionsActions);
  } else {
    children = getEmptyTransactions();
  }

  return (

      {children}

  );
}

function mapStateToProps(state) {
  return {
    transactions: state.transactions.transactions
  };
}

function mapDispatchToProps(dispatch) {
  return {
    transactionsActions: bindActionCreators(transactionsActionCreators, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Transactions);

import React from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import Transaction from '../transaction';
import * as transactionsActionCreators from '../../../redux/transactions/action-creators';

function getTransactionsList(transactions, transactionsActions) {
  return transactions.map(t => (
    <Transaction
      key={t.id}
      transaction={t}
      deleteTransaction={transactionsActions.deleteTransaction}
      updateTransaction={transactionsActions.updateTransaction}/>
  ));
}

function getLoadingState() {
  return (
Loading transactions.
);
}

function getEmptyTransactions() {
  return (
There are no transactions.
);
}

export const Transactions = React.createClass({
  componentDidMount() {
    const {transactionsActions} = this.props;
    transactionsActions.retrieveTransactions();
  },

  render() {
    const {transactions, transactionsActions} = this.props;
    var children;

    if (!transactions) {
      children = getLoadingState();
    } else if (!transactions.length) {
      children = getEmptyTransactions();
    } else {
      children = getTransactionsList(transactions, transactionsActions);
    }

    return (

        {children}

    );
  }
});

function mapStateToProps(state) {
  return {
    transactions: state.transactions.transactions
  };
}

function mapDispatchToProps(dispatch) {
  return {
    transactionsActions: bindActionCreators(transactionsActionCreators, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Transactions);

Add loading state to transactions list


'use strict';

module.exports = {
    'properties': {
        'id': require('./id'),
        'name': {
            'type': 'string',
            'description': 'Name of the client'
        },
        'address': {
            'type': 'string',
            'description': 'Address of the client'
        },
        'city': {
            'type': 'string',
            'description': 'City of the client'
        },
        'postalCode': {
            'type': 'string',
            'description': 'Postal code of the client'
        },
        'phone': {
            'type': 'string',
            'description': 'Phone number of the client'
        },
        'companyId': {
            'type': 'string',
            'description': 'Company id of the client'
        },
        'iban': {
            'type': 'string',
            'description': 'IBAN code of the client'
        },
        'bic': {
            'type': 'string',
            'description': 'BIC/SWIFT code of the client'
        },
        'language': {
            '$ref': '#/definitions/Language',
            'description': 'Language of the client'
        },
        'createdAt': {
            'type': 'string',
            'format': 'date-time',
            'description': 'Day in which the invoice was created at',
            'readOnly': true
        },
        'updatedAt': {
            'type': 'string',
            'format': 'date-time',
            'description': 'Day in which the invoice was updated',
            'readOnly': true
        }
    }
};

'use strict';

module.exports = {
    'properties': {
        'id': require('./id'),
        'name': {
            'type': ['string', 'null'],
            'description': 'Name of the client'
        },
        'address': {
            'type': ['string', 'null'],
            'description': 'Address of the client'
        },
        'city': {
            'type': ['string', 'null'],
            'description': 'City of the client'
        },
        'postalCode': {
            'type': ['string', 'null'],
            'description': 'Postal code of the client'
        },
        'phone': {
            'type': ['string', 'null'],
            'description': 'Phone number of the client'
        },
        'companyId': {
            'type': ['string', 'null'],
            'description': 'Company id of the client'
        },
        'iban': {
            'type': ['string', 'null'],
            'description': 'IBAN code of the client'
        },
        'bic': {
            'type': ['string', 'null'],
            'description': 'BIC/SWIFT code of the client'
        },
        'language': {
            '$ref': '#/definitions/Language',
            'description': 'Language of the client'
        },
        'createdAt': {
            'type': 'string',
            'format': 'date-time',
            'description': 'Day in which the invoice was created at',
            'readOnly': true
        },
        'updatedAt': {
            'type': 'string',
            'format': 'date-time',
            'description': 'Day in which the invoice was updated',
            'readOnly': true
        }
    }
};

Allow various fields to be null


/**
 * Listens for the app launching then creates the window.
 * Ignores the provided window size.
 *
 * @see http://developer.chrome.com/trunk/apps/app.window.html
 */

function initPage() {
  addActionButton('Attach onSuspend', function() {
    var buttonTime = new Date();
    chrome.runtime.onSuspend.addListener(function() {
      var callbackTime = new Date();
      log('onSuspend fired: ' + (callbackTime - buttonTime) + 'ms after button');
    });
  });
}

/**
 * Listens for the app launching then creates the window.
 * Ignores the provided window size.
 *
 * @see http://developer.chrome.com/trunk/apps/app.window.html
 */

function initPage() {
  addActionButton('Attach onSuspend', function() {
    var buttonTime = new Date();
    chrome.runtime.onSuspend.addListener(function() {
      var callbackTime = new Date();
      log('onSuspend fired: ' + (callbackTime - buttonTime) + 'ms after button');
    });
  });
  addActionButton('chrome.runtime.reload()', function() {
    chrome.runtime.reload();
  });
}

Add a button that calls chrome.runtime.reload()


const gulp = require("gulp");
const lkpm = require('lkpm');

var taskExporter = new lkpm.TaskExporter({
    moduleBase: __dirname
}, gulp);

taskExporter.exportTasks();

const gulp = require("gulp");
const lkpm = require('lkpm');
const path = require("path");

var buildDir = path.join(__dirname, "build");
var compiledResourcesDir = path.join(buildDir, "compiledResources");

var compileJSLibTask = "javascript-libraries";
gulp.task(compileJSLibTask, function() {
    return gulp.src(path.join("resources", "**", "*"))
        .pipe(gulp.dest(path.join(compiledResourcesDir)));
});

var taskExporter = new lkpm.TaskExporter({
    moduleBase: __dirname
}, gulp);

var stageTask = taskExporter.tasks[lkpm.TASK_NAMES.STAGE_STATIC];
stageTask.dependencies.push(compileJSLibTask);
stageTask.setResourceRoot(compiledResourcesDir);

taskExporter.exportTasks();

Add a precompile step for WebUtils.


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

export default Ember.Mixin.create({
  queryParams: {
    page: { refreshModel: true },
    sort: { refreshModel: true },
    size: { refreshModel: true }
  },
  mergedQueryOptions: {},
  model( params ) {
    const options = {
      sort: params.sort,
      page: {
        number: params.page,
        size: params.size
      } };
    $.extend( options, this.get('mergedQueryOptions') );
    return this.store.query( this.get('modelName'), options );
  }
});

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

export default Ember.Mixin.create({
  queryParams: {
    filter: { refreshModel: true },
    page: { refreshModel: true },
    size: { refreshModel: true },
    sort: { refreshModel: true }
  },
  mergedQueryOptions: {},
  model(params) {
    const options = {
      filter: params.filter,
      sort: params.sort,
      page: {
        number: params.page,
        size: params.size
      }
    };
    $.extend(options, this.get('mergedQueryOptions'));
    return this.store.query(this.get('modelName'), options);
  }
});

Add filter as queryParam that triggers a refreshModel


export default {
  name: 'base',
  configure ({ projectPath }) {
    return {
      basePath: projectPath,

      files: [
        'src/**/*.spec.*',
        { pattern: 'src/**/*', watched: true, included: false }
      ],

      preprocessors: {
        // add webpack as preprocessor
        'src/**/*.spec.*': ['webpack']
      },

      webpackServer: {
        noInfo: true
      },

      singleRun: true,
      autoWatch: false
    }
  }
}

export default {
  name: 'base',
  configure ({ projectPath, watch }) {
    return {
      basePath: projectPath,

      files: [
        'src/**/*.spec.*',
        { pattern: 'src/**/*', watched: true, included: false }
      ],

      preprocessors: {
        // add webpack as preprocessor
        'src/**/*.spec.*': ['webpack']
      },

      webpackServer: {
        noInfo: true
      },

      singleRun: !watch,
      autoWatch: watch
    }
  }
}

Fix execution of test watch 🐝


import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from 'rootReducer'
import rootSaga from 'sagas'
import { extend } from 'lodash'

const middlewares = []
const sagaAdditionalMiddlewares = []

if (process.env.NODE_ENV === 'development') {
  const createLogger = require('redux-logger')
  const sagaMonitor = console.tron.createSagaMonitor()
  const logger = createLogger({
    predicate: (a, b) => true,
    collapsed: (a, b) => true
  })
  middlewares.push(logger)
  sagaAdditionalMiddlewares.push({sagaMonitor})
}

const sagaMiddleware = createSagaMiddleware(...sagaAdditionalMiddlewares)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const createTheStore =
  process.env.NODE_ENV === 'development'
  ? console.tron.createStore
  : createStore

middlewares.push(sagaMiddleware)

export default () => {
  const store = createTheStore(
    rootReducer,
    composeEnhancers(applyMiddleware(...middlewares)),
  )

  sagaMiddleware.run(rootSaga)

  return store
}

import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from 'rootReducer'
import rootSaga from 'sagas'
import { extend } from 'lodash'

const middlewares = []
const sagaAdditionalMiddlewares = []

if (process.env.NODE_ENV === 'development') {
  const { createLogger } = require('redux-logger')
  // const sagaMonitor = console.tron.createSagaMonitor()
  const logger = createLogger({
    predicate: (a, b) => true,
    collapsed: (a, b) => true
  })
  middlewares.push(logger)
  // sagaAdditionalMiddlewares.push({sagaMonitor})
}

const sagaMiddleware = createSagaMiddleware(...sagaAdditionalMiddlewares)
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const createTheStore =
  process.env.NODE_ENV === 'development'
  ? createStore
  : createStore

middlewares.push(sagaMiddleware)

export default () => {
  const store = createTheStore(
    rootReducer,
    composeEnhancers(applyMiddleware(...middlewares)),
  )

  sagaMiddleware.run(rootSaga)

  return store
}

Stop reactotron saga monitor. Fix redux-logger.


$(function () {
	// General-purpose code for showing and hiding modals
	var activeClass = 'modal-active';
	var $overlays = $('.modal-foreground, .modal-background');
	$('.modal').each(function () {
		var $this = $(this);
		var $target = $('a[href=#' + $this.attr('id') + ']');
		$target.click(function (ev) {
			ev.preventDefault();
			$overlays.addClass(activeClass);
			$this.addClass(activeClass);
		});
	});
	function closeModals (ev) {
		if (ev && ev.target !== this) return;
		$('.' + activeClass).removeClass(activeClass);
	}
	$('.modal-foreground, .modal-close').click(closeModals);
	$(window).keyup(function (ev) {
		if (ev.keyCode === 27) closeModals(); // Esc
	});

	// Special-purpose code for individual modal behaviour
 	$embedCode = $('#embed-code');
	$(Z).on('Z:statechange ready', function () {
		$embedCode.val($embedCode.val().replace(/src=".*?"/, 'src="' + window.location + '"'));
	});
	$embedCode.focus(function () { $embedCode.select(); });
});

$(function () {
	// General-purpose code for showing and hiding modals
	var activeClass = 'modal-active';
	var $overlays = $('.modal-foreground, .modal-background');
	$('.modal').each(function () {
		var $this = $(this);
		var $target = $('a[href=#' + $this.attr('id') + ']');
		$target.click(function (ev) {
			ev.preventDefault();
			$overlays.addClass(activeClass);
			$this.addClass(activeClass);
		});
	});
	function closeModals (ev) {
		if (ev && ev.target !== this) return;
		$('.' + activeClass).removeClass(activeClass);
	}
	$('.modal-foreground, .modal-close').click(closeModals);
	$(window).keyup(function (ev) {
		if (ev.keyCode === 27) closeModals(); // Esc
	});

	// Special-purpose code for individual modal behaviour
 	$embedCode = $('#embed-code');
	function updateEmbedCode () {
		$embedCode.val($embedCode.val().replace(/src=".*?"/, 'src="' + window.location + '"'));
	}
	$(Z).on('Z:statechange ready', updateEmbedCode);
	updateEmbedCode();
	$embedCode.focus(function () { $embedCode.select(); });
});

Fix embed code not updating on page load


import urlSlug from './urlSlug';

function getPosts() {
    const req = require.context("../posts", true, /^\.\/.*\.md$/);
    const dirtyFileNames = req.keys();

    return dirtyFileNames.map(f => {
        const spl = f.split('/');
        const filename = spl.slice(-1).pop();
        const category = spl.slice(1)[0];
        const title = filename.split('.md')[0];
        const slug = urlSlug(title);
        return { filename, category, title, slug }
    })
}

export default getPosts;

import urlSlug from './urlSlug';

function getPosts() {
    const req = require.context("../posts", true, /^\.\/.*\.md$/);
    const dirtyFileNames = req.keys();

    return dirtyFileNames.map(f => {
        const spl = f.split('/');
        const filename = spl.slice(-1).pop();
        const category = spl.slice(1)[0];
        const title = filename.split('.md')[0];
        const slugifiedCaterory = urlSlug(category);
        const slugifiedUrl = urlSlug(title);
        return { filename, category, title, slugifiedCaterory, slugifiedUrl }
    })
}

export default getPosts;

Add slugified category and slugified url


import React from 'react';

import refractToComponentsMap from 'refractToComponentMap';

import './attribute.styl'

class AttributeComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    if (!this.props.data) {
      return false;
    }

    var reactComponent = refractToComponentsMap[this.props.data.element];

    return React.createElement(reactComponent, {
      data: this.props.data.content
    });
  }
}

export default AttributeComponent;

import React from 'react';

import refractToComponentsMap from 'refractToComponentMap';

import './attribute.styl'

class AttributeComponent extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    if (!this.props.data) {
      return false;
    }

    var reactComponent = refractToComponentsMap[this.props.data.element];

    if (typeof reactComponent === 'undefined') {
      throw `Unable to find a rendering component for ${this.props.data.element}`;
    }

    return React.createElement(reactComponent, {
      data: this.props.data.content
    });
  }
}

export default AttributeComponent;

Throw an exception when unable to find rendering type


const fs = require('fs-extra');
const express = require('express');
const pathModule = require('path');

const getDistDir = (src, distDir) => {
  const srcRelative = pathModule.relative(process.cwd(), src);
  return pathModule.resolve(pathModule.join(distDir, srcRelative));
};

class StaticApp {
  constructor({ path, src }) {
    this._path = path;
    this._src = src;
  }

  prepareMiddleware({ dev, distDir }) {
    const app = express();
    const folderToServe = dev ? this._src : getDistDir(this._src, distDir);
    app.use(this._path, express.static(folderToServe));
    return app;
  }

  build({ distDir }) {
    const source = pathModule.resolve(this._src);
    const destination = getDistDir(this._src, distDir);
    return fs.copy(source, destination);
  }
}

module.exports = {
  StaticApp,
};

const fs = require('fs-extra');
const express = require('express');
const fallback = require('express-history-api-fallback');
const pathModule = require('path');

const getDistDir = (src, distDir) => {
  const srcRelative = pathModule.relative(process.cwd(), src);
  return pathModule.resolve(pathModule.join(distDir, srcRelative));
};

class StaticApp {
  constructor({ path, src, fallback }) {
    this._path = path;
    this._src = src;
    this._fallback = fallback;
  }

  prepareMiddleware({ dev, distDir }) {
    const app = express();
    const folderToServe = dev ? this._src : getDistDir(this._src, distDir);
    app.use(this._path, express.static(folderToServe));
    if (this._fallback) {
      app.use(fallback(this._fallback, { root: folderToServe }));
    }
    return app;
  }

  build({ distDir }) {
    const source = pathModule.resolve(this._src);
    const destination = getDistDir(this._src, distDir);
    return fs.copy(source, destination);
  }
}

module.exports = {
  StaticApp,
};

Add express-history-api-fallback to static app


/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 */

import { version as VERSION } from '../package.json'

import applyTheme from './applyTheme'
import withStyles from './withStyles'

module.exports = {
  applyTheme,
  withStyles,
  getVersion: () => VERSION,
}

import { version as VERSION } from '../package.json'

import applyTheme from './applyTheme'
import withStyles from './withStyles'
import withClassNames from './withClassNames'

module.exports = {
  applyTheme,
  withStyles,
  withClassNames,
  getVersion: () => VERSION,
}

Refactor DocEdit to work on mobile


/*global $:true, casper:true, document:true */
'use strict';

casper.test.comment('Face Value - Fonts')

casper.start('http://localhost:3001/', function () {
  casper.test.info('Testing for Bitter fonts')
})

casper.waitForResource('Bitter-Regular-webfont.ttf', function() {
  this.test.assertResourceExists('Bitter-Regular-webfont.ttf',
                                 'Bitter font loaded properly')
})

casper.waitForResource('Bitter-Bold-webfont.ttf', function() {
  this.test.assertResourceExists('Bitter-Bold-webfont.ttf',
                                 'Bitter Bold font loaded properly')
})

casper.run(function () {
  this.test.done()
})

/*global $:true, casper:true, document:true */
'use strict';

casper.test.comment('Face Value - Fonts')

casper.start('http://localhost:3001/', function () {
  casper.test.info('Testing for Bitter fonts')

  this.test.assertEval(function() {
    return $('body').css('font-family').match(/^Bitter/g) !== null
  }, '<body> font should be Bitter')
})

casper.waitForResource('Bitter-Regular-webfont.ttf', function() {
  this.test.assertResourceExists('Bitter-Regular-webfont.ttf',
                                 'Bitter font loaded properly')
})

casper.waitForResource('Bitter-Bold-webfont.ttf', function() {
  this.test.assertResourceExists('Bitter-Bold-webfont.ttf',
                                 'Bitter Bold font loaded properly')
})

casper.run(function () {
  this.test.done()
})

Test for bitter font in styles


// Organizational classes
define(function() {
    return function(id, name) {
        this.id = id;
        this.name = ko.observable(name);
    }
});

define(['knockout'], function(ko) {
    return function(id, name) {
        var self = this;

        self.id = id;
        self.name = ko.observable(name);
    };
});

Fix an insidious dependency bug


const docHelper = {
  checkDocDetails: (req) => {
    const document = req.body;
    if (!(document.title && document.content && document.access)) {
      return false;
    }
    return true;
  },
  queryBuilder: (reqQuery) => {
    const dbQuery = {
      order: [['createdAt', 'DESC']]
    };

    const queries = Object.keys(reqQuery);

    if (queries.indexOf('offset') > -1) {
      dbQuery.offset = reqQuery.offset;
    }
    if (queries.indexOf('limit') > -1) {
      dbQuery.limit = reqQuery.limit;
    }
    if (queries.indexOf('date') > -1) {
      const nextDate = new Date(reqQuery.date);

      dbQuery.where = {
        createdAt: {
          $lt: new Date(new Date(nextDate.setDate(nextDate.getDate() + 1))),
          $gt: new Date(new Date(reqQuery.date) - 1)
        }
      };
    }

    return dbQuery;
  }
};

module.exports = docHelper;

const docHelper = {
  checkDocDetails: (req) => {
    const document = req.body;
    if (!(document.title && document.content && document.access &&
      (document.access === 'public' || document.access === 'private' || document.access === 'role'))) {
      return false;
    }
    return true;
  },
  queryBuilder: (reqQuery) => {
    const dbQuery = {
      order: [['createdAt', 'DESC']]
    };

    const queries = Object.keys(reqQuery);

    if (queries.indexOf('offset') > -1) {
      dbQuery.offset = reqQuery.offset;
    }
    if (queries.indexOf('limit') > -1) {
      dbQuery.limit = reqQuery.limit;
    }
    if (queries.indexOf('date') > -1) {
      const nextDate = new Date(reqQuery.date);

      dbQuery.where = {
        createdAt: {
          $lt: new Date(new Date(nextDate.setDate(nextDate.getDate() + 1))),
          $gt: new Date(new Date(reqQuery.date) - 1)
        }
      };
    }

    return dbQuery;
  }
};

module.exports = docHelper;

Check if access field of document has a correct value


import React, { Component, PropTypes } from 'react';
import styles from '../chat.scss';
import Message from './message/Message';

export default class ChatArea extends Component {
  render() {
    const { messages, ...rest } = this.props;
    return (
      <div id="container" className={styles.container}>
        {messages.map(message =>
          <Message
            key={message._id}
            message={message}
            {...rest}
          />
        )}
      </div>
    );
  }
}

ChatArea.propTypes = {
  messages: PropTypes.array.isRequired,
  user: PropTypes.shape({
    id: PropTypes.any
  }).isRequired,
  showAvatars: PropTypes.bool,
  avatarPreviewPosition: PropTypes.string,
  updateInputValue: PropTypes.func
};

import React, { Component, PropTypes } from 'react';
import styles from '../chat.scss';
import Message from './message/Message';

export default class ChatArea extends Component {
  componentDidMount() {
    setTimeout(this.updateScrollTop, 0);
  }

  componentDidUpdate() {
    this.updateScrollTop();
  }

  getRef = node => {
    this.node = node;
    this.updateScrollTop();
  };

  updateScrollTop = () => {
    if (!this.node) return;
    this.node.scrollTop = this.node.scrollHeight;
  };

  render() {
    const { messages, ...rest } = this.props;
    return (
      <div ref={this.getRef} className={styles.container}>
        {messages.map(message =>
          <Message
            key={message._id}
            message={message}
            {...rest}
          />
        )}
      </div>
    );
  }
}

ChatArea.propTypes = {
  messages: PropTypes.array.isRequired,
  user: PropTypes.shape({
    id: PropTypes.any
  }).isRequired,
  showAvatars: PropTypes.bool,
  avatarPreviewPosition: PropTypes.string,
  updateInputValue: PropTypes.func
};

Reimplement auto scroll to bottom


"use strict";
let fetch = require('node-fetch');
module.exports = function searchDeclaration(link) {
    return fetch(link)
        .then(response => response.text())
        .then(data => JSON.parse(data))
};

"use strict";
let fetch = require('node-fetch');
module.exports = function searchDeclaration(link) {
    return fetch(link)
        .then(response => response.text())
        .then(data => JSON.parse(data))
        .catch((err) => {
            console.log(err);
            console.log("...But, I gotta keep trying, and never give up!");
            return module.exports(link);
        })
};

Update fetch json It gotta keep trying, and never give up!


"use strict";

var util = require("util"),
    express = require("express"),
    seneca = require("seneca"),
    routes = require("./routes");

var app = express(),
    port = routes.config.port,
    staticFiles = express.static,
    apiUrl = routes.config.apiUrl;

process.on("uncaughtException", function (err) {
    console.log(err);
});

app.use(staticFiles(routes.config.staticFiles));
app.use(apiUrl, routes.apis);

app.listen(port, function () {
    util.log("Argo listening on http://localhost:" + port);
    util.log("Argo listening apis on http://localhost:" + port + apiUrl);
}).on("upgrade", function (request, socket, body) {
    routes.stream.run(request, socket, body);

    util.log("Argo streaming prices and events on ws://localhost:" +
        port + routes.config.streamUrl);
});

seneca = seneca();
seneca.use("plugin/register.js");
seneca.listen();

seneca.ready(function (error) {
    if (!error) {
        seneca.act("role:plugin", {
            cmd: "register",
            name: "ready"
        }, function (err, result) {
            if (!err) {
                util.log("Argo plugin:", result.value);
            }
        });
    }
});

"use strict";

var util = require("util"),
    express = require("express"),
    Bridge = require("flic").bridge,
    routes = require("./routes");

var app = express(),
    port = routes.config.port,
    staticFiles = express.static,
    apiUrl = routes.config.apiUrl;

process.on("uncaughtException", function (err) {
    console.log(err);
});

app.use(staticFiles(routes.config.staticFiles));
app.use(apiUrl, routes.apis);

app.listen(port, function () {
    util.log("Argo listening on http://localhost:" + port);
    util.log("Argo listening apis on http://localhost:" + port + apiUrl);
}).on("upgrade", function (request, socket, body) {
    routes.stream.run(request, socket, body);

    util.log("Argo streaming prices and events on ws://localhost:" +
        port + routes.config.streamUrl);
});

/*eslint-disable no-new */
new Bridge();
/*eslint-enable no-new */

Add Bridge of flic framework.


const hash = {};
const cache = [];

const add = url => {
    if (!hash[url]) {
        hash[url] = new Image();
        hash[url].src = url;

        cache.push(hash[url]);
    }
    return hash[url];
};

const get = url => {
    return add(url);
};

const stuff = (urls) => {
    if (urls.length > 0) {
        urls.map(add);
    }
};

const ImageCache = {
    add,
    stuff,
    get,
    hash,
    cache,
};

export default ImageCache;

const hash = {};
const cache = [];

const add = url => {
    if (!hash[url]) {
        hash[url] = new Image();
        hash[url].crossorigin = 'anonymous';
        hash[url].crossOrigin = 'anonymous';
        hash[url].src = url;

        cache.push(hash[url]);
    }
    return hash[url];
};

const get = url => {
    return add(url);
};

const stuff = (urls) => {
    if (urls.length > 0) {
        urls.map(add);
    }
};

const ImageCache = {
    add,
    stuff,
    get,
    hash,
    cache,
};

export default ImageCache;

Add crossorigin attribute to images so they can be laoded into canvases


$(document).ready(function () {
    let zone;
    let district;

    let getZone = $.get('/inputs/geojson/zone', function (data) {
        zone = data;
    });

    let getDistrict = $.get('/inputs/geojson/district', function (data) {
        district = data;
    });

    $.when(getZone, getDistrict).done(function () {
        let map = new MapClass('map-div');
        map.init({
            data:{
                zone: zone,
                district: district
            },
            extrude: true
        });
    });
});

$(document).ready(function () {
    let zone;
    let district;

    let getZone = $.get('/inputs/geojson/zone', function (data) {
        zone = data;
    });

    let getDistrict = $.get('/inputs/geojson/district', function (data) {
        district = data;
    });

    $.when(getZone, getDistrict).always(function () {
        let map = new MapClass('map-div');
        map.init({
            data:{
                zone: zone,
                district: district
            },
            extrude: true
        });
    });
});

Make sure function runs even if one of the promise fails


// ------------------------------------
// Constants
// ------------------------------------
const MAKE_SCREENSHOT = 'MAKE_SCREENSHOT'
const MAKE_SCREENSHOT_RESULT = 'MAKE_SCREENSHOT_RESULT'

// ------------------------------------
// Actions
// ------------------------------------
export function makeScreenshot() {
    return {
        type: MAKE_SCREENSHOT
    }
}

// // ------------------------------------
// // Specialized Action Creator
// // ------------------------------------
// export const updateLocation = ({ dispatch }) => {
//   return (nextLocation) => dispatch(locationChange(nextLocation))
//}

export const actions = {
    MAKE_SCREENSHOT,
    MAKE_SCREENSHOT_RESULT,
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {test: 123};
export default function mainReducer(state = initialState, action) {
    switch (action.type) {
        case actions.MAKE_SCREENSHOT_RESULT:
            return {...state, screenshotData: action.data};
            break;

        //
        // case MAKE_SCREENSHOT:
        //     return Object.assign({}, state, {
        //         screenshotData: null,
        //     });
        default:
            return state;
    }
}

// ------------------------------------
// Constants
// ------------------------------------
const MAKE_SCREENSHOT = 'MAKE_SCREENSHOT'
const MAKE_SCREENSHOT_RESULT = 'MAKE_SCREENSHOT_RESULT'

// ------------------------------------
// Actions
// ------------------------------------
export function makeScreenshot() {
    return {
        type: MAKE_SCREENSHOT
    }
}

// // ------------------------------------
// // Specialized Action Creator
// // ------------------------------------
// export const updateLocation = ({ dispatch }) => {
//   return (nextLocation) => dispatch(locationChange(nextLocation))
//}

export const actions = {
    MAKE_SCREENSHOT,
    MAKE_SCREENSHOT_RESULT,
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {};
export default function mainReducer(state = initialState, action) {
    switch (action.type) {
        case actions.MAKE_SCREENSHOT_RESULT:
            return {...state, screenshotData: action.data};
            break;

        //
        // case MAKE_SCREENSHOT:
        //     return Object.assign({}, state, {
        //         screenshotData: null,
        //     });
        default:
            return state;
    }
}

Remove initial state test key


export default function quicksort(list) {
  if (list.length < 2) {
    return list;
  }

  const pivot = list[0]; // TODO: random
  const less = list.filter(i => i < pivot);
  const greater = list.filter(i => i > pivot);

  return [...quicksort(less), pivot, ...quicksort(greater)];
}

function random(list) {
  return list[Math.round(Math.random() * (list.length - 1))];
}

export default function quicksort(list) {
  if (list.length < 2) {
    return list;
  }

  const pivot = random(list);
  const less = list.filter(i => i < pivot);
  const greater = list.filter(i => i > pivot);

  return [...quicksort(less), pivot, ...quicksort(greater)];
}

Select random pivot in quick sort


// import necesary module
import express from 'express';
import verifyToken from '../middlewares/verifyToken.js';
import users from '../controllers/users';

const router = express.Router();

// API index endpoint
router.get('/', (req, res) => res.status(200).send({
  message: 'Welcome to Hello-books API',
}));

router.get('/borrowrequests', verifyToken, users.getBorrowRequests);
router.get('/returnrequests', verifyToken, users.getReturnRequests);

export default router;

// import necesary module
import express from 'express';
import verifyToken from '../middlewares/verifyToken.js';
import users from '../controllers/users';
import checkAdmin from '../middlewares/checkAdmin';

const router = express.Router();

// API index endpoint
router.get('/', (req, res) => res.status(200).send({
  message: 'Welcome to Hello-books API',
}));

// Endpoint to get all borrow requests
router.get(
  '/borrowrequests',
  verifyToken,
  checkAdmin,
  users.getBorrowRequests,
);

// Endpoint to get all return requests
router.get(
  '/returnrequests',
  verifyToken,
  checkAdmin,
  users.getReturnRequests,
);

export default router;

Fix endpoint to get borrow requests and return requests


import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('trix-editor', 'Integration | Component | trix editor', {
  integration: true
});

test('it renders', function(assert) {
  assert.expect(2);

  // Set any properties with this.set('myProperty', 'value');
  // Handle any actions with this.on('myAction', function(val) { ... });

  this.render(hbs`{{trix-editor}}`);

  assert.equal(this.$().text().trim(), '');

  // Template block usage:
  this.render(hbs`
    {{#trix-editor}}
      template block text
    {{/trix-editor}}
  `);

  assert.equal(this.$().text().trim(), 'template block text');
});

import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

moduleForComponent('trix-editor', 'Integration | Component | trix editor', {
  integration: true
});

test('it renders', function (assert) {
  assert.expect(5);

  this.render(hbs``);

  let $trixEditor = this.$().find('> div');
  assert.strictEqual($trixEditor.length, 0);

  this.render(hbs`{{trix-editor}}`);

  $trixEditor = this.$().find('> div');
  assert.strictEqual($trixEditor.length, 1);
  assert.strictEqual($trixEditor.find('> input').length, 1);
  assert.strictEqual($trixEditor.find('> trix-toolbar').length, 1);
  assert.strictEqual($trixEditor.find('> trix-editor').length, 1);
});

Add integration test to confirm proper component rendering


module.exports = function (options) {
    options = options || {};
    var unexpected = (options.unexpected || require('unexpected')).clone();
    unexpected.output.preferredWidth = options.preferredWidth || 80;
    if (typeof options.indentationWidth === 'number') {
        unexpected.output.indentationWidth = options.indentationWidth;
    }
    unexpected.installPlugin(require('magicpen-prism'));

    var themePlugin = options.theme === 'dark' ?
        require('./magicpenDarkSyntaxTheme') :
        require('./magicpenGithubSyntaxTheme');

    return unexpected.clone().installPlugin(themePlugin);
}

var extend = require('./extend');

module.exports = function (options) {
    options = extend({}, options || {});
    var unexpected = (options.unexpected || require('unexpected')).clone();
    unexpected.output.preferredWidth = options.preferredWidth || 80;
    if (typeof options.indentationWidth === 'number') {
        unexpected.output.indentationWidth = options.indentationWidth;
    }
    unexpected.installPlugin(require('magicpen-prism'));

    var themePlugin = options.theme === 'dark' ?
        require('./magicpenDarkSyntaxTheme') :
        require('./magicpenGithubSyntaxTheme');

    return unexpected.clone().installPlugin(themePlugin);
}

Extend options before chaning them


module.exports = function (grunt) {
  grunt.loadNpmTasks('grunt-contrib-csslint');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-docco');

  grunt.initConfig({
    jshint: {
      files: 'js/*.js',
      options: { jshintrc: true }
    },
    csslint: {
      files: '*.css'
    },
    docco: {
      files: 'js/*.js'
    }
  });

  grunt.registerTask('default', ['jshint', 'csslint']);
};

module.exports = function (grunt) {
  grunt.loadNpmTasks('grunt-contrib-csslint');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-docco');

  grunt.initConfig({
    jshint: {
      files: 'js/*.js',
      options: { jshintrc: true }
    },
    csslint: {
      files: '*.css'
    },
    docco: {
      files: 'js/*.js'
    }
  });

  grunt.registerTask('lint', ['jshint', 'csslint']);
  grunt.registerTask('default', 'lint');
};

Add a lint task for Grunt


(function () {
    "use strict";

    var configureGrunt = function (grunt) {

        var cfg = {
            // JSLint all the things!
            jslint: {
                directives: {
                    nomen: true,
                    todo: true,
                    predef: ["__dirname", "module", "exports", "require", "process", "document", "console"]
                },
                files: [
                    // Lint files in the root, including Gruntfile.js
                    "*.js",
                    // Lint core files, but not libs
                    ["core/**/*.js", "!**/assets/lib/**/*.js"]
                ]
            },

            // Unit test all the things!
            nodeunit: {
                all: ['core/test/ghost/**/test-*.js']
            },

            // Compile all the SASS!
            compass: {
                options: {
                    config: "config.rb"
                },
                // No need for config, but separated for future options
                admin: {}
            }
        };

        grunt.initConfig(cfg);

        grunt.loadNpmTasks("grunt-jslint");
        grunt.loadNpmTasks("grunt-contrib-nodeunit");
        grunt.loadNpmTasks("grunt-contrib-compass");

        // Prepare the project for development
        // TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)?
        grunt.registerTask("init", ["compass:admin"]);

        // Run tests and lint code
        grunt.registerTask("validate", ["jslint", "nodeunit:all"]);
    };

    module.exports = configureGrunt;

}());

(function () {
    "use strict";

    var configureGrunt = function (grunt) {

        var cfg = {
            // JSLint all the things!
            jslint: {
                directives: {
                    node: true,
                    browser: true,
                    nomen: true,
                    todo: true
                },
                files: [
                    // Lint files in the root, including Gruntfile.js
                    "*.js",
                    // Lint core files, but not libs
                    ["core/**/*.js", "!**/assets/lib/**/*.js"]
                ]
            },

            // Unit test all the things!
            nodeunit: {
                all: ['core/test/ghost/**/test-*.js']
            },

            // Compile all the SASS!
            compass: {
                options: {
                    config: "config.rb"
                },
                // No need for config, but separated for future options
                admin: {}
            }
        };

        grunt.initConfig(cfg);

        grunt.loadNpmTasks("grunt-jslint");
        grunt.loadNpmTasks("grunt-contrib-nodeunit");
        grunt.loadNpmTasks("grunt-contrib-compass");

        // Prepare the project for development
        // TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)?
        grunt.registerTask("init", ["compass:admin"]);

        // Run tests and lint code
        grunt.registerTask("validate", ["jslint", "nodeunit:all"]);
    };

    module.exports = configureGrunt;

}());

Remove predefined globals list in favor of node:true and browser:true


(function() {

// Determine which window is active.
window.SC_isActive = false;
var handleFocusEvent = function(e) {
    window.SC_isActive = true;
};
var handleBlurEvent = function(e) {
    window.SC_isActive = false;
};
window.addEventListener("focus", handleFocusEvent)
window.addEventListener("blur", handleBlurEvent)

// Handle keyboard events.
var handleKeyboardEvent = function(e) {
    var inputIsActive = false;
    var activeElement = document.activeElement;
    if (activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA")) {
        inputIsActive = true;
    }

    if (e.keyCode === 191 && !inputIsActive) {        // '/'
        top.chatpane.focus();
        top.chatpane.chatform.graf.focus();
        top.chatpane.chatform.graf.value = "/";
    }
    else if (e.keyCode === 190 && !inputIsActive) {   // '.'
        top.chatpane.focus();
        top.chatpane.chatform.graf.focus();
    }
    else if (e.keyCode === 68 && e.ctrlKey) {         // 'd'
        top.mainpane.focus();
    }
};

window.addEventListener("keydown", handleKeyboardEvent)

})();

(function() {

// Determine which window is active.
window.SC_isActive = false;
var handleFocusEvent = function(e) {
    window.SC_isActive = true;
};
var handleBlurEvent = function(e) {
    window.SC_isActive = false;
};
window.addEventListener("focus", handleFocusEvent)
window.addEventListener("blur", handleBlurEvent)

// Handle keyboard events.
var handleKeyboardEvent = function(e) {
    var inputIsActive = false;
    var activeElement = document.activeElement;
    if (activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA")) {
        inputIsActive = true;
    }

    var handledEvent = false;
    if (e.keyCode === 191 && !inputIsActive) {        // '/'
        top.chatpane.focus();
        top.chatpane.chatform.graf.focus();
        top.chatpane.chatform.graf.value = "/";
        handledEvent = true;
    }
    else if (e.keyCode === 190 && !inputIsActive) {   // '.'
        top.chatpane.focus();
        top.chatpane.chatform.graf.focus();
        handledEvent = true;
    }
    else if (e.keyCode === 68 && e.ctrlKey) {         // '^d'
        top.mainpane.focus();
        handledEvent = true;
    }

    if (handledEvent) {
        e.preventDefault();
        e.stopPropagation();
    }
};

window.addEventListener("keydown", handleKeyboardEvent)

})();

Fix an issue with chat field focus commands.


define(function(require, exports, module) {
  'use strict';
  var TSCORE = require("tscore");
  console.log("Loading: ext.api.js");

  window.addEventListener("message", handleMessage , false);

  function handleMessage(msg) {
    var data = JSON.parse(msg.data);
    var command = data.command;

    switch (command) {
      case "openLinkExternally":
        if (data.link) {
          openLinkExternally(data.link);
        }
        break;
      case "contentChangedInEditor":
        TSCORE.FileOpener.setFileChanged(true);
        break;
      default:
        console.log("Not recognized messaging command: " + msg);
        break;
    }
  }

  function openLinkExternally(uri) {
    uri = decodeURIComponent(uri);
    if (
        uri.indexOf("http://") === 0 ||
        uri.indexOf("https://") === 0 ||
        uri.indexOf("file://") === 0
    ) {
      TSCORE.IO.openFile(uri);
    } else {
      console.log("Not supported URL format: " + uri);
    }
  }

});

define(function(require, exports, module) {
  'use strict';
  var TSCORE = require("tscore");
  console.log("Loading: ext.api.js");

  window.addEventListener("message", handleMessage , false);

  function handleMessage(msg) {
    var data = JSON.parse(msg.data);
    var command = data.command;

    switch (command) {
      case "endedVideo":
        if(data){
          console.log(data);
          openVideoFileFormat(data);
        } else {console.log('err')}
        break;
      case "openLinkExternally":
        if (data.link) {
          openLinkExternally(data.link);
        }
        break;
      case "contentChangedInEditor":
        TSCORE.FileOpener.setFileChanged(true);
        break;
      default:
        console.log("Not recognized messaging command: " + msg);
        break;
    }
  }

  function openVideoFileFormat(filePath) {

    if(TSCORE.selectedFiles[0]) {
      TSCORE.FileOpener.getNextFile(filePath);
    }
  }

  function openLinkExternally(uri) {
    uri = decodeURIComponent(uri);
    if (
        uri.indexOf("http://") === 0 ||
        uri.indexOf("https://") === 0 ||
        uri.indexOf("file://") === 0
    ) {
      TSCORE.IO.openFile(uri);
    } else {
      console.log("Not supported URL format: " + uri);
    }
  }

});

Update plyr.io version in AudioVideo player


$(document).ready(function() {
    var classview = new window.ClassesView();

    var message_class = '#create-class-message';
    var create_class_form = '.create-class-form';
    var create_class_button = '#create-class';

    var error_display = function(model, xhr, options){
        $(create_class_form).removeClass("has-success").addClass("has-error");
        $(message_class).html("This class name has been taken.  Please try another.");
        $(create_class_button).attr('disabled', false);
    };
    var success_display = function(model, response, options){
        $(create_class_form).removeClass("has-error").addClass("has-success");
        $(message_class).html("Class created!  Click on the class name below to get started.  You may need to reload the page to see it.");
        $("#refresh-sidebar").click();
        classview.refresh();
        $(create_class_button).attr('disabled', false);
    };
    $(create_class_button).click( function(event){
        event.preventDefault();
        $(event.target).attr('disabled', true);
        var class_name = $("#inputClass1").val();
        var classgroup = new Class({'name' : class_name});
        classgroup.save(null,{success : success_display, error: error_display, async: false});
        return false;
    });
});

$(document).ready(function() {
    var classview = new window.ClassesView();

    var message_class = '#create-class-message';
    var create_class_form = '.create-class-form';
    var create_class_button = '#create-class';

    var error_display = function(model, xhr, options){
        $(create_class_form).removeClass("has-success").addClass("has-error");
        $(message_class).html("This class name has been taken.  Please try another.");
        $(create_class_button).attr('disabled', false);
    };
    var success_display = function(model, response, options){
        $(create_class_form).removeClass("has-error").addClass("has-success");
        $(message_class).html("Class created!  Click on the class name below to get started.  You may need to reload the page to see it.");
        window.setTimeout(
            function(){
                $("#refresh-sidebar").click();
                classview.refresh();
                $(create_class_button).attr('disabled', false);
            },
            1000
        );
    };
    $(create_class_button).click( function(event){
        event.preventDefault();
        $(event.target).attr('disabled', true);
        var class_name = $("#inputClass1").val();
        var classgroup = new Class({'name' : class_name});
        classgroup.save(null,{success : success_display, error: error_display, async: false});
        return false;
    });
});

Add in settimeout for class creation


import React, { Component } from 'react';

class FileLoader extends Component {
  render() {
    return (
      <div className='flex justify-center mt4 b'>
        Loading file : {this.props.fileLoadStatus}%
      </div>
    );
  }
}

export default FileLoader;

import React, { Component } from 'react';

class FileLoader extends Component {
  render() {
    return (
      <div className='flex justify-center mt4 b'>
        Loading file: {this.props.fileLoadStatus}%
      </div>
    );
  }
}

export default FileLoader;

Remove white space before colon


var TrieNode = require('../app/trie-node');

describe('TrieNode', function() {
  var trienode;

  beforeEach(function() {
    trienode = new TrieNode();
  });

  it('should be an object', function() {
    expect(trienode).to.be.ok;
  });

});

var TrieNode = require('../app/trie-node');

describe('TrieNode', function() {
  var trienode;

  beforeEach(function() {
    trienode = new TrieNode();
  });

  it('should be an object', function() {
    expect(trienode).to.be.ok;
  });

  it('should set a character', function() {
    trienode = new TrieNode('t');

    expect(trienode.character).to.equal('t');
  });

});

Add test for character constructor


'use strict';

Connector.playerSelector = '#player-controller';

Connector.trackArtSelector = '.imgEl > img';

Connector.artistTrackSelector = '.currentSong';

Connector.isPlaying = () => $('.progressBg').width() > 0;

Connector.filter = MetadataFilter.getYoutubeFilter();

Apply YouTube filter for dubtrack


(function() {
'use strict';

angular
  .module('semantic.ui.elements.button', [])

  .directive('smButton', smButton);

  function smButton() {
    return {
      restrict:'E',
      replace: true,
      transclude: true,
      template: setTemplate,
      link: function(scope, element, attrs, ctrl, transclude) {
        var node = element[0];

        transclude(function(nodes) {
          element.append(nodes);
        });

        if (isAnchorBtn(attrs)) {
          scope.$watch(attrs.ngDisabled, function(isDisabled) {
            element.attr('tabindex', isDisabled ? -1 : 0);
            if (isDisabled) {
              element.addClass('disabled');
            }
          });
        }

        if (attrs.ariaLabel === void 0) {
          element.attr('aria-label', node.textContent.trim());
        }

        scope.$watch(attrs.ngDisabled, function(isDisabled) {
          if (isDisabled) {
            element.addClass('disabled');
          }
        });
      }
    };

    function isAnchorBtn(attrs) {
      return attrs.href !== void 0 || attrs.ngHref !== void 0 || attrs.xlinkHref !== void 0;
    }

    function setTemplate(element, attrs) {
      if (isAnchorBtn(attrs)) {
        return '';
      } else {
        return '<button class="ui button"></button>';
      }
    }
  }

})();

(function() {
'use strict';

angular
  .module('semantic.ui.elements.button', [])

  .directive('smButton', smButton);

  smButton['$inject'] = ['$animate'];

  function smButton($animate) {
    return {
      restrict:'E',
      replace: true,
      transclude: true,
      template: setTemplate,
      link: function(scope, element, attrs, ctrl, transclude) {
        var node = element[0];

        transclude(scope, function(nodes) {
          element.append(nodes);
        });

        if (isAnchorBtn(attrs)) {
          scope.$watch(attrs.ngDisabled, function(isDisabled) {
            element.attr('tabindex', isDisabled ? -1 : 0);
            if (isDisabled) {
              $animate.addClass(element, 'disabled');
            }
          });
        }

        if (attrs.ariaLabel === void 0) {
          element.attr('aria-label', node.textContent.trim());
        }

        scope.$watch(attrs.ngDisabled, function(isDisabled) {
          if (isDisabled) {
            $animate.addClass(element, 'disabled');
          }
        });
      }
    };

    function isAnchorBtn(attrs) {
      return attrs.href !== void 0 || attrs.ngHref !== void 0 || attrs.xlinkHref !== void 0;
    }

    function setTemplate(element, attrs) {
      if (isAnchorBtn(attrs)) {
        return '';
      } else {
        return '<button class="ui button"></button>';
      }
    }
  }

})();

Use to manage css classes


import PropTypes from 'prop-types';

import optionShape from './optionShape';

export default PropTypes.arrayOf(
    PropTypes.oneOfType([
        optionShape,
        PropTypes.shape({
            label: PropTypes.node.isRequired,
            options: PropTypes.arrayOf(optionShape).isRequired,

            // Optional properties
            disabled: PropTypes.bool,
            title: PropTypes.title,
        }),
    ]),
);

import PropTypes from 'prop-types';

import optionShape from './optionShape';

export default PropTypes.arrayOf(
    PropTypes.oneOfType([
        optionShape,
        PropTypes.shape({
            label: PropTypes.node.isRequired,
            options: PropTypes.arrayOf(optionShape).isRequired,

            // Optional properties
            disabled: PropTypes.bool,
            title: PropTypes.string,
        }),
    ]),
);

Fix `propType` check for `option.title`


function MaterialsFilePicker(callback) {
  this.doneCallback = callback;
  this.selectedMaterials = {};
  this.treeElement = $('#file-picker-tree');

  var courseId = gon.course;
  var that = this;
  $.ajax({
    url: '/courses/' + courseId + '/materials.json',
    success: that.onWorkbinStructureReceived
  });
}

MaterialsFilePicker.prototype.onSelectionCompleted = function() {
  var selectedItems = [];
  for (var id in selectedMaterials) {
    var currentTuple = selectedMaterials[id];
    selectedItems.push(currentTuple);
  }

  this.doneCallback(selectedItems);
};

MaterialsFilePicker.prototype.onWorkbinStructureReceived = function(rootNode) {

}

function MaterialsFilePicker(callback) {
  this.doneCallback = callback;
  this.selectedMaterials = {};
  this.treeElement = $('#file-picker-tree');

  var courseId = gon.course;
  var that = this;
  $.ajax({
    url: '/courses/' + courseId + '/materials.json',
    success: that.onWorkbinStructureReceived
  });
}

MaterialsFilePicker.prototype.onSelectionCompleted = function() {
  var selectedItems = [];
  for (var id in selectedMaterials) {
    var currentTuple = selectedMaterials[id];
    selectedItems.push(currentTuple);
  }

  this.doneCallback(selectedItems);
};

MaterialsFilePicker.prototype.onWorkbinStructureReceived = function(rootNode) {
  var shouldIncludeFiles = true;
  var treeData = parseFileJsonForJqTree(rootNode, shouldIncludeFiles);

  treeElement.tree({
    data: treeData,
    autoOpen: true,
    keyboardSupport: false
  });
};

Add initialization callback for file picker.


module.exports = function(grunt){
  'use strict';
  require('load-grunt-tasks')(grunt);
  grunt.loadNpmTasks('grunt-contrib-jshint');
  var jsFiles = ['npactweb/static/js/**/*.js'];

  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: true
      },
      all: jsFiles,
      jenkins: {
        files: {src: jsFiles},
        options: {
          reporter: 'checkstyle',
          reporterOutput: 'jshint.xml',
          force: true
        }
      }
    },
    karma: {
      options: {
	configFile: 'karma.conf.js'
      },
      unit: {
        reporters: 'dots'
      },
      jenkins: {
        reporters: 'junit',
        outputFile: 'test-results.xml',
        singleRun: true
      }
    },
    html2js: {
      options:{
	singleModule:true
      },
      main: {
	src: ['npactweb/static/js/**/*.html'],
	dest: '.tmp/templates.js'
      }
    }
  });

  grunt.registerTask('test', ['karma']);
  grunt.registerTask('jenkins', ['jshint:jenkins',
                                 'html2js:main',
                                 'karma:jenkins']);
};

module.exports = function(grunt){
  'use strict';
  require('load-grunt-tasks')(grunt);
  grunt.loadNpmTasks('grunt-contrib-jshint');
  var jsFiles = ['npactweb/static/js/**/*.js'];

  grunt.initConfig({
    jshint: {
      options: {
        jshintrc: true
      },
      all: jsFiles,
      jenkins: {
        files: {src: jsFiles},
        options: {
          reporter: 'checkstyle',
          reporterOutput: 'jshint.xml',
          force: true
        }
      }
    },
    karma: {
      options: {
	configFile: 'karma.conf.js'
      },
      unit: {
        reporters: 'dots'
      },
      jenkins: {
        reporters: 'junit',
        outputFile: 'test-results.xml',
        singleRun: true
      }
    },
    html2js: {
      options:{
	singleModule:true
      },
      main: {
	src: ['npactweb/static/js/**/*.html'],
	dest: '.tmp/templates.js'
      }
    }
  });

  grunt.registerTask('test', ['karma:unit']);
  grunt.registerTask('jenkins', ['jshint:jenkins',
                                 'html2js:main',
                                 'karma:jenkins']);
};

Make `grunt test` not invoke jenkins tests as well


const Dotenv = require('dotenv-webpack');
const WebpackShellPlugin = require('webpack-shell-plugin');

module.exports = {
    plugins: [
        new Dotenv(),
        new WebpackShellPlugin({onBuildExit:['./scripts/package.js']})
    ],
    entry: {
        pivotal_tracker_content_scripts: './src/content_scripts/pivotal_tracker/main.js',
        gmail_content_scripts: './src/content_scripts/gmail/main.js',
        background_scripts: './src/background/main.js',
        options_page: './src/options/main.jsx'
    },
    output: {
        path: `${__dirname}/dist/bundles/`,
        filename: '[name].bundle.js'
    },
    module: {
        loaders: [
            {test: /\.css$/, loader: "style!css"},
            {test: /\.json$/, exclude: /node_modules/, loader: 'json'},
            {
                test: /.jsx?$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                query: {
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
};

const Dotenv = require('dotenv-webpack');
const WebpackShellPlugin = require('webpack-shell-plugin');

module.exports = {
    plugins: [
        new Dotenv(),
        new WebpackShellPlugin({onBuildExit:['./scripts/package.js']})
    ],
    entry: {
        pivotal_tracker_content_scripts: './src/content_scripts/pivotal_tracker/main.js',
        gmail_content_scripts: './src/content_scripts/gmail/main.js',
        background_scripts: './src/background/main.js',
        options_page: './src/options/main.jsx'
    },
    output: {
        path: `${__dirname}/dist/bundles/`,
        filename: '[name].bundle.js'
    },
    module: {
        rules: [
            {
                test: /.jsx?$/,
                exclude: /node_modules/,
                use: {
                  loader: 'babel-loader',
                  options:{
                    presets: ['es2015', 'react']
                  }
                }
            }
        ]
    }
};

Remove unused webpack loaders and use latest webpack Rules syntax


'use strict';

define('<%= main_module_name %>',
  [
    // Add your dependencies here
    'jquery',
    'jquery-ujs'
  ],

  function($){

    function initialize(){
      // Add your initialization code here
    }

    return initialize;

  }
);

'use strict';

define('<%= main_module_name %>',
  [
    // Add your dependencies here
    'jquery',
    'jquery-ujs'
  ],

  function($){

      // Add your initialization code here

  }
);

Remove nesting for init module


var Promise = require('promise');

var Deferred = function Deferred() {
	"use strict";

	if (!(this instanceof Deferred)) { return new Deferred(); }

	var self = this;
	self.promise = new Promise(function (resolve, reject) {
		self.resolve = resolve;
		self.reject = reject;
	});
	return self;
};
Deferred.Promise = Promise;

module.exports = Deferred;

"use strict";

var Promise = require('promise');

var Deferred = function Deferred() {
	if (!(this instanceof Deferred)) { return new Deferred(); }

	var self = this;
	self.promise = new Promise(function (resolve, reject) {
		self.resolve = resolve;
		self.reject = reject;
	});
	return self;
};
Deferred.Promise = Promise;

module.exports = Deferred;

Make the entire module be in strict mode


import React from 'react'
import Tabs from 'react-toolbox/lib/tabs/Tabs'
import Tab from 'react-toolbox/lib/tabs/Tab'
import ClickPath from './ClickPath'
import JSONTree from 'react-json-tree'

class DetailsTabs extends React.Component {
  constructor(props) {
    super(props);
    this.state = { index: 0 };
  }
  tabChanged = (index) => {
    this.setState({ index });
  }
  render() {
    let selection = this.props.selection;
    return (
      <section style={{
        backgroundColor: 'lavender',
        height: '100%'
      }}>
        <Tabs inverse fixed
          index={this.state.index}
          onChange={this.tabChanged}>
          <Tab label="Details">
            { selection ? <JSONTree data={selection.data()} /> : "Select Something" }
          </Tab>
          <Tab label="Click Path">
            <ClickPath
              visit={this.props.visit}
              actions={this.props.actions} />
          </Tab>
        </Tabs>
      </section>
      );
  }
}

export default DetailsTabs;

import React from 'react'
import Tabs from 'react-toolbox/lib/tabs/Tabs'
import Tab from 'react-toolbox/lib/tabs/Tab'
import ClickPath from './ClickPath'
import JSONTree from 'react-json-tree'

class DetailsTabs extends React.Component {
  constructor(props) {
    super(props);
    this.state = { index: 0 };
  }
  tabChanged = (index) => {
    this.setState({ index });
  }
  render() {
    let selection = this.props.selection;
    return (
      <section style={{
        backgroundColor: 'lavender',
        height: '100%'
      }}>
        <Tabs inverse fixed
          index={this.state.index}
          onChange={this.tabChanged}>
          <Tab label="Click Path">
            { selection ?
            <ClickPath
              visit={this.props.visit}
              actions={this.props.actions} />
                : "Select something" }
          </Tab>
          <Tab label="Details">
            { selection ? <JSONTree data={selection.data()} /> : "Select something" }
          </Tab>
        </Tabs>
      </section>
      );
  }
}

export default DetailsTabs;

Put ClickPath as first tab


import React, {Component} from 'react';
import $ from 'jquery';
import CurrentWeatherCard from './CurrentWeatherCard';

export default class App extends Component {
  constructor(){
    super()
    this.state = {
      location:'',
      weatherData: null,
    };
  }

  handleInput(e) {
    this.setState({
      location: e.target.value,
    })
  }

  handleSubmit(){
    const currentLocation = this.state.location
    $.get(`http://api.wunderground.com/api/878e77b9c3411d19/hourly/conditions/forecast10day/q/${currentLocation}.json`).then((data)=> {
      this.setState({
        location:'',
        weatherData: data,
      })
    })
  }

  render() {
    return (

        <navbar>
          <input type="text" value={this.state.location} onChange={this.handleInput.bind(this)}/>
          <input type="submit" onClick={this.handleSubmit.bind(this)}/>
        </navbar>

Weathrly

        <CurrentWeatherCard weatherData={this.state.weatherData}/>

    )
  }
}

import React, {Component} from 'react';
import $ from 'jquery';
import CurrentWeatherCard from './CurrentWeatherCard';

export default class App extends Component {
  constructor(){
    super()
    this.state = {
      location:'',
      responseData: null,
      currentData: null,
      forecastData: null,
      hourlyData: null,
    };
  }

  handleInput(e) {
    this.setState({
      location: e.target.value,
    })
  }

  handleSubmit(){
    const currentLocation = this.state.location
    $.get(`http://api.wunderground.com/api/878e77b9c3411d19/hourly/conditions/forecast10day/q/${currentLocation}.json`).then((data)=> {
      this.handleData(data)
    })
  }

  handleData(weatherData){
    const keys = Object.keys(weatherData)
    this.setState({
      location:'',
      responseData:weatherData[keys[0]],
      currentData:weatherData[keys[1]],
      forecastData:weatherData[keys[2]],
      hourlyData:weatherData[keys[3]],
    })
  }

  render() {
    return (

        <navbar>
          <input type="text" value={this.state.location} onChange={this.handleInput.bind(this)}/>
          <input type="submit" onClick={this.handleSubmit.bind(this)}/>
        </navbar>

Weathrly

        <CurrentWeatherCard weatherData={this.state.weatherData}/>

    )
  }
}

Handle data into individual states.


#! /usr/bin/env node
"use strict";

require('babel-polyfill');

function showHelpAndExit() {
    console.log("Usage: extract-messages [-m EXISTING] [-o OUTPUT] ...FILES/DIRECTORIES");
    console.log("Prints a JSON document with messages in FILES/DIRECTORIES mapped to themselves.");
    console.log("If -o is passed, writes to OUTPUT instead of stdout.");
    process.exit();
}

var argv = require('minimist')(process.argv.slice(2), {
    string: 'oh',
    alias: {o: 'output', h: 'help'}
});

if (argv._.length === 0 || argv.h) {
    showHelpAndExit();
}

var chalk = require('chalk');
var fs = require('fs');

var filesFromMixedPaths = require('./filesFromMixedPaths');
var extractFromPaths = require('../build/extract').default;

var output = extractFromPaths(argv._);
if (argv.o) {
    fs.writeFileSync(argv.o, output);
} else {
    console.log(output);
}

#! /usr/bin/env node
"use strict";

require('babel-polyfill');

function showHelpAndExit() {
    console.log("Usage: extract-messages [-m EXISTING] [-o OUTPUT] [-f FORMAT (po|json)]...FILES/DIRECTORIES");
    console.log("Prints a document (gettext PO format by default) with messages in")
    console.log("FILES/DIRECTORIES mapped to themselves.");
    console.log("If -o is passed, writes to OUTPUT instead of stdout.");
    process.exit();
}

var argv = require('minimist')(process.argv.slice(2), {
    string: ['ohf'],
    alias: {o: 'output', h: 'help', f: 'format'}
});

if (argv._.length === 0 || argv.h) {
    showHelpAndExit();
}

var chalk = require('chalk');
var fs = require('fs');

var filesFromMixedPaths = require('./filesFromMixedPaths');
var extractFromPaths = require('../build/extract').default;

var output = extractFromPaths(argv._, { outputFormat: argv.f || 'po' });
if (argv.o) {
    fs.writeFileSync(argv.o, output);
} else {
    console.log(output);
}

Add support for output format


/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('SlateAdmin.store.people.People', {
    extend: 'Ext.data.Store',

    model: 'SlateAdmin.model.person.Person',
    proxy: {
        type: 'slaterecords',
        url: '/people',
        startParam: false,
        limitParam: false,
        include: 'groupIDs'
    }
});

/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('SlateAdmin.store.people.People', {
    extend: 'Ext.data.Store',

    model: 'SlateAdmin.model.person.Person',
    proxy: {
        type: 'slaterecords',
        url: '/people',
        startParam: false,
        limitParam: false,
        include: ['groupIDs','Advisor']
    }
});

Add advisors to included people data


var Botkit = require('botkit');

var controller = Botkit.slackbot({
    debug: false
});

controller.spawn({
    token: process.env.token || ''
}).startRTM(function(err) {
    if (err) {
        throw new Error(err);
    }
});

controller.hears(['hello', 'hi'], ['direct_message', 'direct_mention', 'mention'],
    function(bot, message) {
        bot.reply(message, 'Hello yourself.');
    }
);

controller.on('direct_mention', function(bot, message) {
    bot.reply(message, 'I heard you mention me!');
});

var Botkit = require('botkit');

var controller = Botkit.slackbot({
    debug: false
});

controller.spawn({
    token: process.env.token || ''
}).startRTM(function (err) {
    if (err) {
        throw new Error(err);
    }
});

controller.hears(['hello', 'hi'], ['direct_message', 'direct_mention', 'mention'],
    function (bot, message) {
        bot.reply(message, 'Hello yourself.');
    }
);

controller.on('direct_mention', function (bot, message) {
    bot.reply(message, 'I heard you mention me!');
});

controller.hears(['random'], ['direct_mention'], function (bot, message) {
    bot.say(
        {
            text: 'Message to random channel',
            channel: '#random'
        }
    );
});

Add message to random channel


/* @flow */
import React from 'react';
import Box from 'grommet/components/Box';
import Heading from 'grommet/components/Heading';
import { BlockParagraph } from '../BlockParagraph';
import Swatch from './swatch';

export default function BlockColorSwatch(props: {
  color: ?{
    name: string,
    thumbColor: string,
    content: string
  }
}) {
  const { color } = props;
  if (!color) {
    return null;
  }
  return (
    <Box>
      <Swatch backgroundColor={color.thumbColor} />
      <Heading tag="h4" strong margin="none">
        {color.name}
      </Heading>
      {color.content &&
        <BlockParagraph content={color.content} />
      }
    </Box>
  );
}

/* @flow */
import React from 'react';
import Box from 'grommet/components/Box';
import Heading from 'grommet/components/Heading';
import { BlockParagraph } from '../BlockParagraph';
import Swatch from './swatch';

export default function BlockColorSwatch(props: {
  color: ?{
    name: string,
    thumbColor: string,
    content: string
  }
}) {
  const { color } = props;
  if (!color) {
    return null;
  }
  return (
    <Box>
      <Swatch backgroundColor={color.thumbColor} />
      <Heading
        tag="h4"
        strong
        margin="none"
        style={{ paddingTop: '12px' }}
      >
        {color.name}
      </Heading>
      {color.content &&
        <BlockParagraph content={color.content} />
      }
    </Box>
  );
}

Add padding to color swatch headline.


import { LOCATION_CHANGE } from 'react-router-redux';

export function patchObjectByKey(object, key, patcher) {
  return key in object ? { ...object, [key]: patcher(object[key]) } : object;
}

export function setOnLocationChange(targetState) {
  return (state, action) => {
    if (action.type === LOCATION_CHANGE) {
      return targetState;
    }
    return state;
  };
}

import { LOCATION_CHANGE } from 'react-router-redux';

export function patchObjectByKey(object, key, patcher) {
  return key in object ? { ...object, [key]: patcher(object[key]) } : object;
}

export function setOnLocationChange(targetState, excludePathnames = []) {
  return (state, action) => {
    if (action.type === LOCATION_CHANGE && !excludePathnames.includes(action.payload.pathname)) {
      return targetState;
    }
    return state;
  };
}

Add an excludePathnames parameter to the setOnLocationChange helper


import * as Builder from 'src/server/modules/build';
import * as Queries from 'src/server/modules/queries';

export const publish = async (req, res, next) => {
  const post = await Queries.post(req.params.id);
  post.lastPublished = Date.now();
  post.publishedMarkdown = post.markdown;
  post.save()
    .then(Queries.publishedPosts)
    .then(Builder.rebuild)
    .then(() => res.send('OK'))
    .catch(err => next(err));
}

export const unpublish = async (req, res, next) => {
  const post = await Queries.post(req.params.id);
  post.lastPublished = null;
  post.publishedMarkdown = '';
  post.save()
    .then(Queries.publishedPosts)
    .then(Builder.rebuild)
    .then(() => res.send('OK'))
    .catch(err => next(err));
}

import * as Builder from 'src/server/modules/build';
import * as Queries from 'src/server/modules/queries';

export const publish = async (req, res, next) => {
  const post = await Queries.post(req.params.id);
  post.lastPublished = Date.now();
  post.publishedMarkdown = post.markdown;
  const newPost = await post.save()
    .catch(err => next(err));

  await Queries.publishedPosts()
    .then(Builder.rebuild)
    .catch(err => next(err));

  res.json(newPost);
}

export const unpublish = async (req, res, next) => {
  const post = await Queries.post(req.params.id);
  post.lastPublished = null;
  post.publishedMarkdown = '';
  post.save()
    .then(Queries.publishedPosts)
    .then(Builder.rebuild)
    .then(() => res.send('OK'))
    .catch(err => next(err));
}

Return new post on save


#!/usr/bin/env node

'use strict'

var meow = require('meow')
var extend = require('xtend')
var degree = require('./')

var cli = meow({
  help: [
    'Usage',
    '  degree [entries...]'
  ]
})

var entries = cli.input
if (!entries.length) throw new Error('entries are required')

degree(entries, extend(cli.flags, {stream: process.stdout}))

#!/usr/bin/env node

'use strict'

var meow = require('meow')
var extend = require('xtend')
var degree = require('./')

var cli = meow({
  help: [
    'Usage',
    '  degree [entries...]'
  ]
})

var entries = cli.input
if (!entries.length) throw new Error('entries are required')

degree(entries, extend(cli.flags, {
  stream: process.stdout,
  browserify: {
    plugin: cli.flags.plugin
  }
}))

Allow browserify plugins to be specified via the --plugin flag


import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import App from './containers/App';
import Replacer from './components/Replacer';

const store = configureStore();

const root = document.getElementById('root');
if (root) {
  render(
    <Provider store={store}>
      <App />
    </Provider>,
    root
  );
}

const replacer = document.getElementById('replacer');
if (replacer) {
  render(
    <Replacer />,
    replacer
  );
}

/* global document */
import fs from 'fs';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import App from './containers/App';
import Replacer from './components/Replacer';
import { openFolder, openFile } from './actions/files';

const store = configureStore();

const root = document.getElementById('root');
if (root) {
  render(
    <Provider store={store}>
      <App />
    </Provider>,
    root
  );
}

const replacer = document.getElementById('replacer');
if (replacer) {
  render(
    <Replacer />,
    replacer
  );
}

document.addEventListener('drop', (e) => {
  e.preventDefault();
  e.stopPropagation();
  for (const f of e.dataTransfer.files) {
    const stat = fs.statSync(f.path);
    if (stat.isFile()) {
      store.dispatch(openFile(f.path));
    }
    if (stat.isDirectory()) {
      store.dispatch(openFolder(f.path));
    }
  }
  return false;
});
document.addEventListener('dragover', (e) => {
  e.preventDefault();
  e.stopPropagation();
});

Allow dragging files/folders onto the app


import React from 'react';
import {render} from 'react-dom';

import { Router, Route, applyRouterMiddleware} from 'react-router';

let history;
if(typeof process !== "undefined" && process.env.NODE_ENV === "production") {
	history = require('react-router/lib/hashHistory');
} else {
	history = require('react-router/lib/browserHistory');
}
// console.log(typeof process !== "undefined" && process.env.NODE_ENV);

import App from './components/App';
import NotFound from './components/NotFound';
import Footer from './components/Footer';

// TODO maybe change browserHistory to hashHitory when deploying

import useScroll from 'react-router-scroll/lib/useScroll';

import routes from './helpers/routemap';

render((
	<Router history={history} render={applyRouterMiddleware(useScroll())}>
		<Route path="/" component={App} children={routes}/>
		<Route path="*" component={NotFound}/>
	</Router>
),
document.getElementById("app"));

render(<Footer history= {history}/>, document.getElementById("footer-mount"));

import React from 'react';
import {render} from 'react-dom';

import { Router, Route, applyRouterMiddleware} from 'react-router';

let history;
if(typeof process !== "undefined" && process.env.NODE_ENV === "production") {
	history = require('react-router/lib/hashHistory');
} else {
	history = require('react-router/lib/browserHistory');
}
// console.log(typeof process !== "undefined" && process.env.NODE_ENV);

import App from './components/App';
import NotFound from './components/NotFound';
import Footer from './components/Footer';

// TODO maybe change browserHistory to hashHitory when deploying

import useScroll from 'react-router-scroll/lib/useScroll';

import routes from './helpers/routemap';

render((
	<Router history={history} render={applyRouterMiddleware(useScroll())}>
		<Route path="/" component={App}>
			{routes}
		<Route path="*" component={NotFound}/>
	</Route>
	</Router>
),
document.getElementById("app"));

render(<Footer history= {history}/>, document.getElementById("footer-mount"));

Make menu available in not-found routes


const kspecial = {
  32  : 'Spacebar',
  97  : 'Numpad 1',
  98  : 'Numpad 2',
  99  : 'Numpad 3',
  100 : 'Numpad 4',
  101 : 'Numpad 5',
  102 : 'Numpad 6',
  103 : 'Numpad 7',
  104 : 'Numpad 8',
  105 : 'Numpad 9'
}

const kcode = document.getElementById('key-code');
const kchar = document.getElementById('key-char');

window.addEventListener('keydown', function (event) {
  if (event.defaultPrevented) {
    return; // Should do nothing if the default action has been cancelled
  }

  var handled = false;

  if (event.keyCode !== undefined) {
    // Handle the event with KeyboardEvent.keyCode and set handled true.
    kcode.innerHTML = event.keyCode;
    handled = true;
  }

  if (event.key !== undefined) {
    // Handle the event with KeyboardEvent.key and set handled true.
    kchar.innerHTML = kspecial[event.keyCode] !== undefined ?
        kspecial[event.keyCode] : event.key;

    kchar.style.visibility = 'visible';
    handled = true;
  }

  if (handled) {
    // Suppress "double action" if event handled
    event.preventDefault();
  }
}, true);

const kspecial = {
  32  : 'Spacebar',
  96  : 'Numpad 0',
  97  : 'Numpad 1',
  98  : 'Numpad 2',
  99  : 'Numpad 3',
  100 : 'Numpad 4',
  101 : 'Numpad 5',
  102 : 'Numpad 6',
  103 : 'Numpad 7',
  104 : 'Numpad 8',
  105 : 'Numpad 9'
}

const kcode = document.getElementById('key-code');
const kchar = document.getElementById('key-char');

window.addEventListener('keydown', function (event) {
  if (event.defaultPrevented) {
    return; // Should do nothing if the default action has been cancelled
  }

  var handled = false;

  if (event.keyCode !== undefined) {
    // Handle the event with KeyboardEvent.keyCode and set handled true.
    kcode.innerHTML = event.keyCode;
    handled = true;
  }

  if (event.key !== undefined) {
    // Handle the event with KeyboardEvent.key and set handled true.
    kchar.innerHTML = kspecial[event.keyCode] !== undefined ?
        kspecial[event.keyCode] : event.key;

    kchar.style.visibility = 'visible';
    handled = true;
  }

  if (handled) {
    // Suppress "double action" if event handled
    event.preventDefault();
  }
}, true);

Add missing index of Numpad number


export function translate(load) {
  console.log('compiling template:', load.name);
  load.source = 'module.exports = require(\'handlebars\').compile(\'' + load.source
  .replace(/'/g, '\\\'')
  .replace(/[\f]/g, '\\f')
  .replace(/[\b]/g, '\\b')
  .replace(/[\n]/g, '\\n')
  .replace(/[\t]/g, '\\t')
  .replace(/[\r]/g, '\\r')
  .replace(/[\u2028]/g, '\\u2028')
  .replace(/[\u2029]/g, '\\u2029') + '\');';
}

import Handlebars from 'handlebars';

function instantiate(load) {
    console.log('compiling template:', load.name);
    return Handlebars.compile(load.source);
}

export { instantiate };

Remove indirect injected dependancy on handlebars in consuming config.js


/*
 * Logo
 * ====
 *
 * A sample prefab (extended game object class), for displaying the Phaser
 * logo.
 */

export default class BgLayer extends Phaser.TileSprite {

  constructor (game, x, y, width, height, key, moveMultiplier) {
    super(game, x, y, width, height, key);

    this.moveMultiplier = moveMultiplier;

  }

  update () {
    this.tilePosition.x = this.game.camera.x * this.moveMultiplier;
  }

}

/*
 * Logo
 * ====
 *
 * A sample prefab (extended game object class), for displaying the Phaser
 * logo.
 */

export default class BgLayer extends Phaser.TileSprite {

  constructor (game, x, y, width, height, key, moveMultiplier) {
    super(game, x, y, width, height, key);

    this.moveMultiplier = moveMultiplier;
    this.fixedOffset = new Phaser.Point(x, y);
    this.fixedToCamera = true;
    this.cameraOffset = new Phaser.Point(this.fixedOffset.x, this.fixedOffset.y);
  }

  update () {
    this.tilePosition.x = -(this.game.camera.x * this.moveMultiplier);
    this.cameraOffset.y = -(this.game.camera.y * this.moveMultiplier) + this.fixedOffset.y ;
  }

}

Fix parallax scroll speed and add vertical offsetting


chrome.extension.onMessage.addListener(
    function(request, sender, sendResponse) {
       console.log(request);
       console.log(sender);
       getOtherBookmarksChildren(function(other){
           if (other == null) {
               console.error("'Other Bookmarks' not found.");
               return;
           }
           var folders = retrieveFolders(other);
           var folder = determineBestFolder(request, folders);
       });
});

function getOtherBookmarksChildren(callback) {
    chrome.bookmarks.getTree(function(results){
        var title = "Other Bookmarks";
        var topLevel = results[0].children;
        for (var nodeKey in topLevel){
           var node = topLevel[nodeKey];
           if (node.title == title)
              return callback(node.children);
        }
        return callback(null);
     });
}

function retrieveFolders(folder) {
    var folders = [];
    for (var nodeKey in folder){
      var node = folder[nodeKey];
      if (!node.hasOwnProperty("url"))
          folders.push(node);
    }
    return folders;
}

function getUncategorizedFolder(folders){

}

function determineBestFolder(page, folders){
  return "Folder";
}

chrome.extension.onMessage.addListener(
    function(request, sender, sendResponse) {
       console.log(request);
       console.log(sender);
       getOtherBookmarksChildren(function(other, otherID){
           if (other == null) {
               console.error("'Other Bookmarks' not found.");
               return;
           }
           var folders = retrieveFolders(other, otherID);
           var folder = determineBestFolder(request, folders);
           getUncategorizedFolder(other, otherID, function(uncategorized){
              console.log(uncategorized);
           });
       });
});

function getOtherBookmarksChildren(callback) {
    chrome.bookmarks.getTree(function(results){
        var title = "Other Bookmarks";
        var topLevel = results[0].children;
        for (var nodeKey in topLevel){
           var node = topLevel[nodeKey];
           if (node.title == title)
              return callback(node.children, node.id);
        }
        return callback(null, null);
     });
}

function retrieveFolders(folder) {
    var folders = [];
    for (var nodeKey in folder){
      var node = folder[nodeKey];
      if (!node.hasOwnProperty("url"))
          folders.push(node);
    }
    return folders;
}

function getUncategorizedFolder(folders, otherID, callback){
    for (var folderKey in folders){
        var folder = folders[folderKey];
        if (folder.title == "Uncategorized")
            return callback(folder);
    }
    // Create uncategorized folder
    chrome.bookmarks.create({'parentId': otherID, 'title': 'Uncategorized'},
        function(uncategorizedFolder) {
            callback(uncategorizedFolder);
        }
    );
}

function determineBestFolder(page, folders){

}

Create uncategorized folder if it does not exist.


const electron = require('electron')
const app = require('electron').app
const dialog = electron.dialog
const Channel = require('./channel')
const path = require('path')

module.exports.showAbout = function () {
  dialog.showMessageBox({
    title: 'Brave',
    message: `Brave: ${app.getVersion()}
Electron: ${process.versions['atom-shell']}
libchromiumcontent: ${process.versions['chrome']}
V8: ${process.versions.v8}
Node.js: ${process.versions.node}
Update channel: ${Channel.channel()}

This software uses libraries from the FFmpeg project under the LGPLv2.1`,
    icon: path.join(__dirname, '..', 'app', 'img', 'braveAbout.png'),
    buttons: ['Ok']
  })
}

const electron = require('electron')
const app = require('electron').app
const dialog = electron.dialog
const Channel = require('./channel')
const path = require('path')

module.exports.showAbout = function () {
  dialog.showMessageBox({
    title: 'Brave',
    message: `Brave: ${app.getVersion()}
Electron: ${process.versions['atom-shell']}
libchromiumcontent: ${process.versions['chrome']}
V8: ${process.versions.v8}
Node.js: ${process.versions.node}
Update channel: ${Channel.channel()}

This software uses libraries from the FFmpeg project under the LGPLv2.1`,
    icon: path.join(__dirname, '..', 'app', 'extensions', 'brave', 'img', 'braveAbout.png'),
    buttons: ['Ok']
  })
}

Fix about dialog image reference


/**
 * Utilities for interacting with the Router and getting location data
 * @module routeUtils
 */
import Rx from 'rxjs';
import match from 'react-router/lib/match';

// Create observable from callback-based `match`
const match$ = Rx.Observable.bindNodeCallback(match);

/**
 * From the renderProps provided by React Router's `match`, collect the results
 * of the query properties associated with currently-active routes
 *
 * @param matchCallbackArgs {Array} redirectLocation(ignored) and renderProps
 * @return {Array} The return values of each active route's query function
 */
function getActiveRouteQueries([ , { routes, location, params }]) {
	const queries = routes
		.filter(({ query }) => query)  // only get routes with queries
		.reduce((queries, { query }) => {  // assemble into one array of queries
			const routeQueries = query instanceof Array ? query : [query];
			return queries.concat(routeQueries);
		}, [])
		.map(query => query({ location, params }));  // call the query function

	return queries;
}

export const activeRouteQueries$ = routes => location =>
	match$({ routes, location })
		.map(getActiveRouteQueries)
		.filter(queries => queries.length);

/**
 * Utilities for interacting with the Router and getting location data
 * @module routeUtils
 */
import Rx from 'rxjs';
import match from 'react-router/lib/match';

// Create observable from callback-based `match`
const match$ = Rx.Observable.bindNodeCallback(match);

/**
 * From the renderProps provided by React Router's `match`, collect the results
 * of the query properties associated with currently-active routes
 *
 * @param matchCallbackArgs {Array} redirectLocation(ignored) and renderProps
 * @return {Array} The return values of each active route's query function
 */
function getActiveRouteQueries([ , { routes, location, params }]) {
	const queries = routes
		.filter(({ query }) => query)  // only get routes with queries
		.reduce((queries, { query }) => {  // assemble into one array of queries
			const routeQueries = query instanceof Array ? query : [query];
			return queries.concat(routeQueries);
		}, [])
		.map(queryFn => queryFn({ location, params }))  // call the query function
		.filter(query => query);  // empty return values should not be sent

	return queries;
}

export const activeRouteQueries$ = routes => location =>
	match$({ routes, location })
		.map(getActiveRouteQueries)
		.filter(queries => queries.length);

Allow query functions to return null when no query needed