(function (root, factory) {
  if(typeof define === 'function' && define.amd) {
    define(['knockout'], factory);
  } else {
    factory(root.ko);
  }
}(this, function(ko) {
  ko.bindingHandlers.bindHtml = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.unwrap(valueAccessor());

      var children = element.children;
      for (var i = 0; i < children.length; i++) {
        ko.applyBindings(bindingContext.$data, children[i]);
      }
    }
  };
}));

(function (root, factory) {
  if(typeof define === 'function' && define.amd) {
    define(['knockout'], factory);
  } else {
    factory(root.ko);
  }
}(this, function(ko) {
  ko.bindingHandlers.bindHtml = {
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
      ko.unwrap(valueAccessor());
      ko.applyBindingsToDescendants(bindingContext.$data, element);
    }
  };

  ko.bindingHandlers.bindHtml.preprocess = function(value, name, addBinding) {
      addBinding('html', value);
      return value;
  };
}));

Refactor Use preprocess to handle html binding as well. Use descendants function to handle binding instead of looping through children.


const path = require('path'),
    ProjectType = require('./ProjectType');

module.exports = projectType => {
    projectType = projectType || ProjectType.FRONTEND;
    return {
        "projectType": projectType,
        "port": 5000,
        "liveReloadPort": 35729,
        "bundleFilename": "main",
        "distribution": "dist",
        "targets": "targets",
        "babel": {
            "presets": [
                path.resolve(__dirname, "../node_modules/babel-preset-es2015"),
                path.resolve(__dirname, "../node_modules/babel-preset-stage-1")
            ],
            "plugins": [
                path.resolve(__dirname, "../node_modules/babel-plugin-syntax-async-generators")
            ]
        },
        "typescript": false,
        "styles": "styles",
        "test": "test/**/*",
        "images": "images",
        "views": "views",
        "assets": "assets",
        "autoprefixerRules": ["last 2 versions", "> 1%"],
        "scripts": projectType === ProjectType.FRONTEND ? "lib/*" : "scripts/*",
        "manifest": null,
        "revisionExclude": [],
        "onPreBuild": [],
        "onPostBuild": [],
        "onRebundle": []
    };
};

const path = require('path'),
    ProjectType = require('./ProjectType');

module.exports = projectType => {
    projectType = projectType || ProjectType.FRONTEND;
    return {
        "projectType": projectType,
        "port": 5000,
        "liveReloadPort": 35729,
        "bundleFilename": "main",
        "distribution": "dist",
        "targets": "targets",
        "babel": {
            "presets": [
                path.resolve(__dirname, "../node_modules/babel-preset-es2015"),
                path.resolve(__dirname, "../node_modules/babel-preset-stage-1")
            ],
            "plugins": [
                path.resolve(__dirname, "../node_modules/babel-plugin-syntax-async-generators")
            ]
        },
        "typescript": false,
        "styles": "styles",
        "test": "test/**/*",
        "images": "images",
        "views": "views",
        "assets": "assets",
        "autoprefixerRules": ["last 2 versions", "> 1%"],
        "scripts": projectType === ProjectType.FRONTEND ? "scripts/*" : "lib/*",
        "manifest": null,
        "revisionExclude": [],
        "onPreBuild": [],
        "onPostBuild": [],
        "onRebundle": []
    };
};

Fix default folder for scripts


(function($) {

  $.fn.scancode = function(options) {
    var settings = $.extend({
      dataAttr : 'scancode-callback-path'
    }, options);

    return this.each(function() {
      $(this).click(function(e) {
        e.preventDefault();
        var callbackUrl = document.location.protocol + "//" + document.location.host + $(this).data(settings.dataAttr);
        var scanCodeUrl = "scancode://scan?callback=" + encodeURIComponent(callbackUrl);
        window.location = scanCodeUrl;
      });
    });
  };

})(jQuery);

(function($) {

  $.fn.scancode = function(options) {
    var settings = $.extend({
      dataAttr : 'scancode-callback-path'
    }, options);

    return this.each(function() {
      $(this).click(function(e) {
        e.preventDefault();
        var callbackUrl = document.location.protocol + "//" + document.location.host + $(this).data(settings.dataAttr);
        var scanCodeUrl = "scancode://scan?callback=" + encodeURIComponent(callbackUrl);
        window.location = scanCodeUrl;

        var iTunesUrl = 'https://itunes.apple.com/us/app/scan-code-qr-code-reader/id828167977?ls=1&mt=8';
        var time = (new Date()).getTime();
        setTimeout(function(){
          var now = (new Date()).getTime();

          if((now - time)<400) {
            if(confirm('You do not have the Scan Code app installed. Launch the App Store to download it now?')){
              document.location = iTunesUrl;
            }
          }
        }, 300);
      });
    });
  };

})(jQuery);

Handle devices without Scan Code


/* global define */

define([
  'jquery',
  'underscore',
  'backbone',
  'marionette',
  'dust',
  'dust.helpers',
  'dust.marionette',
  'views/replyable-view',
  'controllers/navigation/navigator',
  'content/comments/comment-template'
], function ($, _, Backbone, Marionette, dust, dustHelpers, dustMarionette, ReplyableView, Navigator) {
  'use strict';

  var view = _.extend(ReplyableView, {
    template: 'content/comments/comment-template.dust',

    tagName:  function () {
      return 'li id="comment-' + this.model.get('ID') + '" class="media comment"';
    },

    events: {
      'click .b3-reply-comment':  'renderReplyBox', // from ReplyableView
      'click .b3-comment-author': 'displayAuthor'
    },

    initialize: function () {
      this.post = null;
      this.user = null;
    },

    serializeData: function () {
      return this.model.toJSON();
    },

    parentId: function () {
      return this.model.get('ID');
    },

    displayAuthor: function (event) {
      var slug = $(event.currentTarget).attr('slug');
      Navigator.navigateToAuthor(slug, null, true);
      event.preventDefault();
    }
  });

  var CommentView = Backbone.Marionette.ItemView.extend(view);
  return CommentView;
});

/* global define */

define([
  'jquery',
  'underscore',
  'backbone',
  'marionette',
  'dust',
  'dust.helpers',
  'dust.marionette',
  'views/replyable-view',
  'controllers/navigation/navigator',
  'content/comments/comment-template'
], function ($, _, Backbone, Marionette, dust, dustHelpers, dustMarionette, ReplyableView, Navigator) {
  'use strict';

  var CommentView = ReplyableView.extend({
    template: 'content/comments/comment-template.dust',

    tagName:  function () {
      return 'li id="comment-' + this.model.get('ID') + '" class="media comment"';
    },

    events: {
      'click .b3-reply-comment':  'renderReplyBox', // from ReplyableView
      'click .b3-comment-author': 'displayAuthor'
    },

    initialize: function () {
      this.post = null;
      this.user = null;
    },

    serializeData: function () {
      return this.model.toJSON();
    },

    parentId: function () {
      return this.model.get('ID');
    },

    displayAuthor: function (event) {
      var slug = $(event.currentTarget).attr('slug');
      Navigator.navigateToAuthor(slug, null, true);
      event.preventDefault();
    }
  });

  return CommentView;
});

Extend directly from replyable view


import Ember from 'ember';

export default Ember.Object.extend({
    savedTransition: null,
    isLoggedIn: false,
    currentUser: null,

    init: function() {
        this.set('isLoggedIn', localStorage.getItem('isLoggedIn') === '1');
        this.set('currentUser', null);
    },

    loginUser: function(user) {
        this.set('isLoggedIn', true);
        this.set('currentUser', user);
        localStorage.setItem('isLoggedIn', '1');
    },

    logoutUser: function() {
        this.set('savedTransition', null);
        this.set('isLoggedIn', null);
        this.set('currentUser', null);
        localStorage.removeItem('isLoggedIn');
    },
});

import Ember from 'ember';

export default Ember.Object.extend({
    savedTransition: null,
    isLoggedIn: false,
    currentUser: null,

    init: function() {
        var isLoggedIn;
        try {
            isLoggedIn = localStorage.getItem('isLoggedIn') === '1';
        } catch (e) {
            isLoggedIn = false;
        }
        this.set('isLoggedIn', isLoggedIn);
        this.set('currentUser', null);
    },

    loginUser: function(user) {
        this.set('isLoggedIn', true);
        this.set('currentUser', user);
        try {
            localStorage.setItem('isLoggedIn', '1');
        } catch (e) {}
    },

    logoutUser: function() {
        this.set('savedTransition', null);
        this.set('isLoggedIn', null);
        this.set('currentUser', null);
        try {
            localStorage.removeItem('isLoggedIn');
        } catch (e) {}
    },
});

Handle localStorage not being available on page load


import { Record as record } from 'immutable';

export class CardModel extends record({
  id: null,
  name: '',
  mana: null,
  attack: null,
  defense: null,
}) {
  constructor(obj) {
    super(obj);

    // TODO: find a way to make this even more unique
    this.uniqId = this.id + +new Date();
  }
}

import { Record as record } from 'immutable';

let cardModelCount = 0;

export class CardModel extends record({
  id: null,
  name: '',
  mana: null,
  attack: null,
  defense: null,
}) {
  constructor(obj) {
    super(obj);
    this.uniqId = cardModelCount++;
  }
}

Add uniqueId for each CardModel instance


// Common configuration for webpacker loaded from config/webpacker.yml

const { join, resolve } = require('path')
const { env } = require('process')
const { safeLoad } = require('js-yaml')
const { readFileSync } = require('fs')

const configPath = resolve('config', 'webpacker.yml')
const loadersDir = join(__dirname, 'loaders')
const settings = safeLoad(readFileSync(configPath), 'utf8')[env.NODE_ENV]

