// TODO: To decide and adopt to some logging utility like winston
module.exports = {
  info: function(message,context) {
    if(context) {
      console.log(message,JSON.stringify(context, null, 2));
    }
    else {
      console.log(message);
    }

  },
  error: function(message, error) {
    console.error(message, JSON.stringify(error, null, 2));
  }
};

// TODO: To decide and adopt to some logging utility like winston
var util = require('util');

module.exports = {
  info: function(message,context) {
    if(context) {
      console.log(message, util.format('%j', context));
    }
    else {
      console.log(message);
    }

  },
  error: function(message, error) {
    console.error(message, util.format('%j', error));
  }
};

Fix multi-line log output in app logs


define([

  'lateralus'

  ,'./model'
  ,'./view'
  ,'text!./template.mustache'

], function (

  Lateralus

  ,Model
  ,View
  ,template

) {
  'use strict';

  var {{componentClassName}}Component = Lateralus.Component.extend({
    name: '{{componentName}}'
    ,Model: Model
    ,View: View
    ,template: template
  });

  return {{componentClassName}}Component;
});

define([

  'lateralus'

  ,'./model'
  ,'./view'
  ,'text!./template.mustache'

], function (

  Lateralus

  ,Model
  ,View
  ,template

) {
  'use strict';

  var Base = Lateralus.Component;

  var {{componentClassName}}Component = Base.extend({
    name: '{{componentName}}'
    ,Model: Model
    ,View: View
    ,template: template
  });

  return {{componentClassName}}Component;
});

Add Base pattern to Component template.


module.exports = function models(r) {
    return {
        projects: require('./projects')(r),
        users: require('./users')(r),
        samples: require('./samples')(r),
        access: require('./access')(r),
        files: require('./files')(r)
    };
};

module.exports = function models(r) {
    return {
        projects: require('./projects')(r),
        users: require('./users')(r),
        samples: require('./samples')(r),
        access: require('./access')(r),
        files: require('./files')(r),
        r: r
    };
};

Make database available as an export.


/**
 * Main entry point for budget web app
 */

import React from 'react';
import { AppContainer } from 'react-hot-loader';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

import store from './store';
import Root from '~client/containers/Root';

import './images/favicon.png';

function renderApp(RootComponent = Root) {
    render(
        <AppContainer>
            <BrowserRouter>
                <RootComponent store={store} />
            </BrowserRouter>
        </AppContainer>,
        document.getElementById('root'),
    );
}

if (process.env.NODE_ENV !== 'test') {
    renderApp();
}

if (module.hot) {
    module.hot.accept(
        './containers/Root',
        // eslint-disable-next-line global-require
        () => renderApp(require('./containers/Root').default),
    );
}

import '@babel/polyfill';

import React from 'react';
import { AppContainer } from 'react-hot-loader';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

import store from './store';
import Root from '~client/containers/Root';

import './images/favicon.png';

function renderApp(RootComponent = Root) {
    render(
        <AppContainer>
            <BrowserRouter>
                <RootComponent store={store} />
            </BrowserRouter>
        </AppContainer>,
        document.getElementById('root'),
    );
}

if (process.env.NODE_ENV !== 'test') {
    renderApp();
}

if (module.hot) {
    module.hot.accept(
        './containers/Root',
        // eslint-disable-next-line global-require
        () => renderApp(require('./containers/Root').default),
    );
}

Use babel polyfill on frontend


/**
 * Scroll to element
 * @param element
 */
function scrollTo(element, duration) {
    duration = duration || 500;
    if (!element) return false;
    var elem = $(element).offset();
    if (elem.top) {
        $('html, body').animate({
            scrollTop: elem.top
        }, duration);
    }
}

(function () {
    try {
        var tooltipElementsWhitelist = $.fn.tooltip.Constructor.DEFAULTS.whiteList
        tooltipElementsWhitelist.table = []
        tooltipElementsWhitelist.thead = []
        tooltipElementsWhitelist.tbody = []
        tooltipElementsWhitelist.tr = []
        tooltipElementsWhitelist.td = []
        tooltipElementsWhitelist.kbd = []
    } catch (e) {}
})();

Allow some tags in whitelist


import {createStore, applyMiddleware} from "redux";
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

import thunk from "redux-thunk";

import rootReducer from "../reducers/rootReducer";

export default function configureStore(preloadedState) {
    const middlewares = [thunk];
    const middlewareEnhancer = applyMiddleware(...middlewares);

    const storeEnhancers = [middlewareEnhancer];

    const composedEnhancer = composeWithDevTools(...storeEnhancers);

    const store = createStore(
        rootReducer,
        preloadedState,
        composedEnhancer
    );

    return store;
}

import {createStore, applyMiddleware} from "redux";
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';

import thunk from "redux-thunk";

import rootReducer from "../reducers/rootReducer";

export default function configureStore(preloadedState) {
    const middlewares = [thunk];
    const middlewareEnhancer = applyMiddleware(...middlewares);

    const storeEnhancers = [middlewareEnhancer];

    const composedEnhancer = composeWithDevTools(...storeEnhancers);

    const store = createStore(
        rootReducer,
        preloadedState,
        composedEnhancer
    );

    if(process.env.NODE_ENV !== "production") {
        if(module.hot) {
            module.hot.accept("../reducers/rootReducer", () =>{
                const newRootReducer = require("../reducers/rootReducer").default;
                store.replaceReducer(newRootReducer)
            });
        }
    }

    return store;
}

Configure Hot Module Replacement for the reducer logic


import { expect } from 'chai';

import { updateCellSource, executeCell } from '../../../src/notebook/actions';
import { liveStore, dispatchQueuePromise, waitForOutputs } from '../../utils';

describe('agendas.executeCell', function() {
  this.timeout(5000);
  it('produces the right output', () => {
    return liveStore((kernel, dispatch, store) => {
      const cellId = store.getState().document.getIn(['notebook', 'cellOrder', 0]);
      const source = 'print("a")';
      dispatch(updateCellSource(cellId, source));

      // TODO: Remove hack
      // HACK: Wait 100ms before executing a cell because kernel ready and idle
      // aren't enough.  There must be another signal that we need to listen to.
      return (new Promise(resolve => setTimeout(resolve, 100)))
        .then(() => dispatch(executeCell(kernel.channels, cellId, source, true, undefined)))
        .then(() => dispatchQueuePromise(dispatch))
        .then(() => waitForOutputs(store, cellId))
        .then(() => {
          const output = store.getState().document.getIn(['notebook', 'cellMap', cellId, 'outputs', 0]).toJS();
          expect(output.name).to.equal('stdout');
          expect(output.text).to.equal('a\n');
          expect(output.output_type).to.equal('stream');
        });
    });
  });
});

import { expect } from 'chai';

import { updateCellSource, executeCell } from '../../../src/notebook/actions';
import { liveStore, dispatchQueuePromise, waitForOutputs } from '../../utils';

describe('agendas.executeCell', function() {
  this.timeout(5000);
  it('produces the right output', (done) => {
    setTimeout(done, 5000);
    return liveStore((kernel, dispatch, store) => {
      const cellId = store.getState().document.getIn(['notebook', 'cellOrder', 0]);
      const source = 'print("a")';
      dispatch(updateCellSource(cellId, source));

      // TODO: Remove hack
      // HACK: Wait 100ms before executing a cell because kernel ready and idle
      // aren't enough.  There must be another signal that we need to listen to.
      return (new Promise(resolve => setTimeout(resolve, 100)))
        .then(() => dispatch(executeCell(kernel.channels, cellId, source, true, undefined)))
        .then(() => dispatchQueuePromise(dispatch))
        .then(() => waitForOutputs(store, cellId))
        .then(() => {
          const output = store.getState().document.getIn(['notebook', 'cellMap', cellId, 'outputs', 0]).toJS();
          expect(output.name).to.equal('stdout');
          expect(output.text).to.equal('a\n');
          expect(output.output_type).to.equal('stream');
        });
    });
  });
});

Call setTimeout in test case


// In a real use case, the endpoint could point to another origin.
var LOG_ENDPOINT = 'report/logs';

// The code in `oninstall` and `onactive` force the service worker to
// control the clients ASAP.
self.oninstall = function(event) {
  event.waitUntil(self.skipWaiting());
};

self.onactivate = function(event) {
  event.waitUntil(self.clients.claim());
};

// Fetch is as simply as log the request and passthrough.
// Water clear thanks to the promise syntax!
self.onfetch = function(event) {
  event.respondWith(log(event.request).then(fetch));
};

// Post basic information of the request to a backend for historical purposes.
function log(request) {
  var returnRequest = function() { return Promise.resolve(request); };
  var data = { method: request.method, url: request.url };
  return fetch(LOG_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: { 'content-type': 'application/json' }
  }).then(returnRequest, returnRequest);
}

// In a real use case, the endpoint could point to another origin.
var LOG_ENDPOINT = 'report/logs';

// The code in `oninstall` and `onactive` force the service worker to
// control the clients ASAP.
self.oninstall = function(event) {
  event.waitUntil(self.skipWaiting());
};

self.onactivate = function(event) {
  event.waitUntil(self.clients.claim());
};

self.onfetch = function(event) {

  event.respondWith(
    // Log the request ...
    log(event.request)
    // .. and then actually perform it.
    .then(fetch)
  );
};

// Post basic information of the request to a backend for historical purposes.
function log(request) {
  var returnRequest = function() {
    return request;
  };

  var data = {
    method: request.method,
    url: request.url
  };

  return fetch(LOG_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: { 'content-type': 'application/json' }
  })
  .then(returnRequest, returnRequest);
}

Clarify fetch handler in the api-analytics recipe


const _ = require('lodash');
const uuidV4 = require('uuid/v4');
const Sentence = require('./sentence');

class Text {
  constructor(text) {
    let paragraphs = _.chain(text).split('\n').compact()
      .map((text, index) => new Paragraph(text, index))
      .value();

    return paragraphs;
  }
}

let sentences = new WeakMap();

class Paragraph {
  constructor(text, index = 0) {
    this.id = uuidV4();
    this.index = index;
    this.text = text;
    this.parseSentence();
    return this;
  }

  get sentencesObject() {
    return sentences.get(this);
  }

  get sentences() {
    return _.map(sentences.get(this), 'id');
  }

  set sentences(s) {
    sentences.set(this, s);
  }

  parseSentence() {
    let {id: paragraphId, text} = this;
    this.sentences = _.chain(text).split('. ').compact()
      .map((text, index) =>
        new Sentence(paragraphId, text, index)
      ).value();
  }
}

module.exports = {Text, Paragraph};

const _ = require('lodash');
const uuidV4 = require('uuid/v4');
const Sentence = require('./sentence');

class Text {
  constructor(parent, text) {
    let paragraphs = _.chain(text).split('\n').compact()
      .map((text, index) => new Paragraph(parent, text, index))
      .value();

    return paragraphs;
  }
}

let sentences = new WeakMap();
let parent = new WeakMap();
class Paragraph {
  constructor($parent, text, index = 0) {
    parent.set(this, $parent);
    this.id = uuidV4();
    this.index = index;
    this.text = text;
    this.parseSentence();
    return this;
  }

  parent() {
    return parent.get(this);
  }

  findSentenceById(id) {
    return _.find(this.sentencesCollection, {id});
  }

