describe('The List Page', () => {
  beforeEach(() => {
    cy.visit('/list.html')
  })
  it('successfully loads', () => {})
  context('navbar', () => {
    it("the 'Home' link works", () => {
      cy.contains('Home').click({force: true})
      cy.title().should('not.equal', 'Error')
    })
    it("the 'About' link works", () => {
      cy.contains('About').click({force: true})
      cy.title().should('not.equal', 'Error')
    })
  })
  context('footer', () => {
    it("the 'MIT License' link works", () => {
      cy.contains('MIT License').click()
    })
    it("the 'CC BY-NC-SA 4.0' link works", () => {
      cy.contains('CC BY-NC-SA 4.0').click()
    })
  })
})

describe('The List Page', () => {
  beforeEach(() => {
    cy.visit('/list.html')
  })

  it('successfully loads', () => {})

  context('navbar', () => {
    it("the 'Home' link works", () => {
      cy.contains('Home').click({force: true})
      cy.title().should('not.equal', 'Error')
    })
    it("the 'About' link works", () => {
      cy.contains('About').click({force: true})
      cy.title().should('not.equal', 'Error')
    })
  })

  context('footer', () => {
    it("the 'MIT License' link works", () => {
      cy.contains('MIT License').click()
    })
    it("the 'CC BY-NC-SA 4.0' link works", () => {
      cy.contains('CC BY-NC-SA 4.0').click()
    })
  })

  context('the list', () => {

    context('location based', () => {
      it("the 'TECH404' link works", () => {
        cy.contains('TECH404').click()
      })
      it("the 'Charleston Tech' link works", () => {
        cy.contains('Charleston Tech').click()
      })
      it("the 'memtech' link works", () => {
        cy.contains('memtech').click()
      })
      it("the 'NashDev' link works", () => {
        cy.contains('NashDev').click()
      })
    })

    context('it based', () => {
      it("the 'APIs You Won't Hate' link works", () => {
        cy.contains('APIs You Won't Hate').click()
      })
    })

    context('programming based', () => {
      it("the 'Larachat' link works", () => {
        cy.contains('Larachat').click()
      })
    })
  })
})

Add tests for all current list items


function NoteApplication (author) {
	this.author = author;
	this.notes = [];

	this.create = function(note_content) {
		if (note_content.length > 0) {
      		this.notes.push(note_content);
      		return "You have created a note";
      	}
    	else {
      		return "You haven't entered a valid note";
      	}

	}

	this.listNotes = function(){
        if(this.noteArray.length < 1){
            console.log("There are no notes in the collection.");
        }
        else{for(let i = 0; i < this.notes.length; i++){
                console.log("\nNote ID: " + this.notes[i]);
                console.log(this.notes[i]);
                console.log("\n\nBy Author " + this.author + "\n");
         }

    this.get = function(note_id) {
      return "Note ID: " + this.notes[note_id];
    }

}

function NoteApplication (author) {
	this.author = author;
	this.notes = [];

	this.create = function(note_content) {
		if (note_content.length > 0) {
      		this.notes.push(note_content);
      		return "You have created a note";
      	}
    	else {
      		return "You haven't entered a valid note";
      	}

	}

	this.listNotes = function(){
        if (this.noteArray.length < 1){
            console.log("There are no notes in the collection.");
        }
        else {
        	for(let i = 0; i < this.notes.length; i++) {
                console.log("\nNote ID: " + this.notes[i]);
                console.log(this.notes[i]);
                console.log("\n\nBy Author " + this.author + "\n");
            }
        }
    }

    this.get = function(note_id) {
      return "Note ID: " + this.notes[note_id];
    }

    this.search = function(search_text){
    	if (this.notes.includes(search_text)){
        	return "Note ID: " + this.note_id + "\n" + note_content + "\n\n" + "By Author " + this.author;
    	} else {
        return "search text not found"
    }

    this.delete = function(note_id){
    	delete this.notes[note_id]
	}

	this.edit = function(note_id, new_content){
    	this.notes[note_id] = new_content;
	}

}

Add search, delete and edit methods


angular.module('app').config(function ($translateProvider) {
  $translateProvider.useStaticFilesLoader({
    prefix: '/locale/locale-',
    suffix: '.json'
  });

  $translateProvider.preferredLanguage('fr_FR');
});

angular.module('app').config(function ($translateProvider) {
  $translateProvider.useStaticFilesLoader({
    prefix: '/locale/locale-',
    suffix: '.json'
  });

  $translateProvider.useSanitizeValueStrategy('escape');

  $translateProvider.determinePreferredLanguage();
});

Determine preferred language and sanitizy stings


import { createSelector } from 'reselect';

import ActionTypes from 'constants/ActionTypes';
import ModalTypes from 'constants/ModalTypes';
import modalIsOpenSelectorFactory from 'selectors/factories/modalIsOpenSelectorFactory';
import requestIsActiveSelectorFactory from 'selectors/factories/requestIsActiveSelectorFactory';

const toDeleteSelector = (state) => state.ui.reservation.toDelete;
const resourcesSelector = (state) => state.data.resources;

const reservationDeleteModalSelector = createSelector(
  modalIsOpenSelectorFactory(ModalTypes.DELETE_RESERVATION),
  requestIsActiveSelectorFactory(ActionTypes.API.RESERVATIONS_DELETE_REQUEST),
  resourcesSelector,
  toDeleteSelector,
  (
    deleteReservationModalIsOpen,
    isDeletingReservations,
    resources,
    reservationsToDelete
  ) => {
    return {
      isDeletingReservations,
      reservationsToDelete,
      resources,
      show: deleteReservationModalIsOpen,
    };
  }
);

export default reservationDeleteModalSelector;

import { createSelector } from 'reselect';

import ActionTypes from 'constants/ActionTypes';
import ModalTypes from 'constants/ModalTypes';
import modalIsOpenSelectorFactory from 'selectors/factories/modalIsOpenSelectorFactory';
import requestIsActiveSelectorFactory from 'selectors/factories/requestIsActiveSelectorFactory';

const toDeleteSelector = (state) => state.ui.reservation.toDelete;
const resourcesSelector = (state) => state.data.resources;

const reservationDeleteModalSelector = createSelector(
  modalIsOpenSelectorFactory(ModalTypes.DELETE_RESERVATION),
  requestIsActiveSelectorFactory(ActionTypes.API.RESERVATION_DELETE_REQUEST),
  resourcesSelector,
  toDeleteSelector,
  (
    deleteReservationModalIsOpen,
    isDeletingReservations,
    resources,
    reservationsToDelete
  ) => {
    return {
      isDeletingReservations,
      reservationsToDelete,
      resources,
      show: deleteReservationModalIsOpen,
    };
  }
);

export default reservationDeleteModalSelector;

Fix typo in reservation delete modal selector


'use strict'

let imponderous = require('../../index')

class User extends imponderous.Model {
  static get schema () {
    return {
      name: {
        index: true
      },
      email: {
        unique: true,
        required: true,
        validator: 'email'
      },
      dob: {
        type: Date
      }
    }
  }
}

module.exports = User

'use strict'

let imponderous = require('../../index')

class User extends imponderous.Model {
  static get schema () {
    return {
      name: {
        index: true
      },
      email: {
        unique: true,
        required: true,
        validator: 'email'
      },
      dob: {
        type: Date
      },
      spam: {
        type: String
      }
    }
  }
}

module.exports = User

Make "spam" property on test User model a String.


#!/usr/bin/node

var convert = require('../'),
    localProj = require('local-proj'),
    fs = require('fs'),
    geojson = JSON.parse(fs.readFileSync(process.argv[2])),
    mtl = process.argv[3],
    mtllibs = process.argv.slice(4),
    options = {
        projection: localProj.find(geojson),
        mtllib: mtllibs
    };

if (mtl) {
    options.featureMaterial = function(f, cb) {
        process.nextTick(function() { cb(undefined, mtl); });
    };
}

convert.toObj(geojson, process.stdout, function(err) {
    if (err) {
        process.stderr.write(err + '\n');
    }
}, options);

#!/usr/bin/env node

var convert = require('../'),
    localProj = require('local-proj'),
    fs = require('fs'),
    geojson = JSON.parse(fs.readFileSync(process.argv[2])),
    mtl = process.argv[3],
    mtllibs = process.argv.slice(4),
    options = {
        projection: localProj.find(geojson),
        mtllib: mtllibs
    };

if (mtl) {
    options.featureMaterial = function(f, cb) {
        process.nextTick(function() { cb(undefined, mtl); });
    };
}

convert.toObj(geojson, process.stdout, function(err) {
    if (err) {
        process.stderr.write('Error: ' + JSON.stringify(err, null, 2) + '\n');
    }
}, options);

Use env to find node


var helpers = require('./../helpers.js')

module.exports = {
  'Test Sending Message' : function (browser) {
    helpers.setupTest(browser)
    helpers.enterText('textarea._1eY_aSnr3MDhZqacV8fugZ', 'test', browser)
    browser.end();
  }
};

var helpers = require('./../helpers.js')

module.exports = {
  'Test Sending Message' : function (browser) {
    helpers.setupTest(browser)
    helpers.enterText('textarea._1eY_aSnr3MDhZqacV8fugZ', 'test', browser)
    browser.end();
  },
  'Check Sent Message' : function (browser) {
    helpers.setupTest(browser)
    helpers.enterText('textarea._1eY_aSnr3MDhZqacV8fugZ', 'test', browser)
    browser
    .pause(1000)
    .assert.containsText("div.XnctIpXq756HWNS1GLwPT", "test")
    .end();
  }
};

Create test for checking sent message


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

var net = require('net');
var fs = require('fs');
var eslint = require('eslint');

var engine = new eslint.CLIEngine();
var formatter = engine.getFormatter('compact');

var server = net.createServer({
  allowHalfOpen: true
}, function (con) {
  var data = '';
  con.on('data', function (chunk) {
    data += chunk;
  });
  con.on('end', function () {
    var report = engine.executeOnFiles([data]);
    con.write(formatter(report.results));
    con.end();
  });
});

var portFile = process.env.HOME + '/.eslint_d_port';
server.listen(function () {
  var port = server.address().port;
  fs.writeFileSync(portFile, String(port));
});

process.on('exit', function () {
  fs.unlinkSync(portFile);
});
process.on('SIGINT', function () {
  process.exit();
});

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

var net = require('net');
var fs = require('fs');
var eslint = require('eslint');

var engine = new eslint.CLIEngine();
var formatter = engine.getFormatter('compact');

var server = net.createServer({
  allowHalfOpen: true
}, function (con) {
  var data = '';
  con.on('data', function (chunk) {
    data += chunk;
  });
  con.on('end', function () {
    var report = engine.executeOnFiles([data]);
    con.write(formatter(report.results));
    con.end();
  });
});

var portFile = process.env.HOME + '/.eslint_d_port';
server.listen(function () {
  var port = server.address().port;
  fs.writeFileSync(portFile, String(port));
});

process.on('exit', function () {
  fs.unlinkSync(portFile);
});
process.on('SIGTERM', function () {
  process.exit();
});
process.on('SIGINT', function () {
  process.exit();
});

Remove port file on kill


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

moduleForComponent('f-account', 'Integration | Component | f account', {
  integration: true
});

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

  this.render(hbs`{{f-account}}`);

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

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

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

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

moduleForComponent('f-account', 'Integration | Component | f account', {
  integration: true
});

const accountStub = Ember.Object.create({
  name: 'name',
  email: 'email',
  phone: 'phone'
});

test('it can be submitted', function(assert) {
  this.on('submit', () => { assert.ok(true); })

  this.render(hbs`{{f-account submit=(action 'submit')}}`);
  this.$('button').click()
});

test('it bound to account model', function(assert) {
  this.set('account', accountStub);
  this.on('submit', () => {});

  this.render(hbs`{{f-account account=account submit=(action 'submit')}}`);
  this.$('input').val('new name');
  this.$('input').change();
  this.$('button').click();

  assert.ok(this.get('account.name') == 'new name');
});

Add test suite for f-account component


import View from '../base/view';
import Feed from '../models/feed';
import {capfirst} from '../utils';

import FeedView from './feed-view';

import itemTemplate from '../../templates/feed-place.ejs';

export default class PlacesView extends View {
  constructor({app, model}) {
    super({app, model});

    this.feed = new Feed(
      '/places/', {
        categories: 'theatre',
        fields: 'images,title,id,address',
        expand: 'images',
        order_by: '-favorites_count',
        page_size: 24,
      });

    this.feedView = new FeedView({app, itemTemplate, model: this.feed});
  }

  render() {
    this.element.innerHTML = '';
    this.feedView.render();
    this.element.appendChild(this.feedView.element);
    this.update();
  }

  unbind() {
    this.feedView.unbind();
    super.unbind();
  }

  onModelChange() {
    this.update();
  }

  update() {
    this.updateFeedQuery();
    this.updateAppState();
  }

  updateFeedQuery() {
    this.feed.query.set('location', this.model.get('location'));
  }

  updateAppState() {
    const location = this.app.locations.get(this.model.get('location'));
    this.app.setTitle(`Театры – ${location.name}`);
    this.app.settings.set('location', location.slug);
  }
}

import View from '../base/view';
import Feed from '../models/feed';
import {capfirst} from '../utils';

import FeedView from './feed-view';

import itemTemplate from '../../templates/feed-place.ejs';

export default class PlacesView extends View {
  constructor({app, model}) {
    super({app, model});

    this.feed = new Feed(
      '/places/', {
        fields: 'images,title,id,address',
        categories: 'theatre,-cafe',
        expand: 'images',
        order_by: '-favorites_count',
        page_size: 24,
      });

    this.feedView = new FeedView({app, itemTemplate, model: this.feed});
  }

  render() {
    this.element.innerHTML = '';
    this.feedView.render();
    this.element.appendChild(this.feedView.element);
    this.update();
  }

  unbind() {
    this.feedView.unbind();
    super.unbind();
  }

  onModelChange() {
    this.update();
  }

  update() {
    this.updateFeedQuery();
    this.updateAppState();
  }

  updateFeedQuery() {
    this.feed.query.set('location', this.model.get('location'));
  }

  updateAppState() {
    const location = this.app.locations.get(this.model.get('location'));
    this.app.setTitle(`Театры – ${location.name}`);
    this.app.settings.set('location', location.slug);
  }
}

Exclude cafes from place list


const _ = require('lodash');
const TYPES_OF_ASSESSMENT_NEEDING_USER = ['PLACEMENT', 'CERTIFICATION'];
const { ObjectValidationError } = require('../errors');

class Assessment {

  constructor(attributes) {
    Object.assign(this, attributes);
  }

  isCompleted() {
    return this.state === 'completed';
  }

  getLastAssessmentResult() {
    if(this.assessmentResults) {
      return _(this.assessmentResults).sortBy(['createdAt']).last();
    }
    return null;
  }

  getPixScore() {
    if(this.getLastAssessmentResult()) {
      return this.getLastAssessmentResult().pixScore;
    }
    return null;
  }

  getLevel() {
    if(this.getLastAssessmentResult()) {
      return this.getLastAssessmentResult().level;
    }
    return null;
  }

  setCompleted() {
    this.state = 'completed';
  }

  validate() {
    if(TYPES_OF_ASSESSMENT_NEEDING_USER.includes(this.type) && typeof this.userId !== 'number') {
      return Promise.reject(new ObjectValidationError(`Assessment ${this.type} needs an User Id`));
    }
    return Promise.resolve();
  }
}

module.exports = Assessment;

const _ = require('lodash');
const TYPES_OF_ASSESSMENT_NEEDING_USER = ['PLACEMENT', 'CERTIFICATION'];
const { ObjectValidationError } = require('../errors');

class Assessment {

  constructor(attributes) {
    Object.assign(this, attributes);
  }

  isCompleted() {
    return this.state === 'completed';
  }

  getLastAssessmentResult() {
    if(this.assessmentResults) {
      return _(this.assessmentResults).sortBy(['createdAt']).last();
    }
    return null;
  }

  getPixScore() {
    if(this.getLastAssessmentResult()) {
      return this.getLastAssessmentResult().pixScore;
    }
    return null;
  }

  getLevel() {
    if(this.getLastAssessmentResult()) {
      return this.getLastAssessmentResult().level;
    }
    return null;
  }

  setCompleted() {
    this.state = 'completed';
  }

  validate() {
    console.log('Type of userId');
    console.log(typeof this.userId);
    console.log(this.userId);
    if(TYPES_OF_ASSESSMENT_NEEDING_USER.includes(this.type) && typeof this.userId !== 'number') {
      return Promise.reject(new ObjectValidationError(`Assessment ${this.type} needs an User Id`));
    }
    return Promise.resolve();
  }
}

module.exports = Assessment;

Add console.log to logs in RA with staging db


/*global Ghost */
(function () {
    'use strict';

    Ghost.Collections.Tags = Ghost.ProgressCollection.extend({
        url: Ghost.paths.apiRoot + '/tags/'
    });
}());

/*global Ghost */
(function () {
    'use strict';

    Ghost.Collections.Tags = Ghost.ProgressCollection.extend({
        url: Ghost.paths.apiRoot + '/tags/',

        parse: function (resp) {
            return resp.tags;
        }
    });
}());

Tag API: Primary Document Format


'use strict';

module.exports = function(app) {
  var users = require('../../app/controllers/users.server.controller');
  var courses = require('../../app/controllers/courses.server.controller');
  var authorized = ['manager', 'admin'];

  // Courses Routes
  app.route('/courses')
    .get(users.requiresLogin, users.hasAuthorization(authorized), courses.list)
    .post(users.requiresLogin, users.hasAuthorization(authorized), courses.create);

  app.route('/list/myCourses')
    .get(users.requiresLogin, users.hasAuthorization(['teacher']), courses.listMyCourses);

  app.route('/courses/:courseId')
    .post(users.requiresLogin, users.hasAuthorization(authorized), courses.create)
    .get(users.requiresLogin, users.hasAuthorization(authorized), courses.read)
    .put(users.requiresLogin, users.hasAuthorization(authorized), courses.update)
    .delete(users.requiresLogin, users.hasAuthorization(authorized), courses.delete);

  // Finish by binding the Course middleware
  app.param('courseId', courses.courseByID);
};

'use strict';

module.exports = function(app) {
  var users = require('../../app/controllers/users.server.controller');
  var courses = require('../../app/controllers/courses.server.controller');
  var authorized = ['manager', 'admin'];

  // Courses Routes
  app.route('/courses')
    .get(users.requiresLogin, users.hasAuthorization(authorized), courses.list)
    .post(users.requiresLogin, users.hasAuthorization(authorized), courses.create);

  app.route('/list/myCourses')
    .get(users.requiresLogin, users.hasAuthorization(['teacher']), courses.listMyCourses);

  app.route('/courses/:courseId')
    .post(users.requiresLogin, users.hasAuthorization(authorized), courses.create)
    .get(users.requiresLogin, users.hasAuthorization(['manager', 'admin', 'teacher']), courses.read)
    .put(users.requiresLogin, users.hasAuthorization(authorized), courses.update)
    .delete(users.requiresLogin, users.hasAuthorization(authorized), courses.delete);

  // Finish by binding the Course middleware
  app.param('courseId', courses.courseByID);
};

Update permission to show course detail for teachers


import React from 'react';
import GithubAPI from '../../api/githubAPI';

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

    this.state = {
      showResult: "l"
    };

    this.invokeGitHubAPI = this.invokeGitHubAPI.bind(this);

  }

  invokeGitHubAPI(){
    GithubAPI.testOctokat().then(testResult => {
      let parsedTestResult = testResult.updatedAt.toDateString();
      console.log(testResult);
      console.log(parsedTestResult);
      this.setState({showResult: parsedTestResult});
    }).catch(error => {
      throw(error);
    });
  }

  render() {
      return (

GitHub Status API GUI

Open browser console to see JSON data returned from GitHub API

              <button type="button" className="btn btn-primary" onClick={this.invokeGitHubAPI}>Test GitHub API Call</button>

              {this.state.showResult}

      );
  }
}

export default HomePage;

import React from 'react';
import GithubAPI from '../../api/githubAPI';

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

    this.state = {
      showResult: "l"
    };

    this.invokeGitHubAPI = this.invokeGitHubAPI.bind(this);

  }

  invokeGitHubAPI(){
    GithubAPI.testOctokat().then(testResult => {
      let parsedTestResult = testResult.pushedAt.toTimeString();
      console.log(testResult);
      console.log(parsedTestResult);
      this.setState({showResult: parsedTestResult});
    }).catch(error => {
      throw(error);
    });
  }

  render() {
      return (

GitHub Status API GUI

Open browser console to see JSON data returned from GitHub API

              <button type="button" className="btn btn-primary" onClick={this.invokeGitHubAPI}>Test GitHub API Call</button>

              {this.state.showResult}

      );
  }
}