function removeOuterSlashes(string) {
  return string.replace(/^\/*/, '').replace(/\/*$/, '')
}

function formatPublicPath(host = '', path = '') {
  let formattedHost = removeOuterSlashes(host)
  if (formattedHost && !/^http/i.test(formattedHost)) {
    formattedHost = `//${formattedHost}`
  }
  const formattedPath = removeOuterSlashes(path)
  return `${formattedHost}/${formattedPath}/`
}

const output = {
  path: resolve('public', settings.public_output_path),
  publicPath: formatPublicPath(env.ASSET_HOST, settings.public_output_path)
}

module.exports = {
  settings,
  env,
  loadersDir,
  output
}

// Common configuration for webpacker loaded from config/webpacker.yml

const { join, resolve } = require('path')
const { env } = require('process')
const { load } = require('js-yaml')
const { readFileSync } = require('fs')

const configPath = resolve('config', 'webpacker.yml')
const loadersDir = join(__dirname, 'loaders')
const settings = load(readFileSync(configPath), 'utf8')[env.NODE_ENV]

function removeOuterSlashes(string) {
  return string.replace(/^\/*/, '').replace(/\/*$/, '')
}

function formatPublicPath(host = '', path = '') {
  let formattedHost = removeOuterSlashes(host)
  if (formattedHost && !/^http/i.test(formattedHost)) {
    formattedHost = `//${formattedHost}`
  }
  const formattedPath = removeOuterSlashes(path)
  return `${formattedHost}/${formattedPath}/`
}

const output = {
  path: resolve('public', settings.public_output_path),
  publicPath: formatPublicPath(env.ASSET_HOST, settings.public_output_path)
}

module.exports = {
  settings,
  env,
  loadersDir,
  output
}

Update safeLoad to load to fix errer: Function yaml.safeLoad is removed in js-yaml 4. Use yaml.load instead, which is now safe by default.


'use strict';

var _ = require('lodash');
var base = require('./base');

const development = _.merge({}, base, {
  env: 'development',
  auth0: {
    callbackURL: 'http://localhost:9000/auth/callback',
    clientID: 'uyTltKDRg1BKu3nzDu6sLpHS44sInwOu'
  },
  cip: {
    client: {
      logRequests: true
    }
  },
  siteTitle: 'kbhbilleder.dk (dev)',
  allowRobots: true,
  es: {
    log: 'error' //use 'trace' for verbose mode
  },
  features: {
    feedback: true,
    motifTagging: true,
    users: true,
    requireEmailVerification: true,
    feedback: false,
    geoTagging: false,
    motifTagging: false,
    requireEmailVerification: true,
    users: false
  }
});

module.exports = development;

'use strict';

var _ = require('lodash');
var base = require('./base');

const development = _.merge({}, base, {
  env: 'development',
  auth0: {
    callbackURL: 'http://localhost:9000/auth/callback',
    clientID: 'uyTltKDRg1BKu3nzDu6sLpHS44sInwOu'
  },
  cip: {
    client: {
      logRequests: true
    }
  },
  siteTitle: 'kbhbilleder.dk (dev)',
  allowRobots: true,
  es: {
    log: 'error' //use 'trace' for verbose mode
  },
  features: {
    feedback: true,
    motifTagging: true,
    requireEmailVerification: true,
    geoTagging: true,
    users: true
  }
});

module.exports = development;

Fix duplicate keys in dev config


import Ember from 'ember';
import config from '../config/environment';

export function initialize(application) {

  if (Ember.get(config, 'sentry.development') === true) {
    return;
  }

  if (!config.sentry) {
    throw new Error('`sentry` should be configured when not in development mode.');
  }

  const {
    dsn,
    debug = true,
    includePaths = [],
    whitelistUrls = []
  } = config.sentry;

  try {
    window.Raven.debug = debug;

    window.Raven.config(dsn, {
      includePaths,
      whitelistUrls,
      release: config.APP.version
    });
  } catch (e) {
    return;
  }

  window.Raven.install();

  const { globalErrorCatching = true } = config.sentry;

  if (globalErrorCatching === true) {
    const { serviceName = 'logger' } = config.sentry;
    const lookupName = `service:${serviceName}`;
    application.container.lookup(lookupName).enableGlobalErrorCatching();
  }
}

export default {
  initialize,
  name: 'sentry-setup'
};

import Ember from 'ember';
import config from '../config/environment';

export function initialize(application) {

  if (Ember.get(config, 'sentry.development') === true) {
    return;
  }

  if (!config.sentry) {
    throw new Error('`sentry` should be configured when not in development mode.');
  }

  const {
    dsn,
    debug = true,
    includePaths = [],
    whitelistUrls = [],
    serviceName = 'logger',
    serviceReleaseProperty = 'release'
  } = config.sentry;

  const lookupName = `service:${serviceName}`;
  const service = application.container.lookup(lookupName);

  try {
    window.Raven.debug = debug;

    window.Raven.config(dsn, {
      includePaths,
      whitelistUrls,
      release: service.get(serviceReleaseProperty) || config.APP.version
    });
  } catch (e) {
    return;
  }

  window.Raven.install();

  const { globalErrorCatching = true } = config.sentry;

  if (globalErrorCatching === true) {
    service.enableGlobalErrorCatching();
  }
}

export default {
  initialize,
  name: 'sentry-setup'
};

Read sentry release from service if available


import Passthrough from 'components/shared/passthrough.jsx';

import { getParameterByName } from 'libs/getParameterByName';

const playRoute = {
  path: ':lessonID',
  getComponent: (nextState, cb) => {
    System.import(/* webpackChunkName: "teach-classroom-lesson" */'components/classroomLessons/play/container.tsx')
    .then((component) => {
      cb(null, component.default);
    });
  },
};

const indexRoute = {
  component: Passthrough,
  onEnter: (nextState, replaceWith) => {
    const classroom_activity_id = getParameterByName('classroom_activity_id');
    const lessonID = getParameterByName('uid');
    const student = getParameterByName('student');
    if (lessonID) {
      document.location.href = `${document.location.origin + document.location.pathname}#/play/class-lessons/${lessonID}?student=${student}&classroom_activity_id=${classroom_activity_id}`;
    }
  },
};

const route = {
  path: 'class-lessons',
  indexRoute,
  childRoutes: [
    playRoute
  ],
  component: Passthrough,
};

export default route;

import Passthrough from 'components/shared/passthrough.jsx';

import { getParameterByName } from 'libs/getParameterByName';

const playRoute = {
  path: ':lessonID',
  onEnter: (nextState, replaceWith) => {
    document.title = 'Quill Lessons';
  },
  getComponent: (nextState, cb) => {
    System.import(/* webpackChunkName: "teach-classroom-lesson" */'components/classroomLessons/play/container.tsx')
    .then((component) => {
      cb(null, component.default);
    });
  },
};

const indexRoute = {
  component: Passthrough,
  onEnter: (nextState, replaceWith) => {
    const classroom_activity_id = getParameterByName('classroom_activity_id');
    const lessonID = getParameterByName('uid');
    document.title = 'Quill Lessons';
    const student = getParameterByName('student');
    if (lessonID) {
      document.location.href = `${document.location.origin + document.location.pathname}#/play/class-lessons/${lessonID}?student=${student}&classroom_activity_id=${classroom_activity_id}`;
    }
  },
};

const route = {
  path: 'class-lessons',
  indexRoute,
  childRoutes: [
    playRoute
  ],
  component: Passthrough,
};

export default route;

Update page titles on Lessons.


/**
 * SurveyViewTrigger component.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import { useMount } from 'react-use';
import PropTypes from 'prop-types';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';

const { useSelect, useDispatch } = Data;

const SurveyViewTrigger = ( { triggerID, ttl } ) => {
	const usingProxy = useSelect( ( select ) => select( CORE_SITE ).isUsingProxy() );

	const { triggerSurvey } = useDispatch( CORE_USER );

	useMount( () => {
		if ( usingProxy ) {
			triggerSurvey( { triggerID, ttl } );
		}
	} );

	return null;
};

SurveyViewTrigger.propTypes = {
	triggerID: PropTypes.string.isRequired,
	ttl: PropTypes.number,
};

SurveyViewTrigger.defaultProps = {
	ttl: null,
};

export default SurveyViewTrigger;

Add the appropriate file header.


module.exports = function(grunt) {
  grunt.initConfig({

    jshint: {
      files: ['*.js', 'client/app/*.js', 'server/**/*.js', 'database/**/*.js'],
      options: {
        ignores: [
          // (TODO: add lib files here)
        ]
      }
    },

    uglify: {
      my_target: {
        files: {
          'client/lib/dest/libs.min.js': ['client/lib/angular.js', 'client/lib/angular-ui-router.js']

        }
      }
    },
    // TODO: add uglify, concat, cssmin tasks

    watch: {
      files: ['client/app/*.js', 'server/**/*.js', 'database/**/*.js'],
      tasks: ['jshint']
    }
  });

  //Automatic desktop notifications for Grunt errors and warnings
  grunt.loadNpmTasks('grunt-notify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');

  /*************************************************************
  Run `$ grunt jshint` before submitting PR
  Or run `$ grunt` with no arguments to watch files
  **************************************************************/

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

