angular.module('anol.featurestyleeditor')

.directive('color', function() {
    var COLOR_REGEX = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
    return {
        require: 'ngModel',
        link: function(scope, elem, attrs, ctrl) {
            ctrl.$validators.color = function(modelValue, viewValue) {
                if(ctrl.$isEmpty(modelValue)) {
                    return false;
                }
                if(ctrl.$isEmpty(viewValue)) {
                    return false;
                }
                var result =  COLOR_REGEX.test(viewValue);
                return result;
            };
        }
    };
});

angular.module('anol.featurestyleeditor')

.directive('color', function() {
    var COLOR_REGEX = /^#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?$/;
    return {
        require: 'ngModel',
        link: function(scope, elem, attrs, ctrl) {
            ctrl.$validators.color = function(modelValue, viewValue) {
                if(ctrl.$isEmpty(modelValue)) {
                    return true;
                }
                if(ctrl.$isEmpty(viewValue)) {
                    return false;
                }
                var result =  COLOR_REGEX.test(viewValue);
                return result;
            };
        }
    };
});

Allow empty model value for color inputs


$(document).ready(function(){
    var graph = Graph ();

    function getGraph(url, btnLoading) {
      $("#graph").html("");
      url = url ? url : "/graph";
      $('#graph-loading').show();
      if (btnLoading) {
           btnLoading.start();
      }
      $.get(url, function( data ) {
        graph.createGraph(data);
        $('#graph-loading').hide();
        if (btnLoading) {
            btnLoading.stop();
        }
      });
    }

    $(".updateGraph").click(function(e){
      e.preventDefault();
      $('.selected').removeClass('selected');
      $(this).closest('tr').addClass('selected');
      var l = Ladda.create(this);
       getGraph($(this).attr('href'), l);
    });

    $('#expandAllBtn').click(function(e){
      e.preventDefault();
      graph.expandAll();
    });

    $('#collapseAllBtn').click(function(e){
      e.preventDefault();
      graph.collapseAll();
    });

    getGraph();
});

$(document).ready(function(){
    var graph = Graph ();

    function getGraph(url, btnLoading) {
      $("#graph").html("");
      url = url ? url : "/graph";
      $('#graph-loading').show();
      if (btnLoading) {
           btnLoading.start();
      }

      $.get(APP_URL + url, function( data ) {
        graph.createGraph(data);
        $('#graph-loading').hide();
        if (btnLoading) {
            btnLoading.stop();
        }
      });
    }

    $(".updateGraph").click(function(e){
      e.preventDefault();
      $('.selected').removeClass('selected');
      $(this).closest('tr').addClass('selected');
      var l = Ladda.create(this);
       getGraph($(this).attr('href'), l);
    });

    $('#expandAllBtn').click(function(e){
      e.preventDefault();
      graph.expandAll();
    });

    $('#collapseAllBtn').click(function(e){
      e.preventDefault();
      graph.collapseAll();
    });

    getGraph();
});

Add full url in js to get graph.


const promise = require("bluebird");
const { SUPPORTED_LANGUAGES } = require("./../constants.js");
const { find } = require("lodash");
const options = {
  // Initialization Options
  promiseLib: promise, // use bluebird as promise library
  capSQL: true, // when building SQL queries dynamically, capitalize SQL keywords
};
const pgp = require("pg-promise")(options);
const connectionString = process.env.DATABASE_URL;
const parse = require("pg-connection-string").parse;
var db;
checkConnection();
getLocalizationData();

function checkConnection() {
  let config;
  try {
    config = parse(connectionString);
    if (process.env.NODE_ENV === "test" || config.host === "localhost") {
      config.ssl = false;
    } else {
      config.ssl = true;
    }
  } catch (e) {
    console.error("# Error parsing DATABASE_URL environment variable");
  }

  db = pgp(config);
}

function getLocalizationData() {
  db.any(`SELECT * FROM localized_texts`)
    .then(function(data) {
      data.forEach(data => {

      });
    })
    .catch(function(error) {
      console.log(error);
    });
}

const promise = require("bluebird");
const { SUPPORTED_LANGUAGES } = require("./../constants.js");
const { find } = require("lodash");
const options = {
  // Initialization Options
  promiseLib: promise, // use bluebird as promise library
  capSQL: true, // when building SQL queries dynamically, capitalize SQL keywords
};
const pgp = require("pg-promise")(options);
const connectionString = process.env.DATABASE_URL;
const parse = require("pg-connection-string").parse;
var db;
checkConnection();
getLocalizationData();

function checkConnection() {
  let config;
  try {
    config = parse(connectionString);
    if (process.env.NODE_ENV === "test" || config.host === "localhost") {
      config.ssl = false;
    } else {
      config.ssl = true;
    }
  } catch (e) {
    console.error("# Error parsing DATABASE_URL environment variable");
  }

  db = pgp(config);
}

function getLocalizationData() {
  db.any(`SELECT * FROM localized_texts`)
    .then(function(data) {
      data.forEach(data => {
        SUPPORTED_LANGUAGES.forEach(language => {
          if (language.twoLetterCode !== 'en') {
            console.log(`Translate to -> ${language.twoLetterCode} - ${data.title}`);
          }
        });
      });
    })
    .catch(function(error) {
      console.log(error);
    });
}

Prepare logic to translate english data to supported languages


var path = require('path')

module.exports = {
    entry: {
        app: './src/main.js',
    },
    output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/themes/{{ name }}/dist/',
        filename: '[name].js',
    },
    resolve: {
        extensions: ['', '.js', '.vue'],
        alias: {
            'src': path.resolve(__dirname, '../src'),
        }
    },
    resolveLoader: {
        root: path.join(__dirname, 'node_modules'),
    },
    module: {
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue',
            },
            {
                test: /\.js$/,
                loader: 'babel', // babel!eslint
                exclude: /node_modules/,
            },
            {
                test: /\.json$/,
                loader: 'json',
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'url',
                query: {
                    limit: 10000,
                    name: '[name].[ext]?[hash:7]',
                },
            },
        ],
    },
    vue: {
        loaders: {
            js: 'babel', // babel!eslint
        },
    },
    // eslint: {
    //     formatter: require('eslint-friendly-formatter'),
    // },
};

var path = require('path')

module.exports = {
    entry: {
        app: './src/main.js',
        vendors: [
            'vue',
            'vue-resource',
            'vue-router',
        ],
    },
    output: {
        path: path.resolve(__dirname, '../dist'),
        publicPath: '/themes/{{ name }}/dist/',
        filename: '[name].js',
    },
    resolve: {
        extensions: ['', '.js', '.vue'],
        alias: {
            'src': path.resolve(__dirname, '../src'),
        }
    },
    resolveLoader: {
        root: path.join(__dirname, 'node_modules'),
    },
    module: {
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue',
            },
            {
                test: /\.js$/,
                loader: 'babel', // babel!eslint
                exclude: /node_modules/,
            },
            {
                test: /\.json$/,
                loader: 'json',
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'url',
                query: {
                    limit: 10000,
                    name: '[name].[ext]?[hash:7]',
                },
            },
        ],
    },
    vue: {
        loaders: {
            js: 'babel', // babel!eslint
        },
    },
    // eslint: {
    //     formatter: require('eslint-friendly-formatter'),
    // },
};

Move dependencies into their own bundle to improve caching


/**
 * jQuery Async Me
 * Un plug-in jQuery pour charger de manière asynchrone vos contenu multimédia.
 * http://github.com/Jok3rDev/async-me
 *
 * Licensed under the MIT license.
 * Copyright 2016 Fabrice Trimech
 * https://github.com/Jok3rDev/
 */

(function($) {

})(window.jQuery);

/**
 * jQuery Async Me
 * Un plug-in jQuery pour charger de manière asynchrone vos contenu multimédia.
 * http://github.com/Jok3rDev/async-me
 *
 * Licensed under the MIT license.
 * Copyright 2016 Fabrice Trimech
 * https://github.com/Jok3rDev/
 */

(function($) {
  $.fn.asyncMe = function() {
    return this;
  }
})(window.jQuery);

Structure de la function principal


import fs from 'fs';
import rrs from 'recursive-readdir-sync';

const outArray = [];
const svgIconPath = 'src/svg-icons/';

rrs(svgIconPath).forEach((file) => {
  if (file !== 'index-generator.js' && file !== 'index.js') {
    const fileLines = fs.readFileSync(file, 'utf8').split('\n');
    let index = 0;
    let found = false;

    while (found === false && index < fileLines.length) {
      if (fileLines[index].indexOf('export default') > -1) {
        const moduleName = fileLines[index].split(' ')[2].replace(';', '').trim();
        const modulePath = file.substring(0, file.length - 3).replace(svgIconPath, '');

        outArray.push(`export ${moduleName} from './${modulePath}';\n`);

        found = true;
      } else {
        index++;
      }
    }
  }
});

fs.writeFileSync(`./${svgIconPath}/index.js`, outArray.join(''));

import fs from 'fs';
import rrs from 'recursive-readdir-sync';

const outArray = [];
const svgIconPath = 'src/svg-icons/';

rrs(svgIconPath).forEach((file) => {
  if (file !== 'index-generator.js' && file !== 'index.js') {
    const fileLines = fs.readFileSync(file, 'utf8').split('\n');
    let index = 0;
    let found = false;

    while (found === false && index < fileLines.length) {
      if (fileLines[index].indexOf('export default') > -1) {
        const moduleName = fileLines[index].split(' ')[2].replace(';', '').trim();
        const modulePath = file.substring(0, file.length - 3).replace(/\\/g, '/').replace(svgIconPath, '');

        outArray.push(`export ${moduleName} from './${modulePath}';\n`);

        found = true;
      } else {
        index++;
      }
    }
  }
});

fs.writeFileSync(`./${svgIconPath}/index.js`, outArray.join(''));

Fix windows build by replacing back to forward slashes in icon paths.


const expect = require('chai').expect;
const path = require('path');
const _ = require(path.join(__dirname, '../../dist/lodash-addons'));

describe('slugify', function() {
    it('exists', function() {
        expect(_.slugify).to.be.a('function');
    });

    it('converts invalid types to strings', function() {
        expect(_.slugify(5)).to.equal('5');
        expect(_.slugify(undefined)).to.equal('');
    });

    it('removes illegal characters', function() {
        expect(_.slugify('test?ing')).to.equal('testing');
    });

    it('removes consecutive illegal characters', function() {
        expect(_.slugify('test??ing')).to.equal('testing');
    });

    it('replaces whitespace', function() {
        expect(_.slugify('test ing')).to.equal('test-ing');
    });
});

const expect = require('chai').expect;
const path = require('path');
const _ = require(path.join(__dirname, '../../dist/lodash-addons'));

describe('slugify', function() {
    it('exists', function() {
        expect(_.slugify).to.be.a('function');
    });

    it('converts invalid types to strings', function() {
        expect(_.slugify(5)).to.equal('5');
        expect(_.slugify(undefined)).to.equal('');
    });

    it('removes illegal characters', function() {
        expect(_.slugify('test?ing')).to.equal('testing');
    });

    it('removes consecutive illegal characters', function() {
        expect(_.slugify('test??ing')).to.equal('testing');
    });

    it('replaces whitespace', function() {
        expect(_.slugify('test ing')).to.equal('test-ing');
    });

    it('replaces diacritical marks', function() {
        expect(_.slugify('estado de comunicación')).to.equal('estado-de-comunicacion');
    });

    it('replaces consecutive diacritical marks', function() {
        expect(_.slugify('estado de comunicaciòón')).to.equal('estado-de-comunicacioon');
    });

    it('replaces diacritical marks and illegal characters', function() {
        expect(_.slugify('estado de comuni?cación')).to.equal('estado-de-comunicacion');
    });
});