export default HomePage;

Change display from updatedAt.toDateString to pushedAt.toTimeString


#!/usr/bin/env node

var argv = require('minimist')(process.argv.slice(2));
var injection = require('./services/injection');
var _ = require('lodash');

var port = argv.port || process.env.PORT || 9080;
var gitUrl = argv.git || process.env.GIT_URL;
var dbUrl = argv.db || process.env.DB_URL;

if (!gitUrl || !dbUrl) {
    console.log('Usage: allcountjs --git <Application Git URL> --db <Application MongoDB URL> -port [Application HTTP port]');
    process.exit(0);
}

require('./allcount-server.js');
injection.bindFactory('port', port);
injection.bindFactory('dbUrl', dbUrl);
if (dbUrl.indexOf('postgres') !== -1) {
    injection.bindFactory('storageDriver', require('./services/sql-storage-driver'));
    injection.bindFactory('dbClient', 'pg');
}
injection.bindFactory('gitRepoUrl', gitUrl);

injection.installModulesFromPackageJson("package.json");

var server = injection.inject('allcountServerStartup');
server.startup(function (errors) {
    if (errors) {
        if (_.isObject(errors)) {
            throw new Error(errors);
        } else {
            throw new Error(errors.join('\n'));
        }
    }
});

#!/usr/bin/env node

var argv = require('minimist')(process.argv.slice(2));
var injection = require('./services/injection');
var _ = require('lodash');

var port = argv.port || process.env.PORT || 9080;
var gitUrl = argv.git || process.env.GIT_URL;
var dbUrl = argv.db || process.env.DB_URL;

if (!gitUrl || !dbUrl) {
    console.log('Usage: allcountjs --git <Application Git URL> --db <Application MongoDB URL> -port [Application HTTP port]');
    process.exit(0);
}

require('./allcount-server.js');
injection.bindFactory('port', port);
injection.bindFactory('dbUrl', dbUrl);
if (dbUrl.indexOf('postgres') !== -1) {
    injection.bindFactory('storageDriver', require('./services/sql-storage-driver'));
    injection.bindFactory('dbClient', 'pg');
}
injection.bindFactory('gitRepoUrl', gitUrl);

