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

'use strict';

base.require('ui.overlay');

base.unittest.testSuite('ui.overlay', function() {
  test('showHideUnparented', function() {
    var overlay = new ui.Overlay();
    overlay.innerHTML =
        '
Hello
B1:<button>foo</button>
B2:<button>blah</button>';
    overlay.visible = true;
    assertNotEquals(overlay.parentNode, null);

    overlay.visible = false;
    assertEquals(overlay.parentNode, null);
  });

  test('showHideParented', function() {
    var overlay = new ui.Overlay();
    overlay.innerHTML =
        '
Hello
B1:<button>foo</button>
B2:<button>blah</button>';

    document.body.appendChild(overlay);
    overlay.visible = true;
    assertNotEquals(overlay.parentNode, null);

    overlay.visible = false;
    assertEquals(overlay.parentNode, document.body);
    document.body.removeChild(overlay);
  });
});

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

'use strict';

base.require('ui.overlay');

base.unittest.testSuite('ui.overlay', function() {
  test('instantiate', function() {
    var dlg = new ui.Overlay();
    dlg.classList.add('example-overlay');
    dlg.innerHTML = '
ExampleOverlay
hello';

    var btn = document.createElement('button');
    btn.innerText = 'Launch Overlay';
    btn.addEventListener('click', function(e) {
      dlg.visible = true;
      e.stopPropagation();
    });

    this.addHTMLOutput(btn);
  });

  test('showHideUnparented', function() {
    var overlay = new ui.Overlay();
    overlay.innerHTML =
        '
Hello
B1:<button>foo</button>
B2:<button>blah</button>';
    overlay.visible = true;
    assertNotEquals(overlay.parentNode, null);

    overlay.visible = false;
    assertEquals(overlay.parentNode, null);
  });

  test('showHideParented', function() {
    var overlay = new ui.Overlay();
    overlay.innerHTML =
        '
Hello
B1:<button>foo</button>
B2:<button>blah</button>';

    document.body.appendChild(overlay);
    overlay.visible = true;
    assertNotEquals(overlay.parentNode, null);

    overlay.visible = false;
    assertEquals(overlay.parentNode, document.body);
    document.body.removeChild(overlay);
  });
});

Add test to launch an overlay


// Copyright (c) Microsoft Corporation. All rights reserved.

module.exports = {
    'CoreAndroid': {
        'show': function (success, fail, service, action, args) {
            success();
        },
        'messageChannel': function (success, fail, service, action, args) {
            // This call is used to communicate the messageChannel callback to the native Java code. Since we don't need
            // that, we just swallow this and do nothing.
        }
    }
};

// Copyright (c) Microsoft Corporation. All rights reserved.

module.exports = {
    'CoreAndroid': {
        'show': function (success, fail, service, action, args) {
            success && success();
        },
        'messageChannel': function (success, fail, service, action, args) {
            // This call is used to communicate the messageChannel callback to the native Java code. Since we don't need
            // that, we just swallow this and do nothing.
        }
    }
};

Handle case where CoreAndroid.show is called without success callback


'use strict';

/**
 * Helpers services. A collection of services to be used throughout the application.
 */
module.exports = {

  isOneOfFieldSet(submission) {
    return ['user_id', 'email', 'mobile'].every(userField => !submission[userField]);
  },

};

'use strict';

/**
 * Helpers services. A collection of services to be used throughout the application.
 */
module.exports = {

  isOneOfFieldSet() {
    return ['user_id', 'email', 'mobile'].every(userField => !this[userField]);
  },

};

Use this rather than parameter in isOneOfFieldSet()


var ProjectList,
    Project = require("../models/project"),
    server = require("../server"),
    storage = require("../storage");

ProjectList = Backbone.Collection.extend({
    model: Project,
    initialize: function () {
        var collection = this,
            projects = storage.readProjects(),
            projectId;

        for (projectId in projects) {
            if (projects.hasOwnProperty(projectId)) {
                this.create(projects[projectId]);
            }
        }

    },
    sync: function (method, model, options) {
    },
    deleteProjectModel: function (modelCid) {
        // Remove from collection and destroy model
        var modelToDelete = this.get(modelCid);
        this.remove(modelToDelete);
        modelToDelete.sync("delete", modelToDelete);
    },
    createProjectOnDisk: function (values, callback) {
        var collection = this;
        server.create({
            args: [
                values.location,
                values.name
            ].join(" ")
        }, function (response) {
            var model = collection.create(values);
            callback(response, model);
        });
    },
    deleteProjectFromDisk: function (modelCid, callback) {
        var collection = this,
            model = this.get(modelCid);

        server.delete({
            projectPath: model.get("location")
        }, function (response) {
            collection.deleteProjectModel(modelCid);
            callback(response);
        });
    },
    getDefaultProjectPath: function (callback) {
        server.defaultProjectPath({}, function (response) {
            callback(response);
        });
    }
});

module.exports = ProjectList;

var ProjectList,
    Project = require("../models/project"),
    server = require("../server"),
    storage = require("../storage");

ProjectList = Backbone.Collection.extend({
    model: Project,
    initialize: function () {
        var collection = this,
            projects = storage.readProjects(),
            projectId;

        for (projectId in projects) {
            if (projects.hasOwnProperty(projectId)) {
                this.create(projects[projectId]);
            }
        }

    },
    sync: function (method, model, options) {
    },
    deleteProjectModel: function (modelCid) {
        // Remove from collection and destroy model
        var modelToDelete = this.get(modelCid);
        this.remove(modelToDelete);
        modelToDelete.sync("delete", modelToDelete);
    },
    createProjectOnDisk: function (values, callback) {
        var collection = this;
        server.create({
            args: [
                "\"" + values.location + "\"",
                values.name
            ].join(" ")
        }, function (response) {
            var model = collection.create(values);
            callback(response, model);
        });
    },
    deleteProjectFromDisk: function (modelCid, callback) {
        var collection = this,
            model = this.get(modelCid);

        server.delete({
            projectPath: model.get("location")
        }, function (response) {
            collection.deleteProjectModel(modelCid);
            callback(response);
        });
    },
    getDefaultProjectPath: function (callback) {
        server.defaultProjectPath({}, function (response) {
            callback(response);
        });
    }
});

module.exports = ProjectList;

Allow spaces in path for project create


module.exports = {
  moduleNameMapper: {
    "\\.scss$": "<rootDir>/tests/styleMock.js",
  },
  testPathIgnorePatterns: ["/node_modules/", "/dist/", "/es/", "/integration/"],
  coveragePathIgnorePatterns: ["<rootDir>/tests"],
  snapshotSerializers: ["enzyme-to-json/serializer"],
  setupFiles: ["<rootDir>/tests/polyfills.js"],
  setupTestFrameworkScriptFile: "<rootDir>/tests/setupTest.js",
};

module.exports = {
  moduleNameMapper: {
    "\\.scss$": "<rootDir>/tests/styleMock.js",
  },
  testPathIgnorePatterns: ["/node_modules/", "/dist/", "/es/", "/integration/"],
  coveragePathIgnorePatterns: ["<rootDir>/tests", "/examples/"],
  snapshotSerializers: ["enzyme-to-json/serializer"],
  setupFiles: ["<rootDir>/tests/polyfills.js"],
  setupTestFrameworkScriptFile: "<rootDir>/tests/setupTest.js",
};

Exclude examples code from unit test coverage metrics for now


/**
 * Register TagField functions with fields.
 */
(function ($) {

	/*
	 * The multiple-select plugin (Chosen) applies itself to everything!
	 * We have to remove it before selectively applying select2, or
	 * we'll see an extra field where there should be only one.
	 */
	$.fn.chosenDestroy = function () {
		$(this)
			.removeClass('chzn-done')
			.removeClass('has-chzn')
			.next()
				.remove();

		return $(this);
	};

	$.entwine('ss', function($) {

		$('.silverstripe-tag-field + .chzn-container').entwine({
			onmatch: function() {
				var $select = $(this).prev();

				$select
					.chosenDestroy()
					.select2({
						'tags': true,
						'tokenSeparators': [',', ' ']
					});

				/*
				 * Delay a cycle so select2 is initialised before
				 * selecting values (if data-selected-values is present).
				 */
				setTimeout(function () {
					if ($select.attr('data-selected-values')) {
						var values = $select.attr('data-selected-values');

						$select.select2('val', values.split(','));
					}
				}, 0);
			}
		});
	});
})(jQuery);

/**
 * Register TagField functions with fields.
 */
(function ($) {

	/*
	 * The multiple-select plugin (Chosen) applies itself to everything!
	 * We have to remove it before selectively applying select2, or
	 * we'll see an extra field where there should be only one.
	 */
	$.fn.chosenDestroy = function () {
		$(this)
			.show() // The field needs to be visible so Select2 evaluates the width correctly.
			.removeClass('chzn-done')
			.removeClass('has-chzn')
			.next()
				.remove();

		return $(this);
	};

	$.entwine('ss', function($) {

		$('.silverstripe-tag-field + .chzn-container').entwine({
			applySelect2: function () {
				var self = this,
					$select = $(this).prev();

				// There is a race condition where Select2 might not
				// be bound to jQuery yet. So here we make sure Select2
				// is defined before trying to invoke it.
				if ($.fn.select2 === void 0) {
					return setTimeout(function () {
						self.applySelect2();
					}, 0);
				}

				$select
					.chosenDestroy()
					.select2({
						'tags': true,
						'tokenSeparators': [',', ' ']
					});

				/*
				 * Delay a cycle so select2 is initialised before
				 * selecting values (if data-selected-values is present).
				 */
				setTimeout(function () {
					if ($select.attr('data-selected-values')) {
						var values = $select.attr('data-selected-values');

						$select.select2('val', values.split(','));
					}
				}, 0);
			},
			onmatch: function() {
				this.applySelect2();
			}
		});
	});
})(jQuery);

Fix race condition with Select2


var queryBlockChain = function(e) {

var blockChainUrl = " https://blockchain.info/address/";

if (e.selectionText) {
	blockChainUrl += e.selectionText;
	}

   chrome.tabs.create(
          {"url" : blockChainUrl });

}

 chrome.contextMenus.create({
    "title" : "Check BTC Address",
    "contexts" : ["selection"],
    "onclick" : queryBlockChain
  });

var queryBlockChain = function(e) {

var blockChainUrl = " https://blockchain.info/address/";

if (e.selectionText) {
	blockChainUrl += e.selectionText;
	}

   chrome.tabs.create(
          {"url" : blockChainUrl });

};

 chrome.contextMenus.create({
    "title" : "Check BTC Address",
    "contexts" : ["selection"],
    "onclick" : queryBlockChain
  });

Add ; - line 13


/* globals blanket, module */