  findSentenceByIndex(index) {
    return _.find(this.sentencesCollection, {index});
  }

  next() {
    let index = this.index;
    return this.parent().findParagraphByIndex(index + 1);
  }

  prev() {
    let index = this.index;
    return this.parent().findParagraphByIndex(index - 1);
  }

  get sentencesCollection() {
    return sentences.get(this);
  }

  get sentences() {
    return _.map(sentences.get(this), 'id');
  }

  set sentences(s) {
    sentences.set(this, s);
  }

  parseSentence() {
    let paragraph = this;
    let {text} = paragraph;
    this.sentences = _.chain(text).split('. ').compact()
      .map((text, index) =>
        new Sentence(paragraph, text, index)
      ).value();
  }
}

module.exports = {Text, Paragraph};

Add more method and refactor code.


/**
 * @file init.js
 * Custom initialization for your app. Use this space to attach any pre/post
 * hooks your setup will need. This is executed after the Router and
 * AutoLoader have been set up, but before drive() is called.
 */

var turnpike = require('turnpike');

// This hook lets you attach your own middleware to Turnpike's Connect object.
turnpike.Driver.pre('startServer', function(next, app, cb){

  next(app, cb);
});

/**
 * @file init.js
 * Custom initialization for your app. Use this space to attach any pre/post
 * hooks your setup will need.
 */

var turnpike = require('turnpike');

// This hook lets you attach your own middleware to Turnpike's Connect object.
turnpike.Driver.pre('startServer', function(next, app, cb){

  next(app, cb);
});

Fix a lie in a comment


const spawnSync = require('child_process').spawnSync;

class Linguist {

  /**
   * Returns the languages found in the project.
   * Associate Array of language String to Array of filenames that are written in that language
   *
   * Throws 'Linguist not installed' error if command line of 'linguist' is not available.
   */
  identify_languages(targetDir) {
    const linguistOutput = spawnSync('linguist', [targetDir, '--json']).stdout;
    if(linguistOutput == null) {
      throw 'Linguist not installed';
    }
    const json=linguistOutput.toString();
    return JSON.parse(json);
  }
}

module.exports = new Linguist();

const spawnSync = require('child_process').spawnSync;

class Linguist {

  /**
   * Returns the languages found in the project.
   * Associate Array of language String to Array of filenames that are written in that language
   *
   * Throws 'Linguist not installed' error if command line of 'linguist' is not available.
   */
  identifyLanguagesSync(targetDir) {
    const linguistOutput = spawnSync('linguist', [targetDir, '--json']).stdout;
    if(linguistOutput == null) {
      throw 'Linguist not installed';
    }
    const json=linguistOutput.toString();
    return JSON.parse(json);
  }
}

module.exports = new Linguist();

Add Sync to method name as it's Sync reliant


import path from 'path';
import mixins from 'postcss-mixins';
import nested from 'postcss-nested';
import extend from 'postcss-extend';
import repeat from 'postcss-for';
import simpleVars from 'postcss-simple-vars';
import each from 'postcss-each';
import cssMqpacker from 'css-mqpacker';
import gulp from 'gulp';
import gulpLoadPlugins from 'gulp-load-plugins';

var pkg =  require('./package.json');
var $ = gulpLoadPlugins();

const CSS_SRC_DIR = './css/';

const DIST_DIR = './dist/';
const CSS_DIST_DIR = path.join(DIST_DIR, 'css/');
const JS_DIST_DIR = path.join(DIST_DIR, 'js/');

gulp.task('css', () => {
  return gulp.src(CSS_SRC_DIR + '**/' + pkg.name + '.css')
    .pipe($.cssnext({
      compress: process.env.NODE_ENV === 'production',
      plugins: [
        mixins,
        nested,
        extend,
        each,
        repeat,
        simpleVars,
        cssMqpacker,
      ]
    }))
    .pipe(gulp.dest(CSS_DIST_DIR))
    .pipe($.livereload());
});

gulp.task('watch', () => {
  $.livereload.listen();
  return gulp.watch(CSS_SRC_DIR + '**/*.css', ['css']);
});

gulp.task('build', ['css']);

gulp.task('default', ['build']);

import path from 'path';
import mixins from 'postcss-mixins';
import nested from 'postcss-nested';
import extend from 'postcss-sass-extend';
import repeat from 'postcss-for';
import simpleVars from 'postcss-simple-vars'
import cssMqpacker from 'css-mqpacker';
import gulp from 'gulp';
import gulpLoadPlugins from 'gulp-load-plugins';

var pkg =  require('./package.json');
var $ = gulpLoadPlugins();

const CSS_SRC_DIR = './css/';

const DIST_DIR = './dist/';
const CSS_DIST_DIR = path.join(DIST_DIR, 'css/');
const JS_DIST_DIR = path.join(DIST_DIR, 'js/');

gulp.task('css', () => {
  return gulp.src(CSS_SRC_DIR + '**/' + pkg.name + '.css')
    .pipe($.cssnext({
      compress: process.env.NODE_ENV === 'production',
      plugins: [
        mixins,
        extend,
        nested,
        repeat,
        simpleVars,
        cssMqpacker,
      ]
    }))
    .pipe(gulp.dest(CSS_DIST_DIR))
    .pipe($.livereload());
});

gulp.task('watch', () => {
  $.livereload.listen();
  return gulp.watch(CSS_SRC_DIR + '**/*.css', ['css']);
});

gulp.task('build', ['css']);

gulp.task('default', ['build']);

Use postcss-sass-extend instead of postcss-extend.


import React, { PropTypes } from 'react';
import {
    Text,
    TouchableHighlight
} from 'react-native';

const Button = ({theme, disabled}) => {
  const {styles} = theme;
  const btnStyles = [styles.button];
  if (disabled) {
    btnStyles.push(styles.buttonDisabled);
  }

  return (
    <TouchableHighlight
        onPress={this.props.onPress}
        style={btnStyles}
        underlayColor="#a30000"
        activeOpacity={1}>
      <Text style={styles.buttonText}>{this.props.children}</Text>
    </TouchableHighlight>
  );
};

Button.propTypes = {
  styles: PropTypes.object,
  onPress: PropTypes.func
};

export default Button;

import React, { PropTypes } from 'react';
import {
    Text,
    TouchableHighlight
} from 'react-native';

const Button = ({theme, disabled, children, onPress}) => {
  const {styles} = theme;
  const btnStyles = [styles.button];
  if (disabled) {
    btnStyles.push(styles.buttonDisabled);
  }

  return (
    <TouchableHighlight
        onPress={onPress}
        style={btnStyles}
        underlayColor="#a30000"
        activeOpacity={1}>
      <Text style={styles.buttonText}>{children}</Text>
    </TouchableHighlight>
  );
};

Button.propTypes = {
  styles: PropTypes.object,
  onPress: PropTypes.func
};

export default Button;

Fix button problem introduced in previous commit


'use strict';

/*
 * Module for playing audio, now the Angular way!
 */

angular.module('audiohandler', [])
.factory('audioFactory', function ($ionicPlatform, $window, $cordovaNativeAudio) {
    $ionicPlatform.ready(function () {
        if ($window.cordova) {
            $cordovaNativeAudio.preloadComplex('call', 'res/sounds/dial.wav', 1, 1);
            $cordovaNativeAudio.preloadComplex('dial', 'res/sounds/incoming.mp3', 1, 1);
        }
    });
    return {
        playSound: function (sound) {
            if ($window.cordova) {
                console.log('playing sound: ' + sound);
                $cordovaNativeAudio.play(sound);
            }
        },
        loopSound: function (sound) {
            if ($window.cordova) {
                console.log('looping sound: ' + sound);
                $cordovaNativeAudio.loop(sound);
            }
        },
        stopSound: function (sound) {
            if ($window.cordova) {
                console.log('stopping sound: ' + sound);
                $cordovaNativeAudio.stop(sound);
            }
        },
        stopAllSounds: function () {
            if ($window.cordova) {
                this.stopSound('call');
                this.stopSound('dial');
            }
        }
    };
});

'use strict';

/*
 * Module for playing audio, now the Angular way!
 */

angular.module('audiohandler', [])
.factory('audioFactory', function ($ionicPlatform, $window, $cordovaNativeAudio) {
    $ionicPlatform.ready(function () {
        if ($window.cordova) {
            $cordovaNativeAudio.preloadComplex('dial', 'res/sounds/dial.wav', 1, 1);
            $cordovaNativeAudio.preloadComplex('call', 'res/sounds/incoming.mp3', 1, 1);
        }
    });
    return {
        playSound: function (sound) {
            if ($window.cordova) {
                console.log('playing sound: ' + sound);
                $cordovaNativeAudio.play(sound);
            }
        },
        loopSound: function (sound) {
            if ($window.cordova) {
                console.log('looping sound: ' + sound);
                $cordovaNativeAudio.loop(sound);
            }
        },
        stopSound: function (sound) {
            if ($window.cordova) {
                console.log('stopping sound: ' + sound);
                $cordovaNativeAudio.stop(sound);
            }
        },
        stopAllSounds: function () {
            if ($window.cordova) {
                this.stopSound('call');
                this.stopSound('dial');
            }
        }
    };
});

Switch accidentally changed call and dial sounds


/**
 * @name jquery-steamytext
 * @version 1.0.0
 * @description jQuery SteamyText animation plugin
 * @author Maxim Zalysin <max@zalysin.ru>
 */
(function($){
    $.fn.steamyText = function(data){
        var defaults = {
            text: ''
            , duration: 1500
            , displace: 50
            , tag: 'span'
            , zIndex: 0
        }
        , options
        switch(typeof data){
            case 'string':
                defaults.text = data
                options = defaults
                break
            case 'object':
                if(data === null) return this
                options = $.extend({}, defaults, data)
                break
            default:
                return this
        }
        var $text = $('<'+options.tag+' style="position: absolute; z-index: '+options.zIndex+'; display: none;">'+options.text+'</'+options.tag+'>').appendTo('body')
        , textWidth = $text.outerWidth()
        , thisWidth = this.outerWidth()
        , thisOffset = this.offset()
        , thisOffsetLeft
        if(thisWidth > textWidth) thisOffsetLeft = thisOffset.left + ((thisWidth - textWidth)/2)
        else thisOffsetLeft = thisOffset.left - ((textWidth - thisWidth)/2)
        $text.css({'top': thisOffset.top, 'left': thisOffsetLeft}).show()
        .animate({top: '-='+options.displace+'px', opacity: 'toggle'}, options.duration, function(){ /*$(this).remove()*/ })
        return this
    }
}(jQuery))

/**
 * @name jquery-steamytext
 * @version 1.1.0
 * @description jQuery SteamyText animation plugin
 * @author Maxim Zalysin <max@zalysin.ru>
 */