injection.installModulesFromPackageJson("package.json");

var server = injection.inject('allcountServerStartup');
server.startup(function (errors) {
    if (errors) {
        if (_.isArray(errors)) {
            throw new Error(errors.join('\n'));
        } else {
            throw errors;
        }
    }
});

Fix exception when one error is thrown during startup


module.exports = {
  assetPath: '{{govukAssetPath}}',
  afterHeader: '{{$afterHeader}}{{/afterHeader}}',
  bodyClasses: '{{$bodyClasses}}{{/bodyClasses}}',
  bodyEnd: '{{$bodyEnd}}{{/bodyEnd}}',
  content: '{{$main}}{{/main}}',
  cookieMessage: '{{$cookieMessage}}{{/cookieMessage}}',
  footerSupportLinks: '{{$footerSupportLinks}}{{/footerSupportLinks}}',
  footerTop: '{{$footerTop}}{{/footerTop}}',
  head: '{{$head}}{{/head}}',
  headerClass: '{{$headerClass}}{{/headerClass}}',
  insideHeader: '{{$insideHeader}}{{/insideHeader}}',
  pageTitle: '{{$pageTitle}}{{/pageTitle}}',
  propositionHeader: '{{$propositionHeader}}{{/propositionHeader}}',
  skipLinkMessage: '{{$skipLinkMessage}}Skip to main content{{/skipLinkMessage}}',
  globalHeaderText: '{{$globalHeaderText}}GOV.UK{{/globalHeaderText}}',
  licenceMessage: '{{$licenceMessage}}
All content is available under the Open Government Licence v3.0, except where otherwise stated
{{/licenceMessage}}',
  crownCopyrightMessage: '{{$crownCopyrightMessage}}© Crown copyright{{/crownCopyrightMessage}}'
};

module.exports = {
  htmlLang: '{{htmlLang}}',
  assetPath: '{{govukAssetPath}}',
  afterHeader: '{{$afterHeader}}{{/afterHeader}}',
  bodyClasses: '{{$bodyClasses}}{{/bodyClasses}}',
  bodyEnd: '{{$bodyEnd}}{{/bodyEnd}}',
  content: '{{$main}}{{/main}}',
  cookieMessage: '{{$cookieMessage}}{{/cookieMessage}}',
  footerSupportLinks: '{{$footerSupportLinks}}{{/footerSupportLinks}}',
  footerTop: '{{$footerTop}}{{/footerTop}}',
  head: '{{$head}}{{/head}}',
  headerClass: '{{$headerClass}}{{/headerClass}}',
  insideHeader: '{{$insideHeader}}{{/insideHeader}}',
  pageTitle: '{{$pageTitle}}{{/pageTitle}}',
  propositionHeader: '{{$propositionHeader}}{{/propositionHeader}}',
  skipLinkMessage: '{{$skipLinkMessage}}Skip to main content{{/skipLinkMessage}}',
  globalHeaderText: '{{$globalHeaderText}}GOV.UK{{/globalHeaderText}}',
  licenceMessage: '{{$licenceMessage}}
All content is available under the Open Government Licence v3.0, except where otherwise stated
{{/licenceMessage}}',
  crownCopyrightMessage: '{{$crownCopyrightMessage}}© Crown copyright{{/crownCopyrightMessage}}'
};

Add local for htmlLang into template


let http = require('http');
let url = require('url');
let fs = require('fs');

http.createServer(function (req, res) {
    let q = url.parse(req.url, true);
    let filename = "app/." + q.pathname;
    fs.readFile(filename, function (err, data) {
        if (err) {
            res.writeHead(404, {'Content-Type': 'text/html'});
            return res.end("404 Not Found");
        }

        let mimeType = 'text/html';
        if (filename.endsWith(".css")) {
            mimeType = "text/css";
        } else if (filename.endsWith(".js")) {
            mimeType = "application/javascript";
        }

        res.writeHead(200, {'Content-Type': mimeType});
        res.write(data);
        return res.end();
    });
}).listen(8080);

console.log("Web server is running...");
console.log("URL to access the app: http://localhost:8080/app.html");

Add informational message when running server


module.exports = {
  rules: {
    // enforce spacing inside array brackets
    'array-bracket-spacing': [ 'error', 'always', {
      objectsInArrays: false,
      arraysInArrays: false,
    }],
    // specify the maximum length of a line in your program
    // https://github.com/eslint/eslint/blob/master/docs/rules/max-len.md
    'max-len': [ 'error', 80, 2, {
      ignoreUrls: true,
      ignoreComments: false,
    }],
    // allow/disallow an empty newline after var statement
    // https://github.com/eslint/eslint/blob/master/docs/rules/newline-after-var.md
    'newline-after-var': [ 'error', 'always' ],
    // Require newline before return statement
    // https://github.com/eslint/eslint/tree/master/docs/rules/newline-before-return.md
    'newline-before-return': 'error',
    // disallow multiple empty lines and only one newline at the end
    // https://github.com/eslint/eslint/blob/master/docs/rules/no-multiple-empty-lines.md
    'no-multiple-empty-lines': [ 'error', { max: 2, maxBOF: 0, maxEOF: 1 }],
    // require or disallow use of semicolons instead of ASI
    // I use Babel, it takes care of this for me
    semi: [ 'error', 'never' ],
    // require or disallow spaces inside parentheses
    // disabled, because this rule is strange
    // TODO: write and implement a better rule
    'space-in-parens': 'off',
  },
}

module.exports = {
  rules: {
    // enforce spacing inside array brackets
    'array-bracket-spacing': [ 'error', 'always', {
      objectsInArrays: false,
      arraysInArrays: false,
    }],
    // allow/disallow an empty newline after var statement
    // https://github.com/eslint/eslint/blob/master/docs/rules/newline-after-var.md
    'newline-after-var': [ 'error', 'always' ],
    // Require newline before return statement
    // https://github.com/eslint/eslint/tree/master/docs/rules/newline-before-return.md
    'newline-before-return': 'error',
    // disallow multiple empty lines and only one newline at the end
    // https://github.com/eslint/eslint/blob/master/docs/rules/no-multiple-empty-lines.md
    'no-multiple-empty-lines': [ 'error', { max: 2, maxBOF: 0, maxEOF: 1 }],
    // require or disallow use of semicolons instead of ASI
    // I use Babel, it takes care of this for me
    semi: [ 'error', 'never' ],
    // require or disallow spaces inside parentheses
    // disabled, because this rule is strange
    // TODO: write and implement a better rule
    'space-in-parens': 'off',
  },
}

Remove `max-len` rule and fall back to AirBnb's (100 char)


var expect = require('chai').expect;
var storage = require('../storage');
var utility = require('../utility');

describe('text.localize', function() {
    var text = new storage.Localizable({
        key1: 'value',
        key2: 'value [_1]',
    });
    it('should try to return a string from the localised texts', function() {
        expect(text.localize('key')).to.equal('key');
        expect(text.localize('key1')).to.equal('value');
    });

    it('should only template when required', function() {
        expect(text.localize('key2')).to.equal('value [_1]');
        // inject global template function
        global.template = utility.template;
        expect(text.localize('key2', [1])).to.equal('value 1');
        delete global.template;
    });
});

var expect = require('chai').expect;
var storage = require('../storage');
var utility = require('../utility');

describe('text.localize', function() {
    var text = new storage.Localizable({
        key1: 'value',
        key2: 'value [_1]',
        'You_can_view_your_[_1]trading_limits_here_': 'Ihre [_1] Handelslimits sind hier ersichtlich.',
    });
    it('should try to return a string from the localised texts', function() {
        expect(text.localize('key')).to.equal('key');
        expect(text.localize('key1')).to.equal('value');
        expect(text.localize('You can view your [_1]trading limits here.'))
            .to.equal('Ihre [_1] Handelslimits sind hier ersichtlich.');
    });

    it('should only template when required', function() {
        expect(text.localize('key2')).to.equal('value [_1]');
        // inject global template function
        global.template = utility.template;
        expect(text.localize('key2', [1])).to.equal('value 1');
        expect(text.localize('You can view your [_1]trading limits here.', ['something']))
            .to.equal('Ihre something Handelslimits sind hier ersichtlich.');
        delete global.template;
    });
});

Test with data from real localisation


'use strict'

const resources = require('../resources')
const querySyntax = require('./querySyntax')

module.exports = (propToBind, dispatch) => {
  resources.forEach(resource => {
    propToBind[resource.name] = query =>
      new Promise((resolve, reject) => {
        let config = { params: querySyntax(query) }

        if (query.noCache) {
          config.headers = { 'Cache-Control': 'no-cache' }
        }

        let resourceURL = resource.name
        if (query.id) {
          resourceURL += '/' + query.id
        }

        dispatch.get(resourceURL, config)
          .then(res => resolve(res.data))
          .catch(reject)
      })
  })
}

'use strict'

const resources = require('../resources')
const querySyntax = require('./querySyntax')

module.exports = (propToBind, dispatch) => {
  resources.forEach(resource => {
    propToBind[resource.name] = query => {
      let resourceURL = resource.name

      let config = null

      if (query) {
        config = { params: querySyntax(query) }

        if (query.noCache) {
          config.headers = { 'Cache-Control': 'no-cache' }
        }

        if (Number.isInteger(query) || query.id) {
          resourceURL += '/' + (query || query.id)
        }
      }

      return new Promise((resolve, reject) => {
        dispatch.get(resourceURL, config)
          .then(res => resolve(res.data))
          .catch(reject)
      })
    }
  })
}

Adjust bindResource and allow integer query


var gulp = require('gulp')
var connect = require('gulp-connect')
var browserify = require('browserify')
var source = require('vinyl-source-stream');
var gulp = require('gulp');
var mocha = require('gulp-mocha');

gulp.task('connect', function () {
    connect.server({
        root: '',
        port: 4000
    })
})

gulp.task('build', function() {
    // Grabs the app.js file
    return browserify('./js/app.js')
        // bundles it and creates a file called build.js
        .bundle()
        .pipe(source('build.js'))
        // saves it the js/ directory
        .pipe(gulp.dest('./js/'));
})

gulp.task('watch', function() {
    gulp.watch(['./**/*', '!./js/build.js'], { interval: 500 }, ['build'])
})

gulp.task('test', function() {
    connect.server({
        root: '',
        port: 4000
    });
    return gulp.src('./test/test.js').pipe(mocha())
    .once('end', function () {
      process.exit();
    });
})

gulp.task('default', ['connect', 'watch'])

var gulp = require('gulp')
var connect = require('gulp-connect')
var browserify = require('browserify')
var source = require('vinyl-source-stream');
var gulp = require('gulp');
var mocha = require('gulp-mocha');

gulp.task('connect', function () {
    connect.server({
        root: '',
        port: 4000
    })
})

gulp.task('build', function() {
    // Grabs the app.js file
    return browserify('./js/app.js')
        // bundles it and creates a file called build.js
        .bundle()
        .pipe(source('build.js'))
        // saves it the js/ directory
        .pipe(gulp.dest('./js/'));
})

gulp.task('watch', function() {
    gulp.watch(['./**/*', '!./js/build.js'], { interval: 500 }, ['build'])
})

gulp.task('test', function() {
    //Creates local server, runs mocha tests, then exits process (therefore killing server)
    connect.server({
        root: '',
        port: 4000
    });
    return gulp.src('./test/test.js').pipe(mocha())
    .once('end', function () {
      process.exit();
    });
})

gulp.task('default', ['connect', 'watch'])

Add comment explaining what test task is


var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var imagemin = require('gulp-imagemin');
var open = require('gulp-open');