var options = {
  modulePrefix: 'ember-g-map',
  filter: '//.*ember-g-map/.*/',
  antifilter: '//.*(tests|template).*/',
  loaderExclusions: [],
  enableCoverage: true,
  cliOptions: {
    reporters: ['lcov'],
    autostart: true,
    lcovOptions: {
      outputFile: 'lcov.dat',
      renamer: function(moduleName){
        var expression = /^ember-g-map/;
        return moduleName.replace(expression, 'app') + '.js';
      }
    }
  }
};
if (typeof exports === 'undefined') {
  blanket.options(options);
} else {
  module.exports = options;
}

/* globals blanket, module */

var options = {
  modulePrefix: 'ember-g-map',
  filter: '//.*ember-g-map/.*/',
  antifilter: '//.*(tests|template).*/',
  loaderExclusions: [],
  enableCoverage: true,
  cliOptions: {
    reporters: ['lcov'],
    autostart: true,
    lcovOptions: {
      outputFile: 'lcov.dat',
      renamer: function(moduleName){
        var expression = /^ember-g-map/;
        return moduleName.replace(expression, 'addon') + '.js';
      }
    }
  }
};
if (typeof exports === 'undefined') {
  blanket.options(options);
} else {
  module.exports = options;
}

Fix file path in coverage reports


var coupon = function () {
    var durationSelector = '#duration';

    var $body = $('body');
    var $duration = $(durationSelector);
    var $durationInMonths = $('#duration-in-months');
    var $redeem_by = $('#redeem_by');

    $body.on('change', durationSelector, function () {
        if ($duration.val() === 'repeating') {
            $durationInMonths.show();
        }
        else {
            $durationInMonths.hide();
        }
    });

    if ($redeem_by.length) {
        $redeem_by.datetimepicker({
            widgetParent: '.dt',
            format: 'YYYY-MM-DD HH:mm:ss',
            icons: {
                time: 'fa fa-clock-o',
                date: 'fa fa-calendar',
                up: 'fa fa-arrow-up',
                down: 'fa fa-arrow-down'
            }
        });
    }
};

module.exports = coupon;

var coupon = function () {
    var durationSelector = '#duration';

    var $body = $('body');
    var $duration = $(durationSelector);
    var $durationInMonths = $('#duration-in-months');
    var $redeem_by = $('#redeem_by');

    $body.on('change', durationSelector, function () {
        if ($duration.val() === 'repeating') {
            $durationInMonths.show();
        }
        else {
            $durationInMonths.hide();
        }
    });

    if ($redeem_by.length) {
        $redeem_by.datetimepicker({
            widgetParent: '.dt',
            format: 'YYYY-MM-DD HH:mm:ss',
            icons: {
                time: 'fa fa-clock-o',
                date: 'fa fa-calendar',
                up: 'fa fa-arrow-up',
                down: 'fa fa-arrow-down',
                previous: 'fa fa-chevron-left',
                next: 'fa fa-chevron-right',
                clear: 'fa fa-trash'
            }
        });
    }
};

module.exports = coupon;

Add missing font awesome icons for the datetime picker


import React, { Component } from 'react';
import SignupForm from './SignupForm';
import LoginForm from '../common/LoginForm';

class Intro extends Component {
    render() {
        return (

Σύνδεση

                                <LoginForm />

Εγγραφή

                                <SignupForm />

        );
    }
}

export default Intro;

import React, { Component } from 'react';
import SignupForm from './SignupForm';
import LoginForm from '../common/LoginForm';

class Intro extends Component {
    render() {
        return (

Μίλα ανώνυμα με άλλους φοιτητές

                                <SignupForm />

Σύνδεση

                                <LoginForm />

        );
    }
}

export default Intro;

Move signup form before login for responsive screens


function function_exists (function_name) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Steve Clay
    // +   improved by: Legaev Andrey
	// +   improved by: Brett Zamir (http://brett-zamir.me)
    // *     example 1: function_exists('isFinite');
    // *     returns 1: true

    if (typeof function_name === 'string') {
        function_name = this.window[function_name];
    }
    return typeof function_name === 'function';
}

function function_exists (func_name) {
    // http://kevin.vanzonneveld.net
    // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +   improved by: Steve Clay
    // +   improved by: Legaev Andrey
	// +   improved by: Brett Zamir (http://brett-zamir.me)
    // *     example 1: function_exists('isFinite');
    // *     returns 1: true

    if (typeof func_name === 'string') {
        func_name = this.window[func_name];
    }
    return typeof func_name === 'function';
}

Rename internal variable to avoid compiler conflict


if (!(typeof MochaWeb === 'undefined')){
  MochaWeb.testOnly(function () {
    describe('The Route "/"', function () {
      it('Should have the title "map"', function () {
        var title = document.title
        console.log(title)
        chai.assert.include(title, 'Map')
      })
    })
    describe('A list of places', function () {
      it('Should have a main section with the class map', function () {
        var map = $('main.map')
        chai.assert.ok(map.length > 0)
      })
      it('Should contain map__places', function () {
        var placesList = $('main.map ul.map__places')
        chai.assert.ok(placesList.length > 0)
      })
      it('should at least contain one place', function () {
        var places = $('ul.map__places li.place')
        chai.assert.ok(places.length > 0)
      })
    })
    describe('A place', function () {
      it('Should include a title', function () {
        var title = $('li.place .place__title').text()
        console.log(title)
        chai.assert.ok(title.length > 0)
      })
    })
  })
}

if (!(typeof MochaWeb === 'undefined')){
  MochaWeb.testOnly(function () {
    setTimeout(function (){
      describe('The Route "/"', function () {
        it('Should have the title "Map"', function () {
          var title = document.title
          console.log(title)
          chai.assert.include(title, 'Map')
        })
      })
      describe('A list of places', function () {
        it('Should have a main section with the class map', function () {
          var map = $('main.map')
          chai.assert.ok(map.length > 0)
        })
        it('Should contain map__places', function () {
          var placesList = $('main.map ul.map__places')
          chai.assert.ok(placesList.length > 0)
        })
        it('should at least contain one place', function () {
          var places = $('ul.map__places li.place')
          chai.assert.ok(places.length > 0)
        })
      })
      describe('A place', function () {
        it('Should include a location', function () {
          var title = $('li.place .place__location').text()
          chai.assert.ok(title.length > 0)
        })
      })
    }, 1000)
  })
}

Add a hack to ensure mocha waits for page to load


import { createButton } from './Button';

export default {
  title: 'Example/Button',
  argTypes: {
    label: { control: 'text' },
    primary: { control: 'boolean' },
    backgroundColor: { control: 'color' },
    size: {
      control: { type: 'select', options: ['small', 'medium', 'large'] },
    },
    onClick: { action: 'onClick' },
  },
};

const Template = (args) => createButton(args);

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

import { createButton } from './Button';

export default {
  title: 'Example/Button',
  argTypes: {
    label: { control: 'text' },
    primary: { control: 'boolean' },
    backgroundColor: { control: 'color' },
    size: {
      control: { type: 'select', options: ['small', 'medium', 'large'] },
    },
    onClick: { action: 'onClick' },
  },
};

const Template = ({ label, ...args }) => {
  // You can either use a function to create DOM elements or use a plain html string!
  // return `<div>${label}</div>`;
  return createButton({ label, ...args });
};

export const Primary = Template.bind({});
Primary.args = {
  primary: true,
  label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
  label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
  size: 'large',
  label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
  size: 'small',
  label: 'Button',
};

Add comment for HTML string


import del from 'del';
import path from 'path';
import gulp from 'gulp';
import eslint from 'gulp-eslint';
import babel from 'gulp-babel';
import writeFile from './build/write-file';
import generateRoutes from './src/routes/generate';

import './build/azure';

gulp.task('clean', ['azure:clean'], () =>
  del(['dist']));

gulp.task('lint', () =>
  gulp.src(['gulpfile.js', 'src/**/*.js', 'test/**/*.js'])
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError()));

gulp.task('transform', ['clean'], () =>
  gulp.src('src/**/*.js')
    .pipe(babel())
    .pipe(gulp.dest('dist')));

gulp.task('configuration', ['clean'], () =>
  writeFile(
    path.resolve(__dirname, './dist/services/configuration.json'),
    '{}'));

gulp.task('routes', ['clean'], () =>
  writeFile(
    path.resolve(__dirname, './dist/routes/routes.json'),
    JSON.stringify({ routes: generateRoutes() })));

gulp.task('build', [
  'lint',
  'routes',
  'transform',
  'configuration',
]);

import del from 'del';
import path from 'path';
import gulp from 'gulp';
import eslint from 'gulp-eslint';
import babel from 'gulp-babel';
import writeFile from './build/write-file';
import generateRoutes from './src/routes/generate';

import './build/azure';

gulp.task('clean', ['azure:clean'], () =>
  del(['dist']));

gulp.task('lint', () =>
  gulp.src(['gulpfile.js', 'src/**/*.js', 'test/**/*.js'])
    .pipe(eslint())
    .pipe(eslint.format())
    .pipe(eslint.failOnError()));

gulp.task('transform', ['clean'], () =>
  gulp.src('src/**/*.js')
    .pipe(babel())
    .pipe(gulp.dest('dist')));

gulp.task('services', ['clean'], () =>
  writeFile(
    path.resolve(__dirname, './dist/services/configuration.json'),
    '{}'));

gulp.task('routes', ['clean'], () =>
  writeFile(
    path.resolve(__dirname, './dist/routes/routes.json'),
    JSON.stringify({ routes: generateRoutes() })));

gulp.task('build', [
  'lint',
  'routes',
  'services',
  'transform',
]);

Rename the configuration task to services.


'use strict';

var gulp = require('gulp'),
  batch  = require('gulp-batch'),
  concat = require('gulp-concat'),
  ignore = require('gulp-ignore'),
  jshint = require('gulp-jshint'),
  watch  = require('gulp-watch'),
  path   = require('path');

var dist = 'dist/hypermedia.js';

gulp.task('default', ['jshint'], function(){
  var distDir = path.dirname(dist);
  return gulp.src('src/*.js')
    .pipe(ignore.exclude('*.spec.js'))
    .pipe(concat(path.basename(dist)))
    .pipe(gulp.dest(distDir));
});

gulp.task('watch', function () {
  gulp.start('default');
  watch('src/**', batch(function (events, done) {
    gulp.start('default', done);
  }));
});

gulp.task('jshint', function () {
  return gulp.src(['src/*.js'])
    .pipe(jshint('./.jshintrc'))
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(jshint.reporter('fail'));
});

'use strict';

var gulp = require('gulp'),
  batch  = require('gulp-batch'),
  concat = require('gulp-concat'),
  ignore = require('gulp-ignore'),
  jshint = require('gulp-jshint'),
  jscs   = require('gulp-jscs'),
  watch  = require('gulp-watch'),
  path   = require('path');

var dist = 'dist/hypermedia.js';

gulp.task('default', function () {
  var distDir = path.dirname(dist);
  return gulp.src('src/*.js')
    // jshint
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'))
    .pipe(jshint.reporter('fail'))
    // jscs
    .pipe(jscs())
    .pipe(jscs.reporter())
    .pipe(jscs.reporter('fail'))
    // build
    .pipe(ignore.exclude('*.spec.js'))
    .pipe(concat(path.basename(dist)))
    .pipe(gulp.dest(distDir));
});

