// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//

//= require jquery3
//= require jquery_ujs
//= require jquery-ui/autocomplete
//= require jquery-ui/datepicker

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//

//= require jquery
//= require jquery_ujs
//= require jquery-ui/autocomplete
//= require jquery-ui/datepicker

Revert "Upgrade to jQUery 3.3"


// Vendor assets
//= require jquery
//= require jquery_ujs
//= require jquery-ui-autocomplete
//= require modernizr-custom
//= require dest/respond.min

// GOVUK modules
//= require govuk_toolkit
//= require vendor/polyfills/bind
//= require govuk/selection-buttons

// MOJ elements
//= require moj
//= require src/moj.TimeoutPrompt

// Candidates for re-usable components
//= require modules/moj.analytics
//= require modules/moj.autocomplete
//= require modules/moj.hijacks
//= require modules/moj.submit-once
//= require modules/moj.Conditional
//= require modules/moj.RevealAdditional
//= require modules/moj.checkbox-summary
//= require modules/moj.AgeLabel.js
//= require modules/moj.booking-calendar
//= require modules/moj.set-dimension
//= require modules/moj.AsyncGA

(function() {
  'use strict';
  delete moj.Modules.devs;
  var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']");
  moj.init();
}());

// Vendor assets
//= require jquery
//= require jquery_ujs
//= require jquery-ui-autocomplete
//= require modernizr-custom
//= require dest/respond.min

// GOVUK modules
//= require govuk_toolkit
//= require vendor/polyfills/bind
//= require govuk/selection-buttons

// MOJ elements
//= require moj
//= require src/moj.TimeoutPrompt

// Candidates for re-usable components
//= require modules/moj.analytics
//= require modules/moj.autocomplete
//= require modules/moj.hijacks
//= require modules/moj.submit-once
//= require modules/moj.Conditional
//= require modules/moj.RevealAdditional
//= require modules/moj.checkbox-summary
//= require modules/moj.AgeLabel.js
//= require modules/moj.booking-calendar
//= require modules/moj.set-dimension
//= require modules/moj.AsyncGA

(function() {
  'use strict';
  if (!document.body.classList.contains('js-enabled')) {
    document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');
  }
  delete moj.Modules.devs;
  var selectionButtons = new GOVUK.SelectionButtons("label input[type='radio'], label input[type='checkbox']");
  moj.init();
}());

Add js-enabled class to body if doesn't exist


// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require foundation
//= require ckeditor/init
//= require d3
//= require js-routes
//= require_tree .

$(document).on('page:change', function(){
    $(document).foundation({
        offcanvas : {
            open_method: 'move' // Sets method in which offcanvas opens, can also be 'overlap'
        }
    });
});

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require foundation
//= require ckeditor/init
//= require d3
//= require js-routes
//= require local_time
//= require_tree .

$(document).on('page:change', function(){
    $(document).foundation({
        offcanvas : {
            open_method: 'move' // Sets method in which offcanvas opens, can also be 'overlap'
        }
    });
});

Fix missing require to correctly display local time


import SandboxBrowser from '../sandboxes/SandboxBrowser';
import AppSandboxBrowser from '../sandboxes/AppSandboxBrowser';
import Request from '../browser/Request';
import {RuntimeCatalogue} from 'service-framework/dist/RuntimeCatalogue';
import PersistenceManager from 'service-framework/dist/PersistenceManager';
import StorageManagerFake from './StorageManagerFake';

// import Dexie from 'dexie';

const runtimeFactory = Object.create({
  createSandbox() {
    return new SandboxBrowser();
  },

  createAppSandbox() {
    return new AppSandboxBrowser();
  },

  createHttpRequest() {
    let request = new Request();
    return request;
  },

  atob(b64) {
    return atob(b64);
  },

  storageManager() {
    // const db = new Dexie('cache');
    // const storeName = 'objects';

    return new StorageManagerFake('a', 'b');
  },

  persistenceManager() {
    let localStorage = window.localStorage;
    return new PersistenceManager(localStorage);
  },

  createRuntimeCatalogue(development) {

    if (!this.catalogue)
      this.catalogue = new RuntimeCatalogue(this);

    return this.catalogue;
  }

});

export default runtimeFactory;

import SandboxBrowser from '../sandboxes/SandboxBrowser';
import AppSandboxBrowser from '../sandboxes/AppSandboxBrowser';
import Request from '../browser/Request';
import {RuntimeCatalogue} from 'service-framework/dist/RuntimeCatalogue';
import PersistenceManager from 'service-framework/dist/PersistenceManager';
import StorageManager from 'service-framework/dist/StorageManager';

// import StorageManagerFake from './StorageManagerFake';

import Dexie from 'dexie';

const runtimeFactory = Object.create({
  createSandbox() {
    return new SandboxBrowser();
  },

  createAppSandbox() {
    return new AppSandboxBrowser();
  },

  createHttpRequest() {
    let request = new Request();
    return request;
  },

  atob(b64) {
    return atob(b64);
  },

  storageManager() {

    // Using the implementation of Service Framework
    // Dexie is the IndexDB Wrapper
    const db = new Dexie('cache');
    const storeName = 'objects';

    return new StorageManager(db, storeName);

    // return new StorageManagerFake('a', 'b');
  },

  persistenceManager() {
    let localStorage = window.localStorage;
    return new PersistenceManager(localStorage);
  },

  createRuntimeCatalogue(development) {

    if (!this.catalogue)
      this.catalogue = new RuntimeCatalogue(this);

    return this.catalogue;
  }

});

export default runtimeFactory;

Update the toolkit to use indexedDB Wrapper and the generic implementation of service-framework


export const PRINTER_ADD = 'PRINTER_ADD';
export const PRINTER_UPDATE_STATE = 'PRINTER_UPDATE_STATE';

export const addPrinter = (printer, id) => ({
  type: PRINTER_ADD,
  printer,
  id,
  link,
});

export const updatePrinterState = data => ({
  type: PRINTER_UPDATE_STATE,
  data,
});

export const printerToggle = printer => ({
  type: PRINTER_TOGGLE,
  printer,
});

// export const print =

export const PRINTER_ADD = 'PRINTER_ADD';
export const PRINTER_UPDATE_STATE = 'PRINTER_UPDATE_STATE';
export const PRINTER_TOGGLE = 'PRINTER_TOGGLE';

export const PRINTER_PRINT = 'PRINTER_PRINT';
export const PRINTER_TOGGLE_PAUSE = 'PRINTER_TOGGLE_PAUSE';
export const PRINTER_LOAD = 'PRINTER_LOAD';
export const PRINTER_STOP = 'PRINTER_STOP';

export const PRINTER_REQUEST_COMPLETED = 'PRINTER_REQUEST_COMPLETED';

export const addPrinter = (printer, id, link) => ({
  type: PRINTER_ADD,
  printer,
  id,
  link,
});

export const updatePrinterState = data => ({
  type: PRINTER_UPDATE_STATE,
  data,
});

export const printerToggle = printer => ({
  type: PRINTER_TOGGLE,
  printer,
});

// export const print =

Define action constants for printer control


import 'isomorphic-fetch'

const getFindLocations = q => ({
  type: 'GET_FIND',
  payload: new Promise(resolve => {
    setTimeout(() => fetch(`http://api.openweathermap.org/data/2.5/find?q=${q}&APPID=9f585babc59bf435c42319b4e9b69766`).then(response => {
      resolve(response.json())
    }), 1000)
  })
})
const setFinderSearchValue = (value) => ({
  type: 'SET_FIND_VALUE',
  payload: value
})

export { getFindLocations, setFinderSearchValue }

import 'isomorphic-fetch'

const getFindLocations = q => ({
  type: 'GET_FIND',
  payload: new Promise((resolve, reject) => {
    fetch(`http://api.openweathermap.org/data/2.5/find?q=${q}&APPID=9f585babc59bf435c42319b4e9b69766`).then(response => {
      resolve(response.json())
    })
  })
})
const setFinderSearchValue = (value) => ({
  type: 'SET_FIND_VALUE',
  payload: value
})

export { getFindLocations, setFinderSearchValue }

Add remobe timeout from promise


module.exports = function (str) {
  var palindromes = [];

  var walkPalindrome = function (result, str, leftIndex, rightIndex) {
    while (str[leftIndex - 1] === str[rightIndex + 1]) {
      result = str[leftIndex -= 1] + result + str[rightIndex += 1];
    }
    palindromes.push(result);
  };

  for (var i = 0; i < str.length; i++) {
    if (str[i] === str[i - 1]) {
      walkPalindrome(str[i] + str[i - 1], str, i - 1, i);
    } else if (str[i] === str[i + 1]) {
      walkPalindrome(str[i] + str[i + 1], str, i, i + 1);
    } else if (str[i - 1] === str[i + 1]) {
      walkPalindrome(str[i - 1] + str[i] + str[i + 1], str, i - 1, i + 1);
    }
  }

  return palindromes.reduce(function (memo, str) {
    return str.length > memo.length ? str : memo;
  }, '');
};

function isPalindrome (str) {
  const mid = Math.floor(str.length / 2)

  for (let i = 0; i < mid; i++) {
    if (str[i] !== str[str.length - 1 - i]) {
      return false
    }
  }

  return true
}

module.exports = function longestPalindrome (str) {
  let longest = ''

  for (let i = 0; i < str.length; i++) {
    let len = str.length

    while (len > i && len - i > longest.length) {
      const substring = str.substring(i, len--)

      if (isPalindrome(substring)) {
        longest = substring
      }
    }
  }

  return longest
}

Improve the palindrome solution (way better now!)


(function () {
    var themes = ["red", "blue", "yellow", "green"];
    var randThemeName = themes[(Math.floor(Math.random() * themes.length))];

    document.getElementById("container").className += " theme-" + randThemeName;
}());

(function () {
    let themes = ["red", "blue", "yellow", "green"];
    let randThemeName = themes[(Math.floor(Math.random() * themes.length))];

    document.getElementById("container").className = "theme-" + randThemeName;
}());

Convert js code to es2015


Add support for single quoted multi-line strings in Python


var _ = require('underscore');

module.exports = function(robot){

	robot.slack = {

		baseApiUrl: 'https://slack.com/api/',

		createApiUrl: function (suffix, queryString){
			if (!!queryString){
				if (_.isObject(queryString))
					queryString = robot.util.toQueryString(queryString);
				return robot.slack.baseApiUrl + suffix + queryString;
			}
			return robot.slack.baseApiUrl + suffix;
		}
	};

	robot.respond(/channel list/i, function(res){
		if (robot.auth.isAdmin(res.message.user)){
			var data = {
				token: process.env.HUBOT_SLACK_TOKEN,
				exclude_archived: 1
			};

			robot.http(robot.slack.createApiUrl('channels.list', data))
			.get()(function(err, response, body){
				if (!!err){
					res.reply('ERROR: ' + err);
					robot.errors.log(err);
					return;
				}
				var bodyJson = JSON.parse(body);
				res.send(robot.util.prettifyJson(bodyJson));
			});
		}
	});
}

var _ = require('underscore');