module.exports = function(grunt) {
  grunt.initConfig({

    jshint: {
      files: ['*.js', 'client/app/*.js', 'server/**/*.js', 'database/**/*.js', '*.json'],
      options: {
        ignores: [
          // (TODO: add lib files here)
        ]
      }
    },

    uglify: {
      my_target: {
        files: {
          'client/lib/dest/libs.min.js': ['client/lib/angular.js', 'client/lib/angular-ui-router.js']

        }
      }
    },
    // TODO: add uglify, concat, cssmin tasks

    watch: {
      files: ['client/app/*.js', 'server/**/*.js', 'database/**/*.js'],
      tasks: ['jshint']
    }
  });

  //Automatic desktop notifications for Grunt errors and warnings
  grunt.loadNpmTasks('grunt-notify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-concat');

  /*************************************************************
  Run `$ grunt jshint` before submitting PR
  Or run `$ grunt` with no arguments to watch files
  **************************************************************/

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

Add bower.json and package.json to jshint checks


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

	grunt.initConfig({
		jshint: {
			all: ['*.js', 'test/*.js'],

			options: {
				jshintrc: true
			}
		},
		qunit: {
			all: ['test/index.html']
		}
	});

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

Convert tabs to spaces in gruntfile.js


module.exports = function(grunt) {
    var packageFile = grunt.file.readJSON("package.json");

    grunt.initConfig({
        pkg: packageFile,
        jshint: {
            all: ["Gruntfile.js", "app.js", "index.js", "controllers/*.js", "routes/*.js", "test/*.js"],
            options: packageFile.jshintConfig
        }
    });
    grunt.loadNpmTasks("grunt-contrib-jshint");
};

module.exports = function(grunt) {
    var packageFile = grunt.file.readJSON("package.json");

    grunt.initConfig({
        pkg: packageFile,
        jshint: {
            all: ["Gruntfile.js", "app.js", "config.js", "index.js", "controllers/*.js", "routes/*.js", "test/*.js"],
            options: packageFile.jshintConfig
        }
    });
    grunt.loadNpmTasks("grunt-contrib-jshint");
};

Fix jshint files in gruntfile


const s3 = require('aws-sdk').S3;
const s3Bucket = require('../../../config')('/aws/s3Bucket');
const logger = require('../../lib/logger');

module.exports = () => ({
  uploadImageStream(stream, key) {
    const contentType = (stream.hapi && stream.hapi.headers['content-type']) ?
      stream.hapi.headers['content-type'] : 'application/octet-stream';
    const filename = key;

    const bucket = new s3({ params: { Bucket: s3Bucket } });
    return bucket.upload({ Body: stream, Key: filename, ContentType: contentType })
      .promise()
      .then(data => data.Location)
      .catch(error => {
        const trace = new Error(error);
        logger.error(trace);
        throw error;
      });
  },

  deleteImage(filename) {
    const bucket = new s3({ params: { Bucket: s3Bucket } });

    return new Promise((resolve, reject) => {
      bucket.deleteObject({ Key: filename })
        .send((err, data) => {
          if (err) {
            reject(err);
          } else {
            resolve(filename);
          }
        });
    })
      .catch((error) => {
        const trace = new Error(error);
        logger.error(trace);
        throw error;
      });
  },
});

module.exports['@singleton'] = true;
module.exports['@require'] = [];

const s3 = require('aws-sdk').S3;
const s3Bucket = require('../../../config')('/aws/s3Bucket');
const logger = require('../../lib/logger');

module.exports = () => ({
  uploadImageStream(stream, key) {
    const contentType = (stream.hapi && stream.hapi.headers['content-type']) ?
      stream.hapi.headers['content-type'] : 'application/octet-stream';
    const filename = key;

    const bucket = new s3({ params: { Bucket: s3Bucket } });
    return bucket.upload({ Body: stream, Key: filename, ContentType: contentType, CacheControl: 'max-age=0' })
      .promise()
      .then(data => data.Location)
      .catch(error => {
        const trace = new Error(error);
        logger.error(trace);
        throw error;
      });
  },

  deleteImage(filename) {
    const bucket = new s3({ params: { Bucket: s3Bucket } });

    return new Promise((resolve, reject) => {
      bucket.deleteObject({ Key: filename })
        .send((err, data) => {
          if (err) {
            reject(err);
          } else {
            resolve(filename);
          }
        });
    })
      .catch((error) => {
        const trace = new Error(error);
        logger.error(trace);
        throw error;
      });
  },
});

module.exports['@singleton'] = true;
module.exports['@require'] = [];

Set max-age=0 for CacheControl on profile image S3 uploads


import React from 'react';
import PropTypes from 'prop-types';

export default class UpdateButton extends React.Component {
  static propTypes = {
    label: PropTypes.string.isRequired,
    login: PropTypes.string.isRequired,
    path: PropTypes.string.isRequired
  };

  constructor(props) {
    super(props);
    this.state = { updateStatus: 'OUTDATED' };

    this.hookUpdateStatus();
  }

  hookUpdateStatus() {
    setTimeout(function() {
      this.setState({ updateStatus: 'UPDATED' });
    }.bind(this), 3000);
  }

  render() {
    switch (this.state.updateStatus) {
      case 'UPDATING':
        return React.createElement('span',
          { className: 'btn btn-default disabled col-xs-12' },
          'Updating stars ...'
        );
      case 'UPDATED':
        return React.createElement('span',
          { className: 'btn btn-default disabled col-xs-12' },
          'Up to date'
        );
      default:
        return React.createElement('a',
          { className: 'btn btn-info col-xs-12', href: this.props.path, method: 'post' },
          this.props.label
        );
    }
  }
}

import React from 'react';
import PropTypes from 'prop-types';

export default class UpdateButton extends React.Component {
  static propTypes = {
    label: PropTypes.string.isRequired,
    login: PropTypes.string.isRequired,
    path: PropTypes.string.isRequired
  };

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

  updateStatus() {
    var query = `
      query {
        user(login:"${this.props.login}") {
          updateStatus
        }
      }
    `;
    $.post('/graphql', { query: query }, function(data) {
      var updateStatus = data.data.user.updateStatus;
      this.setState({ updateStatus: updateStatus });
      if (updateStatus == 'UPDATING') {
        setTimeout(function() { this.updateStatus() }.bind(this), 3000);
      }
    }.bind(this));
  }

  render() {
    switch (this.state.updateStatus) {
      case 'UPDATING':
        return React.createElement('span',
          { className: 'btn btn-default disabled col-xs-12' },
          'Updating stars ...'
        );
      case 'UPDATED':
        return React.createElement('span',
          { className: 'btn btn-default disabled col-xs-12' },
          'Up to date'
        );
      case 'OUTDATED':
        return React.createElement('a',
          { className: 'btn btn-info col-xs-12', href: this.props.path, 'data-method': 'post' },
          this.props.label
        );
      default:
        return React.createElement('span',
          { className: 'btn btn-default disabled col-xs-12' },
          'Loading status ...'
        );
    }
  }
}

Use GraphQL API to update component


//= require jquery.ui.tabs
//= require vendor/select2
//= require vendor/redactor

$(document).ready(function(){
  if($("select.select2").length) {
    $("select.select2").select2();
  }

  // In one line for easy removal using sed
  $('#entry_body').redactor({ focus: false, buttons: ['bold', 'italic', 'link', 'unorderedlist'], lang: 'pl' })
});

//= require jquery.ui.tabs
//= require vendor/select2
//= require vendor/redactor

// ActiveAdmin
//= require jquery.ui.datepicker
//= require jquery_ujs
//= require active_admin/application

$(document).ready(function(){
  if($("select.select2").length) {
    $("select.select2").select2();
  }

  // In one line for easy removal using sed
  $('#entry_body').redactor({ focus: false, buttons: ['bold', 'italic', 'link', 'unorderedlist'], lang: 'pl' })
});

Add missing ActiveAdmin js scripts


/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.exports = function (BLOCK_INTERVAL_MS, INVALID_AGENT_INTERVAL_MS) {

  function IpRecord() {}

  IpRecord.parse = function (object) {
    var rec = new IpRecord()
    object = object || {}
    rec.ba = object.ba
    rec.bk = object.bk
    return rec
  }

  function isBadAgent(agent) {
    return false // TODO
  }

  IpRecord.prototype.isBlocked = function () {
    return !!(this.bk && (Date.now() - this.bk < INVALID_AGENT_INTERVAL_MS))
  }

  IpRecord.prototype.block = function () {
    this.bk = Date.now()
  }

  IpRecord.prototype.retryAfter = function () {
    if (!this.isBlocked()) { return 0 }
    return Math.floor((this.bk + BLOCK_INTERVAL_MS - Date.now()) / 1000)
  }

  IpRecord.prototype.update = function (agent) {
    if (isBadAgent(agent)) {
      if (Date.now() - this.ba < INVALID_AGENT_INTERVAL_MS) {
        this.block()
      }
      this.ba = Date.now()
    }
    return this.retryAfter()
  }

  return IpRecord
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

module.exports = function (BLOCK_INTERVAL_MS, INVALID_AGENT_INTERVAL_MS) {

  function IpRecord() {}

  IpRecord.parse = function (object) {
    var rec = new IpRecord()
    object = object || {}
    rec.ba = object.ba
    rec.bk = object.bk
    return rec
  }

  function isBadAgent(agent) {
    return false // TODO
  }

  IpRecord.prototype.isBlocked = function () {
    return !!(this.bk && (Date.now() - this.bk < BLOCK_INTERVAL_MS))
  }

  IpRecord.prototype.block = function () {
    this.bk = Date.now()
  }

  IpRecord.prototype.retryAfter = function () {
    if (!this.isBlocked()) { return 0 }
    return Math.floor((this.bk + BLOCK_INTERVAL_MS - Date.now()) / 1000)
  }

  IpRecord.prototype.update = function (agent) {
    if (isBadAgent(agent)) {
      if (Date.now() - this.ba < INVALID_AGENT_INTERVAL_MS) {
        this.block()
      }
      this.ba = Date.now()
    }
    return this.retryAfter()
  }

  return IpRecord
}

Check for blocks against the right interval


module.exports = function (browser, module, suite, config) {

  module.axes = module.axes || {};

  module.axes.x = module.axes.x || {
    label: 'Date'
  };
  module.axes.y = module.axes.y || [
    {
      label: 'User satisfaction'
    }
  ];

  module.trim = module.trim === undefined ? true : module.trim;

  require('./completion_rate')(browser, module, suite, config);
};

module.exports = function (browser, module, suite, config) {

  module.axes = module.axes || {};

  module.axes.x = module.axes.x || {
    label: 'Date'
  };
  module.axes.y = module.axes.y || [
    { label: 'User satisfaction' },
    { label: 'Not satisfied' },
    { label: 'Dissatisfied' },
    { label: 'Neither satisfied or dissatisfied' },
    { label: 'Satisfied' },
    { label: 'Very satisfied' }
  ];

  module.trim = module.trim === undefined ? true : module.trim;

  require('./completion_rate')(browser, module, suite, config);
};

Update default axes for user satisfaction tables


let defaultOptions = {
  wait: false,
  renderNullWhileWaiting: true,
  withRef: false,
  bindI18n: 'languageChanged loaded',
  bindStore: 'added removed',
  translateFuncName: 't',
  nsMode: 'default',
  usePureComponent: false
};

let i18n;

export function setDefaults(options) {
  defaultOptions = { ...defaultOptions, ...options };
}

export function getDefaults() {
  return defaultOptions;
}

export function setI18n(instance) {
  i18n = instance;
}

export function getI18n() {
  return i18n;
}

export const reactI18nextModule = {
  type: '3rdParty',

  init(instance) {
    setDefaults(instance.options.react);
    setI18n(instance);
  }
};

let defaultOptions = {
  wait: false,
  withRef: false,
  bindI18n: 'languageChanged loaded',
  bindStore: 'added removed',
  translateFuncName: 't',
  nsMode: 'default',
  usePureComponent: false
};

let i18n;

export function setDefaults(options) {
  defaultOptions = { ...defaultOptions, ...options };
}

export function getDefaults() {
  return defaultOptions;
}

export function setI18n(instance) {
  i18n = instance;
}

export function getI18n() {
  return i18n;
}

export const reactI18nextModule = {
  type: '3rdParty',

  init(instance) {
    setDefaults(instance.options.react);
    setI18n(instance);
  }
};

Remove last reference to renderNullWhileWaiting


import Em from 'ember';
import { task, timeout } from 'ember-concurrency';

const { computed } = Em;

var applicationController = Em.Controller.extend({
	activeNote: null,

  hideSidePanel: false,

  init: function() {
    Em.run.scheduleOnce('afterRender', () => {
      $('body div').first().addClass('app-container');
    });
  },

	// data
	sortedNotes: computed('model.[]', 'model.@each.updatedAt', function() {
		return this.get('model').sortBy('updatedAt', 'createdAt').reverseObjects();
	}),

  deleteNote: task(function *(note) {
    yield timeout(150);
    this.get('model').removeObject(note);
    if (Em.isEmpty(this.get('model'))) {
      this.send('createNote');
    } else {
      this.send('showNote', this.get('sortedNotes.firstObject'));
    }
    yield note.destroyRecord();
  }).drop(),

	actions: {
    createNote() {
      this.transitionToRoute('new');
    },

    deleteNote(note) {
      this.get('deleteNote').perform(note);
    },

    signOut() {
      this.get('session').close().then(()=> {
        this.transitionToRoute('signin');
      });
    },

    toggleSidePanelHidden() {
      this.toggleProperty('hideSidePanel');
    }
	}
});

export default applicationController;

import Em from 'ember';
import { task, timeout } from 'ember-concurrency';

const { computed } = Em;

var applicationController = Em.Controller.extend({
	activeNote: null,

  hideSidePanel: false,

  init: function() {
    Em.run.scheduleOnce('afterRender', () => {
      Em.$('body div').first().addClass('app-container');
    });
  },

	// data
	sortedNotes: computed('model.[]', 'model.@each.updatedAt', function() {
		return this.get('model').sortBy('updatedAt', 'createdAt').reverseObjects();
	}),

  deleteNote: task(function *(note) {
    yield timeout(150);
    this.get('model').removeObject(note);
    if (Em.isEmpty(this.get('model'))) {
      this.send('createNote');
    } else {
      this.send('showNote', this.get('sortedNotes.firstObject'));
    }
    yield note.destroyRecord();
  }).drop(),

	actions: {
    createNote() {
      this.transitionToRoute('new');
    },

    deleteNote(note) {
      this.get('deleteNote').perform(note);
    },

    signOut() {
      this.get('session').close().then(()=> {
        this.transitionToRoute('signin');
      });
    },

    toggleSidePanelHidden() {
      this.toggleProperty('hideSidePanel');
    }
	}
});

export default applicationController;

Use Ember namespace for jquery use


import React, { PropTypes } from 'react'
import { Link } from 'react-router'

//TODO make it generic (it's duplicated for gsim inputs and outputs)
export default function ServiceSubEntities(
    { entities, uriName, remove, disabled, noMsg, makeLink }) {
  if (entities.length === 0)
    return {noMsg}
  return (

      { entities.map(entity => {
          const uri = entity[uriName]
          return (

              <Link to={makeLink(uri)}>
                {entity.label}
              </Link>
              { !disabled &&
                remove(uri)} >

              }

          )
        })
      }

  )
}

ServiceSubEntities.propTypes = {
  entities: PropTypes.array.isRequired,
  remove: PropTypes.func.isRequired,
  disabled: PropTypes.bool.isRequired,
  noMsg: PropTypes.string.isRequired,
  makeLink: PropTypes.func.isre
}

import React, { PropTypes } from 'react'
import { Link } from 'react-router'

export default function ServiceSubEntities(
    { entities, uriName, remove, disabled, noMsg, makeLink }) {
  if (entities.length === 0)
    return {noMsg}
  return (

      { entities.map(entity => {
          const uri = entity[uriName]
          return (

              <Link to={makeLink(uri)}>
                {entity.label}
              </Link>
              { !disabled &&
                remove(uri)} >

              }

          )
        })
      }

  )
}