gulp.task('watch', function () {
  gulp.start('default');
  watch('src/**', batch(function (events, done) {
    gulp.start('default', done);
  }));
});

Move jshint and jscs into default build pipeline


import React from 'react'
import {ApplyTheme} from '@instructure/ui-themeable'
import '@instructure/canvas-high-contrast-theme'
import '@instructure/canvas-theme'

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}

export const globalTypes = {
  canvasTheme: {
    name: 'Canvas Theme',
    description: 'Default or High Contrast',
    defaultValue: 'canvas',
    toolbar: {
      icon: 'user',
      items: ['canvas', 'canvas-high-contrast']
    }
  }
}

const canvasThemeProvider = (Story, context) => {
  const canvasTheme = context.globals.canvasTheme
  return (
    <ApplyTheme theme={ApplyTheme.generateTheme(canvasTheme)}>
      <Story {...context}/>
    </ApplyTheme>
  )
}

export const decorators = [canvasThemeProvider]

import React from 'react'
import {ApplyTheme} from '@instructure/ui-themeable'
import {ApplyTextDirection} from '@instructure/ui-i18n'
import '@instructure/canvas-high-contrast-theme'
import '@instructure/canvas-theme'

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
}

export const globalTypes = {
  canvasTheme: {
    name: 'Canvas Theme',
    description: 'Default or High Contrast',
    defaultValue: 'canvas',
    toolbar: {
      icon: 'user',
      items: ['canvas', 'canvas-high-contrast']
    }
  },
  bidirectional: {
    name: 'Bidirectional',
    description: 'Left-to-right or Right-to-left',
    defaultValue: 'ltr',
    toolbar: {
      icon: 'transfer',
      items: ['ltr', 'rtl']
    }
  }
}

const canvasThemeProvider = (Story, context) => {
  const canvasTheme = context.globals.canvasTheme
  return (
    <ApplyTheme theme={ApplyTheme.generateTheme(canvasTheme)}>
      <Story {...context}/>
    </ApplyTheme>
  )
}

const bidirectionalProvider = (Story, context) => {
  const direction = context.globals.bidirectional
  return (
    <ApplyTextDirection dir={direction}>
          <Story {...context}/>
    </ApplyTextDirection>
  )
}

export const decorators = [canvasThemeProvider, bidirectionalProvider]

Add bidirectional toggle to storybook


var isFunction = require('lodash.isfunction');
var joi = require('joi');
var pc = require('pascal-case');

/* Public */

function attach(schema) {
  var attachments = {};

  schema && schema._inner && schema._inner.children.forEach(function (child) {
    attachments[pc(child.key)] = {
      get: function () {
        return child.schema;
      }
    };
  });

  return Object.defineProperties(schema, attachments);
}

function spec(type, params) {
  var after, spec;

  if (arguments.length === 1) {
    params = type;
    type = params.type;
    delete params.type;
  }

  if (isFunction(params.after)) {
    after = params.after;
    delete params.after;
  }

  spec = joi[type]();

  Object.keys(params).forEach(function (key) {
    spec = Array.isArray(params[key])
      ? spec[key].apply(spec, params[key])
      : spec[key](params[key]);
  });

  if (after) {
    after(spec);
  }

  return spec;
}

/* Exports */

module.exports.attach = attach;
module.exports.spec = spec;

var isFunction = require('lodash.isfunction');
var joi = require('joi');
var pc = require('pascal-case');

/* Public */

function attach(schema) {
  var attachments = {};

  schema &&
  schema._inner &&
  schema._inner.children &&
  schema._inner.children.forEach(function (child) {
    attachments[pc(child.key)] = {
      get: function () {
        return child.schema;
      }
    };
  });

  return Object.defineProperties(schema, attachments);
}

function spec(type, params) {
  var after, spec;

  if (arguments.length === 1) {
    params = type;
    type = params.type;
    delete params.type;
  }

  if (isFunction(params.after)) {
    after = params.after;
    delete params.after;
  }

  spec = joi[type]();

  Object.keys(params).forEach(function (key) {
    spec = Array.isArray(params[key])
      ? spec[key].apply(spec, params[key])
      : spec[key](params[key]);
  });

  if (after) {
    after(spec);
  }

  return spec;
}

/* Exports */

module.exports.attach = attach;
module.exports.spec = spec;

Fix an issue with children missing


import React, {Component} from 'react'
import {Provider} from 'react-redux'
import Nav from '../shared/nav.desktop'
import RemoteManager from './remote-manager'
import {MuiThemeProvider} from 'material-ui/styles'
import materialTheme from '../shared/styles/material-theme.desktop'
import {reduxDevToolsEnable} from '../shared/local-debug.desktop'

export default class Root extends Component {
  state: {
    panelShowing: boolean
  };

  constructor () {
    super()

    this.state = {
      panelShowing: false,
    }

    if (__DEV__) { // eslint-disable-line no-undef
      window.addEventListener('keydown', event => {
        if (event.ctrlKey && event.keyCode === 72) {
          this.setState({panelShowing: !this.state.panelShowing})
        }
      })
    }
  }

  render () {
    let dt = null
    if (__DEV__ && reduxDevToolsEnable) { // eslint-disable-line no-undef
      const DevTools = require('./redux-dev-tools').default
      dt = <DevTools />
    }

    const {store} = this.props

    return (
      <MuiThemeProvider muiTheme={materialTheme}>
        <Provider store={store}>
          <div style={{display: 'flex', flex: 1}}>
            <RemoteManager />
            <Nav />
            {dt}
          </div>
        </Provider>
      </MuiThemeProvider>
    )
  }
}

import React from 'react'
import {Provider} from 'react-redux'
import Nav from '../shared/nav.desktop'
import RemoteManager from './remote-manager'
import {MuiThemeProvider} from 'material-ui/styles'
import materialTheme from '../shared/styles/material-theme.desktop'
import {reduxDevToolsEnable} from '../shared/local-debug.desktop'

export default function Root ({store}) {
  let dt = null
  if (__DEV__ && reduxDevToolsEnable) { // eslint-disable-line no-undef
    const DevTools = require('./redux-dev-tools').default
    dt = <DevTools />
  }

  return (
    <MuiThemeProvider muiTheme={materialTheme}>
      <Provider store={store}>
        <div style={{display: 'flex', flex: 1}}>
          <RemoteManager />
          <Nav />
          {dt}
        </div>
      </Provider>
    </MuiThemeProvider>
  )
}

Clean up old panelShowing code


import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TableRow from './TableRow/index.js'
import Button from '../../Components/Button/index.js'

export default class Table extends Component {

  static propTypes = {
    tableName: PropTypes.string,
    columnTypes: PropTypes.object.isRequired
  }

  state = {
    addedRows: []
  }

  addRow = () => {
    const defaultValues = Object.keys(this.props.columnTypes).map(() => {return ""})
    const newRow = <TableRow key={this.state.addedRows.length + 1} editing cellTypes={Object.values(this.props.columnTypes)} cellValues={defaultValues} />
    this.setState((prevState) => ({
          addedRows: prevState.addedRows.concat([newRow])
        }))
  }

  render(){
    if(this.props.readOnly){
      return(

          {this.props.children}

      )
    }else{
      return(

          <TableRow
            readOnly
            cellValues={Object.keys(this.props.columnTypes)}
            cellTypes={Object.values(this.props.columnTypes)}
          />
          {this.props.children}
          {this.state.addedRows}
          <Button
            className="round"
            buttonText="+"
            onClick={this.addRow}
          />

      )
    }
  }

}

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import TableRow from './TableRow/index.js'
import Button from '../../Components/Button/index.js'

export default class Table extends Component {

  static propTypes = {
    tableName: PropTypes.string,
    columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string.isRequired,
      type: PropTypes.string
    }).isRequired)
  }

  static defaultProps = {
    columns: {
      name: "",
      type: "text"
    }
  }

  state = {
    addedRows: []
  }

  addRow = () => {
    const defaultValues = this.props.columns.map(() => {return ""})
    const newRow = <TableRow key={this.state.addedRows.length + 1} editing cellTypes={this.props.columns.map((column) => {return column.type})} cellValues={defaultValues} />
    this.setState((prevState) => ({
          addedRows: prevState.addedRows.concat([newRow])
        }))
  }

  render(){
    if(this.props.readOnly){
      return(

          {this.props.children}

      )
    }else{
      return(

          <TableRow
            readOnly
            cellValues={this.props.columns.map((column) => {return column.name})}
            cellTypes={this.props.columns.map((column) => {return column.type})}
          />
          {this.props.children}
          {this.state.addedRows}
          <Button
            className="round"
            buttonText="+"
            onClick={this.addRow}
          />

      )
    }
  }

}

Make table prop columns an array of column objects


module.exports = function(gulp, speck) {
  return gulp.task('assets:iconify', ['assets:optimise_images'], function() {
    var iconify = require('gulp-iconify');

    iconify({
      src: speck.assets.build.images + '/**/*.inc.svg',
      pngOutput: speck.assets.build.images + '/render',
      cssOutput: speck.assets.build.css + '/assets'
    });
  });
};

module.exports = function(gulp, speck) {
  return gulp.task('assets:iconify', ['assets:optimise_images'], function() {
    var iconify = require('gulp-iconify');

    iconify({
      src: speck.assets.build.images + '/icons/**/*.svg',
      pngOutput: speck.assets.build.images + '/render',
      cssOutput: speck.assets.build.css + '/assets'
    });
  });
};

Use icons directory for including icons with ss


var _ = require('lodash');

exports.printSeparator = function (s) {
    console.log((new Array(51)).join(s || "-"));
};

exports.printObject = function (object, indent) {
    function padRight(string, length) {
        return string + (new Array((length - string.length) + 1).join(" "));
    }

    var indentPad = Array(indent).join(" ") || "",
        names = _.keys(object),
        longestName = names.reduce(function (currentLongestName, name) {
            return name.length > currentLongestName.length ? name : currentLongestName;
        }, "");

    names.forEach(function (name) {
        console.log(indentPad + padRight(name + ": ", longestName.length + 2) + object[name]);
    });
};

exports.printStory = function (story, content) {
    exports.printSeparator("=");
    console.log(story.id.toString().cyan + " | " + "Phase: ".cyan + story.phase.name + " | " + "Owner: ".cyan + story.owner.name)
    exports.printSeparator("=");

    console.log(story.text[story.color]);
    exports.printSeparator();

    if (content) {
        console.log(content);
        exports.printSeparator();
    }

    if (story.blockedReason) {
        console.log(story.blockedReason.red);
        exports.printSeparator();
    }
};

var _ = require('lodash');

exports.printSeparator = function (s) {
    console.log((new Array(51)).join(s || "-"));
};