gulp.task('scripts', function () {
  return browserify('./js/main.js')
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./build/js'));
});

gulp.task('css', function () {
  return gulp.src('./css/**/*.css')
    .pipe(gulp.dest('./build/css'));
});

gulp.task('html', function () {
  return gulp.src('./*.html')
    .pipe(gulp.dest('./build'));
});

gulp.task('images', function () {
  return gulp.src('./public/images/**/*')
    .pipe(imagemin())
    .pipe(gulp.dest('./build/public/images'))
});

gulp.task('watch', function () {
  gulp.watch('./js/**/*.js', ['scripts']);
  gulp.watch('./css/**/*.css', ['css']);
  gulp.watch('./*.html', ['html']);
});

gulp.task('open', function () {
  var options = {
    app: 'google chrome'
  };

  return gulp.src("./build/index.html").pipe(open('./build/index.html', options));
});

gulp.task('default', ['scripts', 'css', 'html', 'images', 'watch', 'open']);

var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var imagemin = require('gulp-imagemin');
var open = require('gulp-open');

gulp.task('scripts', function () {
  return browserify('./js/main.js')
    .bundle()
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./build/js'));
});

gulp.task('css', function () {
  return gulp.src('./css/**/*.css')
    .pipe(gulp.dest('./build/css'));
});

gulp.task('html', function () {
  return gulp.src('./*.html')
    .pipe(gulp.dest('./build'));
});

gulp.task('images', function () {
  return gulp.src('./public/images/**/*')
    .pipe(imagemin())
    .pipe(gulp.dest('./build/public/images'))
});

gulp.task('watch', function () {
  gulp.watch('./js/**/*.js', ['scripts']);
  gulp.watch('./css/**/*.css', ['css']);
  gulp.watch('./*.html', ['html']);
});

gulp.task('open', function () {
  var options = {
    app: 'google chrome'
  };

  return gulp.src('./build/index.html')
    .pipe(open('./build/index.html', options));
});

gulp.task('default', ['scripts', 'css', 'html', 'images', 'watch', 'open']);

Break chaining into different lines and change string to single quotes


var gulp        = require('gulp');
var concat      = require('gulp-concat');
var sass        = require('gulp-sass');
var minifyCSS   = require('gulp-minify-css');
var uglify      = require('gulp-uglify');

var paths = {
  js:     ['js/**.js'],
  sass: {
    src: ['css/sass/**.scss'],
    build: 'css/'
  }
};

gulp.task('js', function() {
  gulp.src(paths.js)
    .pipe(uglify())
    .pipe(concat('firstpr.js'))
    .pipe(gulp.dest('.'));
});

gulp.task('sass', function () {
  gulp.src( paths.sass.src )
    .pipe(sass())
    .pipe(gulp.dest( paths.sass.build ))
    .pipe(minifyCSS({keepSpecialComments: 0}))
    .pipe(concat('firstpr.css'))
    .pipe(gulp.dest('.'));
});

// Continuous build
gulp.task('watch', function() {
  gulp.watch( paths.js,       ['js']);
  gulp.watch( paths.sass.src, ['sass']);
});

gulp.task('default', ['js', 'sass', 'watch']);

var gulp        = require('gulp');
var concat      = require('gulp-concat');
var sass        = require('gulp-sass');
var minifyCSS   = require('gulp-minify-css');
var uglify      = require('gulp-uglify');

var paths = {
  js:     ['js/**.js'],
  sass: {
    src: ['css/sass/**.scss'],
    build: 'css/'
  }
};

gulp.task('js', function() {
  gulp.src(paths.js)
    .pipe(uglify())
    .pipe(concat('firstpr.js'))
    .pipe(gulp.dest('.'));
});

gulp.task('sass', function () {
  gulp.src( paths.sass.src )
    .pipe(sass())
    .pipe(gulp.dest( paths.sass.build ))
    .pipe(minifyCSS({keepSpecialComments: 0}))
    .pipe(concat('firstpr.css'))
    .pipe(gulp.dest('.'));
});

// Continuous build
gulp.task('watch', function() {
  gulp.watch( paths.js,       ['js']);
  gulp.watch( paths.sass.src, ['sass']);
});

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

Build gulp task for generating css and js


import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';

import { Link } from 'react-router';

import { createPureComponent } from 'utils/createPureComponent';

export default createPureComponent({

  displayName: 'App',

  renderLink() {
    const isEditing = this.props.location.pathname === '/editor';
    const to = isEditing ? '/' : '/editor';
    const text = isEditing ? 'Play this level' : 'Edit this level';
    return (<Link to={to}>{text}</Link>);
  },

  render() {
    return (
      <div className="app">
        {this.props.children}
      </div>
    );
  }

});

import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';

import { Link } from 'react-router';

import { createPureComponent } from 'utils/createPureComponent';

export default createPureComponent({

  displayName: 'App',

  renderLink() {
    const isEditing = this.props.location.pathname === '/editor';
    const to = isEditing ? '/' : '/editor';
    const text = isEditing ? 'Play this level' : 'Edit this level';
    return (<Link to={to}>{text}</Link>);
  },

  render() {
    return (

        {this.props.children}

            {this.renderLink()}

    );
  }

});

Revert "Revert "Revert "Revert "Revert "Revert "Revert "kein editor"""""""


import { fetchAnswers, fetchQuestion, fetchComparedAnswers, postAnswer, postSkipQuestion } from '../utils/APIUtils';

export function getAnswers(url = `answers`){
    return fetchAnswers(url);
}

export function getComparedAnswers(otherUserId, filters, url = `answers/compare-new/${otherUserId}?locale=es${filters.map(filter => '&'+filter+'=1')}`){
    return fetchComparedAnswers(url);
}

export function getQuestion(questionId, url = `questions/${questionId}?locale=es`){
    return fetchQuestion(url);
}

export function getNextQuestion(url = `questions/next?locale=es`){
    return fetchQuestion(url);
}

export function answerQuestion(questionId, answerId, acceptedAnswers, rating, url = `answers`){
    return postAnswer(url, questionId, answerId, acceptedAnswers, rating);
}

export function skipQuestion(questionId, url = `questions/${questionId}/skip`){
    return postSkipQuestion(url);
}

import { fetchAnswers, fetchQuestion, fetchComparedAnswers, postAnswer, postSkipQuestion } from '../utils/APIUtils';

export function getAnswers(url = `answers`){
    return fetchAnswers(url);
}

export function getComparedAnswers(otherUserId, filters, url = `answers/compare/${otherUserId}?locale=es${filters.map(filter => '&'+filter+'=1')}`){
    return fetchComparedAnswers(url);
}

export function getQuestion(questionId, url = `questions/${questionId}?locale=es`){
    return fetchQuestion(url);
}

export function getNextQuestion(url = `questions/next?locale=es`){
    return fetchQuestion(url);
}

export function answerQuestion(questionId, answerId, acceptedAnswers, rating, url = `answers`){
    return postAnswer(url, questionId, answerId, acceptedAnswers, rating);
}

export function skipQuestion(questionId, url = `questions/${questionId}/skip`){
    return postSkipQuestion(url);
}

Rename compare route to use definitive one as 'compare'


import React from 'react'
import PropTypes from 'prop-types'
import Component from 'hyper/component'
import decorate from 'hyper/decorate'

class HyperLine extends Component {
  static propTypes() {
    return {
      plugins: PropTypes.array.isRequired
    }
  }

  styles() {
    return {
      line: {
        display: 'flex',
        alignItems: 'center',
        position: 'absolute',
        overflow: 'hidden',
        bottom: '-1px',
        width: '100%',
        height: '18px',
        font: 'bold 10px Monospace',
        pointerEvents: 'none',
        background: 'rgba(0, 0, 0, 0.08)',
        margin: '10px 10px 0 10px'
      },
      wrapper: {
        display: 'flex',
        flexShrink: '0',
        alignItems: 'center',
        paddingLeft: '10px',
        paddingRight: '10px'
      }
    }
  }
  template(css) {
    const { plugins } = this.props

    return (

        {plugins.map((Component, index) => (

            <Component />

        ))}

    )
  }
}

export default decorate(HyperLine, 'HyperLine')

import React from 'react'
import PropTypes from 'prop-types'
import Component from 'hyper/component'
import decorate from 'hyper/decorate'

class HyperLine extends Component {
  static propTypes() {
    return {
      plugins: PropTypes.array.isRequired
    }
  }

  styles() {
    return {
      line: {
        display: 'flex',
        alignItems: 'center',
        position: 'absolute',
        overflow: 'hidden',
        bottom: '-1px',
        width: '100%',
        height: '18px',
        font: 'bold 10px Monospace',
        pointerEvents: 'none',
        background: 'rgba(0, 0, 0, 0.08)',
        margin: '10px 0',
        padding: '0 10px',
      },
      wrapper: {
        display: 'flex',
        flexShrink: '0',
        alignItems: 'center',
        paddingLeft: '10px',
        paddingRight: '10px'
      }
    }
  }
  template(css) {
    const { plugins } = this.props

    return (

        {plugins.map((Component, index) => (

            <Component />

        ))}

    )
  }
}

export default decorate(HyperLine, 'HyperLine')

Use padding instead of margin for x-axis.


document.addEventListener('DOMContentLoaded', () => {
  setTimeout(window.browserCheck, 1000);

  window.setupGol();
  window.navUpdate(true);
  window.setupLaptops();

  window.onscroll();

  // Configure scrolling when navigation trigger clicked
  document.getElementById('navigation-trigger').addEventListener('click', () => {
    window.scrollTo({ top: window.innerHeight / 2, behavior: 'smooth' });
  });
});

document.addEventListener('DOMContentLoaded', () => {
  setTimeout(window.browserCheck, 1000);

  window.setupGol();
  window.navUpdate(true);
  window.setupLaptops();

  window.onscroll();

  // Configure scrolling when navigation trigger clicked
  document.getElementById('navigation-trigger').addEventListener('click', () => {
    // Scroll to top if user is scrolled more than 10 pixels, otherwise scroll to open navigation
    const scrollDest = window.scrollY < 10 ? window.innerHeight / 2 : 0;
    window.scrollTo({ top: scrollDest, behavior: 'smooth' });
  });
});

Allow going back up by clicking navigation toggle again


/**
 * The LandRights layer module.
 *
 * @return LandRightsLayer class (extends CartoDBLayerClass)
 */
define([
  'abstract/layer/CartoDBLayerClass',
  'text!map/cartocss/land_rights.cartocss'
], function(CartoDBLayerClass, LandRightsCartocss) {

  'use strict';

  var LandRightsLayer = CartoDBLayerClass.extend({

    options: {
      sql: "with gfw_land_rights_1 as (SELECT cartodb_id, the_geom_webmercator, name, category, doc_status , country, data_src FROM gfw_land_rights_1 union SELECT (cartodb_id+100000) as cartodb_id, the_geom_webmercator, name, category, doc_status, country, data_src FROM gfw_land_rights_pt) select cartodb_id, the_geom_webmercator, name, category, doc_status, country, data_src source, 'gfw_land_rights_1' AS tablename, 'gfw_land_rights_1' as layer, {analysis} AS analysis from gfw_land_rights_1",
      infowindow: true,
      interactivity: 'cartodb_id, tablename, layer, name, country, source, doc_status, analysis',
      analysis: true,
      cartocss: LandRightsCartocss
    }

  });

  return LandRightsLayer;

});

/**
 * The LandRights layer module.
 *
 * @return LandRightsLayer class (extends CartoDBLayerClass)
 */