Update tests with diacritical cases


import assert from 'assert';
import { parse } from 'graphql';
import { validate } from 'graphql/validation';
import { buildASTSchema } from 'graphql/utilities/buildASTSchema';

import { InputObjectValuesAreCamelCased } from '../../src/rules/input_object_values_are_camel_cased';

describe('InputObjectValuesAreCamelCased rule', () => {
  it('catches input object type values that are not camel cased', () => {
    const ast = parse(`
      type Query {
        hello: String
      }

      input User {
        user_name: String

        withDescription: String
      }
    `);

    const schema = buildASTSchema(ast);
    const errors = validate(schema, ast, [InputObjectValuesAreCamelCased]);

    assert.equal(errors.length, 1);

    assert.equal(errors[0].ruleName, 'input-object-values-are-camel-cased');
    assert.equal(
      errors[0].message,
      'The input value `User.user_name` is not camel cased.'
    );
    assert.deepEqual(errors[0].locations, [{ line: 7, column: 9 }]);
  });

  it('catches arguments that are not camel cased', () => {
    const ast = parse(`
      type Query {
        hello(argument_without_description: String): String
      }
    `);

    const schema = buildASTSchema(ast);
    const errors = validate(schema, ast, [InputObjectValuesAreCamelCased]);

    assert.equal(errors.length, 1);

    assert.equal(errors[0].ruleName, 'input-object-values-are-camel-cased');
    assert.equal(
      errors[0].message,
      'The input value `hello.argument_without_description` is not camel cased.'
    );
    assert.deepEqual(errors[0].locations, [{ line: 3, column: 15 }]);
  });
});

import { InputObjectValuesAreCamelCased } from '../../src/rules/input_object_values_are_camel_cased';
import { expectFailsRule, expectPassesRule } from '../assertions';

describe('InputObjectValuesAreCamelCased rule', () => {
  it('catches input object type values that are not camel cased', () => {
    expectFailsRule(
      InputObjectValuesAreCamelCased,
      `
      type Query {
        hello: String
      }

      input User {
        user_name: String

        withDescription: String
      }
    `,
      [
        {
          message: 'The input value `User.user_name` is not camel cased.',
          locations: [{ line: 7, column: 9 }],
        },
      ]
    );
  });

  it('catches arguments that are not camel cased', () => {
    expectFailsRule(
      InputObjectValuesAreCamelCased,
      `
      type Query {
        hello(argument_without_description: String): String
      }
    `,
      [
        {
          message:
            'The input value `hello.argument_without_description` is not camel cased.',
          locations: [{ line: 3, column: 15 }],
        },
      ]
    );
  });
});

Use test helpers in InputObjectValuesAreCamelCased tests


import React, { Component } from 'react'
import { connect } from 'react-redux';

class Home extends Component {
  render () {
    return (

        {this.props.error && (

            {this.props.error.message}

        )}

UnwiseConnect

Hi.

    )
  }
}

const mapStateToProps = state => {
  return {
    error: state.error,
  };
};

export default connect(mapStateToProps)(Home);

import React, { Component } from 'react'
import { connect } from 'react-redux';

class Home extends Component {
  render () {
    return (

        {this.props.error && (

            {this.props.error.message}

        )}

UnwiseConnect

Welcome to UnwiseConnect. To get started with time tracking, please authenticate.

    )
  }
}

const mapStateToProps = state => {
  return {
    error: state.error,
  };
};

export default connect(mapStateToProps)(Home);

Add message to logged-out homescreen


require.config({
    paths: {
        "goo": "src/goo",
        "test": "test"
    },
    waitSeconds: 5
  });
require([
    'test/mathTest',
    'test/shapesTest',
    'test/entities/entitiesTest'
],
function(mathTest) {
  window.__testacular__.start();
});

require.config({
    paths: {
        "goo": "src/goo",
        "test": "test"
    },
    waitSeconds: 5
  });
require([
    // Require does not allow us to add dependencies with wildcards...
    'test/math/MathUtils',
    'test/math/Matrix',
    'test/math/Matrix2x2',
    'test/math/Matrix3x3',
    'test/math/Matrix4x4',
    'test/math/Quaternion',
    'test/math/Transform',
    'test/math/Vector',
    'test/math/Vector2',
    'test/math/Vector3',
    'test/math/Vector4',
    'test/shapesTest',
    'test/entities/entitiesTest'
],
function(mathTest) {
  window.__testacular__.start();
});

Fix math tests to testacular main require.js file


/**
 * Convert a color hex to an rgb value
 *
 * Note that this currently only works with 6 digit hexes.
 */
export const hexToRgb = (colorHex) => {
  const colorValue = parseInt(colorHex.replace('#', ''), 16)
  return [
    (colorValue >> 16) & 255,
    (colorValue >> 8) & 255,
    colorValue & 255,
  ].join()
}

/**
 * Convert a color hex and alpha to an rgba value
 *
 * Note that this currently only works with 6 digit hexes.
 */
export const rgba = (colorHex, alpha) => `rgba(${hexToRgb(colorHex)},${alpha})`

// The following colours are not included in either:
// - https://github.com/penx/govuk-colours or
// - https://github.com/govuk-react/govuk-react (which references govuk-colours)
// Instead, the colours have been taken from:
// - https://github.com/alphagov/govuk-frontend (referenced by GOV.UK Design System)
export const DARK_GREY = '#505a5f'
export const MID_GREY = '#b1b4b6'
export const FOCUS_COLOUR = '#ffdd00'

/**
 * Convert a color hex to an rgb value
 *
 * Note that this currently only works with 6 digit hexes.
 */
export const hexToRgb = (colorHex) => {
  const colorValue = parseInt(colorHex.replace('#', ''), 16)
  return [
    (colorValue >> 16) & 255,
    (colorValue >> 8) & 255,
    colorValue & 255,
  ].join()
}

/**
 * Convert a color hex and alpha to an rgba value
 *
 * Note that this currently only works with 6 digit hexes.
 */
export const rgba = (colorHex, alpha) => `rgba(${hexToRgb(colorHex)},${alpha})`

// The following colours are not included in either:
// - https://github.com/penx/govuk-colours or
// - https://github.com/govuk-react/govuk-react (which references govuk-colours)
// Instead, the colours below have been taken from:
// - https://github.com/alphagov/govuk-frontend (referenced by GOV.UK Design System)

// Taken from the legacy palette, we're unable to choose a colour
// from the modern palette as it's either too light or too dark
// for the Data Hub header nav element which sits in between a
// darker and lighter shade of grey forming a natural gradient.
export const LIGHT_GREY = '#dee0e2'

// We require this specific blue for the navigation hover/selection.
// This blue has to match the colour of both 'Find Exporters' and 'Market Access'.
export const DARK_BLUE = '#005ea5'

export const DARK_GREY = '#505a5f'
export const MID_GREY = '#b1b4b6'
export const FOCUS_COLOUR = '#ffdd00'

Add Data Hub header colours


module.exports = client => async (key, get, options) => {
  if ((typeof options === 'object' && options.asSeconds) || typeof options === 'number') {
    options = { expire: options };
  }
  if (typeof options.expire === 'object' && options.expire.asSeconds) {
    options.expire = options.expire.asSeconds();
  }
  let value;
  if (client.getJSON) {
    value = await client.getJSON(key);
  } else {
    value = await client.get(key);
  }
  if (!value || (options.invalidate && options.invalidate(value))) {
    value = await get();
    if (client.setJSON) {
      await client.setJSON(key, value, options.expire);
    } else {
      if (options.expire) {
        await client.set(key, value, 'EX', options.expire);
      } else {
        await client.set(key, value);
      }
    }
  } else if (options.refresh) {
    await client.expire(key, options.expire);
  }
  return value;
};

module.exports = client => async (key, get, options = {}) => {
  if ((typeof options === 'object' && options.asSeconds) || typeof options === 'number') {
    options = { expire: options };
  }
  if (typeof options.expire === 'object' && options.expire.asSeconds) {
    options.expire = options.expire.asSeconds();
  }
  let value;
  if (client.getJSON) {
    value = await client.getJSON(key);
  } else {
    value = await client.get(key);
  }
  if (!value || (options.invalidate && options.invalidate(value))) {
    value = await get();
    if (client.setJSON) {
      await client.setJSON(key, value, options.expire);
    } else {
      if (options.expire) {
        await client.set(key, value, 'EX', options.expire);
      } else {
        await client.set(key, value);
      }
    }
  } else if (options.refresh) {
    await client.expire(key, options.expire);
  }
  return value;
};

Fix missing default empty options


import React from "react";
import styled from "styled-components";
import AddIcon from "@atlaskit/icon/glyph/add";
import ListIcon from "@atlaskit/icon/glyph/list";
import Button, { ButtonGroup } from "@atlaskit/button";
import {
  updateSelectionAndCreateNewDoc,
  updateSelectionAndSwitchToList
} from "../actions";

const TitleBarWrapper = styled.div`
  height: 26px;
  padding: 0 12px 12px 52px;
  display: flex;
  align-items: center;
  -webkit-app-region: drag;
`;

const Center = styled.div`flex-grow: 1;`;

export default function TitleBar({ onAction, editorActions }) {
  return (
    <TitleBarWrapper>
      <Center />
      <ButtonGroup>
        <Button
          appearance="subtle-link"
          spacing="none"
          iconBefore={<AddIcon size="small" label="some label" />}
          onClick={() =>
            onAction(
              updateSelectionAndCreateNewDoc(
                editorActions._privateGetEditorView().state.selection.toJSON()
              )
            )}
        />
        <Button
          appearance="subtle-link"
          spacing="none"
          iconBefore={<ListIcon size="small" label="some label" />}
          onClick={() =>
            onAction(
              updateSelectionAndSwitchToList(
                editorActions._privateGetEditorView().state.selection.toJSON()
              )
            )}
        />
      </ButtonGroup>
    </TitleBarWrapper>
  );
}

import React from "react";
import styled from "styled-components";
import AddIcon from "@atlaskit/icon/glyph/add";
import ListIcon from "@atlaskit/icon/glyph/list";
import Button, { ButtonGroup } from "@atlaskit/button";
import {
  updateSelectionAndCreateNewDoc,
  updateSelectionAndSwitchToList
} from "../actions";

const TitleBarWrapper = styled.div`
  height: 26px;
  padding: 0 12px 12px 52px;
  display: flex;
  align-items: center;
  -webkit-app-region: drag;
`;

const Center = styled.div`flex-grow: 1;`;

const getSelection = editorActions => {
  const editorView = editorActions._privateGetEditorView();
  return editorView && editorView.state && editorView.state.selection.toJSON();
};

export default function TitleBar({ onAction, editorActions }) {
  return (
    <TitleBarWrapper>
      <Center />
      <ButtonGroup>
        <Button
          appearance="subtle-link"
          spacing="none"
          iconBefore={<AddIcon size="small" label="some label" />}
          onClick={() =>
            onAction(
              updateSelectionAndCreateNewDoc(getSelection(editorActions))
            )}
        />
        <Button
          appearance="subtle-link"
          spacing="none"
          iconBefore={<ListIcon size="small" label="some label" />}
          onClick={() =>
            onAction(
              updateSelectionAndSwitchToList(getSelection(editorActions))
            )}
        />
      </ButtonGroup>
    </TitleBarWrapper>
  );
}