exports.printObject = function (object, indent) {
    function padRight(string, length) {
        return string + (new Array((length - string.length) + 1).join(" "));
    }

    var indentPad = Array(indent).join(" ") || "",
        names = _.keys(object),
        longestName = names.reduce(function (currentLongestName, name) {
            return name.length > currentLongestName.length ? name : currentLongestName;
        }, "");

    names.forEach(function (name) {
        console.log(indentPad + padRight(name + ": ", longestName.length + 2) + object[name]);
    });
};

exports.printStory = function (story, content) {
    exports.printSeparator("=");
    console.log(story.id.toString().cyan +
        " | " + "Phase: ".cyan + story.phase.name +
        (story.owner ? " | " + "Owner: ".cyan + story.owner.name : ""))
    exports.printSeparator("=");

    console.log(story.text[story.color]);
    exports.printSeparator();

    if (content) {
        console.log(content);
        exports.printSeparator();
    }

    if (story.blockedReason) {
        console.log(story.blockedReason.red);
        exports.printSeparator();
    }
};

Support for stories without owners.


import CellProperty from './CellProperty';
import CheckboxProperty from './CheckboxProperty';
import EnumProperty from './EnumProperty';
import PropertyFactory from './PropertyFactory';
import PropertyPanel from './PropertyPanel';
import SliderProperty from './SliderProperty';

export default {
  CellProperty,
  CheckboxProperty,
  EnumProperty,
  PropertyFactory,
  PropertyPanel,
  SliderProperty,
};

import CellProperty from './CellProperty';
import CheckboxProperty from './CheckboxProperty';
import EnumProperty from './EnumProperty';
import MapProperty from './MapProperty';
import PropertyFactory from './PropertyFactory';
import PropertyPanel from './PropertyPanel';
import SliderProperty from './SliderProperty';

export default {
  CellProperty,
  CheckboxProperty,
  EnumProperty,
  MapProperty,
  PropertyFactory,
  PropertyPanel,
  SliderProperty,
};

Add MapProperty into the generated file


var falafel = require('falafel');
module.exports = replace;

function replace (src, deps) {
    return falafel(src, function (node) {
        if (isRequire(node)) {
            var value = node.arguments[0].value;
            if (has(deps, value) && deps[value]) {
                node.update('require(' + JSON.stringify(deps[value]) + ')');
            }
        }
    }).toString();
}

function isRequire (node) {
    var c = node.callee;
    return c
        && node.type === 'CallExpression'
        && c.type === 'Identifier'
        && c.name === 'require'
        && node.arguments[0]
        && node.arguments[0].type === 'Literal'
    ;
}

function has (obj, key) {
    return Object.prototype.hasOwnProperty.call(obj, key);
}

var falafel = require('falafel');
module.exports = replace;

function replace (src, deps) {
    return falafel(src, {ecmaVersion: 6}, function (node) {
        if (isRequire(node)) {
            var value = node.arguments[0].value;
            if (has(deps, value) && deps[value]) {
                node.update('require(' + JSON.stringify(deps[value]) + ')');
            }
        }
    }).toString();
}

function isRequire (node) {
    var c = node.callee;
    return c
        && node.type === 'CallExpression'
        && c.type === 'Identifier'
        && c.name === 'require'
        && node.arguments[0]
        && node.arguments[0].type === 'Literal'
    ;
}

function has (obj, key) {
    return Object.prototype.hasOwnProperty.call(obj, key);
}

Set "{ecmaVersion: 6}" for falafel's acorn


const functions = require('firebase-functions');
const admin = require('firebase-admin');

const BATCH_SIZE = 500;
const deleteBatch = (query, resolve, reject) =>
  query
    .limit(BATCH_SIZE)
    .get()
    .then(snapshot => {
      if (snapshot.size === 0) {
        return 0;
      }
      const batch = admin.firestore().batch();
      snapshot.docs.forEach(doc => batch.delete(doc.ref));
      return batch.commit().then(() => snapshot.size);
    })
    .then(numDeleted => {
      if (numDeleted === 0) {
        return resolve();
      }
      return process.nextTick(() => {
        deleteBatch(query, resolve, reject);
      });
    })
    .catch(reject);

module.exports = functions.firestore
  .document('entities/sector/entity/{sectorId}')
  .onDelete((snapshot, context) => {
    const { sectorId } = context.params;

    const navigationQuery = admin
      .firestore()
      .collection('navigation')
      .doc(sectorId)
      .collection('routes');
    const layerQuery = admin
      .firestore()
      .collection('layer')
      .doc(sectorId)
      .collection('layers');

    return Promise.all([
      new Promise((resolve, reject) =>
        deleteBatch(navigationQuery, resolve, reject)
      ),
      new Promise((resolve, reject) =>
        deleteBatch(layerQuery, resolve, reject)
      ),
    ]).catch(
      error => new functions.https.HttpsError('unknown', error.message, error)
    );
  });

const functions = require('firebase-functions');
const admin = require('firebase-admin');

const BATCH_SIZE = 500;
const deleteBatch = (query, resolve, reject) =>
  query
    .limit(BATCH_SIZE)
    .get()
    .then(snapshot => {
      if (snapshot.size === 0) {
        return 0;
      }
      const batch = admin.firestore().batch();
      snapshot.docs.forEach(doc => batch.delete(doc.ref));
      return batch.commit().then(() => snapshot.size);
    })
    .then(numDeleted => {
      if (numDeleted === 0) {
        return resolve();
      }
      return process.nextTick(() => {
        deleteBatch(query, resolve, reject);
      });
    })
    .catch(reject);

module.exports = functions.firestore
  .document('entities/sector/entity/{sectorId}')
  .onDelete((snapshot, context) => {
    const { sectorId } = context.params;

    const navigationQuery = admin
      .firestore()
      .collection('navigation')
      .doc(sectorId)
      .collection('routes');
    const layerQuery = admin
      .firestore()
      .collection('layers')
      .doc(sectorId)
      .collection('layer');

    return Promise.all([
      new Promise((resolve, reject) =>
        deleteBatch(navigationQuery, resolve, reject)
      ),
      new Promise((resolve, reject) =>
        deleteBatch(layerQuery, resolve, reject)
      ),
    ]).catch(
      error => new functions.https.HttpsError('unknown', error.message, error)
    );
  });

Fix the deletion of sector objects


import React from 'react';
import addons from '@storybook/addons';

import PanelTitle from './components/PanelTitle';
import Panel from './components/Panel';

addons.register('storybook/tests', api => {
  const channel = addons.getChannel();
  addons.addPanel('storybook/tests/panel', {
    title: <PanelTitle channel={addons.getChannel()} api={api} />,
    // eslint-disable-next-line react/prop-types
    render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
  });
});

import React from 'react';
import addons from '@storybook/addons';

import PanelTitle from './components/PanelTitle';
import Panel from './components/Panel';

addons.register('storybook/tests', api => {
  const channel = addons.getChannel();
  addons.addPanel('storybook/tests/panel', {
    // eslint-disable-next-line react/prop-types
    title: () => <PanelTitle channel={addons.getChannel()} api={api} />,
    // eslint-disable-next-line react/prop-types
    render: ({ active }) => <Panel channel={channel} api={api} active={active} />,
  });
});

Update `addon-jest` to new propType


import {
  GET_DEFAULT_DIRECTORIES_REQUEST, GET_DEFAULT_DIRECTORIES_FAILURE, GET_DEFAULT_DIRECTORIES_SUCCESS,
  LOAD_DIRECTORY_REQUEST, LOAD_DIRECTORY_SUCCESS, LOAD_DIRECTORY_FAILURE,
  SET_DIRECTORY_SORT_OPTION
} from '../constants/ActionTypes';
import { CALL_API, Schemas } from '../middleware/api';

const getDefaultDirectoriesRequest = () => ({
  [CALL_API]: {
    types: [ GET_DEFAULT_DIRECTORIES_REQUEST, GET_DEFAULT_DIRECTORIES_SUCCESS, GET_DEFAULT_DIRECTORIES_FAILURE ],
    endpoint: `directories`,
    method: 'GET',
    schema: Schemas.DIRECTORY_ARRAY
  }
});

export const getDefaultDirectories = (values) => (dispatch, getState) =>  {
  return dispatch(getDefaultDirectoriesRequest(values));
};

const loadDirectoryRequest = ({ directory }) => ({
  [CALL_API]: {
    types: [ LOAD_DIRECTORY_REQUEST, LOAD_DIRECTORY_SUCCESS, LOAD_DIRECTORY_FAILURE ],
    endpoint: `d/${directory}`,
    method: 'GET',
    schema: Schema.DIRECTORY
  }
});

export const loadDirectory = (values) => (dispatch, getState) =>  {
  return dispatch(loadDirectoryRequest(values));
};

export const setDirectorySortOption = (sortValue) => (dispatch, getState) =>  {
  return dispatch({
    type: SET_DIRECTORY_SORT_OPTION,
    sortBy: sortValue
  });
};

import {
  GET_DEFAULT_DIRECTORIES_REQUEST, GET_DEFAULT_DIRECTORIES_FAILURE, GET_DEFAULT_DIRECTORIES_SUCCESS,
  LOAD_DIRECTORY_REQUEST, LOAD_DIRECTORY_SUCCESS, LOAD_DIRECTORY_FAILURE,
  SET_DIRECTORY_SORT_OPTION
} from '../constants/ActionTypes';
import { CALL_API, Schemas } from '../middleware/api';

const getDefaultDirectoriesRequest = () => ({
  [CALL_API]: {
    types: [ GET_DEFAULT_DIRECTORIES_REQUEST, GET_DEFAULT_DIRECTORIES_SUCCESS, GET_DEFAULT_DIRECTORIES_FAILURE ],
    endpoint: `directories`,
    method: 'GET',
    schema: Schemas.DIRECTORY_ARRAY
  }
});

export const getDefaultDirectories = (values) => (dispatch, getState) =>  {
  return dispatch(getDefaultDirectoriesRequest(values));
};

const loadDirectoryRequest = ({ directory }) => ({
  [CALL_API]: {
    types: [ LOAD_DIRECTORY_REQUEST, LOAD_DIRECTORY_SUCCESS, LOAD_DIRECTORY_FAILURE ],
    endpoint: `d/${directory}`,
    method: 'GET',
    schema: Schemas.DIRECTORY
  }
});

export const loadDirectory = ({ directory }) => (dispatch, getState) =>  {

  const directoryItem = getState().entities.directories[directory];
  if(directoryItem){
    return Promise.resolve();
  }

  return dispatch(loadDirectoryRequest({ directory }));
};

export const setDirectorySortOption = (sortValue) => (dispatch, getState) =>  {
  return dispatch({
    type: SET_DIRECTORY_SORT_OPTION,
    sortBy: sortValue
  });
};

Check if directory already exists sending request


"use strict";

var m  = require("mithril"),
    db = require("../lib/firebase");