ServiceSubEntities.propTypes = {
  entities: PropTypes.array.isRequired,
  remove: PropTypes.func.isRequired,
  disabled: PropTypes.bool.isRequired,
  noMsg: PropTypes.string.isRequired,
  makeLink: PropTypes.func.isRequired
}

Fix typo in propTypes which led to invalid prop type error messages


import Ember from 'ember';
import OAuth2 from 'simple-auth-oauth2/authenticators/oauth2';

export default OAuth2.extend({
  authenticate: function (options) {
    return this.fetchOauthData(options).then(this.fetchAccessToken.bind(this));
  },

  fetchAccessToken: function (oauthCredentials) {
    var _this = this;

    return new Ember.RSVP.Promise(function (resolve, reject) {
      _this.makeRequest(_this.serverTokenEndpoint, oauthCredentials).then(function (response) {
        Ember.run(function () {
          var expiresAt = _this.absolutizeExpirationTime(response.expires_in);
          _this.scheduleAccessTokenRefresh(response.expires_in, expiresAt, response.refresh_token);
          resolve(Ember.$.extend(response, {expires_at: expiresAt}));
        });
      }, function (xhr) {
        Ember.run(function () {
          reject(xhr.responseJSON || xhr.responseText);
        });
      });
    });
  },

  fetchOauthData: function (options) {
    return new Ember.RSVP.Promise(function (resolve, reject) {
      options.torii.open(options.provider).then(function (oauthData) {
        resolve({
          grant_type: 'authorization_code',
          provider: oauthData.provider,
          code: oauthData.authorizationCode,
        });
      }, function (error) {
        reject(error);
      });
    });
  }
});

import Ember from 'ember';
import OAuth2 from 'simple-auth-oauth2/authenticators/oauth2';

export default OAuth2.extend({
  authenticate: function (options) {
    return this.fetchOauthData(options).then(this.fetchAccessToken.bind(this));
  },

  fetchAccessToken: function (oauthCredentials) {
    var _this = this;

    return new Ember.RSVP.Promise(function (resolve, reject) {
      _this.makeRequest(_this.serverTokenEndpoint, oauthCredentials).then(function (response) {
        Ember.run(function () {
          resolve(Ember.$.extend(response, {id: response.user.id}));
        });
      }, function (xhr) {
        Ember.run(function () {
          reject(xhr.responseJSON || xhr.responseText);
        });
      });
    });
  },

  fetchOauthData: function (options) {
    return new Ember.RSVP.Promise(function (resolve, reject) {
      options.torii.open(options.provider).then(function (oauthData) {
        resolve({
          grant_type: 'authorization_code',
          provider: oauthData.provider,
          code: oauthData.authorizationCode,
        });
      }, function (error) {
        reject(error);
      });
    });
  }
});

Add ID of current user to simple-auth session


;(function(exports) {
  function Eventer() {
    var callbacks = {};

    this.addListener = function(obj, event, callback) {
      callbacks[event] = callbacks[event] || [];
      callbacks[event].push({
        obj: obj,
        callback: callback
      });

      return this;
    };

    this.addListener
    this.on = this.addListener;

    this.removeListener = function(obj, event) {
      for(var i in callbacks) {
        if(callbacks[i].obj === obj) {
          delete callbacks[i];
          break;
        }
      }
    };

    this.emit = function(event, data) {
      var eventCallbacks = callbacks[event];
      if(eventCallbacks !== undefined) {
        for(var i = 0; i < eventCallbacks.length; i++) {
          var callbackObj = eventCallbacks[i];
          callbackObj.callback.call(callbackObj.obj, data);
        }
      }

      return this;
    };
  }

  exports.Eventer = Eventer;
})(typeof exports === 'undefined' ? this : exports)

;(function(exports) {
  function Eventer() {
    var callbacks = {};

    this.addListener = function(obj, event, callback) {
      callbacks[event] = callbacks[event] || [];
      callbacks[event].push({
        obj: obj,
        callback: callback
      });

      return this;
    };

    this.addListener
    this.on = this.addListener;

    this.removeListener = function(obj, event) {
      for(var i = 0; i < callbacks[event].length; i++) {
        if(callbacks[event][i].obj === obj) {
          callbacks[event].splice(i, 1);
          break;
        }
      }
    };

    this.emit = function(event, data) {
      var eventCallbacks = callbacks[event];
      if(eventCallbacks !== undefined) {
        for(var i = 0; i < eventCallbacks.length; i++) {
          var callbackObj = eventCallbacks[i];
          callbackObj.callback.call(callbackObj.obj, data);
        }
      }

      return this;
    };
  }

  exports.Eventer = Eventer;
})(typeof exports === 'undefined' ? this : exports)

Make Eventer.removeListener actually, like, remove passed listener.


/**
 *
 *
 * @copyright 2017 Government of Canada
 * @license MIT
 * @author igboyes
 *
 */
import React from "react";
import { capitalize } from "lodash";
import { connect } from "react-redux";
import { Label } from "react-bootstrap";

import { Flex, FlexItem } from "../../base";
import ChangePassword from "./Password";
import { Identicon } from "../../base";

class AccountGeneral extends React.Component {

    constructor (props) {
        super(props);
    }

    render () {
        const groupLabels = this.props.groups.map(groupId =>

                {capitalize(groupId)}

        );

        return (

                <Flex alignItems="stretch">
                    <FlexItem>
                        <Identicon hash={this.props.hash} />
                    </FlexItem>
                    <FlexItem pad={10}>

                                {this.props.id}

                            {groupLabels}

                    </FlexItem>
                </Flex>

                <ChangePassword />

        );
    }
}

const mapStateToProps = (state) => {
    return {
        id: state.account.id,
        hash: state.account.identicon,
        groups: state.account.groups
    };
};

const Container = connect(mapStateToProps)(AccountGeneral);

export default Container;

/**
 *
 *
 * @copyright 2017 Government of Canada
 * @license MIT
 * @author igboyes
 *
 */
import React from "react";
import { capitalize } from "lodash";
import { connect } from "react-redux";
import { Label } from "react-bootstrap";

import { Flex, FlexItem } from "../../base";
import ChangePassword from "./Password";
import { Identicon } from "../../base";

const AccountGeneral = ({ id, groups, hash }) => {

    const groupLabels = groups.map(groupId =>

            {capitalize(groupId)}

    );

    return (

            <Flex alignItems="stretch" style={{marginBottom: "15px"}}>
                <FlexItem>
                    <Identicon hash={hash} />
                </FlexItem>
                <FlexItem pad={10}>

                            {id}

                        {groupLabels}

                </FlexItem>
            </Flex>

            <ChangePassword />

    );
};

const mapStateToProps = (state) => {
    return {
        id: state.account.id,
        hash: state.account.identicon,
        groups: state.account.groups
    };
};

const Container = connect(mapStateToProps)(AccountGeneral);

export default Container;

Add bottom margin to header in account > general


const
    {app} = require('electron'),
    fs = require('fs-extra'),
    Path = require('path'),
    klaw = require('klaw-sync');

var PluginManager = {
    loadedPlugins: {},

    LoadPlugins: function(mod, appEvents) {
        const appPath = Path.resolve(app.getAppPath() + '/plugins');
        var pluginFolders = fs.readdirSync( appPath )
        console.log('Loading ' + pluginFolders.length + ' plugins from ' + appPath)

        pluginFolders.forEach((pluginFolder) => {
            var pluginPath = Path.resolve('plugins', pluginFolder),
                stat = fs.statSync(pluginPath);

            if(stat && stat.isDirectory()) {
                loadPlugin(pluginPath, mod, appEvents);
            }
        });
    }
}

function loadPlugin(dir, mod) {
    const pluginManifestPath = Path.resolve(dir, 'package.json');

    // Register the plugin
    var pluginManifest = fs.readJsonSync(pluginManifestPath);
    PluginManager.loadedPlugins[pluginManifest.name] = {
        manifest: pluginManifest,
        module: mod.require(dir)
    };

    // Execute the plugin's initial script
    PluginManager.loadedPlugins[pluginManifest.name].module.onLoad();
    console.log('Plugin:', pluginManifest.name, 'loaded');
}

module.exports = PluginManager;

const
    {app} = require('electron'),
    fs = require('fs-extra'),
    Path = require('path'),
    klaw = require('klaw-sync');

var PluginManager = {
    loadedPlugins: {},

    LoadPlugins: function(mod, appEvents) {
        const appPath = Path.resolve(app.getAppPath() + '/plugins');
        var pluginFolders = fs.readdirSync( appPath )
        console.log('Loading ' + pluginFolders.length + ' plugins from ' + appPath)

        pluginFolders.forEach((pluginFolder) => {
            var pluginPath = Path.resolve('plugins', pluginFolder),
                stat = fs.statSync(pluginPath);

            if(stat && stat.isDirectory()) {
                loadPlugin(pluginPath, mod, appEvents);
            }
        });
    }
}

function loadPlugin(dir, mod) {
    const pluginManifestPath = Path.resolve(dir, 'package.json');

    // Register the plugin
    var pluginManifest = fs.readJsonSync(pluginManifestPath),
        PluginClass = new require('./Plugin.js')(pluginManifest.name);

    PluginManager.loadedPlugins[pluginManifest.name] = {
        manifest: pluginManifest,
        module: mod.require(dir)(PluginClass)
    };

    // Execute the plugin's initial script
    PluginManager.loadedPlugins[pluginManifest.name].module.onLoad();
    console.log('Plugin:', pluginManifest.name, 'loaded');
}

module.exports = PluginManager;

Restructure plugin loading to pass Plugin class to each loaded plugin


import { observable } from 'mobx'
import getCookie from './utils/getCookie'

const config = observable({
  projectName: '',
  spaceKey: '',
  requiredExtensions: [],
  baseURL: getCookie('BACKEND_URL') || __BACKEND_URL__ || window.location.origin,
  packageDev: getCookie('PACKAGE_DEV') || __PACKAGE_DEV__,
  packageServer: getCookie('PACKAGE_SERVER') || __PACKAGE_SERVER__ || window.location.origin,
  wsURL: getCookie('WS_URL') || __WS_URL__ || __BACKEND_URL__ || window.location.origin,
  runMode: __RUN_MODE__,
  isPlatform: Boolean(__RUN_MODE__),
  fsSocketConnected: false,
  ttySocketConnected: false,
  fileExcludePatterns: ['/.git', '/.coding-ide'],
  preventAccidentalClose: false,
  hasRehydrated: getCookie('skipRehydrate') || false,
  estimatedMap: observable.map({})
})

window.config = config
export default config

import { observable, autorun } from 'mobx'
import getCookie from './utils/getCookie'

const config = observable({
  projectName: '',
  spaceKey: '',
  requiredExtensions: [],
  baseURL: getCookie('BACKEND_URL') || __BACKEND_URL__ || window.location.origin,
  packageDev: getCookie('PACKAGE_DEV') || __PACKAGE_DEV__,
  packageServer: getCookie('PACKAGE_SERVER') || __PACKAGE_SERVER__ || window.location.origin,
  wsURL: getCookie('WS_URL') || __WS_URL__ || __BACKEND_URL__ || window.location.origin,
  runMode: __RUN_MODE__,
  isPlatform: Boolean(__RUN_MODE__),
  fsSocketConnected: false,
  ttySocketConnected: false,
  fileExcludePatterns: ['/.git', '/.coding-ide'],
  preventAccidentalClose: false,
  hasRehydrated: getCookie('skipRehydrate') || false,
  estimatedMap: observable.map({})
})