module.exports = function(robot){

	robot.slack = {

		baseApiUrl: 'https://slack.com/api/',

		createApiUrl: function (suffix, queryString){
			if (!!queryString){
				if (_.isObject(queryString))
					queryString = robot.util.toQueryString(queryString);
				return robot.slack.baseApiUrl + suffix + queryString;
			}
			return robot.slack.baseApiUrl + suffix;
		}
	};

	robot.respond(/channel list/i, function(res){
		if (robot.auth.isAdmin(res.message.user)){
			var data = {
				token: process.env.HUBOT_SLACK_TOKEN,
				exclude_archived: 1
			};

			robot.http(robot.slack.createApiUrl('channels.list', data))
			.header('Accept', 'application/json')
			.get()(function(err, response, body){
				if (!!err){
					res.reply('ERROR: ' + err);
					robot.errors.log(err);
					return;
				}
				var bodyJson = JSON.parse(body);
				res.send(robot.util.prettifyJson(bodyJson));
			});
		}
	});
}

Set "Accept: 'application/json'" header on Slack API calls


import createBrowserHistory from 'history/createBrowserHistory'
import promiseMiddleware from 'redux-promise-middleware'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { createStore, applyMiddleware } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from 'main/reducer'
import initState from 'main/initState'

export const history = createBrowserHistory()

const middleware = [
  routerMiddleware(history),
  promiseMiddleware(),
  thunk
]

export const store = createStore(
  createRootReducer(history),
  initState,
  composeWithDevTools(applyMiddleware(...middleware))
)

import { createBrowserHistory } from 'history'
import promise from 'redux-promise-middleware'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import { createStore, applyMiddleware } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from 'main/reducer'
import initState from 'main/initState'

export const history = createBrowserHistory()

const middleware = [
  routerMiddleware(history),
  promise,
  thunk
]

export const store = createStore(
  createRootReducer(history),
  initState,
  composeWithDevTools(applyMiddleware(...middleware))
)

Fix Redux middleware initialization to prevent warnings


module.exports = {
    "parser": "babel-eslint",
    "extends": "airbnb-base",
    "rules": {
        "keyword-spacing": 2,
        "generator-star-spacing": 0, // Temporary, avoids a bug in eslint babel parser
        "indent": ["error", 4, { "SwitchCase": 1 }],
        "strict": ["error", "safe"],
        "comma-dangle": ["error", "never"],
        "no-use-before-define": ["error", { "functions": false, "classes": true }],
        "no-param-reassign": ["error", { "props": false }],
        "array-bracket-spacing": ["error", "always"],
        "no-underscore-dangle": ["off"],
        "radix": ["error", "as-needed"],
        "import/no-extraneous-dependencies": ["error", {"devDependencies": true, "optionalDependencies": false}],
        "no-continue": ["off"]
    },
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "script"
    }
}

module.exports = {
    "parser": "babel-eslint",
    "extends": "airbnb",
    "rules": {
        "keyword-spacing": 2,
        "generator-star-spacing": 0, // Temporary, avoids a bug in eslint babel parser
        "indent": ["error", 4, { "SwitchCase": 1 }],
        "strict": ["error", "safe"],
        "comma-dangle": ["error", "never"],
        "no-use-before-define": ["error", { "functions": false, "classes": true }],
        "no-param-reassign": ["error", { "props": false }],
        "array-bracket-spacing": ["error", "always"],
        "no-underscore-dangle": ["off"],
        "radix": ["error", "as-needed"],
        "import/no-extraneous-dependencies": ["error", {"devDependencies": true, "optionalDependencies": false}],
        "no-continue": ["off"]
    },
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "script"
    }
}

Use full airbnb config including React rules


/**
 * @fileoverview DatatableHeader.js
 * Pure component that renders the header of the table.
 */

import React from 'react';
import { DatatableHeaderPropTypes } from '../PropTypes.js';

const DatatableHeader = ({ columns, onSort, sort }) =>
  <thead>
    <tr>
      {columns.map(c =>
        <th
			    key={c.key}
			    onClick={c.sort ? (e) => onSort(e, c) : undefined}
			    style={{ cursor: c.sort ? 'pointer' : 'default' }}
		    >
          {sort.sortColumn === c.key ? (sort.sortOrder ? '▾ ' : '▴ ') : ''}{c.label}
        </th>
      )}
    </tr>
  </thead>

DatatableHeader.propTypes = DatatableHeaderPropTypes;

export default DatatableHeader;

/**
 * @fileoverview DatatableHeader.js
 * Pure component that renders the header of the table.
 */

import React from 'react';
import { DatatableHeaderPropTypes } from '../PropTypes.js';

const DatatableHeader = ({ columns, onSort, sort }) =>

      {columns.map(c =>
         onSort(e, c) : undefined}
			    style={{ cursor: c.sort ? 'pointer' : 'default' }}
		    >
          {sort.sortColumn === c.key ?
            {sort.sortOrder ? '▼ ' : '▲ '}
            : ''
          }
          {c.label}

      )}

DatatableHeader.propTypes = DatatableHeaderPropTypes;

export default DatatableHeader;

Update the sort arrow heads.


function onDocumentReady(firebaseApp) {

  //[START fs_emulator_connect]
  // firebaseApp previously initialized using firebase.initializeApp().
  var db = firebaseApp.firestore();
  if (location.hostname === "localhost") {
    // Note that the Firebase Web SDK must connect to the WebChannel port
    db.settings({
      host: "localhost:8081",
      ssl: false
    });
  }
  // [END fs_emulator_connect]
}

function onDocumentReady(firebaseApp) {

  //[START fs_emulator_connect]
  // firebaseApp previously initialized using firebase.initializeApp().
  var db = firebaseApp.firestore();
  if (location.hostname === "localhost") {
    db.settings({
      host: "localhost:8080",
      ssl: false
    });
  }
  // [END fs_emulator_connect]
}

Stop mentioning the WebChannel port


module.exports = {
  "extends": "airbnb",
  "rules": {
    "comma-dangle": ["error", "never"],
    "import/no-unresolved": "off",
    "import/no-extraneous-dependencies": "off",
    "import/extensions": "off",
    "no-console": "off",
    semi: ['error', 'never'],
  },
  "env": {
    "browser": true,
    "node": true,
  },
};

module.exports = {
  'extends': 'airbnb',
  'rules': {
    'comma-dangle': ['error', 'never'],
    'import/no-unresolved': 'off',
    'import/no-extraneous-dependencies': 'off',
    'import/extensions': 'off',
    'no-console': 'off',
    semi: ['error', 'never'],
  },
  'env': {
    'browser': true,
    'node': true,
  },
};

Use single quote for strings


/**
 * Check if given `slug` could conflict with existing routes or
 * if it's a reserved keyword.
 *
 * The list is mostly based on frontend `src/server/pages.js` file and
 * `src/pages/static` content.
 *
 * @param {String} slug
 */
export function isBlacklistedCollectiveSlug(slug) {
  return [
    'about',
    'chapters',
    'create',
    'discover',
    'faq',
    'hosts',
    'learn-more',
    'opensource',
    'privacypolicy',
    'redeem',
    'redeemed',
    'search',
    'signin',
    'subscriptions',
    'tos',
    'widgets',
    'edit',
    'expenses',
    'orders',
  ].includes(slug);
}

/**
 * Check if given `slug` could conflict with existing routes or
 * if it's a reserved keyword.
 *
 * The list is mostly based on frontend `src/server/pages.js` file and
 * `src/pages/static` content.
 *
 * @param {String} slug
 */
export function isBlacklistedCollectiveSlug(slug) {
  return [
    'about',
    'admin',
    'applications',
    'become-a-sponsor',
    'chapters',
    'collective',
    'contact',
    'contribute',
    'create',
    'create-account',
    'discover',
    'donate',
    'edit',
    'expenses',
    'faq',
    'gift-card',
    'gift-cards',
    'gift-cards-next',
    'gift-of-giving',
    'help',
    'home',
    'host',
    'hosts',
    'how-it-works',
    'join',
    'join-free',
    'learn-more',
    'opensource',
    'order',
    'orders',
    'pledge',
    'pledges',
    'privacypolicy',
    'redeem',
    'redeemed',
    'register',
    'search',
    'signin',
    'signup',
    'subscriptions',
    'tos',
    'transactions',
    'widgets',
  ].includes(slug);
}

Add more keywords to slugs blacklist


/**
 * @license
 * Copyright 2016 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.
 */

CLASS({
  package: 'foam.u2',
  name: 'DefaultValidator',

  documentation: 'Default Element validator.',

  methods: [
    function validateNodeName(name) {
      return true;
    },

    function validateClass(cls) {

    },

    function validateAttributeName(name) {

    },

    function validateAttributeValue(value) {

    },

    function validateStyleName(name) {

    },

    function validateStyleValue(value) {

    },

    function sanitizeText(text) {
      if ( ! text ) return text;
      text = text.toString();
//      return text.replace(/[&<"']/g, function (m) {
      return text.replace(/[<"']/g, function (m) {
        switch ( m ) {
//          case '&': return '&';
          case '<': return '<';
          case '"': return '"';
          default:  return '&#039';
        }
      });
    }
  ]
});

/**
 * @license
 * Copyright 2016 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.
 */

CLASS({
  package: 'foam.u2',
  name: 'DefaultValidator',

  documentation: 'Default Element validator.',

  methods: [
    function validateNodeName(name) {
      return true;
    },

    function validateClass(cls) {

    },

    function validateAttributeName(name) {

    },

    function validateAttributeValue(value) {

    },

    function validateStyleName(name) {

    },

    function validateStyleValue(value) {

    },

    function sanitizeText(text) {
      if ( ! text ) return text;
      text = text.toString();
      return text.replace(/[&<"']/g, function (m) {
        switch ( m ) {
          case '&': return '&';
          case '<': return '<';
          case '"': return '"';
          default:  return '&#039';
        }
      });
    }
  ]
});

Put & escaping back in Validator now that entities are supported in the template parser.


class ExtendableError extends Error {
  constructor (message) {
    super(message)
    this.name = this.constructor.name
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

class NotImplementedException extends ExtendableError {
  constructor () {
    super('Required method is not implemented.')
  }
}

class UnauthorizedQueryException extends ExtendableError {
  constructor () {
    super('Query not authorized.')
  }
}

class UserReachedMaxLevelException extends ExtendableError {
  constructor () {
    super('There are no more levels. The user is at the max level.')
  }
}

class MockDatabaseException extends ExtendableError {

}

class EmptyOperationStackException extends MockDatabaseException {
  constructor () {
    super('Operation stack is empty. Check if you set your expected data in the test.')
  }
}

//

export {
  NotImplementedException,
  UnauthorizedQueryException,
  UserReachedMaxLevelException,
  EmptyOperationStackException
}

class ExtendableError extends Error {
  constructor (message) {
    super(message)
    this.name = this.constructor.name
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor)
    } else {
      this.stack = (new Error(message)).stack
    }
  }
}

export const METHOD_NOT_IMPLEMENTED = 'METHOD_NOT_IMPLEMENTED'
class NotImplementedException extends ExtendableError {
  constructor () {
    super('Required method is not implemented.')
    this.code = UNAUTHORIZED_QUERY
  }
}