define([
  'abstract/layer/CartoDBLayerClass'
], function(CartoDBLayerClass) {

  'use strict';

  var LandRightsLayer = CartoDBLayerClass.extend({

    options: {
      sql: 'SELECT the_geom_webmercator, cartodb_id, name, country, legal_term, legal_reco as legal_recognition, ROUND(area_ha::text::float) AS area_ha, \'{tableName}\' AS tablename,  \'{tableName}\' as layer, {analysis} AS analysis FROM {tableName}',
      infowindow: true,
      interactivity: 'cartodb_id, tablename, layer, name, country, legal_term, legal_recognition, area_ha, analysis',
      analysis: true
    }

  });

  return LandRightsLayer;

});

Revert it to the last state before the update


'use strict';

let request = require('supertest-as-promised');
const instance_url = process.env.INSTANCE_URL;
let app;

let listOfBranches = (res) => {
    if (!('branches' in res.body)) {
        throw new Error('branches not found');
    }
};

let getBranches = () => {
    return request(app)
        .get('/branches')
        .expect(200)
        .expect(listOfBranches);
};

describe('Branches Integration Test', () => {

    beforeEach(() => {
        app = instance_url ? instance_url : require('../../../src/backend/app');
    });

    it('should return a list of branches', (done) => {
        getBranches()
        .then(done, done.fail);
    }, 60000);
});

'use strict';
const specHelper = require('../../support/specHelper'),
    Branch = specHelper.models.Branch,
    Group = specHelper.models.Group;

let request = require('supertest-as-promised');
const instanceUrl = process.env.INSTANCE_URL;
let app;

function seed() {
    let branchWithGroups;
    return Branch.create({
            id: 'fd4f7e67-66fe-4f7a-86a6-f031cb3af174',
            name: 'Branch name groups'
        }).then(function(branch) {
            branchWithGroups = branch;
            return Group.create({
                name: 'Waiting List',
                description: 'This is a description of the waiting list'
            });
        })
        .then(function(group) {
            return branchWithGroups.addGroup(group);
        });
}

let listOfBranches = (res) => {
    if (!('branches' in res.body)) {
        throw new Error('branches not found');
    }
};

let listOfGroups = (res) => {
    if (!('groups' in res.body)) {
        throw new Error('groups not found');
    }
};

let getBranches = () => {
    return request(app)
        .get('/branches')
        .expect(200)
        .expect(listOfBranches);
};

let getGroupsForBranch = () => {
    const branchId = 'fd4f7e67-66fe-4f7a-86a6-f031cb3af174';
    return request(app)
        .get(`/branches/${branchId}/groups`)
        .expect(200)
        .expect(listOfGroups);
};

describe('Branches Integration Test', () => {

    beforeEach((done) => {
        app = instanceUrl ? instanceUrl : require('../../../src/backend/app');
        seed().nodeify(done);
    });

    it('should return a list of branches', (done) => {
        getBranches()
            .then(done, done.fail);
    }, 60000);

    it('should return a list of groups for a branch', (done) => {
        getGroupsForBranch()
            .then(done, done.fail);
    }, 60000);

});

Add route test for branch groups and set to use spec helper


import {materializeReference} from '../data/fetch'
import {ReferencePreview} from '../../..'

export default ReferencePreview.create(materializeReference)

import {materializeReference} from '../data/fetch'
import {ReferencePreview} from '../../index'

export default ReferencePreview.create(materializeReference)

Change path to reference preview


export async function genArrayBufferFromFileReference(fileReference) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function(e) {
      resolve(e.target.result);
    };
    reader.onerror = function(e) {
      reject(e);
    };

    reader.readAsArrayBuffer(fileReference);
  });
}

export async function genArrayBufferFromUrl(url) {
  return new Promise((resolve, reject) => {
    const oReq = new XMLHttpRequest();
    oReq.open("GET", url, true);
    oReq.responseType = "arraybuffer";

    oReq.onload = () => {
      const arrayBuffer = oReq.response; // Note: not oReq.responseText
      resolve(arrayBuffer);
    };
    oReq.onerror = reject;

    oReq.send(null);
  });
}

export async function promptForFileReferences() {
  return new Promise(resolve => {
    // Does this represent a memory leak somehow?
    // Can this fail? Do we ever reject?
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    // Not entirely sure why this is needed, since the input
    // was just created, but somehow this helps prevent change
    // events from getting swallowed.
    // https://stackoverflow.com/a/12102992/1263117
    fileInput.value = null;
    fileInput.addEventListener("change", e => {
      resolve(e.target.files);
    });
    fileInput.click();
  });
}

export async function genArrayBufferFromFileReference(fileReference) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function(e) {
      resolve(e.target.result);
    };
    reader.onerror = function(e) {
      reject(e);
    };

    reader.readAsArrayBuffer(fileReference);
  });
}

export async function genArrayBufferFromUrl(url) {
  return new Promise((resolve, reject) => {
    const oReq = new XMLHttpRequest();
    oReq.open("GET", url, true);
    oReq.responseType = "arraybuffer";

    oReq.onload = () => {
      const arrayBuffer = oReq.response; // Note: not oReq.responseText
      resolve(arrayBuffer);
    };
    oReq.onerror = reject;

    oReq.send(null);
  });
}

export async function promptForFileReferences() {
  return new Promise(resolve => {
    // Does this represent a memory leak somehow?
    // Can this fail? Do we ever reject?
    const fileInput = document.createElement("input");
    fileInput.type = "file";
    fileInput.multiple = true;
    // Not entirely sure why this is needed, since the input
    // was just created, but somehow this helps prevent change
    // events from getting swallowed.
    // https://stackoverflow.com/a/12102992/1263117
    fileInput.value = null;
    fileInput.addEventListener("change", e => {
      resolve(e.target.files);
    });
    fileInput.click();
  });
}

Allow multiple files to be loaded via eject


var childProcess = require('child_process');
var fs = require('fs');

process.chdir('examples');
childProcess.execSync('npm install', { stdio: 'inherit' });
childProcess.execSync('npm run update', { stdio: 'inherit' });
process.chdir('..');

// Build all of the example folders.
dirs = fs.readdirSync('examples');

var cmd;
for (var i = 0; i < dirs.length; i++) {
  if (dirs[i].indexOf('.') !== -1) {
    continue;
  }
  if (dirs[i].indexOf('node_modules') !== -1) {
    continue;
  }
  console.log('Building: ' + dirs[i] + '...');
  process.chdir('examples/' + dirs[i]);
  childProcess.execSync('npm run build', { stdio: 'inherit' });
  process.chdir('../..');
}

var childProcess = require('child_process');
var fs = require('fs');

process.chdir('examples');
childProcess.execSync('npm install', { stdio: 'inherit' });
childProcess.execSync('npm run update', { stdio: 'inherit' });
process.chdir('..');

// Build all of the example folders.
dirs = fs.readdirSync('examples');

var cmd;
for (var i = 0; i < dirs.length; i++) {
  if (dirs[i].indexOf('.') !== -1) {
    continue;
  }
  if (dirs[i].indexOf('node_modules') !== -1) {
    continue;
  }
  console.log('\n***********\nBuilding: ' + dirs[i] + '...');
  process.chdir('examples/' + dirs[i]);
  childProcess.execSync('npm run build', { stdio: 'inherit' });
  process.chdir('../..');

}
console.log('\n********\nDone!');

Make examples build more verbose


/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *   http://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.
 *
*/

(function(win) {
    var cordova = require('cordova'),
        Acceleration = require('org.apache.cordova.device-motion.Acceleration'),
        accelerometerCallback = null;

    module.exports = {
        start: function (successCallback, errorCallback) {
            if (accelerometerCallback) {
                win.removeEventListener("devicemotion", accelerometerCallback, true);
            }

            accelerometerCallback = function (motion) {
                successCallback({
                    x: motion.accelerationIncludingGravity.x,
                    y: motion.accelerationIncludingGravity.y,
                    z: motion.accelerationIncludingGravity.z,
                    timestamp: new Date().getTime()
                });
            };
            win.addEventListener("devicemotion", accelerometerCallback, true);
        },

        stop: function (successCallback, errorCallback) {
            win.removeEventListener("devicemotion", accelerometerCallback, true);
            accelerometerCallback = null;
        }
    };

    require("cordova/tizen/commandProxy").add("Accelerometer", module.exports);
}(window));

Add license headers to Tizen code


'use strict';

describe('pointcloud.service', function() {

  // load the module
  beforeEach(module('pattyApp.pointcloud'));

  var PointcloudService;
  var Potree;
  beforeEach(function() {
    inject(function(_PointcloudService_, _Potree_) {
      PointcloudService = _PointcloudService_;
      Potree = _Potree_;
    });
  });

  describe('initial state', function() {
    it('should have settings', function() {

      var expected = {
        pointCountTarget: 0.4,
        pointSize: 0.7,
        opacity: 1,
        showSkybox: true,
        interpolate: false,
        pointSizeType: Potree.PointSizeType.ADAPTIVE,
        pointSizeTypes: Potree.PointSizeType,
        pointColorType: Potree.PointColorType.RGB,
        pointColorTypes: Potree.PointColorType,
        pointShapes: Potree.PointShape,
        pointShape: Potree.PointShape.SQUARE
      };

      expect(PointcloudService.settings).toEqual(expected);
    });
  });

});

'use strict';

describe('pointcloud.service', function() {

  // load the module
  beforeEach(module('pattyApp.pointcloud'));

  var PointcloudService;
  var Potree;
  beforeEach(function() {
    inject(function(_PointcloudService_, _Potree_) {
      PointcloudService = _PointcloudService_;
      Potree = _Potree_;
    });
  });

  describe('initial state', function() {
    it('should have settings', function() {

      var expected = {
        pointCountTarget: 0.4,
        pointSize: 0.7,
        opacity: 1,
        showSkybox: true,
        interpolate: false,
        pointSizeType: Potree.PointSizeType.ADAPTIVE,
        pointSizeTypes: Potree.PointSizeType,
        pointColorType: Potree.PointColorType.RGB,
        pointColorTypes: Potree.PointColorType,
        pointShapes: Potree.PointShape,
        pointShape: Potree.PointShape.SQUARE,
        showStats: false
      };

      expect(PointcloudService.settings).toEqual(expected);
    });
  });

});

Fix test, showStats was not added to test yet.


const basketItemViews = {
  remove: function remove(el) {
    el.addEventListener("click", function() {
      el.parentNode.remove();
    });
  },

  addToBasket: function addToBasket(el, props) {
    el.addEventListener("click", function() {
      fetch(props.file)
        .then(res => res.text())
        .then(data => {
          const wrapper = document.createElement("div");
          wrapper.innerHTML = data;
          const container = document.querySelector(`.${props.target}`);
          container.appendChild(wrapper);

          viewloader.execute(views, wrapper, true);
        })
        .catch(err => console.log(err));
    });
  }
};

viewloader.execute(basketItemViews);

const basketItemViews = {
  remove: function remove(el) {
    el.addEventListener("click", function() {
      el.parentNode.remove();
    });
  },

  addToBasket: function addToBasket(el, props) {
    const container = document.querySelector(`.${props.target}`);

    el.addEventListener("click", function() {
      fetch(props.file)
        .then(res => res.text())
        .then(data => {
          const wrapper = document.createElement("div");
          wrapper.innerHTML = data;
          container.appendChild(wrapper);
          viewloader.execute(basketItemViews, wrapper, true);
        })
        .catch(err => console.log(err));
    });
  }
};

viewloader.execute(basketItemViews);

Change selector to outside onclik event


/*globals document */
let hasDOM = typeof document !== 'undefined';