autorun(() => {
  if (config.projectName) {
    window.document.title = `${config.projectName} | Coding WebIDE 开启云端开发模式！ -  Coding.net`
  }
})

window.config = config
export default config

Change title with project name


'use strict';

angular.module('ddsCommon').constant('specificSituations', [
    {
        id: 'demandeur_emploi',
        label: 'Demandeur·euse d’emploi'
    },
    {
        id: 'etudiant',
        label: 'Étudiant·e'
    },
    {
        id: 'retraite',
        label: 'Retraité·e'
    },
    {
        id: 'handicap',
        label: 'En situation de handicap'
    },
    {
        id: 'inapte_travail',
        label: 'Inapte au travail'
    }
]);

'use strict';

angular.module('ddsCommon').constant('specificSituations', [
    {
        id: 'demandeur_emploi',
        label: 'En recherche d’emploi'
    },
    {
        id: 'etudiant',
        label: 'Étudiant·e'
    },
    {
        id: 'retraite',
        label: 'Retraité·e'
    },
    {
        id: 'handicap',
        label: 'En situation de handicap'
    },
    {
        id: 'inapte_travail',
        label: 'Inapte au travail'
    }
]);

Make gender neutrality more bearable


const path = require('path')
const assert = require('yeoman-assert')
const helpers = require('yeoman-test')

describe('react-zeal', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../../app'))
      .withPrompts({ name: 'apples' })
  })

  test('creates root level files', () => {
    assert.file(['.gitignore', 'package.json'])
  })

  test('generated package.json has "apples" as the package name', () => {
    assert.jsonFileContent('package.json', { "name": "apples" })
  })

  test('copies .eslintrc.json file', () => {
    assert.jsonFileContent('.eslintrc.json', {
      "extends": [ "zeal", "zeal/react" ]
    })
  })
})

const path = require('path')
const assert = require('yeoman-assert')
const helpers = require('yeoman-test')

describe('react-zeal', () => {
  beforeEach(() => {
    return helpers.run(path.join(__dirname, '../../app'))
      .withPrompts({ name: 'apples' })
  })

  test('creates root level files', () => {
    assert.file(['.gitignore', 'package.json'])
  })

  test('generated package.json has "apples" as the package name', () => {
    assert.jsonFileContent('package.json', { "name": "apples" })
  })

  test('copies .eslintrc.js file', () => {
    assert.fileContent('.eslintrc.js', 'path.resolve')
  })
})

Adjust test for copying eslint config


import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { isPresent } from '@ember/utils';

export default class ApplicationRoute extends Route {

  @service('remotestorage') storage;
  @service localData;
  @service logger;
  @service coms;

  async beforeModel (transition) {
    super.beforeModel(...arguments);

    await this.storage.ensureReadiness();
    await this.localData.setDefaultValues();
    await this.coms.instantiateAccountsAndChannels();
    this.coms.setupListeners();

    if (isPresent(transition.intent.url) &&
        transition.intent.url.includes('add-account')) {
      return;
    }

    if (!this.coms.onboardingComplete) {
      this.transitionTo('welcome');
    }

    // See a list of allowed types in logger.js
    // Add or remove all your log types here:
    // this.logger.add('message');
    // this.logger.remove('join');
    // this.logger.disable();
    // this.logger.enable();
  }

  @action
  leaveChannel (channel) {
    this.coms.removeChannel(channel);

    // Switch to last channel if the channel parted was currently open
    if (channel.visible) {
      let lastChannel = this.coms.sortedChannels.lastObject;
      this.transitionTo('channel', lastChannel);
    }
  }

}

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { isPresent } from '@ember/utils';

export default class ApplicationRoute extends Route {

  @service('remotestorage') storage;
  @service localData;
  @service logger;
  @service coms;

  async beforeModel () {
    super.beforeModel(...arguments);

    await this.storage.ensureReadiness();
    await this.localData.setDefaultValues();
    await this.coms.instantiateAccountsAndChannels();
    this.coms.setupListeners();

    // See a list of allowed types in logger.js
    // Add or remove all your log types here:
    // this.logger.add('message');
    // this.logger.remove('join');
    // this.logger.disable();
    // this.logger.enable();
  }

  redirect (model, transition) {
    if (isPresent(transition.intent.url) &&
        transition.intent.url.includes('add-account')) {
      return;
    }

    if (!this.coms.onboardingComplete) {
      this.transitionTo('welcome');
    }
  }

  @action
  leaveChannel (channel) {
    this.coms.removeChannel(channel);

    // Switch to last channel if the channel parted was currently open
    if (channel.visible) {
      let lastChannel = this.coms.sortedChannels.lastObject;
      this.transitionTo('channel', lastChannel);
    }
  }

}

Fix redirects causing duplicate setup method calls


Template.createDoc.events({

  "submit .new-doc": function(event) {
    console.log("test");
    event.preventDefault();
    var title = event.target.title.value;
    Documents.insert({
      title: title
    }, function(err, _id) {
      Router.go('workpane', {_id: _id})
    });
  }

})

Template.createDoc.events({

  "submit .new-doc": function(event) {
    console.log("test");
    event.preventDefault();
    var title = event.target.title.value;
    Documents.insert({
      title: title,
      createdAt: new Date(),
      owner: Meteor.userId(),
      collaborators: []
    }, function(err, _id) {
      Router.go('workpane', {_id: _id})
    });
  }

})

Insert additional fields for documents


'use strict';

var App = require('../app');
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var _ = require('underscore');
var keyboardShortcuts = require('./keyboardshortcuts.json');
var marked = require('marked');

var controller = {
  showHelp: function () {
    var HelpView = require('./views/HelpView');
    _.each(keyboardShortcuts, function (category) {
      _.each(category.shortcuts, function (shortcut) {
        shortcut.description = marked(shortcut.description);
      });
    });

    var helpModel = new Backbone.Model({keyboardShortcuts: keyboardShortcuts});
    App.mainRegion.show(new HelpView({model: helpModel}));
    App.navigationRegion.currentView.options.collection.deselect();
  }
};

App.addInitializer(function () {
  new Marionette.AppRouter({
    controller: controller,
    appRoutes: {
      'help': 'showHelp'
    }
  });
});

'use strict';

var App = require('../app');
var Backbone = require('backbone');
var Marionette = require('backbone.marionette');
var _ = require('underscore');
var keyboardShortcuts = require('./keyboardShortcuts.json');
var marked = require('marked');

var controller = {
  showHelp: function () {
    var HelpView = require('./views/HelpView');
    _.each(keyboardShortcuts, function (category) {
      _.each(category.shortcuts, function (shortcut) {
        shortcut.description = marked(shortcut.description);
      });
    });

    var helpModel = new Backbone.Model({keyboardShortcuts: keyboardShortcuts});
    App.mainRegion.show(new HelpView({model: helpModel}));
    App.navigationRegion.currentView.options.collection.deselect();
  }
};

App.addInitializer(function () {
  new Marionette.AppRouter({
    controller: controller,
    appRoutes: {
      'help': 'showHelp'
    }
  });
});

Fix case of filename keyboardShortcuts.json


var m               = require('mithril');

//Components
var StudentInfo     = require('../components/StudentInfo.js');
var StudentJob      = require('../components/StudentJob.js');
var Messaging       = require('../components/Messaging.js');
var NewApp          = require('../components/forms/NewApp.js');
var Graph           = require('../components/OutcomesGraph.js');

//Models
var StudentApp     = require('../models/StudentApp.js');
var Message        = require('../models/Message.js');

exports.controller = function (ctrl) {
  //Fetches student apps, info, and messages from db
  StudentApp.fetchApps(ctrl);
  StudentApp.fetchInfo(ctrl);
  Message.fetch(ctrl);
  Message.fetchUsers();
}

exports.view = function (ctrl) {
  // .all() Makes data accessible to the components
  var appsData     = StudentApp.all();
  var messagesData = Message.all();
  return m('.container', [
    m('h1.center-align', 'Pending Applications'),

    m('.row'),
    m('.row', [
      // m.component(StudentInfo, { studentInfo: appsData.studentInfo } ),
      m.component(StudentJob, { apps: appsData.apps, studentInfo: appsData.studentInfo } ),
    ]),
    m('.row.center-align', [
      m('a.btn[href=/newapp]', {config: m.route}, 'New Application')
    ]),
    m.component(Messaging, {
      messages: messagesData.messages,
      users: messagesData.users,
      studentInfo: appsData.studentInfo
    })
  ]);
};

var m               = require('mithril');

//Components
var StudentInfo     = require('../components/StudentInfo.js');
var StudentJob      = require('../components/StudentJob.js');
var Messaging       = require('../components/Messaging.js');
var NewApp          = require('../components/forms/NewApp.js');
var Graph           = require('../components/OutcomesGraph.js');

//Models
var StudentApp     = require('../models/StudentApp.js');
var Message        = require('../models/Message.js');

exports.controller = function (ctrl) {
  //Fetches student apps, info, and messages from db
  StudentApp.fetchApps(ctrl);
  StudentApp.fetchInfo(ctrl);
  Message.fetch(ctrl);
  Message.fetchUsers();
}

exports.view = function (ctrl) {
  // .all() Makes data accessible to the components
  var appsData     = StudentApp.all();
  var messagesData = Message.all();
  return m('.container', [
    m('h1.center-align', 'Pending Applications'),
    m('.row', [
      // m.component(StudentInfo, { studentInfo: appsData.studentInfo } ),
      m.component(StudentJob, { apps: appsData.apps, studentInfo: appsData.studentInfo } ),
    ]),
    m('.row.center-align', [
      m('a.btn[href=/newapp]', {config: m.route}, 'New Application')
    ]),
    m.component(Messaging, {
      messages: messagesData.messages,
      users: messagesData.users,
      studentInfo: appsData.studentInfo
    })
  ]);
};

Delete row element to fix spacing


var assert = chai.assert;
describe('LMLayerWorkerCode', function() {
  it('should exist!', function() {
    assert.isFunction(LMLayerWorkerCode,
      'Could not find LMLayerWorkerCode! Does embedded_worker.js exist?'
    );
  });
});

var assert = chai.assert;
describe('LMLayerWorker', function () {
  describe('LMLayerWorkerCode', function() {
    it('should exist!', function() {
      assert.isFunction(LMLayerWorkerCode,
        'Could not find LMLayerWorkerCode! Does embedded_worker.js exist?'
      );
    });
  });

  describe('Usage within a Web Worker', function () {
    it('should install itself in the worker context', function (done) {
      let wrapper = LMLayerWorkerCode.toString();
      let match = wrapper.match(/function[^{]+{((?:.|\n)+)}[^}]*$/);
      assert.isNotNull(match);
      let code = match[1];
      assert.isString(code);

      let blob = new Blob([code], { type: 'text/javascript' });
      debugger;
      let uri = URL.createObjectURL(blob);

      let worker = new Worker(uri);
      worker.onmessage = function thisShouldBeCalled(message) {
        done();
      };
      worker.postMessage({
        message: 'initialize',
        model: "return {model: {}, configuration: {}}"
      });
    });
  });
})

Write integration test for LMLayerWorker code.


import BaseMeshBehavior from './BaseMeshBehavior'

// base class for geometry behaviors
export default
class BaseGeometryBehavior extends BaseMeshBehavior {

    static get type() { return 'geometry' }

    static get observedAttributes() {
        return [ 'size' ]
    }