export const UNAUTHORIZED_QUERY = 'UNAUTHORIZED_QUERY'
class UnauthorizedQueryException extends ExtendableError {
  constructor () {
    super('Query not authorized.')
    this.code = UNAUTHORIZED_QUERY
  }
}

export const USER_REACHED_MAX_LEVEL = 'USER_REACHED_MAX_LEVEL'
class UserReachedMaxLevelException extends ExtendableError {
  constructor () {
    super('There are no more levels. The user is at the max level.')
    this.code = USER_REACHED_MAX_LEVEL
  }
}

// For tests.
class MockDatabaseException extends ExtendableError {}

export const OPERATION_STACK_IS_EMPTY = 'OPERATION_STACK_IS_EMPTY'
class EmptyOperationStackException extends MockDatabaseException {
  constructor () {
    super('Operation stack is empty. Check if you set your expected data in the test.')
    this.code = OPERATION_STACK_IS_EMPTY
  }
}

// TODO:
// USER_DOES_NOT_EXIST_ERROR

export {
  NotImplementedException,
  UnauthorizedQueryException,
  UserReachedMaxLevelException,
  EmptyOperationStackException
}

Add error codes to custom errors


/* ***** ----------------------------------------------- ***** **
** ***** Global JS
** ***** ----------------------------------------------- ***** */

/* global Modernizr */
import 'lazysizes';
import 'lazysizes/plugins/bgset/ls.bgset';
import objectFitImages from 'object-fit-images';
import 'svgxuse';
import iNoBounce from 'iNoBounce';
import { displaySiteAlert } from '../utilities/display.js';

// Display message for user to upgrade if browser does not support flexbox
const modernizrCheck = () => {
  if (!Modernizr.flexbox || !Modernizr.svg) {
    const upgradeMessage = 'For an improved browsing experience, please upgrade your browser to the latest version. Click here to upgrade.';
    displaySiteAlert(upgradeMessage, 'http://outdatedbrowser.com/');
  }
}

const init = () => {
  modernizrCheck();
  objectFitImages();
  iNoBounce.disable();
}

export { init };

/* ***** ----------------------------------------------- ***** **
** ***** Global JS
** ***** ----------------------------------------------- ***** */

/* global Modernizr */
import 'lazysizes';
import 'lazysizes/plugins/bgset/ls.bgset';
import objectFitImages from 'object-fit-images';
import 'svgxuse';
import iNoBounce from 'inobounce';
import { displaySiteAlert } from '../utilities/display.js';

// Display message for user to upgrade if browser does not support flexbox
const modernizrCheck = () => {
  if (!Modernizr.flexbox || !Modernizr.svg) {
    const upgradeMessage = 'For an improved browsing experience, please upgrade your browser to the latest version. Click here to upgrade.';
    displaySiteAlert(upgradeMessage, 'http://outdatedbrowser.com/');
  }
}

const init = () => {
  modernizrCheck();
  objectFitImages();
  iNoBounce.disable();
}

export { init };

Update case of inobounce js import for netlify


'use strict';

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = opts => {
  const {
    strapiDependencies,
    additionalsDependencies,
    strapiVersion,
    projectName,
    uuid,
    packageJsonStrapi,
  } = opts;

  // Finally, return the JSON.
  return {
    name: projectName,
    private: true,
    version: '0.1.0',
    description: 'A Strapi application',
    scripts: {
      develop: 'strapi develop',
      start: 'strapi start',
      build: 'strapi build',
      strapi: 'strapi',
    },
    devDependencies: {},
    dependencies: Object.assign(
      {},
      strapiDependencies.reduce((acc, key) => {
        acc[key] = strapiVersion;
        return acc;
      }, {}),
      additionalsDependencies
    ),
    author: {
      name: 'A Strapi developer',
    },
    strapi: {
      uuid,
      ...packageJsonStrapi,
    },
    engines: {
      node: '>=12.x.x <=14.x.x',
      npm: '^6.0.0',
    },
    license: 'MIT',
  };
};

'use strict';

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = opts => {
  const {
    strapiDependencies,
    additionalsDependencies,
    strapiVersion,
    projectName,
    uuid,
    packageJsonStrapi,
  } = opts;

  // Finally, return the JSON.
  return {
    name: projectName,
    private: true,
    version: '0.1.0',
    description: 'A Strapi application',
    scripts: {
      develop: 'strapi develop',
      start: 'strapi start',
      build: 'strapi build',
      strapi: 'strapi',
    },
    devDependencies: {},
    dependencies: Object.assign(
      {},
      strapiDependencies.reduce((acc, key) => {
        acc[key] = strapiVersion;
        return acc;
      }, {}),
      additionalsDependencies
    ),
    author: {
      name: 'A Strapi developer',
    },
    strapi: {
      uuid,
      ...packageJsonStrapi,
    },
    engines: {
      node: '>=12.x.x <=16.x.x',
      npm: '>=6.0.0',
    },
    license: 'MIT',
  };
};

Enable Node 16 and NPM 7/8 support on generation


'use strict';

const path = require('path');
const sequelize = require(path.resolve('src/utils/connection'));
const Sequelize = require('sequelize');

const pokemon = sequelize.define('pokemon', {
	name: {
		type: Sequelize.STRING,
		allowNull: false,
		validate: {
            isAlpha: true
		}
	},
	price: {
		type: Sequelize.INTEGER,
		allowNull: false
	},
	stock: {
		type: Sequelize.INTEGER,
		allowNull: true,
		defaultValue: 1
	}
});

module.exports = pokemon;

'use strict';

const path = require('path');
const sequelize = require(path.resolve('src/utils/connection'));
const Sequelize = require('sequelize');

const pokemon = sequelize.define('pokemon', {
	name: {
		type: Sequelize.STRING,
		allowNull: false,
		unique: true
	},
	price: {
		type: Sequelize.FLOAT(2),
		allowNull: false
	},
	stock: {
		type: Sequelize.INTEGER,
		allowNull: true,
		defaultValue: 1
	}
});

module.exports = pokemon;

ADJUST model to more persistence .


export function parseRangeFilters(stringValue = '') {
  if (!stringValue) {
    return [];
  }
  return stringValue.split('~').map(s => {
    const m = s.match(/range-(-?[0-9.]+)_(-?[0-9.]+)/);
    if (!m) {
      return { start: 0, end: 1000 };
    }
    return { start: m[1] * 1000, end: m[2] * 1000 };
  });
}

export function stringifyRangeFilters(arrayValue = []) {
  return arrayValue.map(({ start, end }) => {
    const startStr = (start / 1000).toFixed(4);
    const endStr = (end / 1000).toFixed(4);
    return `range-${startStr}_${endStr}`;
  }).join('~');
}

export function parseRangeFilters(stringValue = '') {
  if (!stringValue) {
    return [];
  }
  return stringValue.split('~').map(s => {
    const m = s.match(/(-?[0-9.]+)_(-?[0-9.]+)/);
    if (!m) {
      return { start: 0, end: 1000 };
    }
    return { start: m[1] * 1000, end: m[2] * 1000 };
  });
}

export function stringifyRangeFilters(arrayValue = []) {
  return arrayValue.map(({ start, end }) => {
    const startStr = (start / 1000).toFixed(4);
    const endStr = (end / 1000).toFixed(4);
    return `${startStr}_${endStr}`;
  }).join('~');
}

Remove the word 'range' from the range filter list items because we are not going to add other types of filters to that list. Callstack focus filters will be a separate filter list.


import React, { PropTypes } from 'react'
import { connect } from 'react-redux'

import ContainerFluid from '../ContainerFluid'
import VmUserMessages from '../VmUserMessages'
import UserMenu from './UserMenu'

import { getAllVms } from '../../actions/vm'

/**
 * Main application header on top of the page
 */
const VmsPageHeader = ({ title, onRefresh }) => {
  const titleStyle = { padding: '0px 0 5px' }

  return (

      <ContainerFluid>

          {title}

               Refresh

          <UserMenu />
          <VmUserMessages />

      </ContainerFluid>

  )
}
VmsPageHeader.propTypes = {
  title: PropTypes.string.isRequired,
  onRefresh: PropTypes.func.isRequired,
}

export default connect(
  (state) => ({ }),
  (dispatch) => ({
    onRefresh: () => dispatch(getAllVms({ shallowFetch: false })),
  })
)(VmsPageHeader)

Fix refresh button highlighted after click


#!/usr/bin/env node

var cp = require('child_process')

var BIN = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
var ARGS = ['--load-and-launch-app=chrome']

var child = cp.spawn(BIN, ARGS)

// Nodemon is trying to kill us, so kill Chrome
process.once('SIGUSR2', function () {
  child.kill()
  process.kill(process.pid, 'SIGUSR2')
})

#!/usr/bin/env node

var cp = require('child_process')

var BIN = process.platform=='linux' ? '/bin/google-chrome' : '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
var ARGS = ['--load-and-launch-app=chrome']

var child = cp.spawn(BIN, ARGS)

// Nodemon is trying to kill us, so kill Chrome
process.once('SIGUSR2', function () {
  child.kill()
  process.kill(process.pid, 'SIGUSR2')
})

Improve the accessibility of the project for linux users.


module.exports = {
    "parser": "babel-eslint",
    "extends": "google",
    "rules": {
        "camelcase": 1,
        "curly": 0,
        "eol-last": 0,
        "indent": ["error", 4],
        "new-cap": 1,
        "no-extend-native": 0,
        "no-loop-func": 0,
        "no-unused-vars": 1,
        "require-jsdoc": 0
    }
};

module.exports = {
    "parser": "babel-eslint",
    "extends": "google",
    "rules": {
        "camelcase": 1,
        "curly": 0,
        "eol-last": 0,
        "indent": ["error", 4],
        "max-len": 0,
        "new-cap": 1,
        "no-extend-native": 0,
        "no-loop-func": 0,
        "no-unused-vars": 1,
        "require-jsdoc": 0
    }
};

Remove warning for max line length


'use strict';
var React = require('react');
var _ = require('underscore');

// BIOSAMPLE UTILITIES

// Construct a biosample summary string from the biosample's summary_object object.
module.exports.BiosampleSummaryString = function(biosample, supressOrganism) {
    var organismName = biosample.organism.scientific_name;
    var organismlessSummary = biosample.summary.replace(organismName + ' ', '');
    if (supressOrganism) {
        return {organismlessSummary};
    }
    return {biosample.summary_object.organism_name} {organismlessSummary};
};

// Some biosample-specific utilities
//   Return an array of biosample scientific names from the given array of biosamples.
module.exports.BiosampleOrganismNames = function(biosamples) {
    return _.uniq(biosamples.map(biosample => biosample.organism.scientific_name));
};

'use strict';
var React = require('react');
var _ = require('underscore');

// BIOSAMPLE UTILITIES

// Construct a biosample summary string from the biosample's organism object.
module.exports.BiosampleSummaryString = function(biosample, supressOrganism) {
    var organismName = biosample.organism.scientific_name;
    var organismlessSummary = biosample.summary.replace(organismName + ' ', '');
    if (supressOrganism) {
        return {organismlessSummary};
    }
    return {biosample.organism.scientific_name} {organismlessSummary};
};