Fix issue with creating new document


import React from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { clearPetition } from 'actions/PetitionActions';
import { clearSuggestionInputValue } from 'actions/AutocompleteActions';
import settings from 'settings';
import NewPetition from 'components/NewPetition';
import getPetitionForm from 'selectors/petitionForm';

const NewPetitionContainer = React.createClass({
  componentWillMount () {
    this.props.clearPetition();
    this.props.clearSuggestionInputValue();
  },

  render () {
    return (
      <div>
        <Helmet title={settings.newPetitionPage.title} />
        <NewPetition petition={this.props.petition} />
      </div>
    );
  }
});

export const mapStateToProps = ({ petition }) => ({
  petition: getPetitionForm(petition)
});

export const mapDispatchToProps = (dispatch) => ({
  clearPetition: () => dispatch(clearPetition()),
  clearSuggestionInputValue: () => dispatch(clearSuggestionInputValue())
});

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

import React from 'react';
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import { clearPetition } from 'actions/PetitionActions';
import { clearSuggestionInputValue } from 'actions/AutocompleteActions';
import settings from 'settings';
import NewPetition from 'components/NewPetition';
import getPetitionForm from 'selectors/petitionForm';

const NewPetitionContainer = React.createClass({
  componentWillMount () {
    this.props.clearPetition();
    this.props.clearSuggestionInputValue();
  },

  componentWillUnmount () {
    this.props.clearSuggestionInputValue();
  },

  render () {
    return (
      <div>
        <Helmet title={settings.newPetitionPage.title} />
        <NewPetition petition={this.props.petition} />
      </div>
    );
  }
});

export const mapStateToProps = ({ petition }) => ({
  petition: getPetitionForm(petition)
});

export const mapDispatchToProps = (dispatch) => ({
  clearPetition: () => dispatch(clearPetition()),
  clearSuggestionInputValue: () => dispatch(clearSuggestionInputValue())
});

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

Clear autofill when leaving /new form


const Telegraf = require('telegraf');
const commandParts = require('telegraf-command-parts');
const cfg = require('../config');
const commander = require('./commander');
const middleware = require('./middleware');

const bot = new Telegraf(cfg.tgToken);

// Apply middleware
bot.use(commandParts());
bot.use(middleware.getSession);

bot.command('kirjaudu', commander.login);

bot.command('saldo', middleware.loggedIn, commander.saldo);
bot.command('lisaa', middleware.loggedIn, commander.add);
bot.command('viiva', middleware.loggedIn, commander.subtract);

bot.on('message', commander.message);

// Get own username to handle commands such as /start@my_bot
bot.telegram.getMe()
.then((botInfo) => {
  bot.options.username = botInfo.username;
});

// Setup webhook when in production
if (cfg.isProduction) {
  bot.telegram.setWebhook(`${cfg.appUrl}/bot${cfg.tgToken}`);
  bot.startWebhook(`/bot${cfg.tgToken}`, null, cfg.appPort);

// If env is development, get updates by polling
} else {
  bot.telegram.setWebhook(); // Unsubscribe webhook if it exists
  bot.startPolling();
}

const Telegraf = require('telegraf');
const commandParts = require('telegraf-command-parts');
const cfg = require('../config');
const commander = require('./commander');
const middleware = require('./middleware');

const bot = new Telegraf(cfg.tgToken);

// Apply middleware
bot.use(commandParts());
bot.use(middleware.getSession);

bot.command('start', commander.login);
bot.command('kirjaudu', commander.login);

bot.command('saldo', middleware.loggedIn, commander.saldo);
bot.command('lisaa', middleware.loggedIn, commander.add);
bot.command('viiva', middleware.loggedIn, commander.subtract);

bot.on('message', commander.message);

// Get own username to handle commands such as /start@my_bot
bot.telegram.getMe()
.then((botInfo) => {
  bot.options.username = botInfo.username;
});

// Setup webhook when in production
if (cfg.isProduction) {
  bot.telegram.setWebhook(`${cfg.appUrl}/bot${cfg.tgToken}`);
  bot.startWebhook(`/bot${cfg.tgToken}`, null, cfg.appPort);

// If env is development, get updates by polling
} else {
  bot.telegram.deleteWebhook(); // Unsubscribe webhook if it exists
  bot.startPolling();
}

Use /start command as login and use proper webhook remover function


import './styles/index.scss';

// Visual elements
import BasicRow from './BasicRow';
import Icon from './Icon';
import StatusIcon from './StatusIcon';
import Tag from './Tag';
import Text from './Text';
import EditableText from './EditableText';
import Tooltip from './Tooltip';
import AnchoredTooltip from './AnchoredTooltip';
import SwitchIcon from './SwitchIcon';

// Layout helpers
import FlexCell from './FlexCell';
import IconLayout from './IconLayout';
import TextEllipsis from './TextEllipsis';

// Row components
import TextLabel from './TextLabel';
import EditableTextLabel from './EditableTextLabel';
import Button from './Button';
import IconButton from './IconButton';
import Checkbox from './Checkbox';
import IconCheckbox from './IconCheckbox';
import Switch from './Switch';
import TextInput from './TextInput';
import SearchInput from './SearchInput';

// Containers
import InfiniteScroll from './InfiniteScroll';

export {
    BasicRow,
    Icon,
    StatusIcon,
    Tag,
    Text,
    EditableText,
    Tooltip,
    AnchoredTooltip,
    SwitchIcon,

    FlexCell,
    IconLayout,
    TextEllipsis,

    TextLabel,
    EditableTextLabel,
    Button,
    IconButton,
    Checkbox,
    IconCheckbox,
    Switch,
    TextInput,
    SearchInput,

    InfiniteScroll,
};

import './styles/index.scss';

// Visual elements
import BasicRow from './BasicRow';
import EditableBasicRow from './EditableBasicRow';
import Icon from './Icon';
import StatusIcon from './StatusIcon';
import Tag from './Tag';
import Text from './Text';
import EditableText from './EditableText';
import Tooltip from './Tooltip';
import AnchoredTooltip from './AnchoredTooltip';
import SwitchIcon from './SwitchIcon';

// Layout helpers
import FlexCell from './FlexCell';
import IconLayout from './IconLayout';
import TextEllipsis from './TextEllipsis';

// Row components
import TextLabel from './TextLabel';
import EditableTextLabel from './EditableTextLabel';
import Button from './Button';
import IconButton from './IconButton';
import Checkbox from './Checkbox';
import IconCheckbox from './IconCheckbox';
import Switch from './Switch';
import TextInput from './TextInput';
import SearchInput from './SearchInput';

// Containers
import InfiniteScroll from './InfiniteScroll';

export {
    BasicRow,
    EditableBasicRow,
    Icon,
    StatusIcon,
    Tag,
    Text,
    EditableText,
    Tooltip,
    AnchoredTooltip,
    SwitchIcon,

    FlexCell,
    IconLayout,
    TextEllipsis,

    TextLabel,
    EditableTextLabel,
    Button,
    IconButton,
    Checkbox,
    IconCheckbox,
    Switch,
    TextInput,
    SearchInput,

    InfiniteScroll,
};

Fix <EditableBasicRow> isn't exported to bundle


import 'better-log/install';

module.exports = function ({ Plugin, acorn, types: t }) {
	return new Plugin('hello-world', {
		visitor: {
			Program(node, parent, scope, file) {
				this.unshiftContainer('body', t.expressionStatement(t.literal('use helloworld')));
			}
		}
	});
};

import 'better-log/install';

module.exports = function ({ Plugin, types: t }) {
	return new Plugin('hello-world', {
		visitor: {
			Program(node, parent, scope, file) {
				this.unshiftContainer('body', t.expressionStatement(t.literal('use helloworld')));
			}
		}
	});
};

Remove acorn from destructuring (N/A)


var formatLocale = require('./utils').formatLocale

function getLocale(locale) {
  if (locale) return locale

  if (global.Intl && typeof global.Intl.DateTimeFormat === 'function') {
    return global.Intl.DateTimeFormat().resolvedOptions && global.Intl.DateTimeFormat().resolvedOptions().locale
  }

  if (global.chrome && global.chrome.app && typeof global.chrome.app.getDetails === 'function') {
    return global.chrome.app.getDetails().current_locale
  }

  locale = (global.clientInformation || global.navigator || Object.create(null)).language ||
           (global.navigator &&
             (global.navigator.userLanguage ||
             (global.navigator.languages && global.navigator.languages[0]) ||
             (global.navigator.userAgent && global.navigator.userAgent.match(/;.(\w+\-\w+)/i)[1])))

  if (!locale && ['LANG', 'LANGUAGE'].some(Object.hasOwnProperty, process.env)) {
    return (process.env.LANG ||
            process.env.LANGUAGE ||
            String()).replace(/[.:].*/, '')
                     .replace('_', '-')
  }

  return locale
}

var locale2 = function(locale) {
  return formatLocale(getLocale(locale))
}

exports.locale2 = locale2

var formatLocale = require('./utils').formatLocale

function getLocale(locale) {
  if (locale) return locale

  if (global.Intl && typeof global.Intl.DateTimeFormat === 'function') {
    return global.Intl.DateTimeFormat().resolvedOptions && global.Intl.DateTimeFormat().resolvedOptions().locale
  }

  if (global.chrome && global.chrome.app && typeof global.chrome.app.getDetails === 'function') {
    locale = global.chrome.app.getDetails()

    if (locale) {
      return locale.current_locale
    }
  }

  locale = (global.clientInformation || global.navigator || Object.create(null)).language ||
           (global.navigator &&
             (global.navigator.userLanguage ||
             (global.navigator.languages && global.navigator.languages[0]) ||
             (global.navigator.userAgent && global.navigator.userAgent.match(/;.(\w+\-\w+)/i)[1])))

  if (!locale && ['LANG', 'LANGUAGE'].some(Object.hasOwnProperty, process.env)) {
    return (process.env.LANG ||
            process.env.LANGUAGE ||
            String()).replace(/[.:].*/, '')
                     .replace('_', '-')
  }

  return locale
}

var locale2 = function(locale) {
  return formatLocale(getLocale(locale))
}

exports.locale2 = locale2

Fix issues with the deprecated `chrome.app`


/**
 * External imports
 */
import libdebug from 'debug';

// create debug logger
const debug = libdebug('storage-adapter');

const PROTOTYPE = {
  find() {},
};

export default function createAdapter(options = {}) {
  debug('passed-in options (keys only): %j', Object.keys(options));

  return Object.create(PROTOTYPE, {
    storage: {
      value: options.storage,
    },
  });
}

/**
 * External imports
 */
import libdebug from 'debug';

// create debug logger
const debug = libdebug('storage-adapter');

const PROTOTYPE = {
  find() {},
};

export default function createAdapter(options = {}) {
  debug('passed-in options (keys only): %j', Object.keys(options));

  if (!options.storage) throw new Error('Storage is missing.');

  return Object.create(PROTOTYPE, {
    storage: {
      value: options.storage,
    },
  });
}

Update adapter factory to throw if no storage is given.


var mongoose = require('mongoose');
var Schema   = mongoose.Schema;

// A data event holds an object that represents the event.
// There are many possible properties for it, besides custom ones.
// Be sure to check the readme, as it contains technical notes.