(function($){
    $.fn.steamyText = function(data){
        var defaults = {
            text: '',
            duration: 1500,
            displace: 50,
            tag: 'span',
            zIndex: 10000
        }
        , options
        switch(typeof data){
            case 'string':
                defaults.text = data
                options = defaults
                break
            case 'object':
                if(data === null) return this
                options = $.extend({}, defaults, data)
                break
            default:
                return this
        }
        var $text = $('<'+options.tag+' style="position: absolute; z-index: '+options.zIndex+'; display: none;">'+options.text+'</'+options.tag+'>').appendTo('body')
        , textWidth = $text.outerWidth()
        , thisWidth = this.outerWidth()
        , thisOffset = this.offset()
        , thisOffsetLeft
        if(thisWidth > textWidth) thisOffsetLeft = thisOffset.left + ((thisWidth - textWidth)/2)
        else thisOffsetLeft = thisOffset.left - ((textWidth - thisWidth)/2)
        $text.css({'top': thisOffset.top, 'left': thisOffsetLeft}).show()
        .animate({top: '-='+options.displace+'px', opacity: 'toggle'}, options.duration, function(){ /*$(this).remove()*/ })
        return this
    }
}(jQuery))

Set default zIndex to 10000


(function() {

    function onPlayerReady() {
        var overlay = videoPlayer.overlay();
        $(overlay).html('<img id="overlaylogo" src="http://cs1.brightcodes.net/ben/img/genericlogo.png" />')
            .css({
                position:"relative"
            });
        $("#overlaylogo").css({
            position:"absolute",
            bottom:"50px",
            right:"10px"
        });
    }

    player = brightcove.api.getExperience();
    videoPlayer = player.getModule(brightcove.api.modules.APIModules.VIDEO_PLAYER);
    experience = player.getModule(brightcove.api.modules.APIModules.EXPERIENCE);

    if (experience.getReady()) {
        onPlayerReady();
    } else {
        experience.addEventListener(brightcove.player.events.ExperienceEvent.TEMPLATE_READY, onPlayerReady);
    }

}());

(function() {

    function onPlayerReady() {
        var overlay = videoPlayer.overlay();
        $(overlay).html('<img id="overlaylogo" src="http://cs1.brightcodes.net/ben/img/genericlogo.png" />')
            .css({
                position:"fixed",
                height:"100%",
                width:"100%"
            });
        $("#overlaylogo").css({
            position:"absolute",
            bottom:"50px",
            right:"10px"
        });
    }

    player = brightcove.api.getExperience();
    videoPlayer = player.getModule(brightcove.api.modules.APIModules.VIDEO_PLAYER);
    experience = player.getModule(brightcove.api.modules.APIModules.EXPERIENCE);

    if (experience.getReady()) {
        onPlayerReady();
    } else {
        experience.addEventListener(brightcove.player.events.ExperienceEvent.TEMPLATE_READY, onPlayerReady);
    }

}());

Update overlay positioning to fixed


(function() {
    "use strict";

    angular
        .module('VinculacionApp')
        .directive('tbHeaderbar', tbHeaderbar);

    function tbHeaderbar() {
        var directive =
        {
            restrict: 'E',
            scope: {
                expand: '='
            },
            templateUrl: 'templates/shared/TB-Headerbar/tb-headerbar.html'
        };

        return directive;
    }

})();

(function() {
    "use strict";

    angular
        .module('VinculacionApp')
        .directive('tbHeaderbar', tbHeaderbar);

    function tbHeaderbar() {
        var directive =
        {
            restrict: 'E',
            scope: {
                expand: '=?'
            },
            templateUrl: 'templates/shared/TB-Headerbar/tb-headerbar.html'
        };

        return directive;
    }

})();

Make expand property optional for headerbar


var _             = require('underscore');
var getSlug       = require('speakingurl');
var SLUG_MAP_FILE = "src/data/slug_map.json";

module.exports = function(grunt) {

  function readMap() {
    try {
      return grunt.file.readJSON(SLUG_MAP_FILE);
    }
    catch (e) {
      return {};
    }
  }

  var contents = readMap();

  function sluggify(resource, record) {
    return '/' + getSlug(record.displayName);
  }

  function urlFor(resource, record) {
    var key  = resource +  '|' + record.id;

    if(!contents[key]) {
      contents[key] = sluggify(resource, record);
    }

    return contents[key];
  }

  function write() {
    grunt.file.write(SLUG_MAP_FILE, JSON.stringify(contents));
  }

  return {
    urlFor: urlFor,
    write:  write,
  };
}

var _             = require('underscore');
var getSlug       = require('speakingurl');
var SLUG_MAP_FILE = "src/data/slug_map.json";

module.exports = function(grunt) {

  function readMap() {
    try {
      return grunt.file.readJSON(SLUG_MAP_FILE);
    }
    catch (e) {
      return {};
    }
  }

  var contents = readMap();

  function sluggify(resource, record, salt) {
    salt = salt || ''; //used to break ties

    return '/' + getSlug(record.displayName) + salt;
  }

  function slugExists(slug) {
    return _.any(contents, function(val) { return val === slug; });
  }

  function urlFor(resource, record) {
    var key  = resource +  '|' + record.id;

    if(!contents[key]) {
      var slug = sluggify(resource, record);

      // just in case there is a collision, tack on the id as a tiebreaker
      if(slugExists(slug)) {
        slug = sluggify(resource, record, record.id);
      }

      contents[key] = slug;
    }

    return contents[key];
  }

  function write() {
    grunt.file.write(SLUG_MAP_FILE, JSON.stringify(contents));
  }

  return {
    urlFor: urlFor,
    write:  write,
  };
}

Handle slug collisions by adding a little salt.


/* eslint-disable no-console */
import vue from 'vue';
import vuex from 'vuex';
import router from 'vue-router';
import vueintl from 'vue-intl';
import 'intl';
import 'intl/locale-data/jsonp/en.js';
import kRouter from 'kolibri.coreVue.router';

kRouter.init([]);

vue.prototype.Kolibri = {};
vue.config.silent = true;
vue.use(vuex);
vue.use(router);
vue.use(vueintl, { defaultLocale: 'en-us' });

function $trWrapper(nameSpace, defaultMessages, formatter, messageId, args) {
  if (args) {
    if (!Array.isArray(args) && typeof args !== 'object') {
      console.error(`The $tr functions take either an array of positional
                      arguments or an object of named options.`);
    }
  }
  const defaultMessageText = defaultMessages[messageId];
  const message = {
    id: `${nameSpace}.${messageId}`,
    defaultMessage: defaultMessageText,
  };

  return formatter(message, args);
}

vue.prototype.$tr = function $tr(messageId, args) {
  const nameSpace = this.$options.name || this.$options.$trNameSpace;
  return $trWrapper(nameSpace, this.$options.$trs, this.$formatMessage, messageId, args);
};

export default vue;

import 'intl';
import 'intl/locale-data/jsonp/en.js';
import Vue from 'vue';
import Vuex from 'vuex';
import VueRouter from 'vue-router';
import kRouter from 'kolibri.coreVue.router';
import { setUpVueIntl } from 'kolibri.utils.i18n';

kRouter.init([]);

Vue.prototype.Kolibri = {};
Vue.config.silent = true;
Vue.use(Vuex);
Vue.use(VueRouter);
setUpVueIntl();

export default Vue;

Use the i18n utils setup function in tests


// eg.
// 3 -> 0.001
// 2 -> 0.01
// 1 -> 0.1
export default pipSize => {
    const zeros = Array(pipSize).join('0');
    const stepStr = '0.' + zeros + 1;
    return stepStr;
};

// example:
// input        output
// 3        ->  0.001
// 2        ->  0.01
// 1        ->  0.1
export default pipSize => {
    const zeros = Array(pipSize).join('0');
    const stepStr = '0.' + zeros + 1;
    return stepStr;
};

Improve comment to clarify function's intention


import React from 'react';
import Archive from '../../component/archive/archive-list-container';
import PropTypes from 'prop-types';

const render = ({ match }) => <Archive name={match.params.name} />;
render.propTypes = {
    match: PropTypes.object,
};

export default render;

import React from 'react';
import Archive from '../../component/archive/archive-list-container';
import PropTypes from 'prop-types';

const render = ({ match: { params }, history }) => <Archive name={params.name} history={history} />;
render.propTypes = {
    match: PropTypes.object,
    history: PropTypes.object,
};

export default render;

Add 'history' prop to the archive-list.


import './AddressList.less';
import AddrLine from './AddrLine.react';
import React, { PropTypes } from 'react';
import Component from 'react-pure-render/component';
import { connect } from 'react-redux';

class AddressList extends Component {

  static propTypes = {
    addresses: PropTypes.object.isRequired,
    msg: PropTypes.object,
  };

  render() {
    const { addresses, msg } = this.props;

    return (
      <section className="device-switch-list">
        {addresses.map(address =>
          <AddrLine {...{ msg, address }} key={address.id} />
         )
        }
      </section>
    );
  }
}

export default connect(state => ({
  msg: state.intl.msg.home, /* PENDING: don't use this, create your own subtree in en.js */
}))(AddressList);

import './AddressList.less';
import AddrLine from './AddrLine.react';
import React, { PropTypes } from 'react';
import Component from 'react-pure-render/component';
import { connect } from 'react-redux';

class AddressList extends Component {

  static propTypes = {
    addresses: PropTypes.object.isRequired,
    msg: PropTypes.object,
  };

  render() {
    const { addresses, msg } = this.props;

    return (
      <section className="device-switch-list">
        {addresses.sortBy(addr => addr.name)
                  .sortBy(addr => !addr.value)
                  .map(address =>
                    <AddrLine {...{ msg, address }} key={address.id} />
                  )
        }
      </section>
    );
  }
}

export default connect(state => ({
  msg: state.intl.msg.home, /* PENDING: don't use this, create your own subtree in en.js */
}))(AddressList);

Sort addresses on home-screen by name and value.


const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(4);
});

const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

Fix test for travis ci


import preInit from '../common/pre-init';

preInit().then(() => {
  require('./init-render'); // eslint-disable-line global-require
});

/* eslint-disable */

import preInit from '../common/pre-init';

preInit().then(() => {
  require('./init-render');
}).catch(e => {
  console.error('Error while rendering client');
  console.error(e);
});

Print error on clients that do not support printing on unhandledRejections


var icalendar = require('icalendar');
var _ = require('underscore');

exports.generateIcal = function(currentUser, boards, params) {
    var ical = new icalendar.iCalendar();
    boards.each(function(board) {
        board.cards().each(function(card) {
            // no arm, no chocolate
            if (!card.get('badges').due) return;
            if (params &&
                params.only_me == 'true' &&
                !_(card.get('idMembers')).include(currentUser.id)) {
                return;
            }
            var event = new icalendar.VEvent(card.id);
            event.setSummary(card.get('name'));
            event.setDescription(card.get('desc'));
            event.setDate(card.get('badges').due);
            event.addProperty('ATTACH', card.get('url'));
            event.addProperty('URL', card.get('url'));
            if (params && params.alarm != '-1') {
                comp = event.addComponent('VALARM');
                comp.addProperty('ACTION', 'DISPLAY');
                comp.addProperty('DESCRIPTION', 'Generated by trello calendar');
                comp.addProperty('TRIGGER', params.alarm);
            }
            ical.addComponent(event);
        });
    });
    return ical;
}

var icalendar = require('icalendar');
var _ = require('underscore');