// Some biosample-specific utilities
//   Return an array of biosample scientific names from the given array of biosamples.
module.exports.BiosampleOrganismNames = function(biosamples) {
    return _.uniq(biosamples.map(biosample => biosample.organism.scientific_name));
};

Fix out-of-date use of summary_object


(function () {

  'use strict';

  angular.module('angularDateInterceptor', [])
    .service('angularDateInterceptor', AngularDateInterceptor)
    .config(config);

  function AngularDateInterceptor() {
    var iso8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/;

    this.response = function (response) {
      convertToDate(response.data);
      return response;
    };

    function isIso8601(value) {
      return angular.isString(value) && iso8601.test(value);
    }

    function convertToDate(input) {
      if (!angular.isObject(input)) {
        return input;
      }

      angular.forEach(input, function (value, key) {
        if (isIso8601(value)) {
          input[key] = new Date(value);
        } else if (angular.isObject(value)) {
          convertToDate(value);
        }
      });
    }
  }

  config.$inject = ['$httpProvider'];
  function config($httpProvider) {
    $httpProvider.interceptors.push('angularDateInterceptor');
  }

})();

(function () {

  'use strict';

  angular.module('angularDateInterceptor', [])
    .service('angularDateInterceptor', AngularDateInterceptor)
    .config(config);

  function AngularDateInterceptor() {
    var iso8601 = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(([+-]\d\d:\d\d)|Z)?$/;

    this.response = function (response) {
      convertToDate(response.data);
      return response;
    };

    function isIso8601(value) {
      return angular.isString(value) && iso8601.test(value);
    }

    function convertToDate(input) {
      if (!angular.isObject(input)) {
        return input;
      }

      angular.forEach(input, function (value, key) {
        if (isIso8601(value)) {
          input[key] = new Date(value);
        } else if (angular.isObject(value)) {
          convertToDate(value);
        }
      });
    }
  }

  config.$inject = ['$httpProvider'];
  function config($httpProvider) {
    $httpProvider.interceptors.push('angularDateInterceptor');
  }

})();

Fix regexp to correctly detect dates


// Saves options to chrome.storage
function save_options() {
    chrome.storage.sync.set({
        enableOverlay: document.getElementById('overlay').checked
    }, () => {
        // Update status to let user know options were saved.
        const status = document.getElementById('status');
        status.textContent = 'Options saved.';
        setTimeout(() => {
            status.textContent = '';
        }, 750);
    });
}

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
    chrome.storage.sync.get({
        enableOverlay: false
    }, ({enableOverlay}) => {
        document.getElementById('overlay').checked = enableOverlay;
    });
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click',
    save_options);

let optionsOverlayNode = document.getElementById('overlay');
let optionsSaveBtn = document.getElementById('save');
let optionsStatus = document.getElementById('status');

// Saves options to chrome.storage
function save_options() {
    chrome.storage.sync.set({
        enableOverlay: optionsOverlayNode.checked
    }, () => {
        // Update status to let user know options were saved.
        optionsStatus.textContent = 'Options saved.';
        setTimeout(() => {
            optionsStatus.textContent = '';
        }, 750);
    });
}

// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
    chrome.storage.sync.get({
        enableOverlay: false
    }, ({enableOverlay}) => {
        optionsOverlayNode.checked = enableOverlay;
    });
}
document.addEventListener('DOMContentLoaded', restore_options);
optionsSaveBtn.addEventListener('click', save_options);

Save overlay node to global scope


import './style/UserMiniCard.scss';

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

import userPlaceholder from '../assets/userPlaceholder.png';

export default class UserMiniCard extends Component {
  render() {
    const { user, hideUsername, hideName } = this.props;
    // Why are we using div tag instead of span?
    // Double clicking selects everything including near span tags.
    // display: inline-block doesn't.
    return (

        <Link to={`/${user.username}`}>

          { !hideName ? (

              {user.name}

          ) : false }
          { !hideUsername ? (

              {user.username}

          ) : false }
        </Link>

    );
  }
}

UserMiniCard.propTypes = {
  user: PropTypes.object.isRequired,
  hideUsername: PropTypes.bool,
  hideName: PropTypes.bool
};

import './style/UserMiniCard.scss';

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

import userPlaceholder from '../assets/userPlaceholder.png';

export default class UserMiniCard extends Component {
  render() {
    const { user, hideUsername, hideName } = this.props;
    // Why are we using div tag instead of span?
    // Double clicking selects everything including near span tags.
    // display: inline-block doesn't.
    return (

        <Link to={`/${user.username}`}>

          { !hideName ? (

              {user.name || user.username}

          ) : false }
          { !hideUsername ? (

              {user.username}

          ) : false }
        </Link>

    );
  }
}

UserMiniCard.propTypes = {
  user: PropTypes.object.isRequired,
  hideUsername: PropTypes.bool,
  hideName: PropTypes.bool
};

Change to display username if there is no name


// MarkerController is reponsible for event handling such as moving to and opening the POI when selected.
class MarkerController {

    constructor(searchbar, markerController, mapController) {
        this._onPoiCardClickedCallback = null;

        this._eegeoPoiSourceIdMap = {};
        this._markerController = markerController;
        this._mapController = mapController;

        searchbar.on("searchresultselect", (event) => { this.goToResult(event); });
    }

    goToResult(event) {
        this._markerController.selectMarker(event.result.sourceId);
        this._mapController.getMap().setView(event.result.location.latLng, 15);
        this._openPoiView(event.result.resultId, event.result);
    }

    _openPoiView(markerId, poi) {
        // Here we create our POI view from the poi data. You may want to add your own POI view here instead.
        const element = PoiViewContainer(poi);
        let location = poi.location;
        const marker = this._markerController.getMarker(markerId);

        if (marker) {
            marker.closePopup();
        }

         this._mapController.addPopupWithOffsetForPois(location, element);

        const options = {
            durationSeconds: 0.0,
            allowInterruption: true
        }
    }
}

// MarkerController is reponsible for event handling such as moving to and opening the POI when selected.
class MarkerController {

    constructor(searchbar, markerController, mapController) {
        this._onPoiCardClickedCallback = null;

        this._eegeoPoiSourceIdMap = {};
        this._markerController = markerController;
        this._mapController = mapController;

        searchbar.on("searchresultselect", (event) => { this.goToResult(event); });
        searchbar.on("searchresultsupdate", (event)=> { this.updateMarkers(event) });
    }

    goToResult(event) {
        this._markerController.selectMarker(event.result.sourceId);
        this._mapController.getMap().setView(event.result.location.latLng, 15);
        this._openPoiView(event.result.resultId, event.result);
    }

    updateMarkers(event) {
        let markerIDs = this._markerController.getAllMarkerIds();
        for (var i = 0; i < markerIDs.length; ++i)
        {
            const marker = this._markerController.getMarker(markerIDs[i]);
            marker.on("click", () => {
                var id = marker.id;
                if(event.results[id])
                {
                    this._openPoiView(id, event.results[id]);
                }
            });
        }
    }

    _openPoiView(markerId, poi) {
        // Here we create our POI view from the poi data. You may want to add your own POI view here instead.
        const element = PoiViewContainer(poi);
        let location = poi.location;
        const marker = this._markerController.getMarker(markerId);

        if (marker) {
            marker.closePopup();
        }

         this._mapController.addPopupWithOffsetForPois(location, element);

        const options = {
            durationSeconds: 0.0,
            allowInterruption: true
        }
    }
}

Fix for MPLy-10287 Can now click on markers to open POIs. Buddy: Jean


var sys = require('sys');
var posix = require('posix');
var path = require('path');

var HTTP301MovedPermanently = require('bomberjs/lib/http_responses').HTTP301MovedPermanently;

exports.index = function(request, response) {
  return "index action";
};
exports.show = function(request, response) {
  if( request.params.id == 2 ) {
    return new HTTP301MovedPermanently('http://google.com');
  }
  else {
    return "show action";
  }
};

// can be accessed at /simple/lorem
exports.lorem = function(request, response) {
  // posix.cat returns a Node promise. Which at this time isn't chainable
  // so this example is pretty simple.  But once we can chain, I'll show
  // how to manipulate the result as you move through the chain.

  var filename = path.join(path.dirname(__filename),'../resources/lorem-ipsum.txt');
  return posix.cat(filename);
};

var sys = require('sys');
var posix = require('posix');
var path = require('path');

var HTTP301MovedPermanently = require('bomberjs/lib/http_responses').HTTP301MovedPermanently;

function htmlHead () {
    return '<title>Bomber.js example app</title>';
}
function htmlFoot () {
    return '';
}

exports.index = function(request, response) {
  var html = htmlHead();
  html += "
index action

See <tt>routes.js</tt> for more.
";
  html += "
Other actions include:
";
  html += "

section/1000
";
  html += "
section/2 (special case)
";
  html += "
lorem
";
  html += "
";
  html += htmlFoot();
  return html;
};
exports.show = function(request, response) {
  if( request.params.id == 2 ) {
    return new HTTP301MovedPermanently('http://google.com');
  }
  else {
    return htmlHead() + "show action" + htmlFoot();
  }
};

// can be accessed at /simple/lorem
exports.lorem = function(request, response) {
  // posix.cat returns a Node promise. Which at this time isn't chainable
  // so this example is pretty simple.  But once we can chain, I'll show
  // how to manipulate the result as you move through the chain.

  var filename = path.join(path.dirname(__filename),'../resources/text.txt');
  return posix.cat(filename);
};

Use HTML in example project and fix linking to wrong file


angular
  .module('app')
  .controller('featureEditController', [
    '$scope',
    '$location',
    '$routeParams',
    'userService',
    'featureService',
    'actionKitService',
    '$rootScope',
    function($scope, $location, $routeParams, userAPI, Feature, actionKitService, $rootScope) {
        $scope.feature = Feature.get({id: $routeParams.id}, function(feature) {
            return feature;
        });

        $scope.update = function() {
            $scope.feature.$update(function(response) {
                alert("Feature updated successfully");
                $location.path('/admin/features');
            });
        };
    }]);

angular
  .module('app')
  .controller('featureEditController', [
    '$scope',
    '$location',
    '$routeParams',
    'userService',
    'featureService',
    'actionKitService',
    'FormHelper',
    '$rootScope',
    function($scope, $location, $routeParams, userAPI, Feature, actionKitService, FormHelper, $rootScope) {
        $scope.feature = Feature.get({id: $routeParams.id}, function(feature) {
            return feature;
        });

        $scope.showError = FormHelper.showError;
        $scope.showSuccess = FormHelper.showSuccess;

        $scope.update = function() {
            // FormHelper.update(form, model, callback)
            FormHelper.update($scope.EditFeatureForm, $scope.feature, function() {
                alert("Feature updated successfully");
                $location.path('/admin/features');
            });
        };
    }]);

Update Edit Feature Form to use FormHelper service. Add validations and alerts.


import React from 'react';
import { Modal, Form, Button, Header } from 'semantic-ui-react';

const ReplyFormModal = () => {
  return (
    <Modal trigger={<Button>Contact Them!</Button>}>
      <Modal.Header>Contact Form</Modal.Header>
      <Modal.Content>
        <Modal.Description>
          <Header>Send Them A Message!</Header>
          <Form>
            <Form.Input
              name="title"
              label="Subject"
              placeholder="Subject"
            />
            <Form.TextArea
              name="body"
              label="Message"
              placeholder="Tell them who you are and why you are contacting them..."
            />
            <Form.Button>Send Message</Form.Button>
          </Form>
        </Modal.Description>
      </Modal.Content>
    </Modal>
  );
};