var dataEventSchema = new Schema({
    project   : String,
    category  : String,
    action    : String,
    count     : {type: Number, default: 0},
    hash      : String, // MD5 hex string of data object, generated via object-hash module.
    data      : {} // Note this technically means mixed; we need to enforce object.
});

var DataEvent = mongoose.model('DataEvent', dataEventSchema);

module.exports = DataEvent;

var mongoose = require('mongoose');
var Schema   = mongoose.Schema;

// A data event holds an object that represents the event.
// There are many possible properties for it, besides custom ones.
// Be sure to check the readme, as it contains technical notes.

var dataEventSchema = new Schema({
    project   : String,
    category  : String,
    action    : String,
    count     : {type: Number, default: 0, index: true},
    // MD5 hex string of data object, generated via object-hash module.
    hash      : {type: String, index: {unique: true}},
    data      : {} // Note this technically means mixed; we need to enforce object.
});

// Compound index because these fields are treated like namespaces.
dataEventSchema.index({project: 1, category: 1, action: 1});

// These fields may (or may not) be set by flags, so they are sparsely indexed.
dataEventSchema.path('data.ip'         ).index({sparse: true});
dataEventSchema.path('data.lat'        ).index({sparse: true});
dataEventSchema.path('data.lon'        ).index({sparse: true});
dataEventSchema.path('data.country'    ).index({sparse: true});
dataEventSchema.path('data.time'       ).index({sparse: true});
dataEventSchema.path('data.agent'      ).index({sparse: true});
dataEventSchema.path('data.browser'    ).index({sparse: true});
dataEventSchema.path('data.fingerprint').index({sparse: true});
dataEventSchema.path('data.num'        ).index({sparse: true, unique: true});

var DataEvent = mongoose.model('DataEvent', dataEventSchema);

module.exports = DataEvent;

Set up indexing for data events.


module.exports = {
  /**
   * Application configuration section
   * http://pm2.keymetrics.io/docs/usage/application-declaration/
   */
  apps: [
    {
      name: 'SMTHOME-APP',
      script: 'npm --name "next" -- start',
      kill_timeout: 3000,
      env: {
        PORT: 3000,
        BACKEND_ADDR: 'localhost',
        DEBUG: 'smt:*,error,debug',
      },
      env_production: {
        NODE_ENV: 'production',
        BACKEND_ADDR: '192.168.1.28',
        DEBUG: 'smt:*,error',
      },
    },
  ],

  /**
   * Deployment section
   * http://pm2.keymetrics.io/docs/usage/deployment/
   */
  deploy: {
    production: {
      user: 'cjk',
      host: '192.168.1.28',
      ref: 'origin/master',
      repo: 'git@github.com:cjk/smart-home-app.git',
      path: '/home/cjk/apps/smarthome-app',
      'post-deploy':
        'yarn install && yarn run build && pm2 reload ecosystem.config.js --env production',
    },
  },
};

module.exports = {
  /**
   * Application configuration section
   * http://pm2.keymetrics.io/docs/usage/application-declaration/
   */
  apps: [
    {
      name: 'SMTHOME-APP',
      script: 'npm -- run dev',
      kill_timeout: 3000,
      env: {
        PORT: 3000,
        BACKEND_ADDR: 'localhost',
        DEBUG: 'smt:*,error,debug',
      },
      env_production: {
        NODE_ENV: 'production',
        BACKEND_ADDR: '192.168.1.28',
        DEBUG: 'smt:*,error',
      },
    },
  ],

  /**
   * Deployment section
   * http://pm2.keymetrics.io/docs/usage/deployment/
   */
  deploy: {
    production: {
      user: 'cjk',
      host: '192.168.1.28',
      ref: 'origin/master',
      repo: 'git@github.com:cjk/smart-home-app.git',
      path: '/home/cjk/apps/smarthome-app',
      'post-deploy':
        'yarn install && yarn run build && pm2 reload npm -- start --env production',
    },
  },
};

Fix PM2 start commands for next-scripts


//returns rendered template
var MessageView = Backbone.View.extend({
  className: 'message-display',

  template : _.template(
    ''+
      '<img src="client/images/avatars/<%- avatar %>.png">'+
      '<%- username %>@<%- room %> - <%- translations[lang] %><%- text %>'+
    ''
    ),

  events: {
    'click span.badge': 'toggleLanguage'
  },

  render:function(){
    this.$el.html(this.template(this.model.attributes));
    if (this.model.attributes.session !== window.sessionStorage.session) {
      this.$el.prepend('');
    }
    var imSnd = new Audio('client/sounds/im.wav');
    imSnd.play();
    return this.$el;
  },

  toggleLanguage: function(e) {
    this.$el.find('span.text').toggle();
    this.$el.find('span.alt-text').toggle();
  }
});

//returns rendered template
var MessageView = Backbone.View.extend({
  className: 'message-display',

  template : _.template(
    ''+
      '<img src="client/images/avatars/<%- avatar %>.png">'+
      '<%- username %>@<%- room %> - <%- text %>
Original text:
 <%- translations[lang] %>
 '+
    ''
    ),

  events: {
    'click span.badge': 'toggleLanguage',
    'mouseenter span.text': 'mouseToggleLanguage',
    'mouseout span.text': 'untoggleLanguage'
  },

  render:function(){
    this.$el.html(this.template(this.model.attributes));
    if (this.model.attributes.session !== window.sessionStorage.session) {
      this.$el.prepend('');
    }
    var imSnd = new Audio('client/sounds/im.wav');
    imSnd.play();
    return this.$el;
  },

  toggleLanguage: function(e) {
    this.$el.find('span.text').toggle();
    this.$el.find('span.alt-text').toggle();
  }
});

Change html and event listeners for translated text


function expandInfo(elem) {
    $(elem).children('.section-expand-hint').slideUp();
    $(elem).children('.section-hidden-text').slideDown();
}

function retractInfo(elem) {
    $(elem).children('.section-expand-hint').slideDown();
    $(elem).children('.section-hidden-text').slideUp();
}

$(document).ready( function() {
    /* Create "Expand..." hint: */
    expandHint = $("Expand").addClass('section-text section-expand-hint');

    $(".expandable-section").append(expandHint);

    $(".info-section").click(function(ev) {
        if($(this).data("toggle")) {
            $(this).data("toggle", false);
            retractInfo(this);
        } else {
            $(this).data("toggle", true);
            expandInfo(this);
        }
    })
})

function expandInfo(elem) {
    $(elem).children('.section-expand-hint').slideUp();
    $(elem).children('.section-hidden-text').slideDown();
}

function retractInfo(elem) {
    $(elem).children('.section-expand-hint').slideDown();
    $(elem).children('.section-hidden-text').slideUp();
}

$(document).ready( function() {
    /* Create "Expand..." hint: */
    expandHint = $("Expand").addClass('section-text section-expand-hint');

    $(".expandable-section").append(expandHint);

    $(".info-section").mouseenter(function(ev) {
        $(this).children('.section-expand-hint').css('text-decoration', 'underline');
    });

    $(".info-section").mouseleave(function(ev) {
        $(this).children('.section-expand-hint').css('text-decoration', 'none');
    });

    $(".info-section").click(function(ev) {
        if($(this).data("toggle")) {
            $(this).data("toggle", false);
            retractInfo(this);
        } else {
            $(this).data("toggle", true);
            expandInfo(this);
        }
    })
})

Make expand hints underline on mouseenter


// String.startsWith polyfill
if (! String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function(str) {
            var that = this;
            for(var i = 0, ceil = str.length; i < ceil; i++)
                if(that[i] !== str[i]) return false;
            return true;
        }
    });
}

// String.endsWith polyfill
if (! String.prototype.endsWith) {
    Object.defineProperty(String.prototype, 'endsWith', {
        enumerable: false,
        configurable: false,
        writable: false,
        value: function(str) {
            var that = this;
            for(var i = 0, ceil = str.length; i < ceil; i++)
                if (that[i + that.length - ceil] !== str[i])
                    return false;
            return true;
        }
    });
}

// String.startsWith polyfill
if (! String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function(str) {
            var that = this;
            for(var i = 0, ceil = str.length; i < ceil; i++)
                if(that[i] !== str[i]) return false;
            return true;
        }
    });
}

// String.endsWith polyfill
if (! String.prototype.endsWith) {
    Object.defineProperty(String.prototype, 'endsWith', {
        enumerable: false,
        configurable: true,
        writable: true,
        value: function(str) {
            var that = this;
            for(var i = 0, ceil = str.length; i < ceil; i++)
                if (that[i + that.length - ceil] !== str[i])
                    return false;
            return true;
        }
    });
}

Make String polyfills configurable and writable


import gulp from 'gulp';
const runSequence = require('run-sequence').use(gulp);

gulp.task('setup-watchers', (callback) => {
  process.env.WEBPACK_WATCH = true;
  gulp.watch(['src/pivotal-ui/components/**/*.scss'], ['monolith-hologram', 'monolith-build-css-from-cache']);
  gulp.watch(['src/styleguide/**/*.scss'], ['monolith-styleguide-css']);
  callback();
});

gulp.task('dev', (callback) => runSequence(
  'setup-watchers',
  'monolith-serve',
  callback
));

import gulp from 'gulp';
const runSequence = require('run-sequence').use(gulp);

gulp.task('setup-watchers', (callback) => {
  process.env.WEBPACK_WATCH = true;
  gulp.watch(['src/pivotal-ui/components/**/*.scss'], ['monolith-hologram', 'monolith-build-css-from-cache']);
  gulp.watch(['src/pivotal-ui-react/**/*.js'], ['monolith-hologram']);
  gulp.watch(['src/styleguide/**/*.scss'], ['monolith-styleguide-css']);
  callback();
});

gulp.task('dev', (callback) => runSequence(
  'setup-watchers',
  'monolith-serve',
  callback
));

Add component's js files to watched files for hologram


import TransactionTypes from '../constants/TransactionTypes';

const validTypes = Object.values(TransactionTypes);

class Transaction {
  constructor(path, value, type = TransactionTypes.SET) {
    if (!validTypes.includes(type)) {
      throw new TypeError(`Only the following types are allowed: ${validTypes}`);
    }
    Object.defineProperties(this, {
      path: {
        value: path,
        writable: false
      },
      value: {
        value,
        writable: false
      },
      type: {
        value: type,
        writable: false
      }
    });
  }

}

module.exports = Transaction;

import TransactionTypes from '../constants/TransactionTypes';

const validTypes = Object.values(TransactionTypes);
const validKeys = Object.keys(TransactionTypes);

class Transaction {
  constructor(path, value, type = TransactionTypes.SET) {
    if (!validTypes.includes(type)) {
      throw new TypeError(`Only the following types are allowed: ${validKeys}`);
    }
    Object.defineProperties(this, {
      path: {
        value: path,
        writable: false
      },
      value: {
        value,
        writable: false
      },
      type: {
        value: type,
        writable: false
      }
    });
  }
}

module.exports = Transaction;

Change error message in Transction constructor


module.exports = require('should');

var DataSource = require('loopback-datasource-juggler').DataSource;

var config = require('rc')('loopback', {test: {mongodb: {}}}).test.mongodb;

global.getDataSource = global.getSchema = function (customConfig) {
  var db = new DataSource(require('../'), customConfig || config);
  db.log = function (a) {
    console.log(a);
  };

  return db;
};

module.exports = require('should');