    async attributeChangedCallback( element, attr, oldVal, newVal ) {
        if (! ( await super.attributeChangedCallback( element ) ) ) return

        if ( attr == 'size' ) {
            // TODO We currently don't rely on the actual attribute values, but
            // on the calculatedSize that is calculated by the Sizeable class that
            // element extends from. This might not be accurate in the future
            // if we defer size calculations to the next animation frame.
            // Maybe we can make it force calculation in these cases, similar
            // to how DOM does forced layout when forced. Or maybe, when/if we
            // have attribute typing, we just react to actual typed attribute
            // values. In either case, the end user should avoid changing
            // attribute values until an animation frame, so that no matter
            // what everything happens in sync with the browser rendering.
            // TODO: if the current calculatedSize is calculated *after* this code,
            // then we may need to defer to a microtask. Let's see in which
            // order it is happening...
            // TODO: how does scaling affect textures? Maybe we have to scale
            // textures, or maybe we have to just generate a new Sphere? Or
            // maybe we can hack it and somehow modify the existing geometry so
            // Three sees it as having a new size.

            this.setMeshComponent( element, 'geometry', this.createComponent(element) )

            // this is not needed because it is already triggered by the
            // attributeChangedCallback of the element for the 'size'
            // attribute.
            //element._needsToBeRendered()
        }
    }

}

import BaseMeshBehavior from './BaseMeshBehavior'

// base class for geometry behaviors
export default
class BaseGeometryBehavior extends BaseMeshBehavior {

    static get type() { return 'geometry' }

    async connectedCallback( element ) {
        if (! ( await super.connectedCallback( element ) ) ) return

        element.on('sizechange', ({ x, y, z }) => {
            this.setMeshComponent( element, 'geometry', this.createComponent(element) )
        })
    }

}

Make geometry behaviors rely on the element's sizechange event rather than on the size attribute. This ensures that element.calculatedSize is up-to-date when it is used, otherwise the size of the geometry can be the wrong value and rendered at the size of the previous frame rather than the size of the current frame.


chrome.webNavigation.onHistoryStateUpdated.addListener(function(details) {
  chrome.tabs.executeScript(null, {file: "lib/jquery-3.1.1.min.js"}, function() {
      chrome.tabs.executeScript(null, {file: "content.js"});
  });
});

Fix bug when navigating insite


;(function() {
    'use strict';
    window.textsecure = window.textsecure || {};
    window.textsecure.protobuf = {};

    function loadProtoBufs(filename) {
        return dcodeIO.ProtoBuf.loadProtoFile({root: 'protos', file: filename}, function(error, result) {
           var protos = result.build('textsecure');
           for (var protoName in protos) {
              textsecure.protobuf[protoName] = protos[protoName];
           }
        });
    };

    loadProtoBufs('IncomingPushMessageSignal.proto');
    loadProtoBufs('SubProtocol.proto');
    loadProtoBufs('DeviceMessages.proto');
})();

;(function() {
    'use strict';
    window.textsecure = window.textsecure || {};
    window.textsecure.protobuf = {};

    function loadProtoBufs(filename) {
        return dcodeIO.ProtoBuf.loadProtoFile({root: 'protos', file: filename}, function(error, result) {
           if (error) {
              throw error;
           }
           var protos = result.build('textsecure');
           for (var protoName in protos) {
              textsecure.protobuf[protoName] = protos[protoName];
           }
        });
    };

    loadProtoBufs('IncomingPushMessageSignal.proto');
    loadProtoBufs('SubProtocol.proto');
    loadProtoBufs('DeviceMessages.proto');
})();

Throw if we get an error


// @flow
import React, { Component } from 'react';
import { Link } from 'react-router';
import { Text } from 'react-dom';
import _ from 'underscore';
import styles from './Window2.css';
import ALL_DATA from '../fixtures/alldata.js';
const ALL_DATA_BY_ID = _.indexBy(ALL_DATA, 'id');

class Window2 extends Component {
  render() {
    function linkify(text){
      return (

          {
            text.split(' ').map(
              function(word){
                if (word.indexOf('<Link>') > -1) {
                  const [,url,text]= word.split('|');
                  return <Link to={url}>{text}</Link>
                } else {
                  return word+' '
                }
              }
            )
          }

      );
    }
    const fullText = linkify(ALL_DATA_BY_ID[this.props.params.id].fullText);
    const { currentSearch, updateSearch } = this.props;
    return (

Window2

        {fullText}

    );
  }
}

export default Window2;

// @flow
import React, { Component } from 'react';
import { Link } from 'react-router';
import { Text } from 'react-dom';
import _ from 'underscore';
import styles from './Window2.css';
import ALL_DATA from '../fixtures/alldata.js';
const ALL_DATA_BY_ID = _.indexBy(ALL_DATA, 'id');

class Window2 extends Component {
  render() {
    function linkify(text){
      let i=0;
      return (

          {
            text.split(' ').map(
              function(word){
                i++;
                if (word.indexOf('<Link>') > -1) {
                  const [,url,text]= word.split('|');
                  return <Link key={i} to={url}>{text}</Link>
                } else {
                  return word+' '
                }
              }
            )
          }

      );
    }
    const fullText = linkify(ALL_DATA_BY_ID[this.props.params.id].fullText);
    const { currentSearch, updateSearch } = this.props;
    return (

Window2

        {fullText}

    );
  }
}

export default Window2;

Update keys to match warnings


const LinkWithTooltip = React.createClass({
  render() {
    let tooltip = <Tooltip placement="top">{this.props.tooltip}</Tooltip>;

    return (
      <OverlayTrigger overlay={tooltip} delayShow={300} delayHide={150}>
        {this.props.children}
      </OverlayTrigger>
    );
  }
});

const copyInstance = (

    Tight pants next level keffiyeh <LinkWithTooltip tooltip="Default tooltip" href="#">you probably</LinkWithTooltip> haven't
    heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's
    fixie sustainable quinoa 8-bit american apparel <LinkWithTooltip tooltip={Another tooltip} href="#">have a</LinkWithTooltip>
    terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four
    loko mcsweeney's cleanse vegan chambray. A really ironic artisan <LinkWithTooltip tooltip="Another one here too" href="#">whatever keytar</LinkWithTooltip>,
    scenester farm-to-table banksy Austin <LinkWithTooltip tooltip="The last tip!" href="#">twitter handle</LinkWithTooltip> freegan
    cred raw denim single-origin coffee viral.

);

ReactDOM.render(copyInstance, mountNode);

const LinkWithTooltip = React.createClass({
  render() {
    let tooltip = <Tooltip>{this.props.tooltip}</Tooltip>;

    return (
      <OverlayTrigger
        overlay={tooltip} placement="top"
        delayShow={300} delayHide={150}
      >
        {this.props.children}
      </OverlayTrigger>
    );
  }
});

const copyInstance = (

    Tight pants next level keffiyeh <LinkWithTooltip tooltip="Default tooltip" href="#">you probably</LinkWithTooltip> haven't
    heard of them. Photo booth beard raw denim letterpress vegan messenger bag stumptown. Farm-to-table seitan, mcsweeney's
    fixie sustainable quinoa 8-bit american apparel <LinkWithTooltip tooltip={Another tooltip} href="#">have a</LinkWithTooltip>
    terry richardson vinyl chambray. Beard stumptown, cardigans banh mi lomo thundercats. Tofu biodiesel williamsburg marfa, four
    loko mcsweeney's cleanse vegan chambray. A really ironic artisan <LinkWithTooltip tooltip="Another one here too" href="#">whatever keytar</LinkWithTooltip>,
    scenester farm-to-table banksy Austin <LinkWithTooltip tooltip="The last tip!" href="#">twitter handle</LinkWithTooltip> freegan
    cred raw denim single-origin coffee viral.

);

ReactDOM.render(copyInstance, mountNode);

Fix tooltip in copy example


API.on(API.ADVANCE, callback);

function callback () {

	var a = API.getMedia().cid;
	setTimeout(function() {
	    var b = API.getMedia().cid;
	    if (a === b) {
	        API.sendChat("Track stuck, force skipping!");
	        API.moderateForceSkip();
	    }
	}, (API.getMedia().duration * 1000) + 5000);

}

API.chatLog("Now running Force Skip. Refresh page to disable");

/* Basically, this will only force skip if the track is definately stuck
   It uses the duration of the track + 5 seconds to ensure the track has
   finished playing and does need a skip to get it changed.              */

API.on(API.ADVANCE, callback);

function callback () {

	var a = API.getMedia().cid;
	var a_dj = API.getDJ().id;
	setTimeout(function() {
	    var b = API.getMedia().cid;
	    var b_dj = API.getDJ().id;
	    if ((a.cid === b.cid) && (a_dj === b_dj)) {
	        API.sendChat("Track stuck, force skipping!");
	        API.moderateForceSkip();
	    }
	}, (API.getMedia().duration * 1000) + 5000);

}

API.chatLog("Now running Force Skip. Refresh page to disable");

/* Basically, this will only force skip if the track is definately stuck
   It uses the duration of the track + 5 seconds to ensure the track has
   finished playing and does need a skip to get it changed.              */

Add check on DJ id as well.


(function(){
	"use strict";

	angular
		.module('VinculacionApp')
		.controller('MainController', MainController);

	function MainController () {
		var vm = this;

		vm.expand = false;
		vm.navItems =
		[
		  { title: "HOME", ref: "dashboard.home",
		  	icon: "glyphicon glyphicon-home", active: true },

		  { title: "PROYECTOS", ref: "dashboard.projects",
		  	icon: "glyphicon glyphicon-folder-open", active: false },

		  { title: "SOLICITUDES", ref: "dashboard.requests",
		  	icon: "glyphicon glyphicon-tasks", active: false },

		  { title: "LOG OUT", ref: "landing",
		  	icon: "glyphicon glyphicon-log-out", active: false }
		];
	}
})();

(function(){
	"use strict";

	angular
		.module('VinculacionApp')
		.controller('MainController', MainController);

	MainController.$inject = [ '$rootScope', '$state' ];

	function MainController ($rootScope, $state) {
		var vm = this;

		vm.expand = false;
		vm.navItems =
		[
		  { title: "HOME", ref: "dashboard.home",
		  	icon: "glyphicon glyphicon-home",
		  	active: $state.current.url === '/home' },

		  { title: "PROYECTOS", ref: "dashboard.projects",
		  	icon: "glyphicon glyphicon-folder-open",
		  	active: $state.current.url.includes('/proyectos') },

		  { title: "SOLICITUDES", ref: "dashboard.requests",
		  	icon: "glyphicon glyphicon-tasks",
		  	active: $state.current.url === '/solicitudes' },

		  { title: "LOG OUT", ref: "landing",
		  	icon: "glyphicon glyphicon-log-out", active: false }
		];

		$rootScope.$on('$stateChangeStart', changeActiveItem);

		function changeActiveItem (event, toState) {
			vm.navItems[0].active = toState.url === '/home';
			vm.navItems[1].active = toState.url.includes('/proyectos');
			vm.navItems[2].active = toState.url === '/solicitudes';
		}

	}
})();

Fix navigation active item bug


/*Deal with the contene of the click on the navbar and display the page correctly.*/
$('.navbar a[data-ex]').on('click', function(event) {
	event.preventDefault();
	//Remove the  active class;
	document.querySelector('.navbar li.active').classList.remove('active');
	//Set the new active class.
	event.target.parentNode.classList.add('active');
	//Hide all the element //Todo: simplify.
	Array.prototype.forEach.call(document.querySelectorAll("div[data-ex]"), function(elt) {
		elt.hidden = true;
	});
	/*Select the active part.*/
	var selector = "div[data-ex='" + event.target.getAttribute('data-ex') + "']";
	document.querySelector(selector).hidden = false;
});
//Global name space.
Demo = {};