export default ReplyFormModal;

import React from 'react';
import { Modal, Form, Button, Header } from 'semantic-ui-react';

const ReplyFormModal = ({ onChange, onSubmit, listingId, userId }) =>
  <Modal trigger={<Button>Contact Them!</Button>}>
    <Modal.Header>Contact Form</Modal.Header>
    <Modal.Content>
      <Modal.Description>
        <Header>Send Them A Message!</Header>
        <Form onSubmit={e => onSubmit(e, listingId, userId)}>
          <Form.Input
            name="title"
            label="Subject"
            placeholder="Subject"
            onChange={e => onChange(e)}
          />
          <Form.TextArea
            name="body"
            label="Message"
            placeholder="Tell them who you are and why you are contacting them..."
            onChange={e => onChange(e)}
          />
          <Form.Button>Send Message</Form.Button>
        </Form>
      </Modal.Description>
    </Modal.Content>
  </Modal>;

ReplyFormModal.propTypes = {
  listingId: React.PropTypes.number.isRequired,
  userId: React.PropTypes.number.isRequired,
  onChange: React.PropTypes.func.isRequired,
  onSubmit: React.PropTypes.func.isRequired,
};

export default ReplyFormModal;

Implement ability to submit the reply form


'use strict'

const path = require('path')
const gulp = require('gulp')
const replace = require('gulp-replace')
const config = require('../config')
const NodeGitVersion = require('@hmrc/node-git-versioning')
let version = ''

const errorPages = (v) => {
  const nextMinorVersion = parseInt(NodeGitVersion().split('.')[1]) + 1

  if (process.env.JENKINS_URL === 'https://build.tax.service.gov.uk/') {
    version = [v.slice(1), nextMinorVersion, '0'].join('.')
  } else {
    version = path.parse(config.snapshotDir[v]).name
  }
  return gulp.src(config.errorPages.src)
    .pipe(replace('{{ assetsPath }}', `${config.errorPages.assetsBaseUri}${version}/`))
    .pipe(gulp.dest(config.snapshotDir[v]))
}

gulp.task('error-pages:v3', () => {
  return errorPages('v3')
})

gulp.task('error-pages:v4', () => {
  return errorPages('v4')
})

'use strict'

const path = require('path')
const gulp = require('gulp')
const replace = require('gulp-replace')
const config = require('../config')
const NodeGitVersion = require('@hmrc/node-git-versioning')
let version = ''

const errorPages = (v) => {
  const nextMinorVersion = parseInt(NodeGitVersion().split('.')[1]) + 1

  if (/https?:\/\/.*\.tax\.service\.gov\.uk\//.test(process.env.JENKINS_URL)) {
    version = [v.slice(1), nextMinorVersion, '0'].join('.')
  } else {
    version = path.parse(config.snapshotDir[v]).name
  }
  return gulp.src(config.errorPages.src)
    .pipe(replace('{{ assetsPath }}', `${config.errorPages.assetsBaseUri}${version}/`))
    .pipe(gulp.dest(config.snapshotDir[v]))
}

gulp.task('error-pages:v3', () => {
  return errorPages('v3')
})

gulp.task('error-pages:v4', () => {
  return errorPages('v4')
})

Use a regex to test for build servers


import React, { PropTypes } from 'react';

import SecretListItemFolderSecret from 'components/secrets/SecretListItem/Secret';
import SecretListBreadcrumb from 'components/secrets/SecretListBreadcrumb';

const propTypes = {
  folder: PropTypes.any,
};

function SecretListFolderInfo({ folder }) {
  return (

      {
        !folder.has('root') &&

              <SecretListBreadcrumb folders={folder.get('breadcrumb')} withTitle={false} />

      }
      {
        folder.get('secrets').map(secret => (
          <SecretListItemFolderSecret
            key={secret.id}
            secret={secret}
          />
        )).toArray()
      }

  );
}

SecretListFolderInfo.propTypes = propTypes;

export default SecretListFolderInfo;

import React, { PropTypes } from 'react';

import SecretListItemFolderSecret from 'components/secrets/SecretListItem/Secret';
import SecretListBreadcrumb from 'components/secrets/SecretListBreadcrumb';

const propTypes = {
  folder: PropTypes.any,
};

function SecretListFolderInfo({ folder }) {
  return (

      {
        !folder.has('root') &&

              <SecretListBreadcrumb folders={folder.get('breadcrumb')} withTitle={false} />

      }
      {
        folder.get('secrets').sortBy(secret => secret.get('title').toLowerCase()).map(secret => (
          <SecretListItemFolderSecret
            key={secret.id}
            secret={secret}
          />
        )).toArray()
      }

  );
}

SecretListFolderInfo.propTypes = propTypes;

export default SecretListFolderInfo;

Order secret behind folder breadcrumb in filtered views


import React from 'react';
import PropTypes from 'prop-types';
import Button from '../Button/Button';

const Card = ( { nounObject } ) => {
  const mappedNounObject = Object.keys( nounObject ).map( key => {
    return (

        {key}

        {nountObject[key]}

    )
  });

  return (

{nounObject[name]}

        <Button />

        {mappedNounObject}

  )
}

export default Card;

Add functionality to card to map over passed object and display definition list


Braintree = Npm.require('braintree');

BrainTreeConnect = function (key) {
  var BraintreeObj;
  var resources = {
    address : ['create', 'delete', 'find', 'update'],
    clientToken : ['generate'],
    customer : ['create', 'delete', 'find', 'search', 'update'],
    merchantAccount : ['create', 'find', 'update'],
    paymentMethod : ['create', 'delete', 'find', 'update'],
    paymentMethodNonce : ['create', 'find'],
    plan : ['all'],
    subscription : ['cancel', 'create', 'find', 'search', 'update'],
    transaction : ['cancelRelease', 'cloneTransaction', 'find', 'holdInEscrow', 'refund', 'releaseFromEscrow', 'sale', 'search', 'submitForSettlement', 'void']
  };

  BraintreeObj = Braintree.connect(key);

  _.each(resources, function (resource, key){
    _.each(resource, function (funcName){
      var func = BraintreeObj[key][funcName];
      BraintreeObj[key][funcName] = Meteor.wrapAsync(func, BraintreeObj[key]);
    });
  });

  return BraintreeObj;
};

BrainTreeConnect.Environment = Braintree.Environment;

Revert "Link Braintree.Environment to BrainTreeConnect.Environment"


// @flow
import memoize from "lru-memoize"

const fieldTypes = {
  context: "string",
  renderer: "function",
  feedsOptions: "object",
  feeds: "object",
  description: "object",
}

const validator = function(
  userConfig: Object = {}
): void {
  const errors = []

  Object.keys(userConfig).forEach((key) => {
    if (!fieldTypes[key]) {
      errors.push(
        `Unknow option '${ key }'.`
      )
    }
    else if (fieldTypes[key] !== typeof userConfig[key]) {
      errors.push(
        `Wrong type for '${ key }': expected '${ fieldTypes[key] }', ` +
        `got '${ typeof userConfig[key] }'.`
      )
    }
  })

  if (errors.length > 0) {
    throw new Error(
      `Your 'content-loader' config is invalid. Please fix the errors: \n- ` +
      errors.join("\n- ") +
      "\n\n" +
      "See 'Configuration' section in documentation."
    )
  }
}

export default memoize(10)(validator)

// @flow
import memoize from "lru-memoize"

const fieldTypes = {
  context: "string",
  renderer: "function",
  feedsOptions: "object",
  feeds: "object",
  description: "object",
  defaultHead: "object",
}

const validator = function(
  userConfig: Object = {}
): void {
  const errors = []

  Object.keys(userConfig).forEach((key) => {
    if (!fieldTypes[key]) {
      errors.push(
        `Unknow option '${ key }'.`
      )
    }
    else if (fieldTypes[key] !== typeof userConfig[key]) {
      errors.push(
        `Wrong type for '${ key }': expected '${ fieldTypes[key] }', ` +
        `got '${ typeof userConfig[key] }'.`
      )
    }
  })

  if (errors.length > 0) {
    throw new Error(
      `Your 'content-loader' config is invalid. Please fix the errors: \n- ` +
      errors.join("\n- ") +
      "\n\n" +
      "See 'Configuration' section in documentation."
    )
  }
}

export default memoize(10)(validator)

Add defaultHead to validated content-loader options


import React from 'react';
import LinkButton from "../LinkButton/LinkButton";
import classes from "./LoginButtonSet.module.css"

const LoginButtonSet = props => {

    return (
        <div className={classes.ButtonSetContainer}>
            <LinkButton inverted>What is sGonks?</LinkButton>
            <LinkButton>Sign in</LinkButton>
        </div>
    )
}

export default LoginButtonSet;

import React from "react"
import LinkButton from "../LinkButton/LinkButton"
import classes from "./LoginButtonSet.module.css"

const LoginButtonSet = (props) => {
  const fetchUserData = () => {
    fetch("./authentication")
      .then((response) => (response.json()))
      .then((data) => {
        console.log(data)
      })
  }

  return (
    <div className={classes.ButtonSetContainer}>
      <LinkButton inverted>What is sGonks?</LinkButton>
      <LinkButton onClick={() => fetchUserData()} to="/page">Sign in</LinkButton>
    </div>
  )
}

export default LoginButtonSet

Add fetch function to login button set


import React from 'react';

import IconButton from '@ichef/gypcrete/src/IconButton';
import FlexRow from 'utils/FlexRow';

export default {
    title: '@ichef/gypcrete|IconButton',
    component: IconButton,
};

export function BasicUsage() {
    return (
        <IconButton icon="printer" />
    );
}

export function WithStatus() {
    return (
        <FlexRow>
            <IconButton
                icon="printer"
                status="loading" />

            <IconButton
                icon="printer"
                status="success"
                statusOptions={{ autohide: false }} />

            <IconButton
                icon="printer"
                status="error"
                errorMsg="Cannot add printer." />
        </FlexRow>
    );
}

import React from 'react';

import Button from '@ichef/gypcrete/src/Button';
import IconButton from '@ichef/gypcrete/src/IconButton';
import FlexRow from 'utils/FlexRow';

export default {
    title: '@ichef/gypcrete|IconButton',
    component: IconButton,
    subcomponents: {
        Button,
    },
};

export function BasicUsage() {
    return (
        <FlexRow>
            <IconButton icon="printer" />
        </FlexRow>
    );
}

export function WithStatus() {
    return (
        <FlexRow>
            <IconButton
                icon="printer"
                status="loading" />

            <IconButton
                icon="printer"
                status="success"
                statusOptions={{ autohide: false }} />

            <IconButton
                icon="printer"
                status="error"
                errorMsg="Cannot add printer." />
        </FlexRow>
    );
}

Add Button into IconButton storybook subcomponents


"use strict";

angular.module('arethusa.search').directive('searchByString', [
  'search',
  function(search) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        scope.search = search;
      },
      templateUrl: 'templates/arethusa.search/search_by_string.html'
    };
  }
]);