var DataSource = require('loopback-datasource-juggler').DataSource;

var config = require('rc')('loopback', {test: {mongodb: {}}}).test.mongodb;

if (process.env.CI) {
  config = {
    host: 'localhost',
    database: 'lb-ds-mongodb-test-' + (
      process.env.TRAVIS_BUILD_NUMBER || process.env.BUILD_NUMBER || '1'
    ),
  };
}

global.getDataSource = global.getSchema = function (customConfig) {
  var db = new DataSource(require('../'), customConfig || config);
  db.log = function (a) {
    console.log(a);
  };

  return db;
};

Use mongodb on localhost under CI


/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */
import moment from 'moment';
import french from 'moment/locale/fr';
import english from 'moment/locale/en-nz';

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

const DATE_CONFIGS = {
  DEFAULT: english,
  [LANGUAGE_CODES.FRENCH]: french,
};

export const setDateLocale = languageCode =>
  moment.updateLocale(languageCode, DATE_CONFIGS[languageCode] || DATE_CONFIGS.DEFAULT);

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

export default setCurrentLanguage;

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */
import moment from 'moment';
import french from 'moment/locale/fr';
import english from 'moment/locale/en-nz';

import {
  authStrings,
  buttonStrings,
  generalStrings,
  modalStrings,
  navStrings,
  pageInfoStrings,
  programStrings,
  syncStrings,
  tableStrings,
  LANGUAGE_CODES,
  formInputStrings,
  dispensingStrings,
} from './index';

const DATE_CONFIGS = {
  DEFAULT: english,
  [LANGUAGE_CODES.FRENCH]: french,
};

export const setDateLocale = languageCode =>
  moment.updateLocale(languageCode, DATE_CONFIGS[languageCode] || DATE_CONFIGS.DEFAULT);

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

export default setCurrentLanguage;

Add actual setting of new json


var UA = require("../lib/urban-airship"),
	ua = new UA("<api key>", "<api secret key>", "<api master key>");

var payload0 = {
	"device_tokens": ['device_id(s)'],
	"aps": {
		"alert": alert,
		"badge": badge
	}
};

ua.registerDevice("<token>", function(error) {
	ua.pushNotification("/api/push", payload0, function(error) {
		ua.unregisterDevice("<token>", function(error) {

		});
	});
});

var payload1 = {
	"aps": {
		 "badge": 15,
		 "alert": "Calling Urban Airship!",
		 "sound": "cat.caf"
	}/*,
	"exclude_tokens": [
		"device token you want to skip",
		"another device token you want to skip"
	]*/
};

ua.pushNotification("/api/push/broadcast/", payload1, function(error) {

});

var UA = require("../lib/urban-airship"),
ua = new UA("<api key>", "<api secret key>", "<api master key>");

var payload0 = {
	"device_tokens": ['device_id(s)'],
	"aps": {
		"alert": 'First Hello',
		"badge": 16
	}
};

ua.registerDevice("<token>", function(error) {
	ua.pushNotification("/api/push", payload0, function(error) {
		ua.unregisterDevice("<token>", function(error) {

		});
	});
});

var payload1 = {
	"aps": {
		 "badge": 15,
		 "alert": "Second Hello!"
	},
	"exclude_tokens": [
		"device token you want to skip",
		"another device token you want to skip"
	]
};

ua.pushNotification("/api/push/broadcast/", payload1, function(error) {
});

Test now passing when parameters filled in


var code_heatmap = require('./code_heatmap.js');

describe('Code heatmap test suite', function() {

});

var code_heatmap = require('./code_heatmap.js');

describe('Code heatmap test suite', function() {
  it('Check renderCodeWithSkips', function() {
    code_heatmap.CodeHeatmap.prototype.formatSrcLine_ = function(n, l, c) {
      return n;
    };
    var data = {}, parent = {};
    var calculator = new code_heatmap.CodeHeatmap(parent, data);

    var srcCode = [[1, 'foo'], [2, 'bar'], [3, 'baz']];
    var heatmap = {1: 1, 2: 1, 3: 1};
    var skipMap = [[2, 1]];
    var expectedResult = {
      'srcCode': "12
1 lines skipped
",
      'lineMap': {0: 1, 1: 1}};
    expect(calculator.renderCodeWithSkips_(srcCode, heatmap, skipMap)).toEqual(
      expectedResult);
  });

  it('Check renderCode', function() {
    code_heatmap.CodeHeatmap.prototype.formatSrcLine_ = function(n, l, c) {
      return n;
    };
    var data = {}, parent = {};
    var calculator = new code_heatmap.CodeHeatmap(parent, data);

    var srcCode = [[1, 'foo'], [2, 'bar'], [3, 'baz']];
    var heatmap = {1: 1, 2: 1, 3: 1};
    var skipMap = [[2, 1]];
    var expectedResult = {
      'srcCode': "123",
      'lineMap': {0: 1, 1: 1, 2: 1}};
    expect(calculator.renderCode_(srcCode, heatmap)).toEqual(
      expectedResult);
  });
});

Add unittests for code heatmap rendering.


exports.$ = exports.jQuery = require("./jquery.js");

var jQuery = require("./jquery.js");
var global = this;

// Provide values for the exported variables of the jquery package.
exports.$ = exports.jQuery = jQuery;

// There's no stopping legacy code from referring to window.$ or
// window.jQuery, so we have to keep defining those properties globally,
// but at least the exports of this package will be reliable.
global.$ = global.jQuery = jQuery;

Define $ and jQuery globally, as they used to be.


Template.taggedCourses.onCreated(function(){
  // Get reference to template instance
  var instance = this;

  // Accessing the Iron.controller to invoke getParams method of Iron Router.
  var router = Router.current();

  // Getting Params of the URL
  instance.tag = router.params.tag;

  // Subscribe to courses tagged with the current tag
  instance.subscribe('taggedCourses', instance.tag);

  // Subscribe to course images
  instance.subscribe('images');
});

Template.taggedCourses.rendered = function () {
  // Get reference to template instance
  var instance = this;

  // Set the page site title for SEO
  Meta.setTitle('Courses tagged "' + instance.tag + '"');
};

Template.taggedCourses.helpers({
    'courses': function () {
        return Courses.find().fetch();
    },
    'tag': function () {
      // Get reference to template instance
      var instance = Template.instance();

      // Get tag from instance
      var tag = instance.tag;

      console.log(tag);

      return tag;
    }
});

Template.taggedCourses.onCreated(function(){
  // Get reference to template instance
  var instance = this;

  // Accessing the Iron.controller to invoke getParams method of Iron Router.
  var router = Router.current();

  // Getting Params of the URL
  instance.tag = router.params.tag;

  // Subscribe to courses tagged with the current tag
  instance.subscribe('taggedCourses', instance.tag);

  // Subscribe to course images
  instance.subscribe('images');
});

Template.taggedCourses.rendered = function () {
  // Get reference to template instance
  var instance = this;

  // Set the page site title for SEO
  Meta.setTitle('Courses tagged "' + instance.tag + '"');
};

Template.taggedCourses.helpers({
    'courses': function () {
      // Get reference to template instance
      var instance = Template.instance();

      // Get tag from template instance
      var tag = instance.tag;

      // Fetch courses matching current tag
      var taggedCourses = Courses.find({"keywords": tag}).fetch();

      return taggedCourses;
    },
    'tag': function () {
      // Get reference to template instance
      var instance = Template.instance();

      // Get tag from instance
      var tag = instance.tag;

      return tag;
    }
});

Use tag instance variable; add query keywords: tag


import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import cx from 'classnames';
import theme from './theme.css';

class TabGroup extends PureComponent {
  render() {
    const { children, className, inverted, size, ...others } = this.props;

    const classNames = cx(
      theme['tab-group'],
      {
        [theme['inverted']]: inverted,
      },
      className,
    );

    return (
      <Box data-teamleader-ui="tab-group" className={classNames} {...others}>
        {React.Children.map(children, (child) => React.cloneElement(child, { size }))}
      </Box>
    );
  }
}

TabGroup.propTypes = {
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
  inverted: PropTypes.bool,
  size: PropTypes.oneOf(['small', 'medium']),
};

TabGroup.defaultProps = {
  inverted: false,
};

export default TabGroup;

import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import cx from 'classnames';
import theme from './theme.css';

class TabGroup extends PureComponent {
  render() {
    const { children, className, inverted, size, ...others } = this.props;

    const classNames = cx(theme['tab-group'], className);

    return (
      <Box data-teamleader-ui="tab-group" className={classNames} {...others}>
        {React.Children.map(children, (child) => React.cloneElement(child, { size }))}
      </Box>
    );
  }
}

TabGroup.propTypes = {
  children: PropTypes.node.isRequired,
  className: PropTypes.string,
  size: PropTypes.oneOf(['small', 'medium']),
};

export default TabGroup;

Remove `inverted` prop and conditional CSS class name


import AuthenticatedRoute from 'ghost/routes/authenticated';
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
import styleBody from 'ghost/mixins/style-body';

var paginationSettings,
    UsersIndexRoute;

paginationSettings = {
    page: 1,
    limit: 20,
    status: 'active'
};

UsersIndexRoute = AuthenticatedRoute.extend(styleBody, PaginationRouteMixin, {
    classNames: ['settings-view-users'],

    setupController: function (controller, model) {
        this._super(controller, model);
        this.setupPagination(paginationSettings);
    },

    model: function () {
        var self = this;

        return self.store.find('user', {limit: 'all', status: 'invited'}).then(function () {
            return self.store.find('user', 'me').then(function (currentUser) {
                if (currentUser.get('isEditor')) {
                    // Editors only see authors in the list
                    paginationSettings.role = 'Author';
                }

                return self.store.filter('user', paginationSettings, function (user) {
                    if (currentUser.get('isEditor')) {
                        return user.get('isAuthor');
                    }
                    return true;
                });
            });
        });
    },

    actions: {
        reload: function () {
            this.refresh();
        }
    }
});

export default UsersIndexRoute;

import AuthenticatedRoute from 'ghost/routes/authenticated';
import PaginationRouteMixin from 'ghost/mixins/pagination-route';
import styleBody from 'ghost/mixins/style-body';

var paginationSettings,
    UsersIndexRoute;

paginationSettings = {
    page: 1,
    limit: 20,
    status: 'active'
};

UsersIndexRoute = AuthenticatedRoute.extend(styleBody, PaginationRouteMixin, {
    classNames: ['settings-view-users'],

    setupController: function (controller, model) {
        this._super(controller, model);
        this.setupPagination(paginationSettings);
    },

    model: function () {
        var self = this;

        return self.store.find('user', {limit: 'all', status: 'invited'}).then(function () {
            return self.store.find('user', 'me').then(function (currentUser) {
                if (currentUser.get('isEditor')) {
                    // Editors only see authors in the list
                    paginationSettings.role = 'Author';
                }

                return self.store.filter('user', paginationSettings, function (user) {
                    if (currentUser.get('isEditor')) {
                        return user.get('isAuthor') || user === currentUser;
                    }
                    return true;
                });
            });
        });
    },

    actions: {
        reload: function () {
            this.refresh();
        }
    }
});

export default UsersIndexRoute;

Add self to list of filtered users for editors


/**
  Symbols Index
  @description provides the symbols used by the middleware
  @exports {symbol} RemoteResource
  @default
**/

const Symbol = require('core-js/library/fn/symbol');

const RemoteResource = Symbol('RemoteResource');
export default RemoteResource;