function appendContainerElement(rootElementId, id) {
  if (!hasDOM) {
    return;
  }

  if (document.getElementById(id)) {
    return;
  }

  let rootEl = document.querySelector(rootElementId);
  let modalContainerEl = document.createElement('div');
  modalContainerEl.id = id;
  rootEl.appendChild(modalContainerEl);
}

export default function(App) {
  let emberModalDialog = App.emberModalDialog || {};
  let modalContainerElId = emberModalDialog.modalRootElementId || 'modal-overlays';

  App.register('config:modals-container-id',
               modalContainerElId,
               { instantiate: false });

  App.inject('service:modal-dialog',
             'destinationElementId',
             'config:modals-container-id');

  appendContainerElement(App.rootElement, modalContainerElId);
}

/*globals document */
let hasDOM = typeof document !== 'undefined';

function appendContainerElement(rootElementOrId, id) {
  if (!hasDOM) {
    return;
  }

  if (document.getElementById(id)) {
    return;
  }

  let rootEl = rootElementOrId.appendChild ? rootElementOrId : document.querySelector(rootElementOrId);
  let modalContainerEl = document.createElement('div');
  modalContainerEl.id = id;
  rootEl.appendChild(modalContainerEl);
}

export default function(App) {
  let emberModalDialog = App.emberModalDialog || {};
  let modalContainerElId = emberModalDialog.modalRootElementId || 'modal-overlays';

  App.register('config:modals-container-id',
               modalContainerElId,
               { instantiate: false });

  App.inject('service:modal-dialog',
             'destinationElementId',
             'config:modals-container-id');

  appendContainerElement(App.rootElement, modalContainerElId);
}

Handle when App.rootElement can be a node, rather than an id


(function () {
  var codes = [
    38, // up
    38, // up
    40, // down
    40, // down
    37, // left
    39, // right
    37, // left
    39, // right
    66, // b
    65  // a
  ];

  kamo.Stream.fromEventHandlerSetter(window, 'onkeyup').map(function (message) {
    return message.keyCode;
  }).windowWithCount(codes.length).filter(function (message) {
    console.log(message);
    return JSON.stringify(message) == JSON.stringify(codes);
  }).subscribe(function (message) {
    alert('Conguraturation!');
  });
})();

(function () {
  var codes = [
    38, // up
    38, // up
    40, // down
    40, // down
    37, // left
    39, // right
    37, // left
    39, // right
    66, // b
    65  // a
  ];

  kamo.Stream.fromEventHandlerSetter(window, 'onkeyup').map(function (message) {
    return message.keyCode;
  }).windowWithCount(codes.length).filter(function (message) {
    return JSON.stringify(message) == JSON.stringify(codes);
  }).subscribe(function (message) {
    alert('Conguraturation!');
  });
})();

Remove debug code left by mistake


/*
  Bootstrap components and api on DOM Load
*/

import Relay from 'react-relay';
import {
  mountComponents,
  unmountComponents,
} from './utils/componentMounter';

/* global Turbolinks document */

// Get auth and csrf tokens
import Authentication from './helpers/authentication.es6';

const auth = new Authentication();

// Setup API url, inject csrf token and auth token
const setupNetworkLayer = () => {
  Relay.injectNetworkLayer(
    new Relay.DefaultNetworkLayer('/graphql', {
      credentials: 'same-origin',
      fetchTimeout: 30000,
      retryDelays: [5000],
      headers: {
        'X-CSRF-Token': auth.csrfToken(),
        Authorization: auth.authToken(),
      },
    })
  );
};

// Finally Render components if available in DOM
document.addEventListener('DOMContentLoaded', () => {
  if (typeof Turbolinks !== 'undefined' && typeof Turbolinks.controller !== 'undefined') {
    setupNetworkLayer();
    document.addEventListener('turbolinks:render', unmountComponents);
    document.addEventListener('turbolinks:load', mountComponents);
  } else {
    setupNetworkLayer();
    mountComponents();
  }
});

/*
  Bootstrap components and api on DOM Load
*/

import Relay from 'react-relay';
import {
  mountComponents,
  unmountComponents,
} from './utils/componentMounter';

/* global Turbolinks document */

// Get auth and csrf tokens
import Authentication from './helpers/authentication.es6';

const auth = new Authentication();

// Setup API url, inject csrf token and auth token
const setupNetworkLayer = () => {
  Relay.injectNetworkLayer(
    new Relay.DefaultNetworkLayer('/graphql', {
      credentials: 'same-origin',
      fetchTimeout: 30000,
      retryDelays: [5000],
      headers: {
        'X-CSRF-Token': auth.csrfToken(),
      },
    })
  );
};

// Finally Render components if available in DOM
document.addEventListener('DOMContentLoaded', () => {
  if (typeof Turbolinks !== 'undefined' && typeof Turbolinks.controller !== 'undefined') {
    setupNetworkLayer();
    document.addEventListener('turbolinks:render', unmountComponents);
    document.addEventListener('turbolinks:load', mountComponents);
  } else {
    setupNetworkLayer();
    mountComponents();
  }
});

Remove authrisation token from requests


/*!
 * angular-katex v0.2.1
 * https://github.com/tfoxy/angular-katex
 *
 * Copyright 2015 Tomás Fox
 * Released under the MIT license
 */

(function() {
  'use strict';

  angular.module('katex', [])
      .provider('katexConfig', function() {
        var self = this;

        self.errorTemplate = function(err) {
          return '' + err + '';
        };
        self.errorHandler = function(err, text, element) {
          element.html(self.errorTemplate(err, text));
        };

        self.render = function(element, text) {
          try {
            katex.render(text || '', element[0]);
          } catch (err) {
            self.errorHandler(err, text, element);
          }
        };

        //noinspection JSUnusedGlobalSymbols
        this.$get = function() {
          return this;
        };
      })
      .directive('katex', ['katexConfig', function(katexConfig) {
        return {
          restrict: 'AE',
          compile: function(element) {
            katexConfig.render(element, element.html());
          }
        };
      }])
      .directive('katexBind', ['katexConfig', function(katexConfig) {
        return {
          restrict: 'A',
          link: function(scope, element, attrs) {
            var model = attrs.katexBind;

            scope.$watch(model, function(text) {
              katexConfig.render(element, text);
            });
          }
        };
      }]);
})();

/*!
 * angular-katex v0.2.1
 * https://github.com/tfoxy/angular-katex
 *
 * Copyright 2015 Tomás Fox
 * Released under the MIT license
 */

(function() {
  'use strict';

  angular.module('katex', [])
      .constant('katex', katex)
      .provider('katexConfig', ['katex', function(katex) {
        var self = this;

        self.errorTemplate = function(err) {
          return '' + err + '';
        };
        self.errorHandler = function(err, text, element) {
          element.html(self.errorTemplate(err, text));
        };

        self.render = function(element, text) {
          try {
            katex.render(text || '', element[0]);
          } catch (err) {
            self.errorHandler(err, text, element);
          }
        };

        //noinspection JSUnusedGlobalSymbols
        this.$get = function() {
          return this;
        };
      }])
      .directive('katex', ['katexConfig', function(katexConfig) {
        return {
          restrict: 'AE',
          compile: function(element) {
            katexConfig.render(element, element.html());
          }
        };
      }])
      .directive('katexBind', ['katexConfig', function(katexConfig) {
        return {
          restrict: 'A',
          link: function(scope, element, attrs) {
            var model = attrs.katexBind;

            scope.$watch(model, function(text) {
              katexConfig.render(element, text);
            });
          }
        };
      }]);
})();

Add katex as an angular constant


'use strict';

angular.module('publicApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'ngRoute'
])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
			.when('/users', {
				templateUrl: 'views/users.html',
				controller: 'UsersCtrl'
			})
      .otherwise({
        redirectTo: '/'
      });
  });

'use strict';

angular.module('publicApp', [
  'ngCookies',
  'ngResource',
  'ngSanitize',
  'ngRoute'
])
  .config(function ($routeProvider) {
    $routeProvider
      .when('/', {
        templateUrl: 'views/main.html',
        controller: 'MainCtrl'
      })
			.when('/users/:id', {
				templateUrl: 'views/user.html',
				controller: 'UserCtrl'
			})
			.when('/admin/users/:id', {
				templateUrl: 'views/admin/users/index.html',
				controller: 'AdminUserCtrl'
			})
			.when('/admin/users', {
				templateUrl: 'views/admin/users/index.html',
				controller: 'AdminUsersCtrl'
			})
			.when('/admin/users/new', {
				templateUrl: 'views/admin/users/new.html',
				controller: 'AdminNewUserCtrl'
			})
			.when('/register', {
				templateUrl: 'views/registration.html',
				templateUrl: 'RegistrationCtrl'
			})
			.when('/login', {
				templateUrl: 'views/registration.html',
				templateUrl: 'LoginCtrl'
			})
      .otherwise({
        redirectTo: '/'
      });
  });

Add routes for login, registration and user admin


import View from "ember-views/views/view";
import { runAppend, runDestroy } from "ember-runtime/tests/utils";
import compile from "ember-template-compiler/system/compile";
import Registry from "container/registry";

var view, registry, container;

QUnit.module("ember-views: attrs-proxy", {
  setup() {
    registry = new Registry();
    container = registry.container();
  },

  teardown() {
    runDestroy(view);
  }
});

QUnit.skip('works with properties setup in root of view', function() {
  registry.register('view:foo', View.extend({
    bar: 'qux',

    template: compile('{{bar}}')
  }));

  view = View.extend({
    container: registry.container(),
    template: compile('{{view "foo" bar="baz"}}')
  }).create();

  runAppend(view);

  equal(view.$().text(), 'baz', 'value specified in the template is used');
});

import View from "ember-views/views/view";
import { runAppend, runDestroy } from "ember-runtime/tests/utils";
import compile from "ember-template-compiler/system/compile";
import Registry from "container/registry";

var view, registry, container;

QUnit.module("ember-views: attrs-proxy", {
  setup() {
    registry = new Registry();
    container = registry.container();
  },

  teardown() {
    runDestroy(view);
  }
});

QUnit.skip('works with properties setup in root of view', function() {
  registry.register('view:foo', View.extend({
    bar: 'qux',

    template: compile('{{view.bar}}')
  }));

  view = View.extend({
    container: registry.container(),
    template: compile('{{view "foo" bar="baz"}}')
  }).create();

  runAppend(view);

  equal(view.$().text(), 'baz', 'value specified in the template is used');
});

Fix typo in compat mode attrs test.


'use strict';

let config = {};

config.port = 3000;
config.apiKey = process.env.LFM_API_KEY;
config.apiSecret = process.env.LFM_SECRET;

module.exports = config;

'use strict';

let config = {};

config.port = 3000;
config.api_key = process.env.LFM_API_KEY;
config.apiSecret = process.env.LFM_SECRET;
config.sk = process.env.LFM_SESSION_KEY;

module.exports = config;

Add session key to config and update api key


//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require foundation

//= require ace/ace
//= require ace/mode-ruby
//= require ace/mode-golang
//= require ace/mode-python
//= require ace/mode-c_cpp
//= require ace/mode-csharp
//= require ace/mode-php

//= require ace/theme-tomorrow_night
//= require ace/theme-textmate
//= require ace/theme-monokai
//= require ace/theme-github

//= require ace/keybinding-vim
//= require ace/keybinding-emacs
//
//= require socket.io

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require foundation

//= require ace/ace
//= require ace/mode-ruby
//= require ace/mode-golang
//= require ace/mode-python
//= require ace/mode-c_cpp
//= require ace/mode-csharp
//= require ace/mode-php

//= require ace/worker-php