"use strict";

angular.module('arethusa.search').directive('searchByString', [
  'search',
  'state',
  function(search, state) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        scope.search = search;
        scope.state = state;

        // Mind that the following watches aren't active all the time!
        // When it is used from within the search plugins template, and it
        // is inactive through ngIf, they won't fire. This is generally a good
        // thing and we won't delete this code: We can still use it on isolation,
        // or we might at some point display it together with plugins that can
        // edit text.
        // Right now only the artificialToken plugin does this: Both are never
        // shown at the same time, which means this watches actually never fire
        // right now.

        var stringWatches = {};
        function initStringWatch(token, id) {
          var childScope = scope.$new();
          childScope.token = token;
          childScope.$watch('token.string', function(newVal, oldVal) {
            if (newVal !== oldVal) {
              search.removeTokenFromIndex(token.id, oldVal);
              search.collectTokenString(search.strings, id, token);
            }
          });
          stringWatches[id] = childScope;
        }

        function initStringWatches() {
          angular.forEach(state.tokens, initStringWatch);
        }

        function removeStringWatch(scope) {
          scope.$destroy();
        }

        function destroyStringWatch() {
          angular.forEach(stringWatches, removeStringWatch);
          stringWatches = {};
        }

        scope.$watch('state.tokens', function(newVal, oldVal) {
          initStringWatches();
        });
      },
      templateUrl: 'templates/arethusa.search/search_by_string.html'
    };
  }
]);

Add watches to searchByString directive


import template from './customer-threshold.html';

const customerThreshold = {
  template: template,
  bindings: {
    horizontal: '<',
    priceEstimate: '=',
    toggleHardLimit: '&',
  },
  controller: class CustomerThresholdController {
    // @ngInject
    constructor(ENV, customerUtils) {
      this.currency = ENV.currency;
      this.isHardLimit = customerUtils.isHardLimit(this.priceEstimate);
    }

    validateThreshold() {
      let isValid = this.priceEstimate.threshold >= this.priceEstimate.total;
      this.thresholdForm.threshold.$setValidity('exceedsThreshold', isValid);
    }
  }
};

export default customerThreshold;

import template from './customer-threshold.html';

const customerThreshold = {
  template: template,
  bindings: {
    horizontal: '<',
    priceEstimate: '=',
    toggleHardLimit: '&',
  },
  controller: class CustomerThresholdController {
    // @ngInject
    constructor(ENV, customerUtils) {
      this.currency = ENV.currency;
      this.customerUtils = customerUtils;
    }

    $onInit() {
      this.isHardLimit = this.customerUtils.isHardLimit(this.priceEstimate);
    }

    validateThreshold() {
      let isValid = this.priceEstimate.threshold >= this.priceEstimate.total;
      this.thresholdForm.threshold.$setValidity('exceedsThreshold', isValid);
    }
  }
};

export default customerThreshold;

Initialize isHardLimit in the right place


// # Gruntfile
// Automation for the project.

'use strict';

var GruntConfig = require('thehelp-project').GruntConfig;

// We simply create an instance of the `GruntConfig` class from
// `thehelp-project`, then call the register functions we need.
module.exports = function(grunt) {
  var config = new GruntConfig(grunt);

  config.standardSetup();
  config.standardDefault();
};

// # Gruntfile
// Automation for the project.

'use strict';

var GruntConfig = require('thehelp-project').GruntConfig;

// We simply create an instance of the `GruntConfig` class from
// `thehelp-project`, then call the register functions we need.
module.exports = function(grunt) {
  var config = new GruntConfig(grunt);

  config.standardSetup();
  config.standardDefault();

  grunt.config('shell.integration', {
    command: 'cd test/default && ./run.sh && ./clean.sh'
  });

  var tasks = config.defaultTasks.concat(['shell:integration']);
  grunt.registerTask('default', tasks);
};

Add integration test to default grunt task


module.exports = function(grunt) {
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        concat: {
            all: {
                src: [
                    'src/module.js',
                    'src/*/**/*.js'
                ],
                dest: 'build/ng-drag.js'
            }
        },
        uglify: {
            drag: {
                files: {
                    'build/ng-drag.min.js': ['build/ng-drag.js']
                }
            }
        },
        ngAnnotate: {
            survey: {
                files: { 'build/ng-drag.js': ['build/ng-drag.js'] }
            }
        },
        watch: {
            all: {
                files: ['src/**/*.js'],
                tasks: ['build'],
                options: { spawn: false }
            }
        },
        ngdocs: {
          options: {
            dest: 'docs'
          },
          ngdrag: {
            src: ['src/**/*.js'],
            title: 'ngDrag'
          }
        }
    });

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

    grunt.registerTask('default', 'build');
};

module.exports = function(grunt) {
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        concat: {
            all: {
                src: [
                    'src/module.js',
                    'src/*/**/*.js'
                ],
                dest: 'build/ng-drag.js'
            }
        },
        uglify: {
            drag: {
                files: {
                    'build/ng-drag.min.js': ['build/ng-drag.js']
                }
            }
        },
        ngAnnotate: {
            survey: {
                files: { 'build/ng-drag.js': ['build/ng-drag.js'] }
            }
        },
        watch: {
            all: {
                files: ['src/**/*.js'],
                tasks: [
                  'build',
                  'docs'
                ],
                options: { spawn: false }
            }
        },
        ngdocs: {
          options: {
            dest: 'docs'
          },
          ngdrag: {
            src: ['src/**/*.js'],
            title: 'ngDrag'
          }
        }
    });

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

    grunt.registerTask('default', 'build');

    grunt.registerTask('docs', [
      'ngdocs'
    ]);
};

Update tasks to generate docs on watch


import React, { Component } from 'react';

import Sessions from './Sessions';
import Empty from './Empty';
import Create from './Create';

class Index extends Component {
  state = {
    page: 0
  };

  switchPage(page) {
    this.setState({ page });
  }

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

    switch (page) {
      case 0:
        return <Sessions />;
      case 1:
        return <Empty />;
      case 2:
        return <Create />;
      default:
        break;
    }
  }
}

export default Index;

import React, { Component } from 'react';
import { array } from 'prop-types';

import Sessions from './Sessions';
import Empty from './Empty';
import Create from './Create';

class Index extends Component {
  state = {
    page: this.props.sessions.length > 0 ? 0 : 1
  };

  switchPage(page) {
    this.setState({ page });
  }

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

    switch (page) {
      case 0:
        return <Sessions />;
      case 1:
        return <Empty />;
      case 2:
        return <Create />;
      default:
        break;
    }
  }
}

Index.propTypes = {
  sessions: array
};

Index.defaultProps = {
  sessions: []
};

export default Index;

Add prop types + fixes


/*
 * HomePage
 *
 * This is the first thing users see of our App, at the '/' route
 *
 * NOTE: while this component should technically be a stateless functional
 * component (SFC), hot reloading does not currently support SFCs. If hot
 * reloading is not a neccessity for you then you can refactor it and remove
 * the linting exception.
 */

import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectPlugins } from 'containers/App/selectors';
import LeftMenu from 'containers/LeftMenu';
import Header from 'components/Header/index';
import Content from 'containers/Content';
import styles from './syles.scss';

export class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function
  render() {
    return (

        <LeftMenu {...this.props}></LeftMenu>

          <Content {...this.props}></Content>

    );
  }
}

HomePage.propTypes = {
  plugins: React.PropTypes.object,
};

const mapStateToProps = createStructuredSelector({
  plugins: selectPlugins(),
});

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
  };
}

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

/*
 * HomePage
 *
 * This is the first thing users see of our App, at the '/' route
 *
 * NOTE: while this component should technically be a stateless functional
 * component (SFC), hot reloading does not currently support SFCs. If hot
 * reloading is not a neccessity for you then you can refactor it and remove
 * the linting exception.
 */

import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectPlugins } from 'containers/App/selectors';
import LeftMenu from 'containers/LeftMenu';
import Header from 'components/Header/index';
import Content from 'containers/Content';
import styles from './syles.scss';

export class HomePage extends React.Component { // eslint-disable-line react/prefer-stateless-function
  render() {
    return (

        <LeftMenu {...this.props}></LeftMenu>

          <Content {...this.props}>

                Welcome to your Strapi application admin panel!

          </Content>

    );
  }
}

HomePage.propTypes = {
  plugins: React.PropTypes.object,
};

const mapStateToProps = createStructuredSelector({
  plugins: selectPlugins(),
});

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
  };
}

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

Add welcome message in admin panel


import { highlight } from 'highlight.js';

export default function highlightCode(text) {
  try {
    const doc = new DOMParser().parseFromString(text, 'text/html');

    [].forEach.call(doc.querySelectorAll('code'), (el) => {
      el.classList.add('hljs');
      if (el.dataset.language && !el.dataset.highlighted) {
        try {
          el.innerHTML = highlight(el.dataset.language, el.innerText).value;
          el.dataset.highlighted = true;
        } catch(e) {
          // unsupported syntax for highlight.js
        }
      }
    });

    return doc.body.innerHTML;
  } catch(e) {
    return text;
  }
};

import { highlight } from 'highlight.js';

export default function highlightCode(text) {
  try {
    const doc = new DOMParser().parseFromString(text, 'text/html');

    [].forEach.call(doc.querySelectorAll('code'), (el) => {
      el.classList.add('hljs');
      if (el.dataset.language && !el.dataset.highlighted) {
        try {
          el.innerHTML = highlight(el.dataset.language, getTextContent(el)).value;
          el.dataset.highlighted = true;
        } catch(e) {
          // unsupported syntax for highlight.js
        }
      }
    });

    return doc.body.innerHTML;
  } catch(e) {
    return text;
  }
};

function getTextContent(el) {
  const contentEl = document.createElement('div');
  contentEl.innerHTML = el.innerHTML.replace(/<br\s*\/?>/g, "\n");
  return contentEl.textContent;
}

Fix code highlighter not to delete line breaks for Firefox


(function (module) {
    module.controller('projectViewProcess', projectViewProcess);
    projectViewProcess.$inject = ["$state", "process"];

    function projectViewProcess($state, process) {
        var ctrl = this;
        ctrl.editProvenance = editProvenance;
        ctrl.process = process;

        function editProvenance() {
            $state.go('projects.project.processes.edit', {process_id: ctrl.process.id});
        }
    }
}(angular.module('materialscommons')));

(function (module) {
    module.controller('projectViewProcess', projectViewProcess);
    projectViewProcess.$inject = ["process"];

    function projectViewProcess(process) {
        var ctrl = this;
        ctrl.process = process;
    }
}(angular.module('materialscommons')));

Remove editProvenance and replace with ui-sref.


#!/usr/bin/env node

var program = require('commander')
var command
var env
program
    .usage('[options]')
    .option('-f, --file [file]', 'config file - defaults to testem.json or testem.yml')
    .option('-p, --port [num]', 'server port - defaults to 7357', Number)
    .option('-l, --launch [list]', 'list of launchers to launch(comma separated)')
    .option('-s, --skip [list]', 'list of launchers to skip(comma separated)')
    .option('-d, --debug', 'output debug to debug log - testem.log')