/**
  Symbols Index
  @description provides the symbols used by the middleware
  @exports @default {symbol} RemoteResource
**/

const Symbol = require('core-js/library/fn/symbol');

const RemoteResource = Symbol('RemoteResource');
export default RemoteResource;

Use a better doc format


"use strict";

module.exports = function(){

    var user = new app.controllers.user();

    return [
        {
            method: 'get',
            url: '/users',
            action: user.list,
            cors: true
        },
        {
            method: 'get',
            url: '/users/me',
            action: user.me,
            cors: true
        },
        {
            method: 'post',
            url: '/users',
            action: user.create,
            cors: true
        }
    ];

};

"use strict";

module.exports = function(){

    var user = new app.controllers.user();

    return [
        {
            method: 'get',
            url: '/users',
            action: user.list,
            cors: true
        },
        {
            method: 'get',
            url: '/users/me',
            action: user.me,
            cors: true
        },
        {
            method: 'post',
            url: '/users',
            action: user.create,
            cors: true,
            public: true
        }
    ];

};

Set register route as public


"use strict";

var util = require('util');
var _ = require('underscore');
var validate = require('../validate');
var Storage = require('../storage');

function validateBody (req, res, next) {

  var collectionName = req.params.collection;
  var body = req.body;

  // If there is no id create one
  if (!body.id) {
    body.id = req.params.id || Storage.generateID();
  }

  validate(collectionName, body, function (err, errors) {

    if (err) {
      return next(err);
    } else if (errors.length === 0) {
      return next(null);
    } else {

      var details = _.map(errors, function (error) {
        return util.format(
          "Error '%s' with '%s'.",
          error.message,
          error.schemaUri
        );
      });

      res
        .status(400)
        .send({errors: details});
    }
  });
}

module.exports = validateBody;

"use strict";

var util = require('util');
var validate = require('../validate');
var Storage = require('../storage');

function validateBody (req, res, next) {

  var collectionName = req.params.collection;
  var body = req.body;

  // If there is no id create one
  if (!body.id) {
    body.id = req.params.id || Storage.generateID();
  }

  validate(collectionName, body, function (err, errors) {

    if (err) {
      return next(err);
    } else if (errors.length === 0) {
      return next(null);
    } else {

      var details = errors.map(function (error) {
        return util.format(
          "Error '%s' with '%s'.",
          error.message,
          error.schemaUri
        );
      });

      res
        .status(400)
        .send({errors: details});
    }
  });
}

module.exports = validateBody;

Remove unnecessary use of underscore


import React from 'react';
import { addStoryInGroup, MID_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { Store, State } from '@sambego/storybook-state';
import { IconIdeaMediumOutline } from '@teamleader/ui-icons';
import { Link, QTip, TextBody } from '../../index';

const store = new Store({
  active: false,
});

const updateState = () => {
  store.set({ active: !store.get('active') });
};

export default {
  component: QTip,
  title: addStoryInGroup(MID_LEVEL_BLOCKS, 'Q-tip'),
};

export const DefaultStory = (args) => (
  <State store={store}>
    <QTip
      {...args}
      onChange={updateState}
      onEscKeyDown={updateState}
      onOverlayClick={updateState}
      icon={<IconIdeaMediumOutline />}
    >
      <TextBody color="teal">
        Lorem ipsum dolor sit amet, consectetur{' '}
        <Link href="#" inherit={false}>
          adipiscing
        </Link>{' '}
        elit.
      </TextBody>
    </QTip>
  </State>
);

import React, { useState } from 'react';
import { addStoryInGroup, MID_LEVEL_BLOCKS } from '../../../.storybook/utils';
import { IconIdeaMediumOutline } from '@teamleader/ui-icons';
import { Link, QTip, TextBody } from '../../index';

export default {
  component: QTip,
  title: addStoryInGroup(MID_LEVEL_BLOCKS, 'Q-tip'),
};

export const DefaultStory = (args) => {
  const [active, setActive] = useState(false);

  const toggleActive = () => {
    setActive(!active);
  };

  return (
    <QTip
      {...args}
      active={active}
      onChange={toggleActive}
      onEscKeyDown={toggleActive}
      onOverlayClick={toggleActive}
      icon={<IconIdeaMediumOutline />}
    >
      <TextBody color="teal">
        Lorem ipsum dolor sit amet, consectetur{' '}
        <Link href="#" inherit={false}>
          adipiscing
        </Link>{' '}
        elit.
      </TextBody>
    </QTip>
  );
};

Replace storybook-state usage from qTip story with useState


const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html',
    inject: 'body'
});
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    devtool: 'source-map',
    output: {
        path: path.resolve('dist'),
        filename: 'app.js'
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader',
                include: /flexboxgrid/
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: 'css-loader?sourceMap!sass-loader?sourceMap'
                })
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.jsx$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    },
    // sassLoader: {
    //     includePaths: [ 'src/styles' ]
    // },
    devServer: {
        historyApiFallback: true,
    },
    plugins: [
        HtmlWebpackPluginConfig,
        new ExtractTextPlugin({
            filename: 'public/style.css',
            allChunks: true
        })
    ]
};

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html',
    inject: 'body'
});
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: './src/index.js',
    devtool: 'source-map',
    output: {
        path: path.resolve('dist'),
        filename: 'app.js',
        publicPath: '/',
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader',
                include: /flexboxgrid/
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract({
                    fallback: 'style-loader',
                    use: 'css-loader?sourceMap!sass-loader?sourceMap'
                })
            },
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.jsx$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    },
    // sassLoader: {
    //     includePaths: [ 'src/styles' ]
    // },
    devServer: {
        historyApiFallback: true,
    },
    plugins: [
        HtmlWebpackPluginConfig,
        new ExtractTextPlugin({
            filename: 'public/style.css',
            allChunks: true
        })
    ]
};

Fix webpack server for uri containing /


module.exports = {
    entry: './src/app.js',
    output: {
        path: __dirname + '/dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
};

module.exports = {
    entry: './src/app.js',
    devtool: 'inline-source-map',
    mode: 'development',
    output: {
        path: __dirname + '/dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                }
            }
        ]
    }
};

Enable development mode and source maps for webpack


// UMD config

const path = require('path')
const webpack = require('webpack')

module.exports = {
  devtool: 'source-map',
  entry: {
    'js-search': './source/index.js'
  },
  output: {
    path: 'dist/umd',
    filename: 'js-search.js',
    libraryTarget: 'umd',
    library: 'JsSearch'
  },
  externals: {
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      beautify: true,
      comments: true,
      mangle: false
    })
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['babel-loader'],
        include: path.join(__dirname, 'source')
      }
    ]
  }
}

// UMD config

const path = require('path')
const webpack = require('webpack')

module.exports = {
  devtool: 'source-map',
  entry: {
    'js-search': './source/index.js'
  },
  output: {
    path: path.join(__dirname,'dist/umd'),
    filename: 'js-search.js',
    libraryTarget: 'umd',
    library: 'JsSearch'
  },
  externals: {
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
      beautify: true,
      comments: true,
      mangle: false
    })
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: ['babel-loader'],
        include: path.join(__dirname, 'source')
      }
    ]
  }
}

Fix the provided value dist/umd is not an absolute path


/* eslint no-use-before-define: 0 */
var path = require('path');

module.exports = {
    entry: './src/Native/HMAC.pack.js',
    output: {
        filename: 'HMAC.out.js',
        path: path.resolve(__dirname, 'src/Native')
    },
    resolve: {
        modules: [
            "node_modules"
        ]
    }
};

const path = require('path');

module.exports = {
  entry: './src/Native/HMAC.pack.js',
  output: {
    filename: 'HMAC.out.js',
    path: path.resolve(__dirname, 'src/Native'),
  },
  resolve: {
    modules: [
      'node_modules',
    ],
  },
};

Fix linter issues in webpack file


#!/usr/bin/env node

const jsf = require('../dist/bundle.js');

// FIXME: load faker/change on startup?

const sample = process.argv.slice(2)[0];

const { inspect } = require('util');
const { Transform } = require('stream');
const { readFileSync } = require('fs');

const pretty = process.argv.indexOf('--pretty') !== -1;
const noColor = process.argv.indexOf('--no-color') !== -1;

function generate(schema, callback) {
  jsf.resolve(JSON.parse(schema)).then(result => {
    let sample;

    if (pretty) {
      sample = inspect(result, { colors: !noColor, depth: Infinity });
    } else {
      sample = JSON.stringify(result);
    }

    callback(null, `${sample}\n`);
  });
}

process.stdin.pipe(new Transform({
  transform(entry, enc, callback) {
    generate(Buffer.from(entry, enc).toString(), callback);
  }
})).pipe(process.stdout);

#!/usr/bin/env node

const jsf = require('../dist/main.umd.js');

// FIXME: load faker/change on startup?

const sample = process.argv.slice(2)[0];

const { inspect } = require('util');
const { Transform } = require('stream');
const { readFileSync } = require('fs');

const pretty = process.argv.indexOf('--pretty') !== -1;
const noColor = process.argv.indexOf('--no-color') !== -1;

function generate(schema, callback) {
  jsf.resolve(JSON.parse(schema)).then(result => {
    let sample;

    if (pretty) {
      sample = inspect(result, { colors: !noColor, depth: Infinity });
    } else {
      sample = JSON.stringify(result);
    }

    callback(null, `${sample}\n`);
  });
}

process.stdin.pipe(new Transform({
  transform(entry, enc, callback) {
    generate(Buffer.from(entry, enc).toString(), callback);
  }
})).pipe(process.stdout);

Use UMD for CLI; fix


var app = angular.module('hadtodoApp', ['satellizer']);

app.controller('AppCtrl', ['$scope', function ($scope) {
    $scope.initNavbar = function () {
        $('.dropdown').dropdown({
            action: 'hide'
        });
    }
}]);

app.config(function($authProvider) {
    $authProvider.facebook({
        clientId: 'CLIENTID'
    });

    $authProvider.google({
        clientId: 'CLIENTID'
    });
});

var app = angular.module('hadtodoApp', ['satellizer']);

app.controller('AppCtrl', ['$scope', function ($scope) {
    $scope.initNavbar = function () {
        jQuery('.dropdown').dropdown({
            action: 'hide'
        });
    }
}]);

app.config(function($authProvider) {
    $authProvider.facebook({
        clientId: 'CLIENTID'
    });

    $authProvider.google({
        clientId: 'CLIENTID'
    });
});

Use 'jQuery' instead of '$'


define(['knockout', 'jquery'], function (ko, $) {
  var getContext = function (elementAccessor) {
    var context;
    var element;

    if (!elementAccessor) {
      return;
    }

    element = $(elementAccessor)[0];
    if (!element) {
      return;
    }

    context = ko.contextFor(element);
    if (!context) {
      return;
    }

    return context;
  };

  var getViewModel = function (elementAccessor) {
    var viewModel;
    var context = getContext(elementAccessor);
    if (context) {
      viewModel = context.$data;
    } else {
      viewModel = {};
    }
    return viewModel;
  };

  var getRoot = function () {
    var context;
    var root;

    context = getContext('#globalBody');

    if (!context) {
      return;
    }

    root = context.$root;
    if (!root) {
      return;
    }

    return root;
  };

  return {
    getRoot: getRoot,
    getContext: getContext,
    getViewModel: getViewModel
  };
});