exports.generateIcal = function(currentUser, boards, params) {
    var ical = new icalendar.iCalendar();
    boards.each(function(board) {
        board.cards().each(function(card) {
            // no arm, no chocolate
            if (!card.get('badges').due) return;
            if (params &&
                params.only_me == 'true' &&
                !_(card.get('idMembers')).include(currentUser.id)) {
                return;
            }
            var event = new icalendar.VEvent(card.id);
            event.setSummary(card.get('name'));
            event.setDescription(card.get('desc'));
            event.setDate(card.get('badges').due, 60*60);
            event.addProperty('ATTACH', card.get('url'));
            event.addProperty('URL', card.get('url'));
            if (params && params.alarm != '-1') {
                comp = event.addComponent('VALARM');
                comp.addProperty('ACTION', 'DISPLAY');
                comp.addProperty('DESCRIPTION', 'Generated by trello calendar');
                comp.addProperty('TRIGGER', params.alarm);
            }
            ical.addComponent(event);
        });
    });
    return ical;
}

Set default duration for events to 1 hour.


import { SET_COMPLETION, SET_BODY, SET_PAGE, SET_RESULTS_PER_PAGE } from '../actions/Search'

const initialState = {
  completion: '',
  body: '',
  page: 1,
  resultsPerPage: 10
}

function search(state = initialState, action) {
  switch (action.type) {
  case SET_COMPLETION:
    return { ...state, completion: action.completion }

  case SET_BODY:
    return { ...state, body: action.body }

  case SET_PAGE:
    return { ...state, page: action.page }

  case SET_RESULTS_PER_PAGE:
    return { ...state, resultsPerPage: action.resultsPerPage }

  default:
    return state
  }
}

export default search

import { SET_COMPLETION, SET_BODY, SET_PAGE, SET_RESULTS_PER_PAGE } from '../actions/Search'
import { SEARCH_REQUEST_RESET } from '../../api/actions/query'

const initialState = {
  completion: '',
  body: '',
  page: 1,
  resultsPerPage: 10
}

function search(state = initialState, action) {
  switch (action.type) {
  case SET_COMPLETION:
    return { ...state, completion: action.completion }

  case SET_BODY:
    return { ...state, body: action.body }

  case SET_PAGE:
    return { ...state, page: action.page }

  case SET_RESULTS_PER_PAGE:
    return { ...state, resultsPerPage: action.resultsPerPage }

  case SEARCH_REQUEST_RESET:
    return { ...initialState }

  default:
    return state
  }
}

export default search

Reset search state on reset action


import BaseApp from '../../lib/javascripts/base_app';

// remove these if not needed
import Base64 from 'base64';
import helpers from 'helpers';

var App = {

  defaultState: 'loading',

  requests: {
    getMe: {
      url: '/api/v2/users/me.json'
    }
  },

  events: {
    'app.created': 'init',
    'getMe.done': 'renderMain'
  },

  init: function() {
    this.ajax('getMe');
  },

  renderMain: function(data) {
    this.switchTo('main', data.user);
  }
}

export default BaseApp.extend(App);

import BaseApp from '../../lib/javascripts/base_app';

// remove these if not needed
// import Base64 from 'base64';
// import helpers from 'helpers';

var App = {

  defaultState: 'loading',

  requests: {
    getMe: {
      url: '/api/v2/users/me.json'
    }
  },

  events: {
    'app.created': 'init',
    'getMe.done': 'renderMain'
  },

  init: function() {
    this.ajax('getMe');
  },

  renderMain: function(data) {
    this.switchTo('main', data.user);
  }
}

export default BaseApp.extend(App);

Comment out unused import to silence eslint


const JobStates = {
  active: {
    stateTypes: ['active'],
    displayName: 'Running'
  },
  failed: {
    stateTypes: ['completed', 'failure'],
    displayName: 'Failed'
  },
  success: {
    stateTypes: ['success'],
    displayName: 'Success'
  }
};

module.exports = JobStates;

const JobStates = {
  starting: {
    stateTypes: ['active'],
    displayName: 'Starting'
  },
  active: {
    stateTypes: ['active'],
    displayName: 'Running'
  },
  failed: {
    stateTypes: ['completed', 'failure'],
    displayName: 'Failed'
  },
  success: {
    stateTypes: ['success'],
    displayName: 'Success'
  }
};

module.exports = JobStates;

Add missing job run status


define(['react'], function (React) {
  'use strict';

  var ul = React.DOM.ul
    , li = React.DOM.li
    , span = React.DOM.span
    , a  = React.DOM.a;

  var Pager = React.createClass({
    displayName: 'Pager',
    render: function () {
      var props = this.props;
      return ul({className: 'pager'},
        li({className: 'previous' + (props.offset ? '' : ' disabled')}, a({onClick: props.back}, "Back")),
        li(null, span(null,
            "Items " + (props.offset + 1) + " to " + (props.offset + props.size) + " of " + props.length)),
        li({className: 'next' + (props.offset + props.size >= props.length ? ' disabled' : '')}, a({onClick: props.next}, "Next")));
    }
  });

  return Pager;
});

define(['react'], function (React) {
  'use strict';

  var ul = React.DOM.ul
    , li = React.DOM.li
    , span = React.DOM.span
    , a  = React.DOM.a;

  var Pager = React.createClass({

    displayName: 'Pager',

    render: function () {

      var props = this.props;

      return ul({className: 'pager'},
        li({className: 'previous' + (props.offset ? '' : ' disabled')}, a({onClick: props.back}, "Back")),
        li(null,
          span(
            null,
            React.DOM.i(
              {title: "0 selected", className: 'pointer pull-right fa fa-star-o'}),
            "Items " + (props.offset + 1) + " to " + (props.offset + props.size) + " of " + props.length)),
        li({className: 'next' + (props.offset + props.size >= props.length ? ' disabled' : '')}, a({onClick: props.next}, "Next")));
    }
  });

  return Pager;
});

Add a selector star - no behaviour yet


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { authStrings } from './authStrings';
import { buttonStrings } from './buttonStrings';
import { generalStrings } from './generalStrings';
import { modalStrings } from './modalStrings';
import { navStrings } from './navStrings';
import { pageInfoStrings } from './pageInfoStrings';
import { syncStrings } from './syncStrings';
import { tableStrings } from './tableStrings';

export function setCurrentLanguage(language) {
  authStrings.setLanguage(language);
  buttonStrings.setLanguage(language);
  generalStrings.setLanguage(language);
  modalStrings.setLanguage(language);
  navStrings.setLanguage(language);
  pageInfoStrings.setLanguage(language);
  tableStrings.setLanguage(language);
  syncStrings.setLanguage(language);
}

export default setCurrentLanguage;

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { authStrings } from './authStrings';
import { buttonStrings } from './buttonStrings';
import { generalStrings } from './generalStrings';
import { modalStrings } from './modalStrings';
import { navStrings } from './navStrings';
import { pageInfoStrings } from './pageInfoStrings';
import { syncStrings } from './syncStrings';
import { tableStrings } from './tableStrings';
import { programStrings } from './programStrings';

export function setCurrentLanguage(language) {
  authStrings.setLanguage(language);
  buttonStrings.setLanguage(language);
  generalStrings.setLanguage(language);
  modalStrings.setLanguage(language);
  navStrings.setLanguage(language);
  pageInfoStrings.setLanguage(language);
  tableStrings.setLanguage(language);
  syncStrings.setLanguage(language);
  programStrings.setLanguage(language);
}

export default setCurrentLanguage;

Add proper exporting of programStrings


import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  session: Ember.inject.service(),

  model() {
    let userId =  this.get('session.session.authenticated.user_id');
    return Ember.RSVP.hash({
      user: Ember.isPresent('userId') ? this.store.findRecord('user', userId) : null,
      project: this.modelFor('project')
    });
  },

  setupController(controller, models) {
    let newPost = this.store.createRecord('post', {
      project: models.project,
      user: models.user });
    controller.set('post', newPost);
  },

  actions: {
    savePost(post) {
      post.save().then((post) => {
        this.transitionTo('project.posts.post', post.get('number'));
      }).catch((error) => {
        if (error.errors.length === 1) {
          this.controllerFor('project.posts.new').set('error', error);
        }
      });
    }
  }
});

import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Ember.Route.extend(AuthenticatedRouteMixin, {
  session: Ember.inject.service(),

  model() {
    let userId =  this.get('session.session.authenticated.user_id');
    return Ember.RSVP.hash({
      user: Ember.isPresent('userId') ? this.store.findRecord('user', userId) : null,
      project: this.modelFor('project')
    });
  },

  setupController(controller, models) {
    let newPost = this.store.createRecord('post', {
      project: models.project,
      user: models.user });
    controller.set('post', newPost);
  },

  actions: {
    savePost(post) {
      post.set('state', 'published');
      post.save().then((post) => {
        this.transitionTo('project.posts.post', post.get('number'));
      }).catch((error) => {
        if (error.errors.length === 1) {
          this.controllerFor('project.posts.new').set('error', error);
        }
      });
    }
  }
});

Set post state to published upon save


Schema = {};

Schema.UserProfile = new SimpleSchema({
  name: {
    type: String,
    optional: true
  },
  body: orion.attribute('summernote', {
    label: 'Body',
    optional: true,
    autoform: {
      summernoteOptions: {
        toolbar: [
          ['style', ['bold', 'italic', 'underline', 'clear']],
          ['para', ['ul', 'ol', 'paragraph']],
          ['insert', ['link']]
        ]
      }
    }
  })
});

Schema.User = new SimpleSchema({
  profile: {
    type: Schema.UserProfile,
    optional: true
  }
  // TODO Allow user to change email address in a secure, verifiable way
  //,
  // emails: {
  //   type: [Object],
  //   // this must be optional if you also use other login services like facebook,
  //   // but if you use only accounts-password, then it can be required
  //   optional: true
  // },
  // "emails.$.address": {
  //   type: String,
  //   regEx: SimpleSchema.RegEx.Email
  // },
  // "emails.$.verified": {
  //   type: Boolean,
  //   autoform: {
  //     omit: true
  //   }
  // }
});

Meteor.users.attachSchema(Schema.User);

Schema = {};

Schema.UserProfile = new SimpleSchema({
  name: {
    type: String,
    optional: true
  },
  body: orion.attribute('summernote', {
    label: 'Body',
    optional: true,
    autoform: {
      summernoteOptions: {
        toolbar: [
          ['style', ['bold', 'italic', 'underline', 'clear']],
          ['para', ['ul', 'ol', 'paragraph']],
          ['insert', ['link']]
        ]
      }
    }
  })
});

Schema.User = new SimpleSchema({
  profile: {
    type: Schema.UserProfile,
    optional: true
  },
  // TODO Allow user to change email address in a secure, verifiable way
  // emails: {
  //   type: [Object],
  //   // this must be optional if you also use other login services like facebook,
  //   // but if you use only accounts-password, then it can be required
  //   optional: true
  // },
  // "emails.$.address": {
  //   type: String,
  //   regEx: SimpleSchema.RegEx.Email
  // },
  // "emails.$.verified": {
  //   type: Boolean,
  //   blackbox: true,
  //   autoform: {
  //     omit: true
  //   }
  // },
  services: {
    type: Object,
    optional: true,
    blackbox: true,
    autoform: {
      omit: true
    }
  }
});

Meteor.users.attachSchema(Schema.User);

Add services to user schema


var rawFeedService = require('../database/raw_feed.js');

this.load = function(app, io, gcm, logger) {
	app.get('/rawfeed/?', function(req, res){
		if(logger.DO_LOG){
			logger.info('Request for list of events');
		}
		rawFeedService.list(req.query, res);
	});
}