program
    .command('launchers')
    .description('Print the list of available launchers (browsers & process launchers)')
    .action(function(e){
        env = e
        console.log(env)

    })

program
    .command('ci')
    .description('Continuous integration mode')
    .option('-t, --timeout [sec]', 'timeout a browser after [sec] seconds', null)
    .action(function(e){
        env = e
        console.log(env.skip)
        command = 'ci'
    })

program.parse(process.argv)

console.log(program)

#!/usr/bin/env node

var program = require('commander')
var command
var env
program
    .usage('[options]')
    .option('-f, --file [file]', 'config file - defaults to testem.json or testem.yml')
    .option('-p, --port [num]', 'server port - defaults to 7357', Number)
    .option('-l, --launch [list]', 'list of launchers to launch(comma separated)')
    .option('-s, --skip [list]', 'list of launchers to skip(comma separated)')
    .option('-d, --debug', 'output debug to debug log - testem.log')

program
    .command('launchers')
    .description('Print the list of available launchers (browsers & process launchers)')
    .action(function(e){
        env = e
        console.log(env)

    })

program
    .command('ci')
    .description('Continuous integration mode')
    .option('-t, --timeout [ms]', 'timeout a browser after [ms] milliseconds', null)
    .action(function(e){
        env = e
        console.log(env.skip)
        command = 'ci'
    })

program.parse(process.argv)

console.log(program)

Update cli help to say milliseconds


var IndoorWatermarkController = function() {
  var _indoorWatermarkElement = null;
  var _elementId = "eegeo-indoor-map-watermark";
  var _urlRoot = "https://cdn-webgl.eegeo.com/eegeojs/resources/indoor-vendors/";

  var _buildUrlForVendor = function(vendorKey) {
    return _urlRoot + vendorKey + "_logo.png";
  };

  var _precacheKnownVendors = function() {
    var knownVendors = ["eegeo", "micello"];

    knownVendors.forEach(function(vendor) {
      var vendorImageUrl = _buildUrlForVendor(vendor);
      var tempImage = new Image();
      tempImage.src = vendorImageUrl;
    });
  };

  _precacheKnownVendors();

  this.showWatermarkForVendor = function(vendorKey) {
    var imageUrl = _buildUrlForVendor(vendorKey);

    if (_indoorWatermarkElement === null) {
      _indoorWatermarkElement = document.getElementById(_elementId);
    }

    _indoorWatermarkElement.src = imageUrl;
    _indoorWatermarkElement.style.bottom = 0;
  };

  this.hideWatermark = function() {
    _indoorWatermarkElement.style.bottom = "-50px";
  };
};

module.exports = IndoorWatermarkController;

var IndoorWatermarkController = function() {
  var _indoorWatermarkElement = null;
  var _elementId = "eegeo-indoor-map-watermark";
  var _urlRoot = "https://cdn-webgl.eegeo.com/eegeojs/resources/indoor-vendors/";

  var _buildUrlForVendor = function(vendorKey) {
    var vendorKeyLower = vendorKey.toLowerCase();
    if (vendorKeyLower === "eegeo")
    {
        vendorKeyLower = "wrld";
    }
    return _urlRoot + vendorKeyLower + "_logo.png";
  };

  var _precacheKnownVendors = function() {
    var knownVendors = ["eegeo", "micello"];

    knownVendors.forEach(function(vendor) {
      var vendorImageUrl = _buildUrlForVendor(vendor);
      var tempImage = new Image();
      tempImage.src = vendorImageUrl;
    });
  };

  _precacheKnownVendors();

  this.showWatermarkForVendor = function(vendorKey) {
    var imageUrl = _buildUrlForVendor(vendorKey);

    if (_indoorWatermarkElement === null) {
      _indoorWatermarkElement = document.getElementById(_elementId);
    }

    _indoorWatermarkElement.src = imageUrl;
    _indoorWatermarkElement.style.bottom = 0;
  };

  this.hideWatermark = function() {
    _indoorWatermarkElement.style.bottom = "-50px";
  };
};

module.exports = IndoorWatermarkController;

Watermark for WRLD branding. Also fixes a bug where the micello logo is not displayed due to case sensitivity. Pair: Malc


'use strict'

/* @flow */

import type {Disposable} from './disposable'

export class CompositeDisposable {
  disposed: boolean;
  disposables: Set<Disposable>;

  constructor(){
    this.disposed = false
    this.disposables = new Set(arguments)
  }
  add(){
    if (!this.disposed) {
      let length = arguments.length
      for (let i = 0; i < length; ++i) {
        this.disposables.add(arguments[i])
      }
    }
  }
  remove(){
    if (!this.disposed) {
      let length = arguments.length
      for (let i = 0; i < length; ++i) {
        this.disposables.delete(arguments[i])
      }
    }
  }
  clear(){
    if (!this.disposed) {
      this.disposables.clear()
    }
  }
  isDisposed(): boolean {
    return this.disposed
  }
  dispose(){
    if (!this.disposed) {
      this.disposed = true
      this.disposables.forEach(function(item) {
        item.dispose()
      })
      this.disposables.clear()
    }
  }
}

'use strict'

/* @flow */

import type {Disposable} from './disposable'

export class CompositeDisposable {
  disposed: boolean;
  disposables: Set<Disposable>;

  constructor(){
    this.disposed = false
    this.disposables = new Set(arguments)
  }
  add(){
    if (!this.disposed) {
      let length = arguments.length
      for (let i = 0; i < length; ++i) {
        this.disposables.add(arguments[i])
      }
    }
  }
  remove(){
    if (!this.disposed) {
      let length = arguments.length
      for (let i = 0; i < length; ++i) {
        this.disposables.delete(arguments[i])
      }
    }
  }
  clear(){
    if (!this.disposed) {
      this.disposables.clear()
    }
  }
  isDisposed(): boolean {
    return this.disposed
  }
  dispose(){
    if (!this.disposed) {
      for (const item of this.disposables) {
        item.dispose()
      }
      this.disposed = true
      this.disposables.clear()
    }
  }
}

Use iterator instead of forEach


import {
  MARKERS_SUBMIT_SUCCESS,
} from '../actions/notifications';

const initialState = ImmutableMap({
  home: '0',
  notifications: '0',
});

import { Map as ImmutableMap } from 'immutable';

export default function markers(state = initialState, action) {
  switch(action.type) {
  case MARKERS_SUBMIT_SUCCESS:
    if (action.home) {
      state = state.set('home', action.home);
    }
    if (action.notifications) {
      state = state.set('notifications', action.notifications);
    }
    return state;
  default:
    return state;
  }
};

import {
  MARKERS_SUBMIT_SUCCESS,
} from '../actions/markers';

const initialState = ImmutableMap({
  home: '0',
  notifications: '0',
});

import { Map as ImmutableMap } from 'immutable';

export default function markers(state = initialState, action) {
  switch(action.type) {
  case MARKERS_SUBMIT_SUCCESS:
    if (action.home) {
      state = state.set('home', action.home);
    }
    if (action.notifications) {
      state = state.set('notifications', action.notifications);
    }
    return state;
  default:
    return state;
  }
};

Fix read marker state not being udpated internally


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

module.exports = {
  name: 'ember-simple-auth',

  included: function(app) {
    this._super.included(app);

    app.import('vendor/ember-simple-auth/register-version.js');
  }
};

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

module.exports = {
  name: 'ember-simple-auth',

  included: function(app) {
    this._super.included.apply(this, arguments);

    // see: https://github.com/ember-cli/ember-cli/issues/3718
    if (typeof app.import !== 'function' && app.app) {
      app = app.app;
    }

    app.import('vendor/ember-simple-auth/register-version.js');
  }
};

Allow for nesting ember-simple-auth in another addon


openerp.web_list_view_sticky = function (instance) {
    var _t = instance.web._t,
        _lt = instance.web._lt;
    var QWeb = instance.web.qweb;

    //Sticky Table Header
    instance.web.ListView.include({
        load_list: function () {
            var self = this;
            self._super.apply(this, arguments);
            var offset = self.$el.parents('.oe_view_manager.oe_view_manager_current').find('.oe_view_manager_header').offset().top;
            var scrollArea = self.$el.parents('.oe_view_manager.oe_view_manager_current').find('.oe_view_manager_wrapper .oe_view_manager_body')[0];
            self.$el.find('table.oe_list_content').each(function(){
                $(this).stickyTableHeaders({scrollableArea: scrollArea})
            });
        },
    });
};

openerp.web_list_view_sticky = function (instance) {
    var _t = instance.web._t,
        _lt = instance.web._lt;
    var QWeb = instance.web.qweb;

    //Sticky Table Header
    instance.web.ListView.include({
        load_list: function () {
            var self = this;
            self._super.apply(this, arguments);
            var scrollArea = self.$el.parents('.oe_view_manager.oe_view_manager_current').find('.oe_view_manager_wrapper .oe_view_manager_body')[0];
            if(scrollArea){
                self.$el.find('table.oe_list_content').each(function(){
                    $(this).stickyTableHeaders({scrollableArea: scrollArea, leftOffset: scrollArea})
                });
            }
        },
    });
};

Update LeftOffset to current parent left offset


var osa = require('osa2')

function play(uri) {
    if (uri)
        return playTrack(uri)
    return osa(() => Application('Spotify').play())()
}

function playTrack(uri) {
    return osa((uri) => Application('Spotify').playTrack(uri))(uri)
}

function pause() {
    return osa(() => Application('Spotify').pause())()
}

function toggle() {
    return osa(() => Application('Spotify').playpause())()
}

function next() {
    return osa(() => Application('Spotify').nextTrack())()
}

function prev() {
    return osa(() => Application('Spotify').previousTrack())()
}

function getCurrentTrack() {
    return osa(() => {
        var track = Application('Spotify').currentTrack
        return {
            id: track.id(),
            name: track.name(),
            album: track.album(),
            artist: track.artist(),
            duration: track.duration(),
            artwork: track.artworkUrl(),
            popularity: track.popularity()
        }
    })()
}

function getPlayerState() {
    return osa(() => {
        var s = Application('Spotify')
        return {
            state: s.playerState(),
            volume: s.soundVolume(),
            repeating: s.repeating(),
            shuffling: s.shuffling(),
        }
    })()
}

module.exports = {
    play, pause, toggle, next, prev,
    getCurrentTrack, getPlayerState
}

var osa = require('osa2')

function play(uri) {
    if (uri)
        return playTrack(uri)
    return osa(() => Application('Spotify').play())()
}

function playTrack(uri) {
    return osa((uri) => Application('Spotify').playTrack(uri))(uri)
}

function pause() {
    return osa(() => Application('Spotify').pause())()
}

function toggle() {
    return osa(() => Application('Spotify').playpause())()
}

function next() {
    return osa(() => Application('Spotify').nextTrack())()
}

function prev() {
    return osa(() => Application('Spotify').previousTrack())()
}

function getCurrentTrack() {
    return osa(() => {
        var track = Application('Spotify').currentTrack
        return {
            id: track.id(),
            name: track.name(),
            album: track.album(),
            artist: track.artist(),
            duration: track.duration(),
            artwork: track.artworkUrl(),
            popularity: track.popularity()
        }
    })()
}