(function(NS) {
	var ajax = function(e) {
		console.log(e);
	};
	NS.ajax = ajax;
	return ajax;
})(Demo);

Demo.ajax('Papa', 'Singe');

Add the module pattern for the code exampl.


module.exports = [].concat(
  require('./biomedical-engineering'),
  require('./chemical-engineering'),
  require('./computer-science')
);

module.exports = [].concat(
  require('./accounting'),
  require('./biomedical-engineering'),
  require('./chemical-engineering'),
  require('./computer-science')
);

Add accounting courses to catalog


import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { configure } from 'redux-auth';
import Relay from 'react-relay';
import configureStore from './stores/configureStore';
import RTRouter from './components/RTRouter';

import './index.css';
import './styles/global.css';

const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store);

Relay.injectNetworkLayer(
  new Relay.DefaultNetworkLayer('http://flea.fubotech.com.tw/graphql')
);

store.dispatch(configure({
  apiUrl: 'http://flea-backend.dev/',
})).then(() => {
  const { getState } = store;
  render(
    <Provider store={store} key="provider">
      <RTRouter history={history} getState={getState} />
    </Provider>,
    document.getElementById('app')
  );
});

import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { configure } from 'redux-auth';
import Relay from 'react-relay';
import configureStore from './stores/configureStore';
import RTRouter from './components/RTRouter';

import './index.css';
import './styles/global.css';

const store = configureStore();
const history = syncHistoryWithStore(browserHistory, store);

Relay.injectNetworkLayer(
  new Relay.DefaultNetworkLayer('http://flea.fubotech.com.tw/graphql')
);

store.dispatch(configure({
  apiUrl: 'http://flea.fubotech.com.tw/',
})).then(() => {
  const { getState } = store;
  render(
    <Provider store={store} key="provider">
      <RTRouter history={history} getState={getState} />
    </Provider>,
    document.getElementById('app')
  );
});

Use real auth api instead of local one.


/* globals require:false */
require('prismjs/themes/prism-solarizedlight.css');
require('./src/scss/docs.scss');
require('envision');
require('envision/dist/envision.css');

/* globals require:false */
require('prismjs/themes/prism.css');
require('./src/scss/docs.scss');
require('envision');
require('envision/dist/envision.css');

Use prism default theme for better contrast


/* global TASK_CONFIG */
const gulp = require("gulp");
const sizereport = require("gulp-sizereport");
const projectPath = require("../lib/project-path");

function sizeReportTask() {
  return gulp
    .src([
      projectPath(TASK_CONFIG.basePaths.dest, "**/*"),
      "*!rev-manifest.json",
    ])
    .pipe(
      sizereport({
        gzip: true,
      })
    );
}

sizeReportTask.displayName = "size-report";
gulp.task(sizeReportTask);
module.exports = sizeReportTask;

/* global TASK_CONFIG */
const gulp = require("gulp");
const sizereport = require("gulp-sizereport");
const projectPath = require("../lib/project-path");

function sizeReportTask() {
  return gulp
    .src([
      projectPath(TASK_CONFIG.basePaths.dest, "**/*"),
      "!" + projectPath(TASK_CONFIG.basePaths.dest, "**/_*{,/**/*}"),
      "*!rev-manifest.json",
    ])
    .pipe(
      sizereport({
        gzip: true,
      })
    );
}

sizeReportTask.displayName = "size-report";
gulp.task(sizeReportTask);
module.exports = sizeReportTask;

Exclude underscore files and folders from size report


var baseline = {
	"aapl" : 92,
	"ibm" : 120,
	"wmt" : 68,
	"abx" : 13,
	"msft" : 35
};

var last = {};

setInterval(function(){

//	//Add imaginary ticker
//	var newTicker = Math.random().toString(36).substring(7);
//	baseline[newTicker] = Math.random();
//
//	//Remove a random ticker
//	var keys = Object.keys(baseline);
//	var random = 	Math.floor(Math.random() * keys.length) + 0;
//	delete baseline[keys[random]];

	var array = [];

	for(var i in baseline){
		baseline[i] = (baseline[i] + ((Math.random() > .5) ? .01 : -.01)).toFixed(2)*1;
		array.push([i,baseline[i]])
	}

	var string = JSON.stringify(array);
	console.log(string);
},500)

process.stdout.on('error',function(){
	process.exit(1);
});

var header = ['SYMBOL','LAST']
var baseline = {
  "aapl" : 92,
  "ibm" : 120.72,
  "wmt" : 68.93,
  "abx" : 13.36,
  "msft" : 35.26
};

var last = {};

setInterval(function(){

//  //Add imaginary ticker
//  var newTicker = Math.random().toString(36).substring(7);
//  baseline[newTicker] = Math.random();
//
//  //Remove a random ticker
//  var keys = Object.keys(baseline);
//  var random =   Math.floor(Math.random() * keys.length) + 0;
//  delete baseline[keys[random]];

  var array = [header];

  for(var i in baseline){
    //give each symbol a 30% chance of changing
    if(Math.random() >= .7){
      baseline[i] = (baseline[i] + ((Math.random() > .5) ? .01 : -.01)).toFixed(2)*1;
    }
    else {
      baseline[i] = baseline[i];
    }
    array.push([i,'$ ' + baseline[i].toFixed(2)])
  }

  var string = JSON.stringify(array);
  console.log(string);
},500)

process.stdout.on('error',function(){
  process.exit(1);
});

Add header values, currency sign


Template.createBetForm.helpers({
  photo: function(){
    return Session.get("photo");
  }
})

Template.createBetForm.events({
  "submit .create-bet" : function(event){
    event.preventDefault();

    var title = event.target.betTitle.value,
        wager = event.target.betWager.value,
        user = Meteor.user(),
        username = user.username,
        defender = event.target.defender.value,
        type = "new";

    if (Meteor.users.find({username: defender}).count() === 0){
      throw new Meteor.Error(
        alert( "Sorry the Username you are trying to challenge is not valid!" )
      );
    }

    if (defender === username) {
      throw new Meteor.Error(
        alert( "You can't bet yourself!" )
      );
    }

    Meteor.call("createBet", username, defender, title, wager);
    Meteor.call("createBetNotification", username, defender, type);
    Router.go('/bets');
  },

  "click .take-photo" : function(event){
    event.preventDefault();

    var cameraOptions = {
      width: 800,
      height: 600
    };

    MeteorCamera.getPicture(cameraOptions, function(error, data){
      Session.set("photo", data);
    });
  },

  getPhoto: function(){
    Session.get("photo");
  }
});

Template.createBetForm.helpers({
  photo: function(){
    return Session.get("photo");
  }
})

Template.createBetForm.events({
  "submit .create-bet" : function(event){
    event.preventDefault();

    var title = event.target.betTitle.value,
        wager = event.target.betWager.value,
        user = Meteor.user(),
        username = user.username,
        defender = event.target.defender.value,
        type = "new";

    if (Meteor.users.find({username: defender}).count() === 0){
      throw new Meteor.Error(
        alert( "Sorry the Username you are trying to challenge is not valid!" )
      );
    }

    if (defender === username) {
      throw new Meteor.Error(
        alert( "You can't bet yourself!" )
      );
    }

    Meteor.call("createBet", username, defender, title, wager);
    Meteor.call("createBetNotification", username, defender, type);
    Router.go('/bets');
  },

  "click .take-photo" : function(event){
    event.preventDefault();

    var cameraOptions = {
      width: 700,
      height: 500
    };

    MeteorCamera.getPicture(cameraOptions, function(error, data){
      Session.set("photo", data);
    });
  }
});

Change camera options and remove bad function call in form event.


var docxReader = require("./docx-reader");
var DocumentConverter = require("./document-to-html").DocumentConverter;
var htmlPaths = require("./html-paths");
var documentMatchers = require("./document-matchers");
var style = require("./style-reader").readStyle;

exports.Converter = Converter;
exports.read = read;
exports.convertDocumentToHtml = convertDocumentToHtml;
exports.htmlPaths = htmlPaths;
exports.standardOptions = {
    styleMap: [
        style("p.Heading1 => h1"),
        style("p.Heading2 => h2"),
        style("p.Heading3 => h3"),
        style("p.Heading4 => h4"),
        style("p.ListParagraph => ul > li:fresh")
    ]
};

function Converter(options) {
    this._options = options;
}

Converter.prototype.convertToHtml = function(inputOptions) {
    var options = this._options;
    return read(inputOptions)
        .then(function(documentResult) {
            return convertDocumentToHtml(documentResult, options);
        });
}

function read(inputOptions) {
    return docxReader.read(inputOptions);
}

function convertDocumentToHtml(documentResult, options) {
    var documentConverter = new DocumentConverter(options);
    return documentResult.flatMapThen(function(document) {
        return documentConverter.convertToHtml(document);
    });
}

var docxReader = require("./docx-reader");
var DocumentConverter = require("./document-to-html").DocumentConverter;
var htmlPaths = require("./html-paths");
var documentMatchers = require("./document-matchers");
var style = require("./style-reader").readStyle;

exports.Converter = Converter;
exports.read = read;
exports.convertDocumentToHtml = convertDocumentToHtml;
exports.htmlPaths = htmlPaths;
exports.standardOptions = {
    styleMap: [
        style("p.Heading1 => h1"),
        style("p.Heading2 => h2"),
        style("p.Heading3 => h3"),
        style("p.Heading4 => h4"),
        style("p:unordered-list(1) => ul > li:fresh"),
        style("p:ordered-list(1) => ol > li:fresh")
    ]
};

function Converter(options) {
    this._options = options;
}

Converter.prototype.convertToHtml = function(inputOptions) {
    var options = this._options;
    return read(inputOptions)
        .then(function(documentResult) {
            return convertDocumentToHtml(documentResult, options);
        });
}

function read(inputOptions) {
    return docxReader.read(inputOptions);
}

function convertDocumentToHtml(documentResult, options) {
    var documentConverter = new DocumentConverter(options);
    return documentResult.flatMapThen(function(document) {
        return documentConverter.convertToHtml(document);
    });
}

Add proper styles for single-level (un)ordered lists to standardOptions


var es = require('event-stream'),
    gutil = require('gulp-util'),
    PluginError = gutil.PluginError;

module.exports = function (ops) {
  ops = ops || {};
  var cheerio = ops.cheerio || require('cheerio');

  return es.map(function (file, done) {
    if (file.isNull()) return done(null, file);
    if (file.isStream()) return done(new PluginError('gulp-cheerio', 'Streaming not supported.'));
    var run = typeof ops === 'function' ? ops : ops.run;
    if (run) {
      var $ = cheerio.load(file.contents.toString());
      if (run.length > 1) {
        run($, next);
      } else {
        run($);
        next();
      }
    } else {
      done(null, file);
    }
    function next(err) {
      file.contents = new Buffer($.html());
      done(err, file);
    }
  });
};

var es = require('event-stream'),
    gutil = require('gulp-util'),
    PluginError = gutil.PluginError;

module.exports = function (ops) {
  ops = ops || {};
  var cheerio = ops.cheerio || require('cheerio');

  return es.map(function (file, done) {
    if (file.isNull()) return done(null, file);
    if (file.isStream()) return done(new PluginError('gulp-cheerio', 'Streaming not supported.'));
    var run = typeof ops === 'function' ? ops : ops.run;
    if (run) {
      var parserOptions = ops.parserOptions;
      var $;
      if (parserOptions) {
        $ = cheerio.load(file.contents.toString(), parserOptions);
      } else {
        $ = cheerio.load(file.contents.toString());
      }
      if (run.length > 1) {
        run($, next);
      } else {
        run($);
        next();
      }
    } else {
      done(null, file);
    }
    function next(err) {
      file.contents = new Buffer($.html());
      done(err, file);
    }
  });
};