//= require ace/theme-tomorrow_night
//= require ace/theme-textmate
//= require ace/theme-monokai
//= require ace/theme-github

//= require ace/keybinding-vim
//= require ace/keybinding-emacs
//
//= require socket.io

Revert "Revert "add missing worker-php for ace editor""


'use strict';

define(["angular", "app/services/LoginChecker", "app/services/FileLoader", "app/services/FigureUploader", "app/services/SnippetLoader", "app/services/TagLoader", "app/services/IdLoader"], function() {

	/* Services */

	angular.module('scooter.services', [])

	.constant('Repo', {
		owner: "ucam-cl-dtg",
		name: "isaac-content-2"
	})

	.constant('ApiServer', "https://staging-2.isaacphysics.org/api/any/api")

	.service('LoginChecker', require("app/services/LoginChecker"))

	.factory('FileLoader', require("app/services/FileLoader"))

	.factory('FigureUploader', require("app/services/FigureUploader"))

	.service('SnippetLoader', require("app/services/SnippetLoader"))

	.factory('TagLoader', require("app/services/TagLoader"))

	.factory('IdLoader', require("app/services/IdLoader"))

});

'use strict';

define(["angular", "app/services/LoginChecker", "app/services/FileLoader", "app/services/FigureUploader", "app/services/SnippetLoader", "app/services/TagLoader", "app/services/IdLoader"], function() {

	/* Services */

	angular.module('scooter.services', [])

	.constant('Repo', {
		owner: "isaacphysics",
		name: "isaac-content-2"
	})

	.constant('ApiServer', "https://staging-2.isaacphysics.org/api/any/api")

	.service('LoginChecker', require("app/services/LoginChecker"))

	.factory('FileLoader', require("app/services/FileLoader"))

	.factory('FigureUploader', require("app/services/FigureUploader"))

	.service('SnippetLoader', require("app/services/SnippetLoader"))

	.factory('TagLoader', require("app/services/TagLoader"))

	.factory('IdLoader', require("app/services/IdLoader"))

});

Migrate content repo to @isaacphysics


document.write("<canvas>")
var Graphics = new Processing(document.querySelector('canvas'));

Graphics.keyPressed = function() {
  World.query("keyboard").forEach(function(e) {
    e.key = Graphics.keyCode;
  });
}

Graphics.keyReleased = function() {
  World.query("keyboard").forEach(function(e) {
    delete e.key;
  });
}

Add key* functions to Graphics backend


importScripts('/js/sha256.js');

onmessage = function(e) {
  console.log('received work!');

  var date = (new Date()).yyyymmdd();
  var bits = 0;
  var rand = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);

  var name = e.data[0];
  var difficulty = e.data[1];

  for (var counter = 0; bits < difficulty; counter++) {
    var chunks = [
      '2', difficulty , 'sha256', date, name, '', rand, counter
    ];
    var cash = chunks.join(':');
    hash = CryptoJS.SHA256(cash).toString();
    var match = hash.match(/^(0+)/);
    bits = (match) ? match[0].length : 0;
  }

  postMessage(cash);

  close();
}

Date.prototype.yyyymmdd = function() {
  var yyyy = this.getFullYear().toString();
  var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
  var dd  = this.getDate().toString();
  return yyyy + (mm[1]?mm:'0'+mm[0]) + (dd[1]?dd:'0'+dd[0]); // padding
};

importScripts('/js/sha256.js');

onmessage = function(e) {
  console.log('received work!');

  var date = (new Date()).yyyymmdd();
  var bits = 0;
  var rand = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);

  var name = e.data[0];
  var difficulty = e.data[1];

  for (var counter = 0; bits < difficulty; counter++) {
    var chunks = [
      '2', // Hashcash version number.  Note that this is 2, as opposed to 1.
      difficulty, // asserted number of bits that this cash matches
      'sha256', // ADDITION FOR VERSION 2: specify the hash function used
      date, // YYYYMMDD format.  specification doesn't indicate HHMMSS or lower?
      name, // Input format protocol change, recommend casting any input to hex.
      '', // empty "meta" field
      rand, // random seed
      counter // our randomized input, the nonce (actually sequential)
    ];
    var cash = chunks.join(':');
    hash = CryptoJS.SHA256(cash).toString();
    var match = hash.match(/^(0+)/);
    bits = (match) ? match[0].length : 0;
  }

  postMessage(cash);

  close();
}

Date.prototype.yyyymmdd = function() {
  var yyyy = this.getFullYear().toString();
  var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
  var dd  = this.getDate().toString();
  return yyyy + (mm[1]?mm:'0'+mm[0]) + (dd[1]?dd:'0'+dd[0]); // padding
};

Add comments for cash input.


// Use the web-push library to hide the implementation details of the communication
// between the application server and the push service.
// For details, see https://tools.ietf.org/html/draft-ietf-webpush-protocol and
// https://tools.ietf.org/html/draft-ietf-webpush-encryption.
var webPush = require('web-push');

webPush.setGCMAPIKey(process.env.GCM_API_KEY);

module.exports = function(app, route) {
  app.post(route + 'register', function(req, res) {
    // A real world application would store the subscription info.
    res.sendStatus(201);
  });

  app.post(route + 'sendNotification', function(req, res) {
    setTimeout(function() {
      webPush.sendNotification({
        endpoint: req.query.endpoint,
        TTL: req.query.ttl,
      })
      .then(function() {
        res.sendStatus(201);
      })
      .catch(function(error) {
        res.sendStatus(500);
        console.log(error);
      })
    }, req.query.delay * 1000);
  });
};

// Use the web-push library to hide the implementation details of the communication
// between the application server and the push service.
// For details, see https://tools.ietf.org/html/draft-ietf-webpush-protocol and
// https://tools.ietf.org/html/draft-ietf-webpush-encryption.
var webPush = require('web-push');

webPush.setGCMAPIKey(process.env.GCM_API_KEY || null);

module.exports = function(app, route) {
  app.post(route + 'register', function(req, res) {
    // A real world application would store the subscription info.
    res.sendStatus(201);
  });

  app.post(route + 'sendNotification', function(req, res) {
    setTimeout(function() {
      webPush.sendNotification({
        endpoint: req.query.endpoint,
        TTL: req.query.ttl,
      })
      .then(function() {
        res.sendStatus(201);
      })
      .catch(function(error) {
        res.sendStatus(500);
        console.log(error);
      })
    }, req.query.delay * 1000);
  });
};

Allow startup without setting GCM API key


var Dictionary = function() {
  var jsonURL = './json/dictionary.json';

  this._wordList = (function() {
    var json = null;
    $.ajax({
      'async': false,
      'global': false,
      'url': jsonURL,
      'dataType': 'json',
      'success': function( data ) {
        json = data;
      }
    });
    return json;
  }) ();
};

Dictionary.prototype.getRandomWord = function() {
  return this._wordList[ Math.floor( Math.random() * ( this._wordList.length + 1 ) ) ];
};

Dictionary.prototype.getSubWords = function( word ) {

};

Dictionary.prototype.createMap = function() {

};

Dictionary.prototype.isWord = function( word ) {
  return this._wordList.lastIndexOf( word );
}

var Dictionary = function() {
  var jsonURL = './json/dictionary.json';

  this._wordList = (function() {
    var json = null;
    $.ajax({
      'async': false,
      'global': false,
      'url': jsonURL,
      'dataType': 'json',
      'success': function( data ) {
        json = data;
      }
    });
    return json;
  }) ();
};

Dictionary.prototype.getRandomWord = function() {
  return this._wordList[ Math.floor( Math.random() * ( this._wordList.length + 1 ) ) ];
};

Dictionary.prototype.getSubWords = function( word ) {

};

Dictionary.prototype.createMap = function() {

};

Dictionary.prototype.isWord = function( word ) {
  return this._wordList.lastIndexOf( word ) !== -1;
}

Fix word search returning the index instead of whether the word exists.


// CHANGE the current collection
var changeCollection=function(new_index){
	dbIndex = new_index;
	imageDB = DATABASE[dbIndex];
	$("#collectionFooter").text(collections[dbIndex]);
	localStorage.setItem("last_visited_index",dbIndex); // saves the state AKA the last collection you had open
	if(imageDB==null){
		imageDB = [];
	}
	applyChanges();
	List();
	$('#txtInput').focus();
};

//CREATE a NEW collection

var newCollection = function(){
	var new_collection;
	new_collection = prompt("Enter a name for your new collection","","New image collection");

	// if its bad input, just do nothing:
	if(new_collection==""||new_collection==null){ // force the user to give the collection a name
			$('#dropdown').prop("selectedIndex",(localStorage.getItem("last_visited_index"))); // a little trick to jump back out of the "new collection..." menu item
			return;
	}
	collections.push(new_collection); // add new collection
	localStorage.setItem("collection_names",JSON.stringify(collections)); // update the list of collection names ;
	changeCollection(collections.length-1); // Switch over to the new collection
	popDropdown();

};

// CHANGE the current collection
var changeCollection=function(new_index){
	dbIndex = new_index;
	imageDB = DATABASE[dbIndex];
	$("#collectionFooter").text(collections[dbIndex]);
	document.title = collections[dbIndex];
	localStorage.setItem("last_visited_index",dbIndex); // saves the state AKA the last collection you had open
	if(imageDB==null){
		imageDB = [];
	}
	applyChanges();
	List();
	$('#txtInput').focus();
};

//CREATE a NEW collection

var newCollection = function(){
	var new_collection;
	new_collection = prompt("Enter a name for your new collection","","New image collection");

	// if its bad input, just do nothing:
	if(new_collection==""||new_collection==null){ // force the user to give the collection a name
			$('#dropdown').prop("selectedIndex",(localStorage.getItem("last_visited_index"))); // a little trick to jump back out of the "new collection..." menu item
			return;
	}
	collections.push(new_collection); // add new collection
	localStorage.setItem("collection_names",JSON.stringify(collections)); // update the list of collection names ;
	changeCollection(collections.length-1); // Switch over to the new collection
	popDropdown();

};

Change window title based on current collection


(function () {
    var server = sinon.fakeServer.create();
    server.xhr.useFilters = true;
    server.xhr.addFilter(function (method, url) {
        //whenever the this returns true the request will not faked
        return !url.match(/starcounter\//);
    });
    server.respondWith('GET', /htmlmerger/, [200, {"Content-Type": "text/html"}, 'fake response']);
}());

(function () {
    var server = sinon.fakeServer.create();
    server.xhr.useFilters = true;
    server.xhr.addFilter(function (method, url) {
        //whenever the this returns true the request will not faked
        return !url.match(/sc\//);
    });
    server.respondWith('GET', /htmlmerger/, [200, {"Content-Type": "text/html"}, 'fake response']);
}());

Update mock server path in test helper for FF and IE


import React, { Component } from 'react';
import Registry from '../utils/Registry';

export default class Markup extends Component {
  render() {
    return (

    )
  }
}

Registry.set('Markup', Markup);

import React, { Component } from 'react';
import Registry from '../utils/Registry';
import { isArray } from 'lodash';

export default class Markup extends Component {
  getContent() {
    if (this.props.data && isArray(this.props.data) && this.props.data.length > 0) {
      return this.props.data[0];
    };

    if (this.props.data && !isArray(this.props.data)) {
      return this.props.data;
    };

    if (this.props.content) {
      return this.props.content;
    }

    return '';
  }

  render() {
    return (

    )
  }
}

Registry.set('Markup', Markup);

Add getContent method to markup component


module.exports = function(grunt) {

  require('load-grunt-tasks')(grunt);

  // Add the grunt-mocha-test tasks.
  [
    'grunt-mocha-test',
    'grunt-contrib-watch'
  ]
  .forEach( grunt.loadNpmTasks );

  grunt.initConfig({
    // Configure a mochaTest task
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/**/*.js' ]
      },
    },

    watch: {
      scripts: {
        files: [ 'src/**/*.js' , 'test/**/*.js' ],
        tasks: ['eslint', 'mochaTest' ]
      }
    },

    eslint: {
        target: ['src/**/*.js']
    }
  });

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

module.exports = function(grunt) {

  require('load-grunt-tasks')(grunt);

  // Add the grunt-mocha-test tasks.
  [
    'grunt-mocha-test',
    'grunt-contrib-watch'
  ]
  .forEach( grunt.loadNpmTasks );

  grunt.initConfig({
    // Configure a mochaTest task
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/**/*.js' ]
      },
    },

    watch: {
      options: {
          atBegin: true
      },
      scripts: {
        files: [ 'src/**/*.js', 'test/**/*.js' ],
        tasks: ['eslint', 'mochaTest' ]
      }
    },

    eslint: {
        target: ['src/**/*.js']
    }
  });

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