var rawFeedService = require('../database/raw_feed.js');

this.load = function(app, io, gcm, logger) {
	app.get('/rawfeed/?', function(req, res){
		if(logger.DO_LOG){
			logger.info('Request for list of feeds');
		}
		rawFeedService.listFeeds(req.query, res);
	});

	// Create
	app.post('/rawfeed/?', function(req, res){
		if(logger.DO_LOG){
			logger.info("Receiving new feed", req.body);
		}
		rawFeedService.createFeed(req.body, res, io, gcm);
	});

	// Review
	app.get('/rawfeed/:id([0-9a-f]+)', function(req, res){
		rawFeedService.getFeed(req.params.id, req.query, res);
	});

	// Update
	app.post('/rawfeed/:id([0-9]+)', function(req,res){
		if(logger.DO_LOG){
			logger.info("Update feed " + req.params.id, req.body);
		}
		rawFeedService.updateFeed(req.params.id, req.body, res);
	});

	// Delete
	app.del('/rawfeed/:id([0-9a-f]+)',function(req, res){
		if(logger.DO_LOG){
			logger.info("Request to delete feed");
		}
		rawFeedService.deleteEvent(req.params.id, res);
	});
};

Add skeleton for CRUD REST services for raw data feed


(function (module) {
    module.controller('ProjectHomeController', ProjectHomeController);
    ProjectHomeController.$inject = ["project", "mcmodal", "templates", "$state"];

    function ProjectHomeController(project, mcmodal, templates, $state) {
        var ctrl = this;

        ctrl.project = project;
        ctrl.chooseTemplate = chooseTemplate;
        ctrl.chooseExistingProcess = chooseExistingProcess;
        ctrl.createSample = createSample;
        ctrl.useTemplate = useTemplate;
        ctrl.templates = templates;

        /////////////////////////

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project, templates).then(function (processTemplateName) {
                $state.go('projects.project.processes.create', {process: processTemplateName});
            });
        }

        function useTemplate(templateName) {
            $state.go('projects.project.processes.create', {process: templateName});
        }

        function createSample() {
            $state.go('projects.project.processes.create', {process: 'As Received'});
        }

        function chooseExistingProcess() {
            mcmodal.chooseExistingProcess(ctrl.project).then(function (existingProcess) {
                $state.go('projects.project.processes.create', {process:  existingProcess.process_name, process_id: existingProcess.id});
            });
        }
    }
}(angular.module('materialscommons')));

(function (module) {
    module.controller('ProjectHomeController', ProjectHomeController);
    ProjectHomeController.$inject = ["project", "mcmodal", "templates", "$state", "Restangular"];

    function ProjectHomeController(project, mcmodal, templates, $state, Restangular) {
        var ctrl = this;

        ctrl.project = project;
        ctrl.chooseTemplate = chooseTemplate;
        ctrl.chooseExistingProcess = chooseExistingProcess;
        ctrl.createSample = createSample;
        ctrl.useTemplate = useTemplate;
        ctrl.templates = templates;

        /////////////////////////

        function chooseTemplate() {
            mcmodal.chooseTemplate(ctrl.project, templates).then(function (processTemplateName) {
                $state.go('projects.project.processes.create', {process: processTemplateName, process_id: ''});
            });
        }

        function useTemplate(templateName) {
            $state.go('projects.project.processes.create', {process: templateName, process_id: ''});
        }

        function createSample() {
            $state.go('projects.project.processes.create', {process: 'As Received', process_id: ''});
        }

        function chooseExistingProcess() {
            Restangular.one('v2').one("projects", project.id).one("processes").getList().then(function(processes) {
                mcmodal.chooseExistingProcess(processes).then(function (existingProcess) {
                    var processName = existingProcess.process_name ? existingProcess.process_name : 'TEM';
                    $state.go('projects.project.processes.create', {process: processName, process_id: existingProcess.id});
                });
            });
        }
    }
}(angular.module('materialscommons')));

Add process_id into the $state.go statements. Have chooseExistingProcess make a REST call to get the list of processes.


module.exports = {
    options: {
        'v': {
            type: 'boolean',
            description: 'Verbose logging',
            alias: 'verbose'
        },
        'd': {
            type: 'string',
            description: 'Output base directory',
            alias: 'outputDir'
        },
        'f': {
            type: 'string',
            description: 'Output File',
            alias: 'outputFile'
        },
        'p': {
            type: 'string',
            description: 'Module prefix',
            alias: 'modulePrefix',
            default: 'z-template'
        },
        'k': {
            type: 'string',
            description: 'Known helpers',
            alias: 'known'
        },
        'o': {
            type: 'boolean',
            description: 'Known helpers only',
            alias: 'knownOnly'
        },
        'b': {
            type: 'boolean',
            description: 'Beautify output',
            alias: 'beautify'
        },
        'm': {
            type: 'boolean',
            description: 'Minimize output',
            alias: 'min'
        },
        's': {
            type: 'boolean',
            description: 'Output template function only',
            alias: 'simple'
        },
        'r': {
            type: 'string',
            description: 'Template root, stripped from template names.',
            alias: 'root'
        }
    },
    defaults: {
        verbose: false,
        outputDir: '',
        outputFile: '',
        known: '',
        knownOnly: false,
        beautify: false,
        min: false,
        simple: false,
        root: ''
    }
};

module.exports = {
    options: {
        'v': {
            type: 'boolean',
            description: 'Verbose logging',
            alias: 'verbose'
        },
        'd': {
            type: 'string',
            description: 'Output base directory',
            alias: 'outputDir'
        },
        'f': {
            type: 'string',
            description: 'Output File',
            alias: 'outputFile'
        },
        'p': {
            type: 'string',
            description: 'Module prefix',
            alias: 'modulePrefix',
            default: 'template'
        },
        'k': {
            type: 'string',
            description: 'Known helpers',
            alias: 'known'
        },
        'o': {
            type: 'boolean',
            description: 'Known helpers only',
            alias: 'knownOnly'
        },
        'b': {
            type: 'boolean',
            description: 'Beautify output',
            alias: 'beautify'
        },
        'm': {
            type: 'boolean',
            description: 'Minimize output',
            alias: 'min'
        },
        's': {
            type: 'boolean',
            description: 'Output template function only',
            alias: 'simple'
        },
        'r': {
            type: 'string',
            description: 'Template root, stripped from template names.',
            alias: 'root'
        }
    },
    defaults: {
        verbose: false,
        outputDir: '',
        outputFile: '',
        known: '',
        knownOnly: false,
        beautify: false,
        min: false,
        simple: false,
        root: ''
    }
};

Make modulePrefix default a little more generic.


var CONTENT_TYPE = 'Content-Type';
var JSON_CONTENT_TYPE = 'application/json';
var _ = require('../../../../utils/mindash');

module.exports = {
  id: 'parseJSON',
  after: function (res) {
    if (isJson(res)) {
      return res.json().then(function (body) {
        res.body = body;

        return res;
      });
    }

    return res;
  }
};

function isJson(res) {
  var contentTypes = res.headers.get(CONTENT_TYPE);

  if (!_.isArray(contentTypes)) {
    if (contentTypes === undefined || contentTypes === null) {
      contentTypes = [];
    } else {
      contentTypes = [contentTypes];
    }
  }

  return _.any(contentTypes, function (contentType) {
    return contentType.indexOf(JSON_CONTENT_TYPE) !== -1;
  });
}

var CONTENT_TYPE = 'Content-Type';
var JSON_CONTENT_TYPE = 'application/json';
var _ = require('../../../../utils/mindash');

module.exports = {
  id: 'parseJSON',
  after: function (res) {
    if (isJson(res)) {
      return res.json().then(function (body) {
        try {
          res.body = body;
        } catch (e) {
          if (e instanceof TypeError) {
            // Workaround for Chrome 43+ where Response.body is not settable.
            Object.defineProperty(res, 'body', {value: body});
          } else {
            throw e;
          }
        }

        return res;
      });
    }

    return res;
  }
};

function isJson(res) {
  var contentTypes = res.headers.get(CONTENT_TYPE);

  if (!_.isArray(contentTypes)) {
    if (contentTypes === undefined || contentTypes === null) {
      contentTypes = [];
    } else {
      contentTypes = [contentTypes];
    }
  }

  return _.any(contentTypes, function (contentType) {
    return contentType.indexOf(JSON_CONTENT_TYPE) !== -1;
  });
}

Work around not being able to set res.body


$('document').ready(function () {
    showOrHideRecurrenceFields();
});

$('#IsRecurring').on('click', function () {
    addOrRemoveOneOffOption();
    showOrHideRecurrenceFields();
});

function addOrRemoveOneOffOption() {
    if ($('#IsRecurring')[0].checked) {
        $('#Recurrence option[value="1"]').remove();
    } else {
        $('#Recurrence').append('<option value="1">OneOff</option>');
    }
}

function showOrHideRecurrenceFields() {
    if ($('#IsRecurring')[0].checked) {
        $('#recurrence-fields').show();
    } else {
        $('#recurrence-fields').hide();
        $('#Recurrence').val("1").change();
    }
}

$('document').ready(function () {
    addOrRemoveOneOffOption();
    showOrHideRecurrenceFields();
});

$('#IsRecurring').on('click', function () {
    addOrRemoveOneOffOption();
    showOrHideRecurrenceFields();
});

function addOrRemoveOneOffOption() {
    if ($('#IsRecurring')[0].checked) {
        $('#Recurrence option[value="1"]').remove();
    } else {
        if (!recurrenceDropdownContainsOneOffOption()) {
            $('#Recurrence').append('<option value="1">OneOff</option>');
        }
    }
}

function showOrHideRecurrenceFields() {
    if ($('#IsRecurring')[0].checked) {
        $('#recurrence-fields').show();
    } else {
        $('#recurrence-fields').hide();
        $('#Recurrence').val("1").change();
    }
}

function recurrenceDropdownContainsOneOffOption() {
    var containsOneOff = false;
    $('#Recurrence option').each(function () {
        if (this.value === '1') {
            containsOneOff = true;
        }
    });
    return containsOneOff;
}

Remove One Off option on page load for recurring tasks.


var RecipeService = function() {

    this.initialize = function() {
        // No Initialization required
        var deferred = $.Deferred();
        deferred.resolve();
        return deferred.promise();
    }

    this.findById = function(id) {
        var deferred = $.Deferred();
        var recipe = null;
        var l = recipes.length;
        for (var i=0; i < l; i++) {
            if (recipes[i].id === id) {
                recipe = recipes[i];
                break;
            }
        }
        deferred.resolve(recipe);
        return deferred.promise();
    }

    this.findByName = function(searchKey) {
        var deferred = $.Deferred();
        var results = recipes.filter(function(element) {
            var title = element.title.rendered;
            return title.toLowerCase().indexOf(searchKey.toLowerCase()) > -1;
        });
        deferred.resolve(results);
        return deferred.promise();
    }

    var recipes = null;

    $.ajaxSetup({
        beforeSend: function(xhr) {
            xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
        }
    });

    $.ajax({
         type: 'GET',
         url:"http://beginveganbegun.es/wp-json/wp/v2/posts",
         crossDomain: true,
         dataType: 'json',
         success:function(data){
            recipes = data;
            debugger;
            $('#content').html(new RecipeListView(recipes).render().$el);
            console.log("Recipes loaded");
         },
         error:function(){
             console.log("Error loading recipes");
         }
    });

    this.getRecipes = function() {
      var deferred = $.Deferred();
      var results = recipes;
      deferred.resolve(results);
      return deferred.promise();
    }
}