Allow parser options as param


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

var exec = require('child_process').exec;
var colors = require('colors');

module.exports = title;

/**
 * Sets title to current CLI tab or window.
 *
 * @param {strint} input - Title to set
 * @param {Boolean} win - Add title to window instead of tab
 */
function title(input, win) {
    var cmd = [
        'printf',
        '"\\e]%d;%s\\a"',
        win ? 2 : 1,
        '"' + input + '"'
    ].join(' ');

    console.log(
        '%s Changing %s title: %s',
        colors.green('>'),
        win ? 'window' : 'tab',
        colors.cyan(input)
    );

    exec(cmd).stdout.pipe(process.stdout);
}

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

var exec = require('child_process').exec;
var colors = require('colors');

module.exports = title;

/**
 * Sets title to current CLI tab or window.
 *
 * @param {strint} input - Title to set
 * @param {Boolean} win - Add title to window instead of tab
 */
function title(input, win) {
    var cmd = [
        'printf',
        '"\\e]%d;%s\\a"',
        win ? 2 : 1,
        '"' + input + '"'
    ].join(' ');

    console.log(
        '\n%s Changing %s title: %s\n',
        colors.green('>'),
        win ? 'window' : 'tab',
        colors.cyan(input)
    );

    exec(cmd).stdout.pipe(process.stdout);
}

Add spacing to console output


var mongoose = require('mongoose');
var ShortId = require('./shortid');

var defaultSave = mongoose.Model.prototype.save;
mongoose.Model.prototype.save = function(cb) {
  for (fieldName in this.schema.tree) {
    if (this.isNew && this[fieldName] === undefined) {
      var idType = this.schema.tree[fieldName];

      if (idType === ShortId || idType.type === ShortId) {
        var idInfo = this.schema.path(fieldName);
        var retries = idInfo.retries;
        var self = this;
        function attemptSave() {
          idInfo.generator(idInfo.generatorOptions, function(err, id) {
            if (err) {
              cb(err);
              return;
            }
            self[fieldName] = id;
            defaultSave.call(self, function(err, obj) {
              if (err &&
                            err.code == 11000 &&
                  err.err.indexOf(fieldName) !== -1 &&
                            retries > 0
                 ) {
                --retries;
                attemptSave();
              } else {
                            // TODO check these args
                cb(err, obj);
              }
            });
          });
        }
        attemptSave();
        return;
      }
    }
  }
  defaultSave.call(this, cb);
};

module.exports = exports = ShortId;

var mongoose = require('mongoose');
var ShortId = require('./shortid');

var defaultSave = mongoose.Model.prototype.save;
mongoose.Model.prototype.save = function(cb) {
  for (fieldName in this.schema.tree) {
    if (this.isNew && this[fieldName] === undefined) {
      var idType = this.schema.tree[fieldName];

      if (idType === ShortId || idType.type === ShortId) {
        var idInfo = this.schema.path(fieldName);
        var retries = idInfo.retries;
        var self = this;
        function attemptSave() {
          idInfo.generator(idInfo.generatorOptions, function(err, id) {
            if (err) {
              cb(err);
              return;
            }
            self[fieldName] = id;
            defaultSave.call(self, function(err, obj) {
              if (err &&
                            err.code == 11000 &&
                  (err.err || err.errmsg || '').indexOf(fieldName) !== -1 &&
                            retries > 0
                 ) {
                --retries;
                attemptSave();
              } else {
                            // TODO check these args
                cb(err, obj);
              }
            });
          });
        }
        attemptSave();
        return;
      }
    }
  }
  defaultSave.call(this, cb);
};

module.exports = exports = ShortId;

Handle alternate form of mongoose 11000 error


'use strict';

module.exports = function () {
  const trackers = [];
  let isFinished = false;
  let onFinished = () => {};

  function tryToFinish () {
    if (all(pluck(trackers, 'executed')) && !isFinished) {
      isFinished = true;
      onFinished(pluck(trackers, 'args'));
    }
  }

  return {
    track () {
      const thisIdx = trackers.push({executed: false}) - 1;
      return function () {
        trackers[thisIdx] = {executed: true, args: argsToArray(arguments)};
        tryToFinish();
      };
    },

    finished (callback) {
      onFinished = callback;
    }
  };
};

function pluck (collection, property) {
  return collection.map(item => item[property]);
}

function all (conditions) {
  for (const condition of conditions) {
    if (!condition) return false;
  }

  return true;
}

function argsToArray (argumentsObject) {
  return Array.prototype.slice.call(argumentsObject);
}

'use strict';

module.exports = function () {
  const state = createInitialState();

  return {
    track () {
      const thisIdx = state.trackers.push({executed: false}) - 1;

      return function () {
        state.trackers[thisIdx] = {executed: true, args: argsToArray(arguments)};
        tryToFinish(state);
      };
    },

    finished (callback) {
      state.onFinished = callback;
    }
  };
};

function createInitialState () {
  return {
    trackers: [],
    isFinished: false,
    onFinished: () => {}
  };
}

function tryToFinish (state) {
  if (all(pluck(state.trackers, 'executed')) && !state.isFinished) {
    state.isFinished = true;
    state.onFinished(pluck(state.trackers, 'args'));
  }
}

function pluck (collection, property) {
  return collection.map(item => item[property]);
}

function all (conditions) {
  for (const condition of conditions) {
    if (!condition) return false;
  }

  return true;
}

function argsToArray (argumentsObject) {
  return Array.prototype.slice.call(argumentsObject);
}

Refactor to seperate state from operations


define(['moxie.conf', 'underscore', 'today/views/CardView', 'hbs!today/templates/rti'], function(conf, _, CardView, rtiTemplate) {

    var RTI_REFRESH = 60000;    // 1 minute
    var RTICard = CardView.extend({
        initialize: function() {
            this.model.on('change', this.render, this);
        },
        weight: 75,
        manage: true,
        attributes: {
            'class': 'today',
            'style': 'display:none;',
        },
        serialize: function() {
            return this.model.toJSON();
        },
        template: rtiTemplate,
        beforeRender: function() {
            this.el.style.display = 'none';
        },
        afterRender: function() {
            this.clearRefresh();
            if (this.model.get('RTI')) {
                this.refreshID = this.model.renderRTI(this.$('#poi-rti')[0], RTI_REFRESH);
                this.model.rti.on('sync', this.showEl, this);
            }
        },
        showEl: function() {
            this.el.style.display = null;
        },
        clearRefresh: function() {
            if (this.refreshID) {
                clearInterval(this.refreshID);
            }
        },
        cleanup: function() {
            this.clearRefresh();
            this.model.off();
            if ('unfollowUser' in this.model) {
                this.model.unfollowUser();
            }
        },
    });
    return RTICard;
});

define(['moxie.conf', 'underscore', 'today/views/CardView', 'hbs!today/templates/rti'], function(conf, _, CardView, rtiTemplate) {

    var RTI_REFRESH = 60000;    // 1 minute
    var RTICard = CardView.extend({
        initialize: function() {
            this.model.on('change', this.render, this);
        },
        weight: 75,
        manage: true,
        attributes: {
            'class': 'today',
            'style': 'display:none;',
        },
        serialize: function() {
            return this.model.toJSON();
        },
        template: rtiTemplate,
        beforeRender: function() {
            this.el.style.display = 'none';
        },
        afterRender: function() {
            this.clearRefresh();
            if (this.model.get('RTI').length > 0) {
                this.refreshID = this.model.renderRTI(this.$('#poi-rti')[0], RTI_REFRESH);
                this.model.rti.on('sync', this.showEl, this);
            }
        },
        showEl: function() {
            this.el.style.display = null;
        },
        clearRefresh: function() {
            if (this.refreshID) {
                clearInterval(this.refreshID);
            }
        },
        cleanup: function() {
            this.clearRefresh();
            this.model.off();
            if ('unfollowUser' in this.model) {
                this.model.unfollowUser();
            }
        },
    });
    return RTICard;
});

Test for any RTI element on the POI model


(function(_) {
  var validationErrorFor = {
        mixin:  'all mixins must be objects.',
        object: 'target must be a constuctor or an object.'
      },

      withA = function(errorType, object) {
        if( !_.isObject(object) ) throw '_.includeIn: ' + validationErrorFor[errorType];

        return object;
      },

      withAn = withA;

  function appendAll(mixins, object) {
    _.each(mixins, function(mixin) {
      _.extend(object, withA('mixin', mixin));
    });
  };

  function includeIn(target, mixin) {
    var mixins = _.isArray(mixin) ? mixin : [mixin],
        object = withAn('object', target.prototype || target);

    appendAll(mixins, object);

    return target;
  };

  _.mixin({
    includeIn: includeIn
  });
})(this._);

(function(_) {
  var validationErrors = {
        fn:     'target must be a function.',
        mixin:  'all mixins must be objects.',
        object: 'target must be a constuctor or an object.',
      },
      validationStrategies = {
        fn:     _.isFunction,
        mixin:  _.isObject,
        object: _.isObject
      }

  function validationErrorFor(errorType, scope) {
    return '_.' + scope + 'In: ' + validationErrors[errorType];
  }

  function valid(errorType, scope, target) {
    var isValid = validationStrategies[errorType];

    if( !isValid(target) ) throw validationErrorFor(errorType, scope);

    return target;
  };

  function mixinsFor(scope, mixin) {
    var mixins = _.isArray(mixin) ? mixin : [mixin];

    return _.map(mixins, function(mixin) { return valid('mixin', scope, mixin) });
  };

  function extended(target, mixins) {
    _.each(mixins, function(mixin) { _.extend(target, mixin); });

    return target;
  };

  function extendIn(target, mixins) {
    var fn = valid('fn', 'extend', target);

    return extended(fn, mixinsFor('extend', mixins));
  };

  function includeIn(target, mixins) {
    var object = valid('object', 'include', target.prototype || target);

    return extended(object, mixinsFor('include', mixins));
  };

  _.mixin({
    extendIn:  extendIn,
    includeIn: includeIn
  });
})(this._);

Add _.extendIn extension: for allow the use of mixins in functions


Groups = new Mongo.Collection('groups');

var GroupsSchema = new SimpleSchema({
  name: {
    type: String
  }
});

// Add i18n tags
GroupsSchema.i18n("groups");

Groups.attachSchema(GroupsSchema);

Groups.helpers({
  'homes': function () {
    // Get group ID
    var groupId = this._id;

    // Get all homes assigned to group
    var homes = Homes.find({'groupId': groupId}).fetch();

    return homes;
  }
});

Groups.allow({
  insert: function () {
    return true;
  }
});

Groups = new Mongo.Collection('groups');

var GroupsSchema = new SimpleSchema({
  name: {
    type: String
  }
});

// Add i18n tags
GroupsSchema.i18n("groups");

Groups.attachSchema(GroupsSchema);

Groups.helpers({
  'homes': function () {
    // Get group ID
    var groupId = this._id;

    // Get all homes assigned to group
    var homes = Homes.find({'groupId': groupId}).fetch();

    return homes;
  }
});

Groups.allow({
  insert () {
    // Get user ID
    const userId = Meteor.userId();

    // Check if user is administrator
    const userIsAdmin = Roles.userIsInRole(userId, ['admin']);

    if (userIsAdmin) {
      // admin user can insert
      return true;
    }
  },
  update () {
    // Get user ID
    const userId = Meteor.userId();

    // Check if user is administrator
    const userIsAdmin = Roles.userIsInRole(userId, ['admin']);

    if (userIsAdmin) {
      // admin user can update
      return true;
    }
  }
});

Add update allow rule; refactor insert rule