Break watch task into separate task


module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        copy: {
            jquery: {
                expand: true,
                flatten: true,
                src: 'bower_components/jquery/dist/jquery.min.js',
                dest: 'dist/share/git-webui/webui/js/',
            },
            bootstrap: {
                expand: true,
                flatten: true,
                src: 'bower_components/bootstrap/dist/js/bootstrap.min.js',
                dest: 'dist/share/git-webui/webui/js/',
            },
            git_webui: {
                expand: true,
                cwd: 'src',
                src: ['lib/**', 'share/**', '!**/less', '!**/*.less'],
                dest: 'dist',
            },
        },

        less: {
            options: {
                paths: 'bower_components/bootstrap/less',
            },
            files: {
                expand: true,
                cwd: 'src',
                src: 'share/git-webui/webui/css/*.less',
                dest: 'dist',
                ext: '.css',
            },
        },

        clean: ['dist'],
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-clean');

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

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        copy: {
            jquery: {
                expand: true,
                flatten: true,
                src: 'bower_components/jquery/dist/jquery.min.js',
                dest: 'dist/share/git-webui/webui/js/',
            },
            bootstrap: {
                expand: true,
                flatten: true,
                src: 'bower_components/bootstrap/dist/js/bootstrap.min.js',
                dest: 'dist/share/git-webui/webui/js/',
            },
            git_webui: {
                options: {
                    mode: true,
                },
                expand: true,
                cwd: 'src',
                src: ['lib/**', 'share/**', '!**/less', '!**/*.less'],
                dest: 'dist',
            },
        },

        less: {
            options: {
                paths: 'bower_components/bootstrap/less',
            },
            files: {
                expand: true,
                cwd: 'src',
                src: 'share/git-webui/webui/css/*.less',
                dest: 'dist',
                ext: '.css',
            },
        },

        clean: ['dist'],
    });

    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-contrib-less');
    grunt.loadNpmTasks('grunt-contrib-clean');

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

Copy file permissions for all files


/**
 * grunt-dco
 *
 * Copyright (c) 2014 Rafael Xavier de Souza
 * Licensed under the MIT license.
 */

"use strict";

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    jshint: {
      all: [
        "Gruntfile.js",
        "tasks/**/*.js"
      ],
      options: {
        jshintrc: ".jshintrc",
      }
    },
    dco: {
      options: {
        exceptionalAuthors: {
          "rxaviers@gmail.com": "Rafael Xavier de Souza"
        }
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-jshint");

  // Actually load this plugin's task(s).
  grunt.loadTasks("tasks");

  grunt.registerTask("default", ["jshint", "dco"]);
};

/**
 * grunt-dco
 *
 * Copyright (c) 2014 Rafael Xavier de Souza
 * Licensed under the MIT license.
 */

"use strict";

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    jshint: {
      all: [
        "Gruntfile.js",
        "tasks/**/*.js"
      ],
      options: {
        jshintrc: ".jshintrc",
      }
    },
    dco: {
      current: {
        options: {
          exceptionalAuthors: {
            "rxaviers@gmail.com": "Rafael Xavier de Souza"
          }
        }
      }
    }
  });

  grunt.loadNpmTasks("grunt-contrib-jshint");

  // Actually load this plugin's task(s).
  grunt.loadTasks("tasks");

  grunt.registerTask("default", ["jshint", "dco"]);
};

Set target on dco grunt task configuration


module.exports = function(grunt) {

  require('load-grunt-tasks')(grunt);

  // Add the grunt-mocha-test tasks.
  [
    'grunt-mocha-test',
    'grunt-contrib-watch'
  ]
  .forEach( grunt.loadNpmTasks );

  grunt.initConfig({
    // Configure a mochaTest task
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/**/*.js' ]
      },
    },

    watch: {
      options: {
          atBegin: true
      },
      scripts: {
        files: [ 'src/**/*.js', 'test/**/*.js' ],
        tasks: ['eslint', 'mochaTest' ]
      }
    },

    eslint: {
        target: ['src/**/*.js']
    }
  });

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

module.exports = function(grunt) {

  require('load-grunt-tasks')(grunt);

  // Add the grunt-mocha-test tasks.
  [
    'grunt-mocha-test',
    'grunt-contrib-watch'
  ]
  .forEach( grunt.loadNpmTasks );

  grunt.initConfig({
    // Configure a mochaTest task
    mochaTest: {
      test: {
        options: {
          reporter: 'spec'
        },
        src: ['test/**/*.js' ]
      },
    },

    watch: {
      options: {
          atBegin: true
      },
      scripts: {
        files: [ 'src/**/*.js', 'test/**/*.js' ],
        tasks: ['eslint', 'mochaTest' ]
      }
    },

    eslint: {
        target: ['src/**/*.js']
    }
  });

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

Add unit tests back to default grunt task


module.exports = function (grunt) {
    var paths = require('./paths');

    grunt.loadNpmTasks('grunt-mocha-test');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.initConfig({
        paths: paths,

        watch: {
            src: {
                files: ['<%= paths.src.all %>'],
                tasks: ['build']
            }
        },

        concat: {
            prd: {
                src: ['<%= paths.src.prd %>'],
                dest: '<%= paths.dest.prd %>'
            },

            demo: {
                src: ['<%= paths.src.demo %>'],
                dest: '<%= paths.dest.demo %>'
            },
        },

        mochaTest: {
            test: {
                src: [
                    '<%= paths.src.lib %>',
                    '<%= paths.test.requires %>',
                    '<%= paths.test.spec %>'
                ],
                options: {
                    reporter: 'spec'
                }
            }
        }
    });

    grunt.registerTask('test', [
        'mochaTest'
    ]);

    grunt.registerTask('build', [
        'concat',
    ]);

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

module.exports = function (grunt) {
    var paths = require('./paths');

    grunt.loadNpmTasks('grunt-mocha-test');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.initConfig({
        paths: paths,

        watch: {
            src: {
                files: ['<%= paths.src.all %>'],
                tasks: ['build']
            }
        },

        concat: {
            prd: {
                src: ['<%= paths.src.prd %>'],
                dest: '<%= paths.dest.prd %>'
            },

            demo: {
                src: ['<%= paths.src.demo %>'],
                dest: '<%= paths.dest.demo %>'
            },
        },

        mochaTest: {
            test: {
                src: [
                    '<%= paths.src.lib %>',
                    '<%= paths.test.requires %>',
                    '<%= paths.test.spec %>'
                ],
                options: {
                    reporter: 'spec'
                }
            }
        }
    });

    grunt.registerTask('test', [
        'build',
        'mochaTest'
    ]);

    grunt.registerTask('build', [
        'concat',
    ]);

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

Build before every test as a way to keep builds up to date (thanks @hodgestar)


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

	// Project configuration.
	grunt.initConfig( {

		pkg: grunt.file.readJSON( 'package.json' ),

		copy: {
			main: {
				src: [
					'includes/**',
					'languages/**',
					'composer.json',
					'CHANGELOG.md',
					'LICENSE.txt',
					'readme.txt',
					'sticky-tax.php'
				],
				dest: 'dist/'
			},
		},

		makepot: {
			target: {
				options: {
					domainPath: '/languages',
					mainFile: 'sticky-tax.php',
					potFilename: 'sticky-tax.pot',
					potHeaders: {
						poedit: true,
						'x-poedit-keywordslist': true
					},
					type: 'wp-plugin',
					updateTimestamp: false
				}
			}
		},
	} );

	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-wp-i18n' );

	grunt.registerTask( 'build', [ 'i18n', 'copy' ] );
	grunt.registerTask( 'i18n', [ 'makepot' ] );

	grunt.util.linefeed = '\n';

};

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

	// Project configuration.
	grunt.initConfig( {

		pkg: grunt.file.readJSON( 'package.json' ),

		copy: {
			main: {
				src: [
					'includes/**',
					'lib/**',
					'languages/**',
					'composer.json',
					'CHANGELOG.md',
					'LICENSE.txt',
					'readme.txt',
					'sticky-tax.php'
				],
				dest: 'dist/'
			},
		},

		makepot: {
			target: {
				options: {
					domainPath: '/languages',
					mainFile: 'sticky-tax.php',
					potFilename: 'sticky-tax.pot',
					potHeaders: {
						poedit: true,
						'x-poedit-keywordslist': true
					},
					type: 'wp-plugin',
					updateTimestamp: false
				}
			}
		},
	} );

	grunt.loadNpmTasks( 'grunt-contrib-copy' );
	grunt.loadNpmTasks( 'grunt-wp-i18n' );

	grunt.registerTask( 'build', [ 'i18n', 'copy' ] );
	grunt.registerTask( 'i18n', [ 'makepot' ] );

	grunt.util.linefeed = '\n';

};

Include lib/* in the build command


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

  require('grunt-horde')
    .create(grunt)
    .demand('projName', 'sinon-doublist')
    .demand('instanceName', 'sinonDoublist')
    .demand('klassName', 'sinonDoublist')
    .loot('node-component-grunt')
    .loot('node-lib-grunt')
    .loot('./config/grunt')
    .attack();
};

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

  require('grunt-horde')
    .create(grunt)
    .demand('projName', 'sinon-doublist')
    .demand('instanceName', 'sinonDoublist')
    .demand('klassName', 'sinonDoublist')
    .loot('node-component-grunt')
    .loot('node-lib-grunt')
    .loot('./config/grunt')
    .home(__dirname)
    .attack();
};

Set home dir for TravisCI


export default class MetaController {
  constructor(PageMetadata) {
    this.meta = PageMetadata;
  }
}
MetaController.$inject = ['PageMetadata'];

class MetaController {
  constructor(PageMetadata) {
    this.meta = PageMetadata;
  }
}
MetaController.$inject = ['PageMetadata'];

export default MetaController;

Upgrade MetaController to ES6 syntax


/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *     http://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.
 */

function launchCalc() {
  chrome.app.window.create('AppCalc.html', {
    id: 'Calculator',
    innerBounds: {
      minWidth: 510,
      minHeight: 440,
      width: 500,
      height: 600
    }
  });
}

chrome.app.runtime.onLaunched.addListener(launchCalc);

/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *     http://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.
 */

function launchCalc() {
  chrome.app.window.create('AppCalc.html', {
    id: 'Calculator',
    innerBounds: {
      minWidth: 330,
      minHeight: 340,
      width: 350,
      height: 450
    }
  });
}

chrome.app.runtime.onLaunched.addListener(launchCalc);

Revert "ACalc: Increased minimum and starting calculator size."