var RecipeService = function() {

    this.initialize = function() {
        // No Initialization required
        var deferred = $.Deferred();
        deferred.resolve();
        return deferred.promise();
    }

    this.findById = function(id) {
        var deferred = $.Deferred();
        var recipe = null;
        var l = recipes.length;
        for (var i=0; i < l; i++) {
            if (recipes[i].id === id) {
                recipe = recipes[i];
                break;
            }
        }
        deferred.resolve(recipe);
        return deferred.promise();
    }

    this.findByName = function(searchKey) {
        var deferred = $.Deferred();
        var results = recipes.filter(function(element) {
            var title = element.title.rendered;
            return title.toLowerCase().indexOf(searchKey.toLowerCase()) > -1;
        });
        deferred.resolve(results);
        return deferred.promise();
    }

    this.printRecipes = function() {
      if(recipes === null){
        $.ajaxSetup({
            beforeSend: function(xhr) {
                xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
            }
        });

        $.ajax({
             type: 'GET',
             crossDomain: true,
             dataType: 'json',
             success:function(data){
                recipes = data;
                debugger;
                new RecipeListView(recipes).render().$el;
                console.log("Recipes loaded");
             },
             error:function(){
                 console.log("Error loading recipes");
             }
        });
      }else{
        new RecipeListView(recipes).render().$el;
      }
    }
}

Modify recipe service to only perform one call to API


import test from 'ava';

test.todo('Write test for Registry.check');

import test from 'ava';
import Registry from '../src/registry';

window.innerWidth = 1280;
window.innerHeight = 700;

test('Registry.check updates current', t => {

    let registry = Registry([{
        getBoundingClientRect() {
            return {
                bottom: 1,
                left: 1,
                right: 1,
                top: 1
            };
        }
    }, {
        getBoundingClientRect() {
            return {
                bottom: -1,
                left: -1,
                right: -1,
                top: -1
            };
        }
    }]);

    t.true(!registry.current.length);

    registry.check(0);
    t.true(registry.current.length === 1);

});

test('Registry.check emits enter events', t => {

    let stub = {
        getBoundingClientRect() {
            return {
                bottom: 1,
                left: 1,
                right: 1,
                top: 1
            };
        }
    };

    let registry = Registry([stub]);

    registry.on('enter', el => t.deepEqual(el, stub));
    registry.check(0);

});

test('Registry.check emits exit events', t => {

    let stub = {
        getBoundingClientRect() {
            return {
                bottom: -1,
                left: -1,
                right: -1,
                top: -1
            };
        }
    };

    let registry = Registry([stub]);

    registry.on('exit', el => t.deepEqual(el, stub));
    registry.check(0);

});

test('Registry.check returns the registry', t => {
    let registry = Registry([]);
    t.deepEqual(registry.check(0), registry);
});

Add initial tests for Registry.check


import lists from '../core/lists';

export default class Clipboard {
  constructor(context) {
    this.context = context;
    this.$editable = context.layoutInfo.editable;
  }

  initialize() {
    this.$editable.on('paste', this.pasteByEvent.bind(this));
  }

  /**
   * paste by clipboard event
   *
   * @param {Event} event
   */
  pasteByEvent(event) {
    const clipboardData = event.originalEvent.clipboardData;
    if (clipboardData && clipboardData.items && clipboardData.items.length) {
      const item = clipboardData.items.length > 1 ? clipboardData.items[1] : lists.head(clipboardData.items);
      if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
        // paste img file
        this.context.invoke('editor.insertImagesOrCallback', [item.getAsFile()]);
        this.context.invoke('editor.afterCommand');
      } else if (item.kind === 'string') {
        // paste text with maxTextLength check
        if (this.context.invoke('editor.isLimited', clipboardData.getData('Text').length)) {
          event.preventDefault();
        } else {
          this.context.invoke('editor.afterCommand');
        }
      }
    }
  }
}

import lists from '../core/lists';

export default class Clipboard {
  constructor(context) {
    this.context = context;
    this.$editable = context.layoutInfo.editable;
  }

  initialize() {
    this.$editable.on('paste', this.pasteByEvent.bind(this));
  }

  /**
   * paste by clipboard event
   *
   * @param {Event} event
   */
  pasteByEvent(event) {
    const clipboardData = event.originalEvent.clipboardData;

    if (clipboardData && clipboardData.items && clipboardData.items.length) {
      const item = clipboardData.items.length > 1 ? clipboardData.items[1] : lists.head(clipboardData.items);
      if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
        // paste img file
        this.context.invoke('editor.insertImagesOrCallback', [item.getAsFile()]);
      } else if (item.kind === 'string') {
        // paste text with maxTextLength check
        if (this.context.invoke('editor.isLimited', clipboardData.getData('Text').length)) {
          event.preventDefault();
        }
      }
    } else if (window.clipboardData) {
      // for IE
      let text = window.clipboardData.getData('text');
      if (this.context.invoke('editor.isLimited', text.length)) {
        event.preventDefault();
      }
    }
    this.context.invoke('editor.afterCommand');
  }
}

Fix paste event for IE


// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

///////////////////////////////////////////////////////////////////////////////
// SystemOptions class:

/**
 * Encapsulated handling of ChromeOS system options page.
 * @constructor
 */
function SystemOptions(model) {
  OptionsPage.call(this, 'system', templateData.systemPage, 'systemPage');
}

SystemOptions.getInstance = function() {
  if (SystemOptions.instance_)
    return SystemOptions.instance_;
  SystemOptions.instance_ = new SystemOptions(null);
  return SystemOptions.instance_;
}

// Inherit SystemOptions from OptionsPage.
SystemOptions.prototype = {
  __proto__: OptionsPage.prototype,

  /**
   * Initializes SystemOptions page.
   * Calls base class implementation to starts preference initialization.
   */
  initializePage: function() {
    OptionsPage.prototype.initializePage.call(this);
    var timezone = $('timezone-select');
    if (timezone) {
      timezone.initializeValues(templateData.timezoneList);
    }

    $('language-button').onclick = function(event) {
      // TODO: Open ChromeOS language settings page.
    };
    $('sync-button').onclick = function(event) {
      OptionsPage.showPageByName('sync');
    }
    $('dummy-button').onclick = function(event) {
      OptionsPage.showOverlay('dummy');
    }
  },
};

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

///////////////////////////////////////////////////////////////////////////////
// SystemOptions class:

/**
 * Encapsulated handling of ChromeOS system options page.
 * @constructor
 */
function SystemOptions(model) {
  OptionsPage.call(this, 'system', templateData.systemPage, 'systemPage');
}

SystemOptions.getInstance = function() {
  if (SystemOptions.instance_)
    return SystemOptions.instance_;
  SystemOptions.instance_ = new SystemOptions(null);
  return SystemOptions.instance_;
}

// Inherit SystemOptions from OptionsPage.
SystemOptions.prototype = {
  __proto__: OptionsPage.prototype,

  /**
   * Initializes SystemOptions page.
   * Calls base class implementation to starts preference initialization.
   */
  initializePage: function() {
    OptionsPage.prototype.initializePage.call(this);
    var timezone = $('timezone-select');
    if (timezone) {
      timezone.initializeValues(templateData.timezoneList);
    }

    $('language-button').onclick = function(event) {
      // TODO: Open ChromeOS language settings page.
    };
  },
};

Fix runtime JavaScript errors in chrome://options.


import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'

const loggerMiddleware = createLogger()

export default function configureStore(mainReducer) {
  return createStore(
    mainReducer,
    undefined,
    compose(
      applyMiddleware(
        thunkMiddleware,
        loggerMiddleware
       ),
      window.devToolsExtension ? window.devToolsExtension() : f => f)
    )
}

import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { createLogger } from 'redux-logger'

const loggerMiddleware = createLogger()

export default function configureStore(mainReducer) {
  return createStore(
    mainReducer,
    undefined,
    compose(
      applyMiddleware(
        thunkMiddleware,
        loggerMiddleware
       ),
      window.devToolsExtension ? window.devToolsExtension() : f => f)
    )
}

Fix issue with redux-logger upgrade


'use strict';

const server = require('./app/server');

server.listen(80, () => {
  console.info('Server listening on port 80');
});

'use strict';

const server = require('./app/server');

let port = Number.parseInt(process.env.PORT || '8080');

server.listen(port, () => {
  console.info(`Server listening on port ${port}`);
});

Allow Heroku to specify a port