function getPlayerState() {
    return osa(() => {
        var s = Application('Spotify')
        return {
            state: s.playerState(),
            volume: s.soundVolume(),
            position: s.playerPosition(),
            repeating: s.repeating(),
            shuffling: s.shuffling(),
        }
    })()
}

module.exports = {
    play, pause, toggle, next, prev,
    getCurrentTrack, getPlayerState
}

Include player position in `getCurrentState`


var extensions = {
  '.co': 'coco',
  '.coffee': 'coffee-script/register',
  '.csv': 'require-csv',
  '.iced': 'iced-coffee-script/register',
  '.ini': 'require-ini',
  '.js': null,
  '.json': null,
  '.litcoffee': 'coffee-script/register',
  '.ls': 'livescript',
  '.toml': 'toml-require',
  '.xml': 'require-xml',
  '.yaml': 'require-yaml',
  '.yml': 'require-yaml'
};

var register = {
  'toml-require': function (module) {
    module.install();
  }
};

var jsVariantExtensions = [
  '.co',
  '.coffee',
  '.iced',
  '.js',
  '.litcoffee',
  '.ls'
];

module.exports = {
  extensions: extensions,
  register: register,
  jsVariants: jsVariantExtensions.reduce(function (result, ext) {
    result[ext] = extensions[ext];
    return result;
  }, {})
};

var extensions = {
  '.co': 'coco',
  '.coffee': 'coffee-script/register',
  '.csv': 'require-csv',
  '.iced': 'iced-coffee-script/register',
  '.ini': 'require-ini',
  '.js': null,
  '.json': null,
  '.litcoffee': 'coffee-script/register',
  '.ls': 'LiveScript',
  '.toml': 'toml-require',
  '.xml': 'require-xml',
  '.yaml': 'require-yaml',
  '.yml': 'require-yaml'
};

var register = {
  'toml-require': function (module) {
    module.install();
  }
};

var jsVariantExtensions = [
  '.co',
  '.coffee',
  '.iced',
  '.js',
  '.litcoffee',
  '.ls'
];

module.exports = {
  extensions: extensions,
  register: register,
  jsVariants: jsVariantExtensions.reduce(function (result, ext) {
    result[ext] = extensions[ext];
    return result;
  }, {})
};

Change casing from livescript to LiveScript


var express = require('express');
var fs = require('fs');
var app = express();
var storagePath = "./toilet_status.txt";

app.set('port', (process.env.PORT || 5000));
app.use(express.static(__dirname + '/public'));

app.get('/update', function(req, res) {
  console.log(new Date().toString(), "Received update:", req.params.status);
  fs.writeFileSync(storagePath, JSON.stringify({ status: req.params.status }));
  res.json({ success: true });
});

app.get('/status', function(req, res) {
  try {
    var data = fs.readFileSync(storagePath);
    res.json(JSON.parse(data));
  } catch(e) {
    res.json({ status: "no data yet" });
  }
});

app.listen(app.get('port'), function() {
  console.log("Node app is running at localhost:" + app.get('port'));
});

var express = require('express');
var fs = require('fs');
var app = express();
var storagePath = "./toilet_status.txt"; // TODO: permission denied in /data

app.set('port', (process.env.PORT || 5000));
app.use(express.static(__dirname + '/public'));

app.get('/update', function(req, res) {
  console.log(new Date().toString(), "Received update:", req.query.status);
  fs.writeFileSync(storagePath, JSON.stringify({ status: req.query.status }));
  res.json({ success: true });
});

app.get('/status', function(req, res) {
  try {
    var data = fs.readFileSync(storagePath);
    res.json(JSON.parse(data));
  } catch(e) {
    res.json({ status: "no data yet" });
  }
});

app.listen(app.get('port'), function() {
  console.log("Node app is running at localhost:" + app.get('port'));
});

Use query rather than params to read GET params


class Book {
  logSomething() {
    console.log( 'Book.logSomething has been called' );
  }
}

module.exports = Book;

export default class {
  logSomething() {
    console.log( 'Book.logSomething has been called' );
  }
}

Change pure ES6 class declaration syntax for consistency with React classes


'use strict';
var filenameReservedRegex = require('filename-reserved-regex');

module.exports = function (str) {
	return str.length <= 255 && !filenameReservedRegex().test(str);
};

'use strict';
var filenameReservedRegex = require('filename-reserved-regex');

module.exports = function (str) {
	return str && (str.length <= 255 && !filenameReservedRegex().test(str));
};

Fix bug that throws error for empty string


require('dotenv').config();

const vorpal = require('vorpal')();
const importer = require('./src/modules/importer');

vorpal
    .command('import', 'Launch the import script')
    .action(function(args, next) {
        importer.run(
            process.env.WP_JSON_ENDPOINT,
            process.env.WP_USER,
            process.env.WP_PASSWORD
        );
        next();
    });

vorpal
    .delimiter('WPimport')
    .show();

require('dotenv').config();

const vorpal    = require('vorpal')();
const importer  = require('./src/modules/importer');

vorpal
    .command('import', 'Launch the import script')
    .action(function(args, next) {
        importer.auth(
            process.env.WP_JSON_ENDPOINT,
            process.env.WP_USER,
            process.env.WP_PASSWORD
        );
        importer.run( args.start || 0, args.nb || 1);
        next();
    });

vorpal
    .delimiter('WPimport')
    .show();

Add start and nb argument to cli


/**
 * Module dependencies.
 */

var matches = require('matches-selector')
  , event = require('event');

/**
 * Delegate event `type` to `selector`
 * and invoke `fn(e)`. A callback function
 * is returned which may be passed to `.unbind()`.
 *
 * @param {Element} el
 * @param {String} selector
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.bind = function(el, selector, type, fn, capture){
  return event.bind(el, type, function(e){
    if (matches(e.target, selector)) fn(e);
  }, capture);
};

/**
 * Unbind event `type`'s callback `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @api public
 */

exports.unbind = function(el, type, fn, capture){
  event.unbind(el, type, fn, capture);
};

/**
 * Module dependencies.
 */

var matches = require('matches-selector')
  , event = require('event');

/**
 * Delegate event `type` to `selector`
 * and invoke `fn(e)`. A callback function
 * is returned which may be passed to `.unbind()`.
 *
 * @param {Element} el
 * @param {String} selector
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @return {Function}
 * @api public
 */

exports.bind = function(el, selector, type, fn, capture){
  return event.bind(el, type, function(e){
    if (matches(e.target, selector)) fn.call(el, e);
  }, capture);
};

/**
 * Unbind event `type`'s callback `fn`.
 *
 * @param {Element} el
 * @param {String} type
 * @param {Function} fn
 * @param {Boolean} capture
 * @api public
 */

exports.unbind = function(el, type, fn, capture){
  event.unbind(el, type, fn, capture);
};

Use `call()` to correctly assign `this` in handler


/**

 Copyright 2015 Covistra Technologies Inc.

 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.
 */

"use strict";
var P = require('bluebird');

exports.register = function (server, options, next) {

    server.dependency(['covistra-socket'], function(plugin, done) {
        plugin.log(['plugin', 'info'], "Registering the messaging plugin");

        var Router = server.plugins['covistra-system'].Router;
        var config = server.plugins['hapi-config'].CurrentConfiguration;
        var systemLog = server.plugins['system'].systemLog;

        // Expose our services
        plugin.expose('channelManager', require('./lib/channel-manager')(config, server, systemLog.child({service:'channel-manager'})));
        plugin.expose('workerService', require('./lib/worker-service')(config, server, systemLog.child({service:'worker-service'})));

        // Register routes
        Router.routes(plugin, __dirname, "./routes");

        done();
    });

    next();
};

exports.register.attributes = {
    pkg: require(__dirname + '/package.json')
};

/**

 Copyright 2015 Covistra Technologies Inc.

 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.
 */

"use strict";
var P = require('bluebird');

exports.register = function (server, options, next) {

    server.dependency(['covistra-socket'], function(plugin, done) {
        plugin.log(['plugin', 'info'], "Registering the messaging plugin");

        var Router = server.plugins['covistra-system'].Router;
        var config = server.plugins['hapi-config'].CurrentConfiguration;
        var systemLog = server.plugins['covistra-system'].systemLog;

        // Expose our services
        plugin.expose('channelManager', require('./lib/channel-manager')(config, server, systemLog.child({service:'channel-manager'})));
        plugin.expose('workerService', require('./lib/worker-service')(config, server, systemLog.child({service:'worker-service'})));

        // Register routes
        Router.routes(plugin, __dirname, "./routes");

        done();
    });

    next();
};

exports.register.attributes = {
    pkg: require(__dirname + '/package.json')
};

Add missing reference to covistra system


'use strict';

const rest = require('midwest/middleware/rest');
const formatQuery = require('midwest/middleware/format-query');
const paginate = require('midwest/middleware/paginate');

const Permission = require('./model');

module.exports = Object.assign(rest(Permission), {
  formatQuery: formatQuery(['limit', 'sort', 'page']),
  paginate: paginate(Permission, 20),
});

'use strict';

const rest = require('midwest/middleware/rest');
const formatQuery = require('midwest/middleware/format-query');
const paginate = require('midwest/middleware/paginate');

const Permission = require('./model');

const mw = rest(Permission);

module.exports = Object.assign({}, mw, {
  formatQuery: formatQuery(['limit', 'sort', 'page']),
  paginate: paginate(Permission, 20),
  findById: (req, res, next) => {
    mw.findById(req, res, (err) => {
      if (err) return next(err);

      if (res.locals.permission) {
        res.locals.permission.regex = res.locals.permission.regex.source;
      }

      next();
    });
  },
  query: (req, res, next) => {
    mw.query(req, res, (err) => {
      if (err) return next(err);

      res.locals.permissions.forEach((permission) => {
        permission.regex = permission.regex.source;
      });

      next();
    });
  },
});

Convert RegExp to strings by using RegExp.prototype.source


/**
 * Migrates the database from one version to another.
 */
function migrateStorage() {
	var extensionVersion = getExtensionVersion().major;

	getStorageVersion(function(storageVersion) {
		if (extensionVersion != storageVersion) {
			console.log("Extension and storage versions are not equal", extensionVersion, storageVersion);
			setStorageVersion(extensionVersion);

			if (storageVersion == 1) {
				if (extensionVersion == 2) {
					// Migrate from a database from 1.x.x to 2.x.x
					migrate_1_to_2(function() {
						console.log("Migration from 1.x.x to 2.x.x complete");
					});
				}
			}
		}
	});
}

/**
 * Migrates the database from one version to another.
 */
function migrateStorage() {
	var extensionVersion = getExtensionVersion().major;

	console.log("Checking to see if a migration is needed...");

	getStorageVersion(function(storageVersion) {
		if (extensionVersion != storageVersion) {
			console.log("Migration needed - storage is at v" + storageVersion + "; extension is at v" + extensionVersion);
			setStorageVersion(extensionVersion);

			if (storageVersion == 1) {
				if (extensionVersion == 2) {
					// Migrate from a database from 1.x.x to 2.x.x
					migrate_1_to_2(function() {
						console.log("Migration from 1.x.x to 2.x.x complete");
					});
				}
			}
		}
	});
}

Add some more clear migration debug logs.