define(['knockout', 'jquery'], function (ko, $) {
  var defaultBodySelector = 'body';

  var createGetContext = function createGetContext () {
    return function getContext (elementAccessor) {
      var context;
      var element;

      if (!elementAccessor) {
        return;
      }

      element = $(elementAccessor)[0];
      if (!element) {
        return;
      }

      context = ko.contextFor(element);
      if (!context) {
        return;
      }

      return context;
    }
  };

  var createGetViewModel = function createGetViewModel () {
    return function getViewModel (elementAccessor) {
      var viewModel;
      var context = getContext(elementAccessor);
      if (context) {
        viewModel = context.$data;
      } else {
        viewModel = {};
      }
      return viewModel;
    };
  };

  var createGetRoot = function createGetRoot (bodySelector) {
    if (typeof bodySelector !== 'string') {
      bodySelector = defaultBodySelector;
    }

    return function getRoot () {
      var context;
      var root;

      context = getContext(bodySelector);

      if (!context) {
        return;
      }

      root = context.$root;
      if (!root) {
        return;
      }

      return root;
    }
  };

  var module = {
    getContext: createGetContext(),
    getRoot: createGetRoot(),
    getViewModel: createGetViewModel()
  }

  module.create = function create (bodySelector) {
    return $.extend({}, module, {
      getRoot: createGetRoot(bodySelector)
    });
  };

  return module;
});

Implement a create function that allows creating a customized module


var app = app || {};

app.KeyView = app.SvgBackboneView.extend({
  tagName: 'rect',

  className: 'white key',

  initialize: function(){
    this.listenTo(this.model, 'change', this.render);
  },

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

  attributes: {
    x: 0,
    y: 0,
    width: 23,
    height: 120,
  },

  events: {
    'click': 'toggleSelect'
  },

  render: function() {
  	return this;
  },

  toggleSelect: function(e){
    this.model.set('selected', !this.model.selected);
    console.log(this.model);
  }
});

var app = app || {};

app.KeyView = app.SvgBackboneView.extend({
  tagName: 'rect',

  className: 'white key',

  initialize: function(){
    this.listenTo(this.model, 'change', this.render);
  },

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

  attributes: {
    x: 0,
    y: 0,
    width: 23,
    height: 120,
  },

  events: {
    'click': 'toggleSelect'
  },

  render: function() {
  	if(this.model.get('selected')){
      this.$el.addClass('selected');
    } else {
      this.$el.removeClass('selected');
    }

    return this;
  },

  toggleSelect: function(e){
    this.model.set('selected', !this.model.get('selected'));
  }
});

Change the colour of the key to reflect model state


var Backbone, $, app, User;

$              = require('jquery');
Backbone       = require('backbone');
Backbone.$     = $;
User           = require('../models/user');

module.exports = Backbone.View.extend({
    el: '.js-app',

    events: {
        'keyup .js-user-search-input': 'searchOnEnter'
    },

    initialize: function() {
        this.user = new User();

        this.input = this.$('.js-user-search-input');
        console.log(this.user);
    },

    render: function() {

    },

    searchOnEnter: function(e) {
        if (e.keyCode != 13) return;
        if (!this.input.val()) return;

        this.user.set('username', this.input.val());
    }
});

app = new module.exports();

var Backbone, $, app, User, TopArtists, UserView, TopArtistsView;

$              = require('jquery');
Backbone       = require('backbone');
Backbone.$     = $;
User           = require('../models/user');
TopArtists     = require('../models/top-artists');
UserView       = require('../views/user-view');
TopArtistsView = require('../views/top-artists-view');

module.exports = Backbone.View.extend({
    el: '.js-app',

    events: {
        'keyup .js-user-search-input': 'searchOnEnter'
    },

    initialize: function() {
        this.user = new User();
        this.topArtists = new TopArtists();

        this.userView = new UserView({
            model: this.user
        });

        this.topArtistsView = new TopArtistsView({
            model: this.topArtists
        });

        this.input = this.$('.js-user-search-input');
        console.log(this.user, this.topArtists, this.userView, this.topArtistsView);
    },

    render: function() {

    },

    searchOnEnter: function(e) {
        if (e.keyCode != 13) return;
        if (!this.input.val()) return;

        this.user.set('username', this.input.val());
        this.topArtists.set('username', this.input.val());

        this.user.fetch();
        this.topArtists.fetch();
    }
});

app = new module.exports();

Set username properties on user search and fetch


'use strict';

const { app } = require('electron');
const fs      = require('fs');
const path    = require('path');

module.exports = {
	init(callback) {
		this.settingsFilePath = path.join(app.getPath('userData'), 'settings.json');

		fs.readFile(this.settingsFilePath, (err, result) => {
			if (err) {
				this.settings = {};
			} else {
				try {
					this.settings = JSON.parse(result);
				} catch (e) {
					process.stderr.write(e);
					this.settings = {};
				}
			}

			callback();
		});
	},

	get(key) {
		return this.settings[key];
	},

	set(key, value) {
		this.settings[key] = value;
	},

	save(callback) {
		fs.writeFile(this.settingsFilePath, JSON.stringify(this.settings), callback);
	}
};

'use strict';

const { app } = require('electron');
const fs      = require('fs');
const path    = require('path');

const { NEGATIVE_SETTINGS_PATH } = process.env;

module.exports = {
	init(callback) {
		const testSettingsFilePath = NEGATIVE_SETTINGS_PATH && path.resolve(__dirname, NEGATIVE_SETTINGS_PATH);

		this.settingsFilePath = path.join(app.getPath('userData'), 'settings.json');

		fs.readFile(testSettingsFilePath || this.settingsFilePath, (err, result) => {
			if (err) {
				this.settings = {};
			} else {
				try {
					this.settings = JSON.parse(result);
				} catch (e) {
					process.stderr.write(e);
					this.settings = {};
				}
			}

			callback();
		});
	},

	get(key) {
		return this.settings[key];
	},

	set(key, value) {
		this.settings[key] = value;
	},

	save(callback) {
		fs.writeFile(this.settingsFilePath, JSON.stringify(this.settings), callback);
	}
};

Allow modifying settings path through env var.


import Resolver from 'ember/resolver';
import AuthenticationInitalizer from 'appkit/initializers/authentication';
import FayeClientInitalizer from 'appkit/initializers/faye-client';
import ClockServiceInitalizer from 'appkit/initializers/clock-service';

var App = Ember.Application.extend({
  LOG_ACTIVE_GENERATION: true,
  LOG_MODULE_RESOLVER: true,
  LOG_TRANSITIONS: true,
  LOG_TRANSITIONS_INTERNAL: true,
  LOG_VIEW_LOOKUPS: true,
  modulePrefix: 'appkit', // TODO: loaded via config
  Resolver: Resolver['default']
});

App.initializer(AuthenticationInitalizer);
App.initializer(FayeClientInitalizer);
App.initializer(ClockServiceInitalizer);

export default App;

import Resolver from 'ember/resolver';
import AuthenticationInitalizer from 'appkit/initializers/authentication';
import FayeClientInitalizer from 'appkit/initializers/faye-client';
import ClockServiceInitalizer from 'appkit/initializers/clock-service';

var App = Ember.Application.extend({
  LOG_ACTIVE_GENERATION: true,
  LOG_MODULE_RESOLVER: true,
  LOG_TRANSITIONS: true,
  LOG_TRANSITIONS_INTERNAL: true,
  LOG_VIEW_LOOKUPS: true,
  modulePrefix: 'appkit',
  podModulePrefix: 'appkit/pods',
  Resolver: Resolver['default']
});

App.initializer(AuthenticationInitalizer);
App.initializer(FayeClientInitalizer);
App.initializer(ClockServiceInitalizer);

export default App;

Use "pods" to organize each widget's code into a single place.


import assert from 'assert';

import { Tooltips } from './Tooltips';
import { Utils } from '../../tests/Utils';

const selectors = {
  toggler: `.js-tooltip-toggle`,
  toggleable: `.js-tooltip`,
  collapsed: `.is-invisible`,
};

describe(`Tooltip`, () => {
  beforeEach(() => {
    const template = nunjucks.render(`tooltips/Tooltips.test.html`);

    document.body.insertAdjacentHTML(`afterbegin`, template);
  });

  afterEach(() => {
    const tooltips = document.querySelector(`#tooltips`);

    tooltips.parentNode.removeChild(tooltips);
  });

  it(`should initialize with hidden tooltips`, () => {
    const togglerSelector = selectors.toggler;
    const tooltipSelector = selectors.toggleable;
    const tooltips = document.querySelectorAll(tooltipSelector);

    Tooltips.tooltip({ togglerSelector, tooltipSelector });

    [...tooltips].forEach((tooltip) => {
      assert(Utils.isHidden(tooltip));
    });
  });

  it(`should expand on click`, () => {
    const firstToggler = document.querySelector(selectors.toggler);
    const firstTooltip = firstToggler.parentNode.querySelector(selectors.toggleable);

    Tooltips.tooltip({ togglerSelector: selectors.toggler, tooltipSelector: selectors.toggleable });

    // simulate click
    firstToggler.dispatchEvent(new Event(`pointerenter`));

    assert(!Utils.isHidden(firstTooltip));
  });
});

import assert from 'assert';

import { Tooltips } from './Tooltips';
import { Utils } from '../../tests/Utils';

const selectors = {
  toggler: `.js-tooltip-toggle`,
  toggleable: `.js-tooltip`,
  collapsed: `.is-invisible`,
};

describe(`Tooltip`, () => {
  beforeEach(() => {
    const template = nunjucks.render(`tooltips/Tooltips.test.html`);

    document.body.insertAdjacentHTML(`afterbegin`, template);
  });

  afterEach(() => {
    const tooltips = document.querySelector(`#tooltips`);

    tooltips.parentNode.removeChild(tooltips);
  });

  it(`should initialize with hidden tooltips`, () => {
    const togglerSelector = selectors.toggler;
    const tooltipSelector = selectors.toggleable;
    const tooltips = document.querySelectorAll(tooltipSelector);

    Tooltips.tooltip({ togglerSelector, tooltipSelector });

    [...tooltips].forEach((tooltip) => {
      assert(Utils.isHidden(tooltip));
    });
  });

  it(`should expand on hover`, () => {
    const firstToggler = document.querySelector(selectors.toggler);
    const firstTooltip = firstToggler.parentNode.querySelector(selectors.toggleable);

    Tooltips.tooltip({ togglerSelector: selectors.toggler, tooltipSelector: selectors.toggleable });

    // simulate click
    firstToggler.dispatchEvent(new Event(`pointerenter`));

    assert(!Utils.isHidden(firstTooltip));
  });
});

Fix comment - tooltips expand on hover


app = app || {};

app.QuestionView = Backbone.View.extend({

  events: {
    'submit .question_form': 'submitQuestion',
  },

  initializeTemplates: function(){
    _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
    this.template = _.template( $('#question_template').html() )
  },

  initialize: function(){
    this.initializeTemplates();
    this.$title = $(".new_question");
    this.$content = $(".new_content");
    this.$form = $(".question_form");
    this.$show = $(".formulated_question")
    this.model = new app.Question();
  },

  render: function(){},

  submitQuestion: function(e){
    e.preventDefault()
    this.model.save({title: this.$title.val(), content: this.$content.val()})
    console.log('geez')
  }

});

app = app || {};