(function(window, Models) {

  function Browser() {
    var self = this;

    self._isMobileSafariStandalone = function() {
      return self.isMobileSafari && window.navigator.standalone;
    };

  }

  Browser.prototype.isMobileSafari = function() {
    return (/Mobile\/\w+ Safari\//).test(window.navigator.userAgent);
  };

  Browser.prototype.canScanCode = function() {
    return this.isMobileSafari() && !this._isMobileSafariStandalone();
  };

  Browser.prototype.hasCryptoGetRandomValues = function() {
    var crypto = window.crypto || window.msCrypto;

    if (crypto && crypto.getRandomValues) {
      return true;
    } else {
      return false;
    }
  };

  Models.browser = new Browser();
})(window, CoinPocketApp.Models);

(function(window, Models) {

  function Browser() {
    var self = this;

    self._isMobileSafariStandalone = function() {
      return self.isMobileSafari && window.navigator.standalone;
    };

  }

  Browser.prototype.isMobileSafari = function() {
    return (/Version\/\S+ Mobile\/\w+ Safari\//).test(window.navigator.userAgent);
  };

  Browser.prototype.canScanCode = function() {
    return this.isMobileSafari() && !this._isMobileSafariStandalone();
  };

  Browser.prototype.hasCryptoGetRandomValues = function() {
    var crypto = window.crypto || window.msCrypto;

    if (crypto && crypto.getRandomValues) {
      return true;
    } else {
      return false;
    }
  };

  Models.browser = new Browser();
})(window, CoinPocketApp.Models);

Exclude iOS Chrome as mobile safari


'use strict';
var walk = require('walk'),
    path = require('path'),
    fs = require('fs');

class FileSystemSourceRepository {
    // Awaiting resolution of https://github.com/jscs-dev/node-jscs/issues/1890
    // jscs:disable
    constructor (configuration) {
        this.exclude = configuration.exclude;
        this.include = ['.js'];
        this.sources = null;
    }

    // Awaiting resolution of https://github.com/jscs-dev/node-jscs/issues/1890
    // jscs:disable
    getAll () {
        if (this.sources === null) {
            this.sources = [];

            walk.walkSync('.', {
                filters: this.exclude,
                listeners: {
                    file: appendSourceFile.bind(this)
                }
            });
        }

        return this.sources;
    }
}

function appendSourceFile (root, stats, next) {
    /*jshint validthis:true */
    var filePath = path.join(root, stats.name);

    if (this.include.indexOf(path.extname(stats.name)) !== -1) {
        this.sources.push({
            getLines: function getLines () {
                return fs.readFileSync(this.location, 'utf8');
            },
            location: filePath
        });
    }

    next();
}

module.exports = FileSystemSourceRepository;

'use strict';
var walk = require('walk'),
    path = require('path'),
    fs = require('fs');

class FileSystemSourceRepository {
    // Awaiting resolution of https://github.com/jscs-dev/node-jscs/issues/1890
    // jscs:disable disallowAnonymousFunctions
    constructor (configuration) {
        this.exclude = configuration.exclude;
        this.include = ['.js'];
        this.sources = null;
    }

    // Awaiting resolution of https://github.com/jscs-dev/node-jscs/issues/1890
    // jscs:disable disallowAnonymousFunctions
    getAll () {
        if (this.sources === null) {
            this.sources = [];

            walk.walkSync('.', {
                filters: this.exclude,
                listeners: {
                    file: appendSourceFile.bind(this)
                }
            });
        }

        return this.sources;
    }
}

function appendSourceFile (root, stats, next) {
    /*jshint validthis:true */
    var filePath = path.join(root, stats.name);

    if (this.include.indexOf(path.extname(stats.name)) !== -1) {
        this.sources.push({
            getLines: function getLines () {
                return fs.readFileSync(this.location, 'utf8');
            },
            location: filePath
        });
    }

    next();
}

module.exports = FileSystemSourceRepository;

Disable specific JSCS rules where necessary


import React, { Component } from 'react';

import Summary from './sections/Summary';
import Table from './sections/Table';

import { processLicense } from './licenseProcessor';

export class App extends Component {
  state = {
    summary: false,
    rawFile: null
  }

  render() {
    const { summary, rawFile } = this.state;
    const View = summary ? Summary : Table;

    return (

        <input type="file" onChange={this.receiveFile}></input>

        <button onClick={e => this.setState({ summary: !summary })}>
          Toggle Summary
        </button>
        {rawFile ? (
          <View {...processLicense(rawFile)} />
        ) : (

        )}

    );
  }

  componentDidMount() {
    this.reader = new FileReader();
    this.reader.onload = e => this.setState({ rawFile: this.reader.result });
  }

  receiveFile = (event) => {
    if (event.target.files[0]) {
      this.reader.readAsText(event.target.files[0]);
    }
  }
}

import React, { Component } from 'react';

import Summary from './sections/Summary';
import Table from './sections/Table';

import { processLicense } from './licenseProcessor';

export class App extends Component {
  state = {
    summary: false,
    rawFile: null
  }

  render() {
    const { summary, rawFile } = this.state;
    const View = summary ? Summary : Table;

    return (

        <input type="file" onChange={this.receiveFile}></input>

        <button onClick={e => this.setState({ summary: !summary })}>
          {summary ? 'Back to Listing' : 'Summary View'}
        </button>
        {rawFile ? (
          <View {...processLicense(rawFile)} />
        ) : (

        )}

    );
  }

  componentDidMount() {
    this.reader = new FileReader();
    this.reader.onload = e => this.setState({ rawFile: this.reader.result });
  }

  receiveFile = (event) => {
    if (event.target.files[0]) {
      this.reader.readAsText(event.target.files[0]);
    }
  }
}

Change button text depending on view


define(
    [
        'jquery',
        'backbone',
        'underscore',
        'moment'
    ],
    function ($, Backbone, _, moment) {
        "use strict";

        return Backbone.Model.extend({

            url : function() {
                var base = $('body').data('api-url') + '/repos';

                return this.isNew() ? base : base + '/' + this.id;
            },

            presenter: function () {
                return _.extend(this.toJSON(), {
                    status: this.getStatus(),
                    travisUrl: this.getTravisUrl(),
                    githubUrl: this.getGithubUrl(),
                    lastBuildFinishedAt: this.getLastBuildFinishedAt(),
                    humanizedLastBuildFinishedAt: this.getHumanizedBuildFinishedAt()
                });
            },

            getStatus: function () {
                var lastBuildStatus = this.get('last_build_status');

                if (0 === lastBuildStatus) {
                    return 'passing';
                } else if (1 === lastBuildStatus) {
                    return 'failing';
                }

                return 'unknown';
            },

            getTravisUrl: function () {
                return 'https://travis-ci.org/' + this.get('slug') + '/builds/' + this.get('last_build_id');
            },

            getGithubUrl: function () {
                return 'https://github.com/' + this.get('slug');
            },

            getLastBuildFinishedAt: function () {
                return this.get('last_build_finished_at');
            },

            getHumanizedBuildFinishedAt: function () {
                if (this.getLastBuildFinishedAt()) {
                    return moment(this.getLastBuildFinishedAt()).fromNow();
                }

                return '';
            }
        });
    }
);

define(
    [
        'jquery',
        'backbone',
        'underscore',
        'moment'
    ],
    function ($, Backbone, _, moment) {
        "use strict";

        return Backbone.Model.extend({

            url : function() {
                var base = $('body').data('api-url') + '/repos';

                return this.isNew() ? base : base + '/' + this.id;
            },

            presenter: function () {
                return _.extend(this.toJSON(), {
                    status: this.getStatus(),
                    travisUrl: this.getTravisUrl(),
                    githubUrl: this.getGithubUrl(),
                    lastBuildFinishedAt: this.getLastBuildFinishedAt(),
                    humanizedLastBuildFinishedAt: this.getHumanizedBuildFinishedAt()
                });
            },

            getStatus: function () {
                var lastBuildStatus = this.get('last_build_result');

                if (0 === lastBuildStatus) {
                    return 'passing';
                } else if (1 === lastBuildStatus) {
                    return 'failing';
                }

                return 'unknown';
            },

            getTravisUrl: function () {
                return 'https://travis-ci.org/' + this.get('slug') + '/builds/' + this.get('last_build_id');
            },

            getGithubUrl: function () {
                return 'https://github.com/' + this.get('slug');
            },

            getLastBuildFinishedAt: function () {
                return this.get('last_build_finished_at');
            },

            getHumanizedBuildFinishedAt: function () {
                if (this.getLastBuildFinishedAt()) {
                    return moment(this.getLastBuildFinishedAt()).fromNow();
                }

                return '';
            }
        });
    }
);

Use last_build_result instead of last_build_status


import React, { Component, PropTypes } from "react"
import styles from "./index.css"
import { Treebeard } from "react-treebeard"
import treeStyle from "./treeStyle"
import decorators from "./decorators"
// import Link from "./LinkWithActiveClass"
import data from "../../content/toc.json"
import { browserHistory } from "phenomic/lib/client"

const cx = require("classnames/bind").bind(styles)

export default class Menu extends Component {
  static propTypes = {
    visible: PropTypes.bool.isRequired,
  };

  constructor(props) {
    super(props)
    this.state = {}
  }

  handleOnToggle = (node, toggled) => {
    if (this.state.cursor) {
      // eslint-disable-next-line
      this.state.cursor.active = false
    }

    node.active = true
    console.log(node)
    if (node.children) {
      node.toggled = toggled
    }
    this.setState({ cursor: node })

    if (node.path) {
      browserHistory.replace(node.path)
    }
  }

  render() {
    const wrapperClass = cx("menu", "toc-menu", {
      menuVisible: this.props.visible,
    })

    return (
      <div className={ wrapperClass }>
        <Treebeard
          style={ treeStyle }
          data={ data }
          decorators={ decorators }
          onToggle={ this.handleOnToggle }
        />
      </div>
    )
  }
}

import React, { Component, PropTypes } from "react"
import styles from "./index.css"
import { Treebeard } from "react-treebeard"
import treeStyle from "./treeStyle"
import decorators from "./decorators"
// import Link from "./LinkWithActiveClass"
import data from "../../content/toc.json"
import { browserHistory } from "phenomic/lib/client"

const cx = require("classnames/bind").bind(styles)

export default class Menu extends Component {
  static propTypes = {
    visible: PropTypes.bool.isRequired,
  };

  constructor(props) {
    super(props)
    this.state = {}
  }

  handleOnToggle = (node, toggled) => {
    if (this.state.cursor) {
      // eslint-disable-next-line
      this.state.cursor.active = false
    }

    node.active = true
    console.log(node)
    if (node.children) {
      node.toggled = toggled
    }
    this.setState({ cursor: node })

    if (node.path) {
      browserHistory.push(node.path)
    }
  }

  render() {
    const wrapperClass = cx("menu", "toc-menu", {
      menuVisible: this.props.visible,
    })

    return (
      <div className={ wrapperClass }>
        <Treebeard
          style={ treeStyle }
          data={ data }
          decorators={ decorators }
          onToggle={ this.handleOnToggle }
        />
      </div>
    )
  }
}

Use history push instead of replace


'use strict';

var mongoose = require('mongoose');

var db = require('../config/db');
var helper = require('../lib/helper');

var ContentsScheme = new mongoose.Schema({
	url_id: String,
	url: String,
	content: String
});

var content = db.model('Contents', ContentsScheme);
module.exports = content;

'use strict';

var mongoose = require('mongoose');

var db = require('../config/db');
var helper = require('../lib/helper');

var ContentsScheme = new mongoose.Schema({
	url_id: String,
	url: String,
	content: { type: String, index: { unique: true, dropDups: true }}
});

var content = db.model('Contents', ContentsScheme);
module.exports = content;

Drop if Contents post duplicates.


let express = require('express');
let bodyParser = require('body-parser');

let index = require('./routes/index');

let app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', index);

app.use(function(req, res, next) {
  let err = new Error('Not Found');
  err.status = 404;
  next(err);
});

app.use(function(err, req, res) {
  res.status(err.status || 500);
  res.send(err.message || err);
});

module.exports = app;

let express = require('express');
let bodyParser = require('body-parser');

let index = require('./routes/index');

let app = express();

app.use(bodyParser.json());
app.use(function (error, req, res, next) {
    if (error instanceof SyntaxError) {
        res.status(400);
        res.send({message: "Malformed JSON document"});
    } else {
        next();
    }
});
app.use(bodyParser.urlencoded({extended: false}));
app.use('/', index);

app.use(function (req, res, next) {
    let err = new Error('Not Found');
    err.status = 404;
    next(err);
});

app.use(function (err, req, res, next) {
    res.status(err.status || 500);
    res.send(err.message || err);
});

module.exports = app;

Add handler for non-json requests


$('a[href^="#"]').on('click', function(event) {
	var target = $(this.getAttribute('href'));
	if (target.length) {
		event.preventDefault();
		$('html, body').stop().animate( {
			scrollTop: target.offset().top
		}, 800);
	}
});

Add code for the smooth scroll on page


const logger = require('../logger')

module.exports = (controller) => {

  controller.hears([/encode base64 (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let encoded = Buffer.from(message.match[1]).toString('base64')
    bot.reply(message, encoded)
  })

  controller.hears([/decode base64 (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let decoded = Buffer.from(message.match[1], 'base64').toString('utf8')
    bot.reply(message, decoded)
  })

  logger.info('Loaded slack-users skill')
}

const logger = require('../logger')
const slackEncoder = require('../slack-encoder')

module.exports = (controller) => {

  controller.hears([/encode base64 (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let encoded = Buffer.from(slackEncoder.decode(message.match[1])).toString('base64')
    bot.reply(message, slackEncoder.encode(encoded))
  })

  controller.hears([/decode base64 (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let decoded = Buffer.from(slackEncoder.decode(message.match[1]), 'base64').toString('utf8')
    bot.reply(message, slackEncoder.encode(decoded))
  })

  controller.hears([/encode url (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let encoded = encodeURIComponent(slackEncoder.decode(message.match[1]))
    bot.reply(message, slackEncoder.encode(encoded))
  })

  controller.hears([/decode url (.*)$/i,], ['direct_message','direct_mention'], (bot, message) => {
    let encoded = decodeURIComponent(slackEncoder.decode(message.match[1]))
    bot.reply(message, slackEncoder.encode(encoded))
  })

  logger.info('Loaded slack-users skill')
}

Add url encoding/decoding and handle slack characters


import React from 'react';
import './Options.css';

export default class Options extends React.Component {

  constructor() {
    super();

    this.state = {
      filename: ''
    };
  }

  componentDidMount() {
    this.setState({ filename: this.props.filename });
  }

  onKeyDown(event) {
    const { onFilenameChange } = this.props;

    //return
    if(event.keyCode === 13) {
      onFilenameChange(this.refs.spreadSheetNameInput.value);
    }
  }

  onBlur() {
    const { onFilenameChange } = this.props;
    onFilenameChange(this.refs.spreadSheetNameInput.value);
  }

  onTextChange(event) {
    this.setState({ filename: event.target.value });
  }

  render() {
    const { filename } = this.state;

    return (
      <div className="spreadsheet-options">
        <input
              ref='spreadSheetNameInput'
              type="text"
              onBlur={this.onBlur.bind(this)}
              onKeyDown={this.onKeyDown.bind(this)}
              placeholder="Spreadsheet name"
              onChange={this.onTextChange.bind(this)}
              value={filename}
        />
      </div>
    );
  }
}

import React from 'react';
import './Options.css';

export default class Options extends React.Component {

  constructor() {
    super();

    this.state = {
      filename: ''
    };
  }

  componentDidMount() {
    this.setState({ filename: this.props.filename });
  }

  onKeyDown(event) {
    const { onFilenameChange } = this.props;

    //return
    if(event.keyCode === 13) {
      onFilenameChange(this.refs.spreadSheetNameInput.value);
    }
  }

  onBlur() {
    const { onFilenameChange } = this.props;
    onFilenameChange(this.refs.spreadSheetNameInput.value);
  }

  onTextChange(event) {
    this.setState({ filename: event.target.value });
  }

  render() {
    const { filename } = this.state;

    return (

Give you spreadsheet a name

        <input
              ref='spreadSheetNameInput'
              type="text"
              onBlur={this.onBlur.bind(this)}
              onKeyDown={this.onKeyDown.bind(this)}
              placeholder="Spreadsheet name"
              onChange={this.onTextChange.bind(this)}
              value={filename}
        />

    );
  }
}

Add explanation to spreadsheet title input


importScripts('localforage.min.js');

self.addEventListener('push', function(event) {
  var data = event.data ? event.data.json() : null;

  var title = data ? data.title : 'Mercurius';
  var body = data ? data.body : 'Notification';

  event.waitUntil(self.registration.showNotification(title, {
    body: body,
  }));
});

self.addEventListener('pushsubscriptionchange', function(event) {
  event.waitUntil(
    localforage.getItem('token')
    .then(function(token) {
      if (!token) {
        return;
      }

      localforage.getItem('machineId')
      .then(function(machineId) {
        return self.registration.pushManager.subscribe({ userVisibleOnly: true })
        .then(function(subscription) {
          var key = subscription.getKey ? subscription.getKey('p256dh') : '';

          return fetch('./updateRegistration', {
            method: 'post',
            headers: {
              'Content-type': 'application/json'
            },
            body: JSON.stringify({
              token: token,
              machineId: machineId,
              endpoint: subscription.endpoint,
              key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
            }),
          });
        });
      });
    })
  );
});

importScripts('localforage.min.js');

self.addEventListener('push', function(event) {
  function getPayload() {
    if (event.data) {
      return Promise.resolve(event.data.json());
    } else {
      return fetch('./getPayload')
      .then(function(response) {
        return response.json();
      });
    }
  }

  event.waitUntil(
    getPayload()
    .then(function(data) {
      var title = data ? data.title : 'Mercurius';
      var body = data ? data.body : 'Notification';

      event.waitUntil(self.registration.showNotification(title, {
        body: body,
      }));
    })
  );
});

self.addEventListener('pushsubscriptionchange', function(event) {
  event.waitUntil(
    localforage.getItem('token')
    .then(function(token) {
      if (!token) {
        return;
      }

      localforage.getItem('machineId')
      .then(function(machineId) {
        return self.registration.pushManager.subscribe({ userVisibleOnly: true })
        .then(function(subscription) {
          var key = subscription.getKey ? subscription.getKey('p256dh') : '';

          return fetch('./updateRegistration', {
            method: 'post',
            headers: {
              'Content-type': 'application/json'
            },
            body: JSON.stringify({
              token: token,
              machineId: machineId,
              endpoint: subscription.endpoint,
              key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
            }),
          });
        });
      });
    })
  );
});

Make the service worker get the payload from the server if the push message didn't contain any


(function ( views ) {

  views.AppView = Backbone.View.extend({
    el : '#content',

    initialize : function () {

      var tags = this.collection;

      tags.on('add', this.addOne, this);
      tags.on('reset', this.addAll, this);
      tags.on('all', this.render, this);

      tags.pager();

    },

    addAll : function () {
      this.$el.empty();
      this.collection.each (this.addOne);
    },

    addOne : function ( item ) {
      var view = new views.ResultView({model:item});
      $('#content').append(view.render().el);
    },

    render: function(){
    }
  });

})( app.views );

(function ( views ) {

  views.AppView = Backbone.View.extend({
    el : '#content',

    initialize : function () {

      var tags = this.collection;

      tags.on('add', this.addOne, this);
      tags.on('all', this.render, this);

      tags.pager();

    },

    addOne : function ( item ) {
      var view = new views.ResultView({model:item});
      $('#content').append(view.render().el);
    },

    render: function(){
    }
  });

})( app.views );

Remove superfluous code from netflix example


const fetch = require('node-fetch');
const composeDictionaryEntry = require('./utils/composeDictionaryEntry.js');
const fs = require('fs');

fs.readFile('./words.txt', 'utf8', function (err, data) {
  if (err) {
    throw err;
  }

  const words = data
    .split(',')
    .map(item => item.trim())

  Promise.all(
    findCorrectUrls(words)
  )
    .then(urls =>
      Promise.all(
        urls.map(url => fetch(url)
          .then(res => res.text())
        )
      )
    )
    .then(bodies => bodies.map(composeDictionaryEntry))
    .then(entries => entries.reduce(
      (body, acc) => `${acc}${body}`
    ))
    .then(result => fs.writeFileSync('result.txt', result))
    .catch(err => console.log(err));
});

function findCorrectUrls(words) {
  return words
    .map(word => `http://www.ldoceonline.com/dictionary/${word}`)
    .map(url => isCorrectUrl(url) ? url : `${url}_1`)
}

function isCorrectUrl(url) {
  fetch(url)
    .then(resp => resp.ok)
    .catch(err => console.log(err))
}

const fetch = require('node-fetch');
const composeDictionaryEntry = require('./utils/composeDictionaryEntry.js');
const fs = require('fs');

fs.readFile('./words.txt', 'utf8', function (err, data) {
  if (err) {
    throw err;
  }

  const words = data
    .split(',')
    .map(item => item.trim())

  Promise.all(
    findCorrectUrls(words)
  )
    .then(urls =>
      Promise.all(
        urls.map(url => fetch(url)
          .then(res => res.text())
        )
      )
    )
    .then(bodies => bodies.map(composeDictionaryEntry))
    .then(entries => entries.reduce(
      (body, acc) => `${acc}${body}`
    ))
    .then(result => fs.writeFileSync('result.txt', result))
    .catch(err => console.log(err));
});

function findCorrectUrls(words) {
  return words
    .map(word => `http://www.ldoceonline.com/dictionary/${word}`)
    .map(url => fetch(url)
      .then(resp => {
        return resp.ok ? url : `${url}_1`}
      )
      .catch(err => console.log(err))
    )
}

Revert "move isCorrectUrl outside of findCorrectUrls"


function r() {
	var strings = arguments[0];									// template strings
	var values = Array.prototype.slice.call(arguments, 1);	// interpolation values

	// concentrate strings and interpolations
	var str = strings.raw.reduce(function(prev, cur, idx) {
		return prev + values[idx-1] + cur;
	})
	.replace(/\r\n/g, '\n'); // replace for window newline \r\n

	// check top-tagged applied
	var newlineAndTabs = str.match(/^\n[\t]*/);

	if( newlineAndTabs != null ) {
		var matched = newlineAndTabs[0];

		str = str.replace(new RegExp(matched, 'g'), '\n')
				.substr(1);
	}

	else {
		var matched = str.match(/^[\t]*/)[0];

		str = str.substr(matched.length)
				.replace(new RegExp('\n'+matched, 'g'), '\n');
	}

	return str;
}

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    } else {
        // Browser globals (root is window)
        root["removeTabs"] = factory;
  }
}(this, r));

function r() {
	var strings = arguments[0];									// template strings
	var values = Array.prototype.slice.call(arguments, 1);	// interpolation values

	// concentrate strings and interpolations
	var str = strings.raw.reduce(function(prev, cur, idx) {
		return prev + values[idx-1] + cur;
	})
	.replace(/\r\n/g, '\n'); // replace for window newline \r\n

	// check top-tagged applied
	var newlineAndTabs = str.match(/^\n[\t]*/);

	if( newlineAndTabs != null ) {
		var matched = newlineAndTabs[0];

		str = str.replace(new RegExp(matched, 'g'), '\n')
				.substr(1);
	}

	else {
		var matched = str.match(/^[\t]*/)[0];

		str = str.substr(matched.length)
				.replace(new RegExp('\n'+matched, 'g'), '\n');
	}

	return str;
}

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define([], function(){
        	return factory;
        });
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    } else {
        // Browser globals (root is window)
        root["removeTabs"] = factory;
  }
}(this, r));

Fix amd part of umd to return r function correctly.


import express from 'express';
import SearchBlocks from '../../collections/search_blocks';
import { extend } from 'underscore';
import apolloMiddleware from 'react/apollo/middleware';
import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

const app = express();

app.set('views', `${__dirname}/templates`);
app.set('view engine', 'jade');

const renderOnboarding = (req, res, next) => {
  res.render('index');
};

const middlewareStack = [
  apolloMiddleware,
  setOnboardingComponentMiddleware
];

app.get('/welcome', ...middlewareStack, renderOnboarding);

module.exports = app;

import express from 'express';
import SearchBlocks from '../../collections/search_blocks';
import { extend } from 'underscore';
import apolloMiddleware from 'react/apollo/middleware';
import ensureLoggedInMiddleware from 'lib/middleware/ensure_logged_in.coffee';
import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

const app = express();

app.set('views', `${__dirname}/templates`);
app.set('view engine', 'jade');

const renderOnboarding = (req, res, next) => {
  res.render('index');
};

const middlewareStack = [
  ensureLoggedInMiddleware,
  apolloMiddleware,
  setOnboardingComponentMiddleware
];

app.get('/welcome', ...middlewareStack, renderOnboarding);

module.exports = app;

Add ensure_logged_in to onboarding middleware stack