module.exports = {
    controller : function() {

    },

    view : function() {
        return m("h1", "HOME");
    }
};

"use strict";

var m  = require("mithril"),
    db = require("../lib/firebase");

module.exports = {
    controller : function() {

    },

    view : function() {
        return [
            m("h1", "HOME"),
            m("ul",
                m("li",
                    m("a", { href : "/schemas", config : m.route }, "Schemas")
                ),
                m("li",
                    m("a", { href : "/content", config : m.route }, "Content")
                )
            )
        ]
    }
};

Add some kind of nav


describe('Filter: path', function () {

  beforeEach(module('ngNephila.filters.path'));

  var pathFilter;
  beforeEach(inject(function($filter) {
    pathFilter = $filter('path');
  }));

  it('has a path filter', function () {
    expect(pathFilter).not.toBeNull();
  });

  it('should compose paths automagically', function () {
    expect(pathFilter('http://hello.com', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com', '/main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', '/main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', '//main')).toEqual('http://hello.com/main/');
  });

  it('should respect noTrailingSlash parameter', function () {
    expect(pathFilter('http://hello.com', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', 'main', true)).toEqual('http://hello.com/main');
  });

});

describe('Filter: path', function () {

  beforeEach(module('ngNephila.filters.path'));

  var pathFilter;
  beforeEach(inject(function($filter) {
    pathFilter = $filter('path');
  }));

  it('has a path filter', function () {
    expect(pathFilter).not.toBeNull();
  });

  it('should compose paths automagically', function () {
    expect(pathFilter('http://hello.com', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com', '/main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', '/main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', '//main')).toEqual('http://hello.com/main/');
  });

  it('should work for relative path', function () {
    expect(pathFilter('/hello', 'main')).toEqual('/hello/main/');
    expect(pathFilter('/hello', 'main', true)).toEqual('/hello/main');
  });

  it('should respect noTrailingSlash parameter', function () {
    expect(pathFilter('http://hello.com', 'main')).toEqual('http://hello.com/main/');
    expect(pathFilter('http://hello.com/', 'main', true)).toEqual('http://hello.com/main');
  });

});

Add tests for relative path


function build(searchTerm) {
  return {
    index: 'profiles',
    type: 'gps',
    body: {
      size: 30,
      query: {
        bool: {
          should: [
            {
              match_phrase: {
                name: {
                  query: searchTerm,
                  boost: 2,
                  slop: 1
                }
              }
            },
            {
              match_phrase: {
                alternativeName: {
                  query: searchTerm,
                  boost: 2,
                  slop: 1
                }
              }
            },
            {
              common: {
                name: {
                  query: searchTerm,
                  cutoff_frequency: 0.0001
                }
              }
            },
            {
              common: {
                alternativeName: {
                  query: searchTerm,
                  cutoff_frequency: 0.0001
                }
              }
            },
            {
              nested: {
                path: 'doctors',
                query: {
                  bool: {
                    should: [
                      {
                        match_phrase: {
                          'doctors.name': {
                            query: searchTerm,
                            boost: 2,
                            slop: 1
                          }
                        }
                      },
                      {
                        match: {
                          'doctors.name': {
                            query: searchTerm,
                            cutoff_frequency: 0.0001
                          }
                        }
                      }
                    ]
                  }
                },
              }
            }]
        }
      },

    }
  };
}

module.exports = {
  build,
};

function build(searchTerm) {
  return {
    index: 'profiles',
    type: 'gps',
    body: {
      size: 30,
      query: {
        bool: {
          should: [
            {
              match_phrase: {
                name: {
                  query: searchTerm,
                  boost: 2,
                  slop: 1
                }
              }
            },
            {
              match_phrase: {
                alternativeName: {
                  query: searchTerm,
                  boost: 2,
                  slop: 1
                }
              }
            },
            {
              common: {
                name: {
                  query: searchTerm,
                  cutoff_frequency: 0.0001
                }
              }
            },
            {
              common: {
                alternativeName: {
                  query: searchTerm,
                  cutoff_frequency: 0.0001
                }
              }
            },
            {
              nested: {
                path: 'doctors',
                query: {
                  bool: {
                    should: [
                      {
                        match_phrase: {
                          'doctors.name': {
                            query: searchTerm,
                            boost: 2,
                            slop: 1
                          }
                        }
                      },
                      {
                        match: {
                          'doctors.name': {
                            query: searchTerm
                          }
                        }
                      }
                    ]
                  }
                },
              }
            }]
        }
      },

    }
  };
}

module.exports = {
  build,
};

Remove parameter left over from version of common terms search


(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.


import fp from 'mostly-func';

const defaultOptions = {
  field: 'primary',
  select: '*'
};

/**
 * Add route service object to hook.params
 */
export default function addRouteObject (name, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  return async context => {
    let options = Object.assign({}, opts);

    if (context.type !== 'before') {
      throw new Error(`The 'addRouteObject' hook should only be used as a 'before' hook.`);
    }

    if (!options.service || !options.field) {
      throw new Error('You need to provide a service and a field');
    }

    context.params = context.params || {};
    const primary = options.field === 'id'? context.id : context.params[options.field];
    if (!primary) {
      throw new Error(`No primary service id found in the context params`);
    }

    if (!context.params[name]) {
      try {
        const service = context.app.service(options.service);
        const object = await service.get(primary, { query: { $select: opts.select }});
        if (!object) {
          throw new Error(`Not found primary service object ${name} of ${primary}`);
        }
        context.params = fp.assoc(name, object, context.params);
      } catch (err) {
        throw new Error(`Not found primary service object ${name}: ` + err.message);
      }
    }
    return context;
  };
}

import fp from 'mostly-func';

const defaultOptions = {
  field: 'primary',
  select: '*'
};

/**
 * Add route service object to hook.params
 */
export default function addRouteObject (name, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  return async context => {
    let options = Object.assign({}, opts);

    if (context.type !== 'before') {
      throw new Error(`The 'addRouteObject' hook should only be used as a 'before' hook.`);
    }

    if (!options.service || !options.field) {
      throw new Error('You need to provide a service and a field');
    }

    context.params = context.params || {};
    const primary = options.field === 'id'? context.id : context.params[options.field];
    if (!primary) {
      throw new Error(`No primary service id found in the context params`);
    }

    if (!context.params[name]) {
      try {
        const service = context.app.service(options.service);
        const object = await service.get(primary, {
          query: { $select: opts.select },
          user: context.params.user
        });
        if (!object) {
          throw new Error(`Not found primary service object ${name} of ${primary}`);
        }
        context.params = fp.assoc(name, object, context.params);
      } catch (err) {
        throw new Error(`Not found primary service object ${name}: ` + err.message);
      }
    }
    return context;
  };
}

Fix get route object with user for permission


var csrftoken = $('input[name=csrf_token]').attr('value');

$.ajaxSetup({
    beforeSend: function (xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken)
        }
    }
});

$(function () {
    $('#single').submit(function (event) {
        event.preventDefault();
        $(':submit').button('loading')
        $.post('/thread', {
            submission: $('input[name="submission"]').val(),
            email: $('input[name="email"]').val()
        })
            .done(function (data) {
                $(':submit').button('reset')
                $('form')
                    .before(
                        '
\
                        <button type="button" class="close" data-dismiss="alert">\
                            ×\
                            Close\
                        </button>\
                        ' + data.text + '\

'
                )
                window.setTimeout(function () {
                    $(".alert:first").alert('close');
                }, 10000);
            });
    });
});

Add CSRF token to JS POST header


/**
 * Sample project code
 */

class Person {
  constructor () {
    this.name = "annonymous"
    this.age = 20
  }

  hello () {
    return `Hello! I'm ${ this.name }! I'm ${ this.age } years old.`
  }

  get nextage () {
    return this.age + 1
  }
}

module.exports = {
  Person: Person
}

/**
 * Sample project code
 */

class Person {
  constructor () {
    this.firstname = ""
    this.lastname = ""
    this.age = 20
  }

  hello () {
    return `Hello! I'm ${ this.fullname }! I'm ${ this.age } years old.`
  }

  get nextage () {
    return this.age + 1
  }

  get fullname () {
    if (this.firstname == "" && this.lastname == "") return "annonymous"
    return this.firstname + " " + this.lastname
  }
}

module.exports = {
  Person: Person
}

Change Person.name to firstname and lastname


app.factory('BioFactory', ['$http', function($http){
  console.log('BioFactory running');

  var mentorInfo = {};
  var mentors = {};
  var mentor = {};

  function getMentors(newSearch){
    console.log("NEW SEARCH:", newSearch);
    var newSearchString = JSON.stringify(newSearch);
    return $http({
        method: 'GET',
        url: '/mentor-search/search',
        headers: {
          newSearchString: newSearchString
        }
      })
      .then(function(response) {

        console.log("--------------------------------------");
        mentors.info = response.data;
        console.log("Mentors list:", mentors);

      }),
      function(err) {
        console.log("Error with search get request ", err);
      };
  };

  function setMentorId(mentorId){
    mentor.id = mentorId;
  }

  var publicApi = {
    mentors: mentors,
    mentor: mentor,
    getMentors: function(newSearch){
      return getMentors(newSearch);
    },
    setMentorId: function(mentorId){
      return setMentorId(mentorId);
    },
    // getMentorId: function(){
    //   return getMentorId();
    // }
  };

  return publicApi;

}]);

app.factory('BioFactory', ['$http', function($http){
  console.log('BioFactory running');

  var mentors = {};
  var mentorBio = {};

  // gets all the mentors that match the newSearch selected fields
  function getMentors(newSearch){
    console.log("NEW SEARCH:", newSearch);
    var newSearchString = JSON.stringify(newSearch);
    return $http({
        method: 'GET',
        url: '/mentor-search/search',
        headers: {
          newSearchString: newSearchString
        }
      })
      .then(function(response) {
        mentors.info = response.data;
        console.log("Mentors list:", mentors);
      }),
      function(err) {
        console.log("Error with search get request ", err);
      };
  };

// Attach the chosen mentor object to the info property on mentorBio.
// This will be accessed by the profile controller
  function setMentor(mentor){
    mentorBio.info = mentor;
    console.log("MENTOR:", mentorBio.info);
  }

  var publicApi = {
    mentors: mentors,
    mentorBio: mentorBio,
    getMentors: function(newSearch){
      return getMentors(newSearch);
    },
    setMentor: function(mentor){
      return setMentor(mentor);
    },
    // getMentorId: function(){
    //   return getMentorId();
    // }
  };

  return publicApi;

}]);

Change mentor to mentorBio to fix bug


define([
    '../jquery/autosubmit'
], function(require) {
    var autosubmit = {
        initContent: function(root) {
            $("[data-autosubmit]", root)
                .find("input[type-search]").andSelf()
                .patternAutosubmit();
        }
    };

    return autosubmit;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab

define([
    '../jquery/autosubmit'
], function() {
    var autosubmit = {
        initContent: function(root) {
            $("[data-autosubmit]", root)
                .find("input[type-search]").andSelf()
                .patternAutosubmit();
        }
    };

    return autosubmit;
});
// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab

Fix error in define call


Package.describe({
  summary: "Facebook OAuth flow",
  version: "1.7.1"
});

Package.onUse(api => {
  api.versionsFrom('1.11.1');

  api.use('ecmascript', ['client', 'server']);
  api.use('oauth2', ['client', 'server']);
  api.use('oauth', ['client', 'server']);
  api.use('http', ['server']);
  api.use('random', 'client');
  api.use('service-configuration', ['client', 'server']);

  api.addFiles('facebook_client.js', 'client');
  api.addFiles('facebook_server.js', 'server');

  api.export('Facebook');
});

Package.describe({
  summary: "Facebook OAuth flow",
  version: "1.7.1"
});

Package.onUse(api => {
  api.use('ecmascript', ['client', 'server']);
  api.use('oauth2', ['client', 'server']);
  api.use('oauth', ['client', 'server']);
  api.use('http', ['server']);
  api.use('random', 'client');
  api.use('service-configuration', ['client', 'server']);

  api.addFiles('facebook_client.js', 'client');
  api.addFiles('facebook_server.js', 'server');

  api.export('Facebook');
});

Support all arguments of OAuth._redirectUri


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

    grunt.initConfig({
        bump: {
            options: {
                files: [ "package.json" ],

                // Commit
                commit: true,
                commitMessage: "Release v%VERSION%",
                commitFiles: [ "package.json" ],

                // Tag
                createTag: true,
                tagName: "%VERSION%",
                tagMessage: "Version %VERSION%",

                // Push
                push: true,
                pushTo: "origin"
            }
        },
        jshint: {
            options: {
                jshintrc: ".jshintrc"
            },
            all: [ "Gruntfile.js", "tasks/*.js", "test/*.js" ]
        },
        jscs: {
            src: "<%= jshint.all %>"
        },
        nodeunit: {
            enmasse: "test/enmasse.js",
            methods: "test/methods.js"
        }
    });

    // Load grunt tasks from NPM packages
    require( "load-grunt-tasks" )( grunt );

    grunt.loadTasks( "tasks" );

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

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

    grunt.initConfig({
        bump: {
            options: {
                files: [ "package.json" ],

                // Commit
                commit: true,
                commitMessage: "Release v%VERSION%",
                commitFiles: [ "package.json" ],

                // Tag
                createTag: true,
                tagName: "%VERSION%",
                tagMessage: "Version %VERSION%",

                // Push
                push: true,
                pushTo: "origin"
            }
        },
        jshint: {
            options: {
                jshintrc: ".jshintrc"
            },
            all: [ "Gruntfile.js", "tasks/*.js", "test/*.js" ]
        },
        jscs: {
            src: "<%= jshint.all %>"
        },
        nodeunit: {
            methods: "test/methods.js",
            enmasse: "test/enmasse.js"
        }
    });

    // Load grunt tasks from NPM packages
    require( "load-grunt-tasks" )( grunt );

    grunt.loadTasks( "tasks" );

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

Change order of test declarations


module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        products: {
            'argos-saleslogix': {
                basePath: '.'
            },
            'argos-sdk':{
                basePath: '../../argos-sdk'
            }
        },
        // modules.json is the same format as the productions configuration above.
        // Use grunt release:all or grunt release:modules to include them in a release build.
        modules: grunt.file.readJSON('modules.json')
    });

    // Load per-task config from separate files
    grunt.loadTasks('grunt-tasks');

    // Register alias tasks
    grunt.registerTask('check', ['jshint', 'jscs']);
    grunt.registerTask('test', ['check', 'connect', 'jasmine:coverage']);
    grunt.registerTask('server', ['connect:server:keepalive']);
    grunt.registerTask('bundle', ['shell:bundle:<%= pkg.version %>']);
    grunt.registerTask('default', ['test']);
};

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        products: {
            'argos-saleslogix': {
                basePath: '.'
            },
            'argos-sdk':{
                basePath: '../../argos-sdk'
            }
        },
        // modules.json is the same format as the productions configuration above.
        // Use grunt release:all or grunt release:modules to include them in a release build.
        modules: grunt.file.readJSON('modules.json')
    });

    // Load per-task config from separate files
    grunt.loadTasks('grunt-tasks');

    // Register alias tasks
    grunt.registerTask('check', ['jshint', 'jscs']);
    grunt.registerTask('test', ['connect', 'jasmine:coverage']);
    grunt.registerTask('server', ['connect:server:keepalive']);
    grunt.registerTask('bundle', ['shell:bundle:<%= pkg.version %>']);
    grunt.registerTask('default', ['test']);
};

Remove check from test task.


module.exports = [
	{
		test: /\.jsx?$/,
		exclude: /node_modules/,
		loader: 'babel',
	},
    {
        test: /\.scss$/,
        exclude: /node_modules/,
        loaders: ['style', 'css', 'postcss', 'sass']
    },
    {
        test: /\.sass$/,
        exclude: /node_modules/,
        loaders: ['style', 'css', 'postcss', 'sass']
    },
	{
		test: /\.css$/,
		exclude: /node_modules/,
		loaders: ['style', 'css']
	},
	{
		test: /\.json$/,
		loader: 'json'
	},
	{
		test: /.(png|gif|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/,
		loader: 'url-loader?limit=100000&name=[name]-[hash].[ext]'
	},
	{
		test: /\.(md|ejs)$/,
		loader: 'ignore-loader'
	},
	{
		test: /\LICENSE$/,
		loader: 'ignore-loader'
	}
];

module.exports = [
	{
		test: /\.jsx?$/,
		exclude: /node_modules/,
		loader: 'babel',
	},
    {
        test: /\.scss$/,
        exclude: /node_modules/,
        loaders: ['style', 'css', 'postcss', 'sass']
    },
    {
        test: /\.sass$/,
        exclude: /node_modules/,
        loaders: ['style', 'css', 'postcss', 'sass']
    },
	{
		test: /\.css$/,
		loaders: ['style', 'css']
	},
	{
		test: /\.json$/,
		loader: 'json'
	},
	{
		test: /.(png|gif|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/,
		loader: 'url-loader?limit=100000&name=[name]-[hash].[ext]'
	},
	{
		test: /\.(md|ejs)$/,
		loader: 'ignore-loader'
	},
	{
		test: /\LICENSE$/,
		loader: 'ignore-loader'
	}
];

Fix error from importing css from node_modules


import Ember from 'ember';
import ExpPlayer from 'exp-player/components/exp-player/component';

export default ExpPlayer.extend({
    // Show an early exit modal (return non-empty string), but only with browser default message- don't require translation of custom text.
    messageEarlyExitModal: ' ',

    currentUser: Ember.inject.service(),

    actions: {
        sessionCompleted() {
            // Mark the current user account as having completed a session, then, also update the session model
            // If this fails to save the account due to normal network issues, it will still update the session but
            // swallow the error
            return this.get('currentUser').getCurrentUser().then(([account]) => {
                // Deal with `extra` field sometimes being undefined
                let dest = account.get('extra') || {};
                dest['hasCompletedStudy'] = true;
                account.set('extra', dest);
                return account.save();
            }).catch( e => console.log('Could not mark account as having completed study:', e)
            ).finally(() => this._super(...arguments));
        },
    }
});

import Ember from 'ember';
import ExpPlayer from 'exp-player/components/exp-player/component';

export default ExpPlayer.extend({
    // Show an early exit modal (return non-empty string), but only with browser default message- don't require translation of custom text.
    messageEarlyExitModal: ' ',

    currentUser: Ember.inject.service(),

    actions: {
        sessionCompleted() {
            // Mark the current user account as having completed a session, then, also update the session model
            // If this fails to save the account due to normal network issues, it will still update the session but
            // swallow the error
            this.get('session').set('completed', true);

            return this.get('currentUser').getCurrentUser().then(([account]) => {
                // Deal with `extra` field sometimes being undefined
                let dest = account.get('extra') || {};
                dest['hasCompletedStudy'] = true;
                account.set('extra', dest);
                return account.save();
            }).catch( e => console.error('Could not mark account as having completed study:', e));
        }
    }
});

Fix race condition that prevented session from being marked as complete


module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    sass: {
      dist: {
        options: {
          sourcemap: 'none'
        },
        files: {
          'test/hocus-pocus.css': 'hocus-pocus.sass'
        }
      }
    },
    autoprefixer: {
      no_dest: {
        src: 'test/hocus-pocus.css'
      }
    },
    watch: {
      files: ['**/*.sass'],
      tasks: ['sass', 'autoprefixer'],
    },
    connect: {
      server: {
        options: {
          port: 3000,
          base: 'test/'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-autoprefixer');

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

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    sass: {
      dist: {
        options: {
          sourcemap: 'none'
        },
        files: {
          'test/hocus-pocus.css': 'hocus-pocus.sass'
        }
      }
    },
    autoprefixer: {
      no_dest: {
        src: 'test/hocus-pocus.css'
      }
    },
    watch: {
      files: ['**/*.sass'],
      tasks: ['sass', 'autoprefixer'],
      options: {
        livereload: true
      }
    },
    connect: {
      server: {
        options: {
          port: 3000,
          base: 'test/'
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-autoprefixer');

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

Enable livereload in grunt watch


// An example configuration file.
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://localhost:4444/wd/hub',

// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
// URL of the app you want to test.
baseUrl: 'http://localhost:8000/',

// Spec patterns are relative to the location of the spec file. They may
// include glob patterns.
specs: [
'login_navigation_spec.js'
],

// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true, // Use colors in the command line report.
isVerbose: true, // List all tests in the console
includeStackTrace: true
}
};

// An example configuration file.
exports.config = {
// The address of a running selenium server.
seleniumAddress: 'http://localhost:4444/wd/hub',

// Capabilities to be passed to the webdriver instance.
capabilities: {
'browserName': 'chrome'
},
// URL of the app you want to test.
baseUrl: 'http://localhost:8000/',

// Spec patterns are relative to the location of the spec file. They may
// include glob patterns.
specs: [
'login_navigation_spec.js',
'lookup_product_spec.js'
],

// Options to be passed to Jasmine-node.
jasmineNodeOpts: {
showColors: true, // Use colors in the command line report.
isVerbose: true, // List all tests in the console
includeStackTrace: true
}
};

Add some testing code for the product lookup operations, both from the index and from the individual product pages.


var apiFactoryApp = angular.module("APIFactoryWeb", ["ngRoute", "Shared", "Main", "Entities"]);

apiFactoryApp.config(['$routeProvider',
    function($routeProvider) {
        $routeProvider.
            when('/entities', {
                templateUrl: 'partials/entities.partial.html',
                controller: 'EntityController'
            }).
            otherwise({
                redirectTo: '/entities'
            });
    }
]);

apiFactoryApp.directive('materialDropdown',  ['$rootScope', function($rootScope) {
    return {
        restrict: 'EA',
        link: function(scope, iElement, attrs) {
            var element = $(iElement);
            element.dropdown({
                    constrain_width: false, // Does not change width of dropdown to that of the activator
                    hover: false // Activate on click
                }
            );
            element.click(function() {
                console.log("Clicked!");
                var dropdown = element.next();
                dropdown.show();
                var height = dropdown.css('height');
                dropdown.css('height', '0');
                dropdown.animate({
                    'opacity': '1',
                    'height': height
                });
            })
            console.log("Applied");
        }
    };
}]);

var apiFactoryApp = angular.module("APIFactoryWeb", ["ngRoute", "Shared", "Main", "Entities"]);

apiFactoryApp.config(['$routeProvider',
    function($routeProvider) {
        $routeProvider.
            when('/entities', {
                templateUrl: 'partials/entities.partial.html',
                controller: 'EntityController'
            }).
            otherwise({
                redirectTo: '/entities'
            });
    }
]);

apiFactoryApp.directive('materialDropdown',  ['$rootScope', function($rootScope) {
    return {
        restrict: 'EA',
        link: function(scope, iElement, attrs) {
            var element = $(iElement);
            element.dropdown({
                    constrain_width: false, // Does not change width of dropdown to that of the activator
                    hover: false // Activate on click
                }
            );

            element.click(function() {
                var dropdown = element.next();
                dropdown.show();
                var height = dropdown.css('height');
                dropdown.css('height', '0');
                dropdown.animate({
                    'opacity': '1',
                    'height': height
                }, 'fast');
                dropdown.attr('visible', 'visible');
            });
        }
    };
}]);

Fix added for faster animation. Conflict with modals not showing up resolved.


define([
	'angular',
    'lib/livedesk/scripts/js/manage-feeds/manage-feeds',
    'lib/livedesk/scripts/js/manage-feeds/services/providers-blogs-data',
    'lib/livedesk/scripts/js/manage-feeds/services/all-blog-sources'
	],function(ngular, feeds){
		feeds.factory('chainedBlogsData', ['$http', '$q','providersBlogsData','allBlogSources',  function($http, $q, providersBlogsData, allBlogSources){
            return {
                getData: function(providersUrl, sourcesUrl) {
                    var deffered = $q.defer();
                    providersBlogsData.getData(providersUrl).then(function(chains){
                        allBlogSources.getData(sourcesUrl).then(function(sources){
                            console.log('chains ', chains);
                            for ( var i = 0; i < chains.length; i++ ) {
                                for ( var j = 0; j < chains[i].blogList.length; j++ ) {
                                    var chained = false;
                                    var sourceId = -1;
                                    for ( var k = 0; k < sources.length; k ++ ) {
                                        if ( sources[k].URI.href == chains[i].blogList[j].href ) {
                                            chained = true;
                                            sourceId = sources[k].Id
                                            break;
                                        }
                                    }
                                    chains[i].blogList[j].chained = chained;
                                    chains[i].blogList[j].sourceId = sourceId;
                                }
                            }
                            deffered.resolve(chains);
                        });
                    });
                    return deffered.promise;
                }
            }
        }]);
});

define([
	'angular',
    'lib/livedesk/scripts/js/manage-feeds/manage-feeds',
    'lib/livedesk/scripts/js/manage-feeds/services/providers-blogs-data',
    'lib/livedesk/scripts/js/manage-feeds/services/all-blog-sources'
	],function(ngular, feeds){
		feeds.factory('chainedBlogsData', ['$http', '$q','providersBlogsData','allBlogSources',  function($http, $q, providersBlogsData, allBlogSources){
            return {
                getData: function(providersUrl, sourcesUrl) {
                    var deffered = $q.defer();
                    providersBlogsData.getData(providersUrl).then(function(chains){
                        allBlogSources.getData(sourcesUrl).then(function(sources){
                            for ( var i = 0; i < chains.length; i++ ) {
                                for ( var j = 0; j < chains[i].blogList.length; j++ ) {
                                    var chained = false;
                                    var sourceId = -1;
                                    for ( var k = 0; k < sources.length; k ++ ) {
                                        if ( sources[k].URI.href == chains[i].blogList[j].href ) {
                                            chained = true;
                                            sourceId = sources[k].Id
                                            break;
                                        }
                                    }
                                    chains[i].blogList[j].chained = chained;
                                    chains[i].blogList[j].sourceId = sourceId;
                                }
                            }
                            deffered.resolve(chains);
                        });
                    });
                    return deffered.promise;
                }
            }
        }]);
});

Check on admin all POST, PUT, DELETE URLs to use my


import expect from 'expect'
import scriptsToHTML from '../src/utils/scriptsToHTML'

describe('scriptsToHTML', () => {
  it('converts a scripts object into an HTML string', () => {
    const scripts = [
      'string.js',
      Object.create({ foo: 'bar' }, { src: { value: 'Test.js', enumerable: true } }),
      { src: 'async.js', async: true, onerror: 'nope' },
    ]
    const result = scriptsToHTML(scripts, 'test(this.src)')
    const expectedResult = `
      <script src="string.js" onerror="test(this.src)"></script>
      <script src="Test.js" onerror="test(this.src)"></script>
      <script src="async.js" async="true" onerror="test(this.src)"></script>
    `.replace(/\n\s*/g, '')
    expect(result).toBe(expectedResult)
  })
})

import expect from 'expect'
import scriptsToHTML from '../src/utils/scriptsToHTML'

describe('scriptsToHTML', () => {
  it('converts a scripts object into an HTML string', () => {
    function ProtoTest() {}
    ProtoTest.prototype = { foo: 'bar' }

    const scripts = [
      'string.js',
      Object.assign(new ProtoTest(), { src: 'Test.js' }),
      { src: 'async.js', async: true, onerror: 'nope' },
    ]
    const result = scriptsToHTML(scripts, 'test(this.src)')
    const expectedResult = `
      <script src="string.js" onerror="test(this.src)"></script>
      <script src="Test.js" onerror="test(this.src)"></script>
      <script src="async.js" async="true" onerror="test(this.src)"></script>
    `.replace(/\n\s*/g, '')
    expect(result).toBe(expectedResult)
  })
})

Fix scriptsToHTML prototype test to work in IE


import {editor} from 'pageflow-scrolled/editor';
import {ColorInputView, FileInputView} from 'pageflow/editor';
import {SliderInputView, TextInputView} from 'pageflow/ui';

editor.contentElementTypes.register('inlineBeforeAfter', {
  supportedPositions: ['inline'],

  configurationEditor() {
    this.tab('general', function() {
      this.input('before_id', FileInputView, {
        collection: 'image_files',
        fileSelectionHandler: 'contentElementConfiguration',
        positioning: false,
      });
      this.input('before_label', TextInputView);
      this.input('after_id', FileInputView, {
        collection: 'image_files',
        fileSelectionHandler: 'contentElementConfiguration',
        positioning: false,
      });
      this.input('after_label', TextInputView);
      this.input('initial_slider_position', SliderInputView);
      this.input('slider_color', ColorInputView);

      this.group('ContentElementPosition');
    });
  },

  defaultConfig: {initial_slider_position: 50},
});

import {editor} from 'pageflow-scrolled/editor';
import {ColorInputView, FileInputView} from 'pageflow/editor';
import {SliderInputView, TextInputView} from 'pageflow/ui';

editor.contentElementTypes.register('inlineBeforeAfter', {
  supportedPositions: ['inline', 'sticky', 'left', 'right', 'full'],

  configurationEditor() {
    this.tab('general', function() {
      this.input('before_id', FileInputView, {
        collection: 'image_files',
        fileSelectionHandler: 'contentElementConfiguration',
        positioning: false,
      });
      this.input('before_label', TextInputView);
      this.input('after_id', FileInputView, {
        collection: 'image_files',
        fileSelectionHandler: 'contentElementConfiguration',
        positioning: false,
      });
      this.input('after_label', TextInputView);
      this.input('initial_slider_position', SliderInputView);
      this.input('slider_color', ColorInputView);

      this.group('ContentElementPosition');
    });
  },

  defaultConfig: {initial_slider_position: 50},
});

Allow more position options for before/after


const queue = require('./queue.js');
const error = require('./error.js');
const filter = require('./filter.js');
const passFilter = require('./filters/pass.js');
const blockFilter = require('./filters/block.js');
const hostFilter = require('./filters/host.js');
const protocolFilter = require('.filters/protocol');
const methodFilter = require('.filters/method');
const pathFilter = require('.filters/path');

module.exports = {
  queue: queue,
  error: error,
  filter: filter,
  filters: {
    pass: passFilter,
    block: blockFilter,
  	host: hostFilter,
    protocol: protocolFilter,
    method: methodFilter,
    path: pathFilter
  }
}

const queue = require('./queue.js');
const error = require('./error.js');
const filter = require('./filter.js');
const passFilter = require('./filters/pass.js');
const blockFilter = require('./filters/block.js');
const hostFilter = require('./filters/host.js');
const protocolFilter = require('.filters/protocol');
const methodFilter = require('.filters/method');
const pathFilter = require('.filters/path');
const simplePathFilter = require('.filters/simple-path');

module.exports = {
  queue: queue,
  error: error,
  filter: filter,
  filters: {
    pass: passFilter,
    block: blockFilter,
  	host: hostFilter,
    protocol: protocolFilter,
    method: methodFilter,
    path: pathFilter,
    simplePath: simplePathFilter
  }
}

Add simple path filter to lib


import React from 'react';
import {Label} from 'react-bootstrap';

export default class ElementCollectionLabels extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      element: props.element
    }
  }

  render() {
    return (

        {this.collectionLabels(this.state.element)}

    );
  }

  labelStyle(label) {
    if(label.is_shared == false) {
      return "info";
    } else {
      return "warning";
    }
  }

  collectionLabels(element) {
    if(element.collection_labels) {
      return element.collection_labels.map((label, index) => {
        return (

            {label.name}

        )
      });
    }

  }
}

import React from 'react';
import {Label, OverlayTrigger, Popover} from 'react-bootstrap';

export default class ElementCollectionLabels extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      element: props.element
    }
  }

  render() {
    return (

        {this.collectionLabels(this.state.element)}

    );
  }

  labelStyle(label) {
    if(label.is_shared == false) {
      return "info";
    } else {
      return "warning";
    }
  }

  format_labels(labels) {
    return labels.map((label, index) => {
      return (

          {label.name}

      )
    });
  }

  collectionLabels(element) {
    if(element.collection_labels) {
      let shared_labels = [];
      let labels = [];
      element.collection_labels.map((label, index) => {
        if (label.is_shared) {
          shared_labels.push(label)
        } else {
          labels.push(label)
        }
      });

      let shared_label_popover = <Popover title="Shared Collections">{this.format_labels(shared_labels)}</Popover>

      let label_popover = <Popover title="Collections">{this.format_labels(labels)}</Popover>

      let shared_label = shared_labels.length > 0 ?
        <OverlayTrigger trigger="hover" placement="top" overlay={shared_label_popover}>

            In {shared_labels.length} Shared Collections

        </OverlayTrigger> : undefined

      let collection_label = labels.length > 0 ?
        <OverlayTrigger trigger="hover" placement="top" overlay={label_popover}>

            In {labels.length} Collections

        </OverlayTrigger>: undefined

      return (

          {collection_label}
          {shared_label}

      )
    }
  }
}

Add a compact collection label view and popup details on hover


/**
 * Created by rerobins on 10/6/15.
 */
var CurrencyDirectiveGenerator = function(formatters) {

    return {
        scope: {
            value: '&'
        },
        template: '{{currencyValue}}',
        link: function($scope) {
            if ($scope.value() > 0.0) {
                $scope.style = {color: 'green'};
                $scope.currencyValue = formatters.currency($scope.value());
            } else if ($scope.value() < 0.0) {
                $scope.style = {color: 'red'};
                $scope.currencyValue = formatters.currency($scope.value());
            }
        }
    };

};

angular.module('gnucash-reports-view.reports.base')
    .directive('currencyFormat', ['formatters', CurrencyDirectiveGenerator]);

/**
 * Created by rerobins on 10/6/15.
 */
var CurrencyDirectiveGenerator = function(formatters) {

    return {
        scope: {
            value: '&'
        },
        template: '{{currencyValue}}',
        link: function($scope) {
            $scope.currencyValue = formatters.currency($scope.value());

            if ($scope.value() > 0.0) {
                $scope.style = {color: 'green'};
            } else if ($scope.value() < 0.0) {
                $scope.style = {color: 'red'};
            }
        }
    };

};

angular.module('gnucash-reports-view.reports.base')
    .directive('currencyFormat', ['formatters', CurrencyDirectiveGenerator]);

Update currency directive to render 0's


/*
 * Handles currency display
 *
 * It process 'socket.send({payout_currencies:1})` response
 * and display them
 */
function displayCurrencies(selected) {
    'use strict';

    var target = document.getElementById('currency'),
        fragment =  document.createDocumentFragment(),
        currencies = JSON.parse(sessionStorage.getItem('currencies'))['payout_currencies'];

    if (!target) {
        return;
    }

    while (target && target.firstChild) {
        target.removeChild(target.firstChild);
    }

    currencies.forEach(function (currency) {
        var option = document.createElement('option'),
            content = document.createTextNode(currency);

        option.setAttribute('value', currency);
        if (selected && selected == key) {
            option.setAttribute('selected', 'selected');
        }

        option.appendChild(content);
        fragment.appendChild(option);
    });

    target.appendChild(fragment);
}

/*
 * Handles currency display
 *
 * It process 'socket.send({payout_currencies:1})` response
 * and display them
 */
function displayCurrencies(selected) {
    'use strict';

    var target = document.getElementById('currency'),
        fragment =  document.createDocumentFragment(),
        currencies = JSON.parse(sessionStorage.getItem('currencies'))['payout_currencies'];

    if (!target) {
        return;
    }

    while (target && target.firstChild) {
        target.removeChild(target.firstChild);
    }

    var client_currencies;
    if(page.client.is_logged_in) {
        client_currencies = Settings.get('client.currencies');
    }

    currencies.forEach(function (currency) {
        if(client_currencies && client_currencies.length > 0 && client_currencies.indexOf(currency) < 0) {
            return;
        }
        var option = document.createElement('option'),
            content = document.createTextNode(currency);

        option.setAttribute('value', currency);
        if (selected && selected == key) {
            option.setAttribute('selected', 'selected');
        }

        option.appendChild(content);
        fragment.appendChild(option);
    });

    target.appendChild(fragment);
}

Fix payout currencies in Trading page


var Amanda = function(engine) {

  if (!hasProperty(engines, engine)) {
    throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.');
  }

  return engines[engine];

};

/**
 * Amanda.validate
 *
 * This method is deprecated, please use ‘amanda('json').validate’ instead.
 */
Amanda.validate = function(instance, schema, options, callback) {
  var json = engines.json;
  return json.validate.apply(json, arguments);
};

/**
 * Amanda.addValidator
 *
 * This method is deprecated, please use ‘amanda('json').addValidator’ instead.
 */
Amanda.addValidator = function(attributeName, attributeFn) {
  var json = engines.json;
  return json.addAttribute.apply(json, arguments);
};

/**
 * Amanda.addAttribute
 *
 * This method is deprecated, please use ‘amanda('json').addAttribute’ instead.
 */
Amanda.addAttribute = function(attributeName, attributeFn) {
  var json = engines.json;
  return json.addAttribute.apply(json, arguments);
};

/**
 * Amanda.addAttributeConstructor
 *
 * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead.
 */
Amanda.addAttributeConstructor = function(attributeName, attributeConstructor) {
  var json = engines.json;
  return json.addAttributeConstructor.apply(json, arguments);
};

var amanda = function(engine) {

  if (!hasProperty(engines, engine)) {
    throw new Error('The ‘' + engine + '’ engine is not supported. Please use a different one.');
  }

  return engines[engine];

};

/**
 * Amanda.validate
 *
 * This method is deprecated, please use ‘amanda('json').validate’ instead.
 */
amanda.validate = function(instance, schema, options, callback) {
  var json = engines.json;
  return json.validate.apply(json, arguments);
};

/**
 * Amanda.addValidator
 *
 * This method is deprecated, please use ‘amanda('json').addValidator’ instead.
 */
amanda.addValidator = function(attributeName, attributeFn) {
  var json = engines.json;
  return json.addAttribute.apply(json, arguments);
};

amanda.getVersion = '';
amanda.getValidators = '';

/**
 * Amanda.addAttribute
 *
 * This method is deprecated, please use ‘amanda('json').addAttribute’ instead.
 */
amanda.addAttribute = function(attributeName, attributeFn) {
  var json = engines.json;
  return json.addAttribute.apply(json, arguments);
};

/**
 * Amanda.addAttributeConstructor
 *
 * This method is deprecated, please use ‘amanda('json').addAttributeConstructor’ instead.
 */
amanda.addAttributeConstructor = function(attributeName, attributeConstructor) {
  var json = engines.json;
  return json.addAttributeConstructor.apply(json, arguments);
};

Convert the export variables to lowercase


import Component from '@ember/component';
import { inject as service } from '@ember/service';

import { task } from 'ember-concurrency';

export default class CommentsList extends Component {
  tagName = '';

  @service account;
  @service ajax;

  addCommentText = '';

  @(task(function* () {
    let id = this.flightId;
    let text = this.addCommentText;
    let user = this.get('account.user');

    yield this.ajax.request(`/api/flights/${id}/comments`, { method: 'POST', json: { text } });

    this.set('addCommentText', '');
    this.comments.pushObject({ text, user });
  }).drop())
  addCommentTask;
}

import Component from '@ember/component';
import { inject as service } from '@ember/service';

import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';

export default class CommentsList extends Component {
  tagName = '';

  @service account;
  @service ajax;

  @tracked addCommentText = '';

  @(task(function* () {
    let id = this.flightId;
    let text = this.addCommentText;
    let user = this.get('account.user');

    yield this.ajax.request(`/api/flights/${id}/comments`, { method: 'POST', json: { text } });

    this.addCommentText = '';
    this.comments.pushObject({ text, user });
  }).drop())
  addCommentTask;
}

Use `@tracked` property instead of `this.set()`


'use strict';

angular.module('mean.teacups').factory('Teacups', [
    '$resource',
    function($resource) {
        return $resource('teacups/:teacupId', {
            teacupId: '@_id'
        }, {
            update: {
                method: 'PUT'
            }
        });
    }
]);

angular.module('mean.teacups').factory('Rooms', [
    '$resource',
    function($resource) {
        return $resource('rooms');
    }
]);

Revert "placed angularjs factory for rooms in seperate file."


export default {
  widgets: [
    {name: 'sanity-tutorials', layout: {width: 'full'}},
    {name: 'document-list'},
    {name: 'document-list', options: {title: 'Last edited', order: '_updatedAt desc'}},
    {name: 'document-list', options: {title: 'Last created books', types: ['book']}},
    {name: 'project-users'},
    {
      name: 'project-info',
      layout: {
        width: 'medium',
        height: 'auto'
      },
      options: {
        data: [
          {title: 'Frontend', value: 'https://asdf.heroku.com/greedy-goblin', category: 'apps'},
          {title: 'Strange endpoint', value: 'https://example.com/v1/strange', category: 'apis'},
          {title: 'With strawberry jam?', value: 'Yes', category: 'Waffles'},
          {title: 'Gummy bears?', value: 'nope', category: 'Cheweies'},
          {title: 'With rømme?', value: 'maybe', category: 'Waffles'}
        ]
      }
    },
    {name: 'cats'}
  ]
}

export default {
  widgets: [
    {name: 'sanity-tutorials', layout: {width: 'full'}},
    {name: 'document-list'},
    {name: 'document-list', options: {title: 'Last edited', order: '_updatedAt desc'}},
    {name: 'document-list', options: {title: 'Last created books', types: ['book']}},
    {name: 'project-users'},
    {name: 'widget-which-does-not-exist'},
    {
      name: 'project-info',
      layout: {
        width: 'medium',
        height: 'auto'
      },
      options: {
        data: [
          {title: 'Frontend', value: 'https://asdf.heroku.com/greedy-goblin', category: 'apps'},
          {title: 'Strange endpoint', value: 'https://example.com/v1/strange', category: 'apis'},
          {title: 'With strawberry jam?', value: 'Yes', category: 'Waffles'},
          {title: 'Gummy bears?', value: 'nope', category: 'Cheweies'},
          {title: 'With rømme?', value: 'maybe', category: 'Waffles'}
        ]
      }
    },
    {name: 'cats'}
  ]
}

Configure up non-existing widget in test-studio


import React from 'react'

import Config from '../../src/ui-components/web/Config'

/**
 * Sample that has a function to enable the overlay.
 */
const App = ({ enableOverlay }) => (
  <div>
    <input
      onFocus={enableOverlay}
      type='search'
      className='top-search-text'
      id='top-search-text'
      value='Search'
      readOnly
    />
    <Config project='sajariptyltd' collection='sajari-com' />
  </div>
)

export default App

import React from 'react'

/**
 * Sample that has a function to enable the overlay.
 */
const App = ({ enableOverlay }) => (
  <div>
    <input
      onFocus={enableOverlay}
      type='search'
      className='top-search-text'
      id='top-search-text'
      value='Search'
      readOnly
    />
  </div>
)

export default App

Remove config (already declared in index)


module.exports = {
	env: {
		node: true
	},

	plugins: [
		'node'
	],

	rules: require('./rules/node')
};

module.exports = {
	env: {
		es6: true,
		node: true
	},

	plugins: [
		'node'
	],

	rules: require('./rules/node')
};

Add `es6` environment to NodeJS