app.QuestionView = Backbone.View.extend({

  events: {
    'submit .question_form': 'submitQuestion',
  },

  initializeTemplates: function(){
    _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
    this.template = _.template( $('#question_template').html() )
  },

  initialize: function(){
    this.initializeTemplates();
    this.$title = $(".new_question");
    this.$content = $(".new_content");
    this.$form = $(".question_form");
    this.$show = $(".formulated_question")
    this.model = new app.Question();

    this.listenTo(this.model, 'sync', this.render);
    this.listenTo(this.model, 'sync', this.exitForm);
  },

  render: function(){
    this.$show.html(this.template(this.model.attributes));
  },

  exitForm: function(){
    this.$title = $(".new_question").val("");
    this.$content = $(".new_content").val("");
    this.$form.hide();
  },

  submitQuestion: function(e){
    e.preventDefault()
    this.model.save({title: this.$title.val(), content: this.$content.val()})
    console.log('geez')
  }

});

Add listeners to Backbone question view that render and exit form -rendering adds model attributes to template and posts them to the page after syncing -exiting the form hides the form after submission


'use strict'

class DocumentIndex {
  constructor () {
    this._index = {}
  }

  get (key, fullOp = false) {
    return fullOp
      ? this._index[key]
      : this._index[key].payload.value
  }

  updateIndex (oplog, onProgressCallback) {
    const reducer = (handled, item, idx) => {
      if (handled[item.payload.key] !== true) {
        handled[item.payload.key] = true
        if(item.payload.op === 'PUT') {
          this._index[item.payload.key] = item
        } else if (item.payload.op === 'DEL') {
          delete this._index[item.payload.key]
        }
      }
      if (onProgressCallback) onProgressCallback(item, idx)
      return handled
    }

    oplog.values
      .slice()
      .reverse()
      .reduce(reducer, {})
  }
}

module.exports = DocumentIndex

'use strict'

class DocumentIndex {
  constructor () {
    this._index = {}
  }

  get (key, fullOp = false) {
    return fullOp
      ? this._index[key]
      : this._index[key] ? this._index[key].payload.value : null
  }

  updateIndex (oplog, onProgressCallback) {
    const reducer = (handled, item, idx) => {
      if (handled[item.payload.key] !== true) {
        handled[item.payload.key] = true
        if(item.payload.op === 'PUT') {
          this._index[item.payload.key] = item
        } else if (item.payload.op === 'DEL') {
          delete this._index[item.payload.key]
        }
      }
      if (onProgressCallback) onProgressCallback(item, idx)
      return handled
    }

    oplog.values
      .slice()
      .reverse()
      .reduce(reducer, {})
  }
}

module.exports = DocumentIndex

Make sure to check that the entry exists when returning the payload


var readJson = require('read-package-json');
var minimist = require('minimist');
var path = require('path');
var url = require('url');

var shields = require('../');

var argv = minimist(process.argv.slice(2));

var p = path.resolve('./package.json');

readJson(p, function(error, pkg) {
  if (error) {
    throw error;
  }

  var slug = url.parse(pkg.repository.url).path.slice(1);

  var links = [''];

  argv._.forEach(function(service) {
    var shield = shields(slug, service);
    var status = service === 'travis' ? 'Build' : 'Dependency';
    console.log('[![' + status + ' Status][' + service + '-svg]][' + service + ']');
    links.push('   [' + service + ']: ' + shield.link);
    links.push('   [' + service + '-svg]: ' + shield.svg);
  });
  console.log(links.join('\n'));
});

var readJson = require('read-package-json');
var minimist = require('minimist');
var path = require('path');
var url = require('url');

var shields = require('../');

var argv = minimist(process.argv.slice(2));

// no args
if (!argv._.length) {
  var usage = [
    'Shield generator for your current project.',
    '',
    'Usage:',
    '  shields [travis] [gemnasium]'
  ];

  console.log(usage.join('\n'));

  return;
}

var p = path.resolve('./package.json');

readJson(p, function(error, pkg) {
  if (error) {
    throw error;
  }

  var slug = url.parse(pkg.repository.url).path.slice(1);

  var links = [''];

  argv._.forEach(function(service) {
    var shield = shields(slug, service);
    var status = service === 'travis' ? 'Build' : 'Dependency';
    console.log('[![' + status + ' Status][' + service + '-svg]][' + service + ']');
    links.push('   [' + service + ']: ' + shield.link);
    links.push('   [' + service + '-svg]: ' + shield.svg);
  });
  console.log(links.join('\n'));
});

Print usage info on no-args


import {
  name as LIB_NAME,
  version as LIB_VERSION
} from '../package.json';

function pretty(arg) {
  let objs = [];
  let keys = [];

  return JSON.stringify(arg, (k, v) => {
    if (v instanceof Object) {
      let seen = objs.indexOf(v);

      if (seen === -1) {
        objs.push(v);
        keys.push(k || 'ROOT');

        return v;
      }

      return `${v} (ref to ${keys[seen]})`;
    }

    return v;
  }, 2);
}

function _output(arg) {
  console.log(`${arg}`);
}

const peek42 = {
  get [Symbol.toStringTag]() {
    return LIB_NAME;
  },
  version: LIB_VERSION,
  pretty,
  _output
};

export {
  pretty,
  _output
};
export default peek42;

import {
  name as LIB_NAME,
  version as LIB_VERSION
} from '../package.json';

function pretty(arg) {
  let objs = [];
  let keys = [];

  return JSON.stringify(arg, (k, v) => {
    if (v instanceof Object) {
      let seen = objs.indexOf(v);

      if (seen === -1) {
        objs.push(v);
        keys.push(k || 'ROOT');

        return v;
      }

      return `${v} (ref to ${keys[seen]})`;
    }

    return v;
  }, 2);
}

function _output(arg) {
  console.log(String(arg));
}

const peek42 = {
  get [Symbol.toStringTag]() {
    return LIB_NAME;
  },
  version: LIB_VERSION,
  pretty,
  _output
};

export {
  pretty,
  _output
};
export default peek42;

Use String instead of interpolation


var updateClock = (function() {
  var sDial = document.getElementById("secondDial");
  var mDial = document.getElementById("minuteDial");
  var hDial = document.getElementById("hourDial");

  function moveDials(hours, minutes, seconds) {
    hDeg = (360 / 12) * (hours + minutes/60);
    mDeg = (360 / 60) * minutes;
    sDeg = (360 / 60) * seconds;
    sDial.setAttribute("transform", "rotate("+sDeg+")");
    mDial.setAttribute("transform", "rotate("+mDeg+")");
    hDial.setAttribute("transform", "rotate("+hDeg+")");
  }

  function update() {
    var currentTime = new Date();
    moveDials(currentTime.getHours(), currentTime.getMinutes(), currentTime.getSeconds());
  };

  return update;
})();

// update every 50ms, just to be sure
setInterval(updateClock, 50);

var updateClock = (function() {
  var sDial = document.getElementById("secondDial");
  var mDial = document.getElementById("minuteDial");
  var hDial = document.getElementById("hourDial");

  function moveDials(hours, minutes, seconds) {
    var hDeg = (360 / 12) * (hours + minutes/60);
    var mDeg = (360 / 60) * minutes;
    var sDeg = (360 / 60) * seconds;
    sDial.setAttribute("transform", "rotate("+sDeg+")");
    mDial.setAttribute("transform", "rotate("+mDeg+")");
    hDial.setAttribute("transform", "rotate("+hDeg+")");
  }

  function update() {
    var currentTime = new Date();
    moveDials(currentTime.getHours(), currentTime.getMinutes(), currentTime.getSeconds());
  };

  return update;
})();

// update every 50ms, just to be sure
setInterval(updateClock, 50);

Add missing `var`s in animation code


Template.editUniverse.helpers({
	workshops() {
		return Workshops.find({
			'universesLinked.universeId': {
				$ne: this._id
			}
		});
	},
	workshopData() {
		return Workshops.findOne(this.workshopId);
	}
});

Template.editUniverse.events({
	'click #save': function(event) {
		event.preventDefault();
		var universeData = {
			universeId: Router.current().params._id,
			name: $('#universeName').val(),
			label: $('#universeLabel').val()
		};
		if (!universeData.name) {
			return throwError('Name must be filled');
		}
		if (!universeData.label) {
			return throwError('Label must be filled');
		}
		if (universeData.name && universeData.label) {
			Meteor.call('updateAUniverse', universeData, (error, result) => {
				if (error) {
					return throwError(error.message);
				} else {
					return throwError('Update succesful !');
				}
			});
		}
	},
	'submit .removeWorkshopFromUniverse': function(event) {
		event.preventDefault();
		var workshopData = {
			universeId: Router.current().params._id,
			workshopId: this.workshopId
		};
		Meteor.call('removeWorkshopFromUniverse', workshopData, (error, result) => {
			if (error) {
				return throwError(error.message);
			} else {
				Meteor.call('removeUniverseFromWorkshop', workshopData, (error, result) => {
					if (error) {
						return throwError(error.message);
					}
				});
			}
		});
	}
});

Template.editUniverse.helpers({
	workshops() {
		return Workshops.find({
			'universesLinked.universeId': {
				$ne: this._id
			}
		});
	}
});

Template.editUniverse.events({
	'click #save': function(event) {
		event.preventDefault();
		var universeData = {
			universeId: Router.current().params._id,
			name: $('#universeName').val(),
			label: $('#universeLabel').val()
		};
		if (!universeData.name) {
			return throwError('Name must be filled');
		}
		if (!universeData.label) {
			return throwError('Label must be filled');
		}
		if (universeData.name && universeData.label) {
			Meteor.call('updateAUniverse', universeData, (error, result) => {
				if (error) {
					return throwError(error.message);
				} else {
					return throwError('Update succesful !');
				}
			});
		}
	}
});

Move event and helper into the new template


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

import { ROUTES } from '../navigation/constants';

import { PRESCRIPTION_ACTIONS } from '../actions/PrescriptionActions';

const initialState = () => ({
  currentTab: 0,
  transaction: null,
  itemSearchTerm: '',
});

export const PrescriptionReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case 'Navigation/NAVIGATE': {
      const { routeName, params } = action;

      if (routeName !== ROUTES.PRESCRIPTION) return state;
      const { transaction } = params;

      return { ...state, transaction };
    }
    case PRESCRIPTION_ACTIONS.REFRESH: {
      return { ...state };
    }

    case PRESCRIPTION_ACTIONS.FILTER: {
      const { payload } = action;
      const { itemSearchTerm } = payload;

      return { ...state, itemSearchTerm };
    }

    default:
      return state;
  }
};

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

import { ROUTES } from '../navigation/constants';
import { UIDatabase } from '../database';
import { PRESCRIPTION_ACTIONS } from '../actions/PrescriptionActions';

const initialState = () => ({
  currentTab: 0,
  transaction: null,
  items: UIDatabase.objects('Item'),
  itemSearchTerm: '',
});

export const PrescriptionReducer = (state = initialState(), action) => {
  const { type } = action;

  switch (type) {
    case 'Navigation/NAVIGATE': {
      const { routeName, params } = action;

      if (routeName !== ROUTES.PRESCRIPTION) return state;
      const { transaction } = params;

      return { ...state, transaction };
    }
    case PRESCRIPTION_ACTIONS.REFRESH: {
      return { ...state };
    }

    case PRESCRIPTION_ACTIONS.FILTER: {
      const { payload } = action;
      const { itemSearchTerm } = payload;

      return { ...state, itemSearchTerm };
    }

    default:
      return state;
  }
};

Add items to prescription state