const React = require("react");
const {connect} = require("react-redux");
const {selectSpotlight} = require("selectors/selectors");
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
const Spotlight = require("components/Spotlight/Spotlight");
const {RequestMoreBookmarks} = require("common/action-manager").actions;
const LoadMore = require("components/LoadMore/LoadMore");

const TimelineBookmarks = React.createClass({
  getMore() {
    const bookmarks = this.props.Bookmarks.rows;
    if (!bookmarks.length) {
      return;
    }
    const beforeDate = bookmarks[bookmarks.length - 1].lastModified;
    this.props.dispatch(RequestMoreBookmarks(beforeDate));
  },
  render() {
    const props = this.props;
    return (<div className="wrapper">
      <Spotlight sites={props.Spotlight.rows} />
      <GroupedActivityFeed title="Just now" sites={props.Bookmarks.rows} length={20} dateKey="bookmarkDateCreated" />
      <LoadMore loading={props.Bookmarks.isLoading} hidden={!props.Bookmarks.canLoadMore || !props.Bookmarks.rows.length} onClick={this.getMore}
        label="See more activity"/>
    </div>);
  }
});

TimelineBookmarks.propTypes = {
  Spotlight: React.PropTypes.object.isRequired,
  Bookmarks: React.PropTypes.object.isRequired
};

module.exports = connect(state => {
  return {
    Spotlight: selectSpotlight(state),
    Bookmarks: state.Bookmarks
  };
})(TimelineBookmarks);

module.exports.TimelineBookmarks = TimelineBookmarks;

const React = require("react");
const {connect} = require("react-redux");
const GroupedActivityFeed = require("components/ActivityFeed/ActivityFeed");
const {RequestMoreBookmarks} = require("common/action-manager").actions;
const LoadMore = require("components/LoadMore/LoadMore");

const TimelineBookmarks = React.createClass({
  getMore() {
    const bookmarks = this.props.Bookmarks.rows;
    if (!bookmarks.length) {
      return;
    }
    const beforeDate = bookmarks[bookmarks.length - 1].lastModified;
    this.props.dispatch(RequestMoreBookmarks(beforeDate));
  },
  render() {
    const props = this.props;
    return (<div className="wrapper">
      <GroupedActivityFeed title="Just now" sites={props.Bookmarks.rows} length={20} dateKey="bookmarkDateCreated" />
      <LoadMore loading={props.Bookmarks.isLoading} hidden={!props.Bookmarks.canLoadMore || !props.Bookmarks.rows.length} onClick={this.getMore}
        label="See more activity"/>
    </div>);
  }
});

TimelineBookmarks.propTypes = {
  Bookmarks: React.PropTypes.object.isRequired
};

module.exports = connect(state => {
  return {
    Bookmarks: state.Bookmarks
  };
})(TimelineBookmarks);

module.exports.TimelineBookmarks = TimelineBookmarks;

Remove Highlights from Bookmarks page


/* global angular, console */

import 'ionic-sdk/release/js/ionic.bundle';

angular.module('app.modals.select-account', [])
.controller('SelectAccountCtrl', function ($scope, Wallet) {
	'use strict';

	const network = $scope.data.network;

	$scope.cancel = cancel;
	$scope.select = select;

	$scope.accounts = getAccounts();
	$scope.heading	= $scope.data.heading;

	function getAccounts() {
		return Wallet.accountList
		.filter(item => item.network === network)
		.map(item => item.alias);
	}

	function cancel() {
		$scope.closeModalService();
	}

	function select(contact) {
		$scope.modalResolve(contact);
	}
});

/* global angular */

import 'ionic-sdk/release/js/ionic.bundle';

angular.module('app.modals.select-account', [])
.controller('SelectAccountCtrl', function ($scope, Wallet) {
	'use strict';

	const network = $scope.data.network;

	$scope.cancel = cancel;
	$scope.select = select;

	$scope.heading	= $scope.data.heading;

	const accountList = getAccounts();
	if ('filter' in $scope.data) {
		$scope.accounts = accountList.filter($scope.data.filter);
	} else {
		$scope.accounts = accountList;
	}

	function getAccounts() {
		return Wallet.accountList
		.filter(item => item.network === network)
		.map(item => item.alias);
	}

	function cancel() {
		$scope.closeModalService();
	}

	function select(contact) {
		$scope.modalResolve(contact);
	}
});

Add account filtering to modal


var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec("bundle " + [
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

    callback();
  });
});

var gulp   = require("gulp");
var CFG    = require("./utils/config.js");
var $      = require("gulp-load-plugins")();
var exec   = require("child_process").exec;
var path   = require("path");
var notify = require("./utils/notify-style-lint");

/**
 * style:lint
 * @see github.com/causes/scss-lint
 * @see rubygems.org/gems/scss-lint
 */
gulp.task("style:lint", function (callback) {
  var scsslintProcess = exec([
    "bundle",
    "exec",
    "scss-lint",
    "--config",
    path.join(CFG.FILE.config.styleLint),
    path.join(CFG.DIR.src, CFG.DIR.style)
  ].join(" "), function(err, stdout, stderr) {
    $.util.log("[style:lint] stdout:", stdout);
    $.util.log("[style:lint] stderr: ", stderr);

    notify(stdout);

    if(null !== err) {
      $.util.log("[style:lint] err: ", err);
    }

    callback();
  });
});

Simplify arguments passed to exec.


'use strict';

(function() {
  angular
      .module('bitcoincom.controllers')
      .controller('buyBitcoinPurchaseHistoryController', purchaseHistoryController);

  function purchaseHistoryController($scope) {
    var vm = this;

    vm.history = [];

    $scope.$on('$ionicView.beforeEnter', _onBeforeEnter);

    function _initVariables() {
      vm.history = [];
      moonPayService.getTransactions().then(
        function onGetTransactionsSuccess(transactions) {
          vm.history = transactions;
        }
      );
    }

    function _onBeforeEnter() {
      _initVariables();
    }

  }
})();

'use strict';

(function() {
  angular
      .module('bitcoincom.controllers')
      .controller('buyBitcoinPurchaseHistoryController', purchaseHistoryController);

  function purchaseHistoryController(
    popupService,
    $scope, $ionicHistory
  ) {
    var vm = this;

    vm.history = [];

    $scope.$on('$ionicView.beforeEnter', _onBeforeEnter);

    function _initVariables() {
      vm.history = [];
      moonPayService.getTransactions().then(
        function onGetTransactionsSuccess(transactions) {
          vm.history = transactions;
        },
        function onGetTransactionsError(err) {
          var title = gettextCatalog.getString('Error Getting Transaction History');
          var message = err.message || gettextCatalog.getString('An error occurred when getting your transaction history.');
          var okText = gettextCatalog.getString('Go Back');
          popupService.showAlert(title, message,
            function onAlertDismissed() {
              $ionicHistory.goBack();
            },
            okText
          );
        }
      );
    }

    function _onBeforeEnter() {
      _initVariables();
    }

  }
})();

Handle error for the purchase history


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

import { regular } from './icon-definitions';

const StyledIcon = styled.i`
  display: inline-block;
  font-family: ${props => props.fontFamily} !important;
  font-size: ${props => props.fontSize};
  font-style: normal;
  font-weight: inherit;
  font-variant: normal;
  speak: none;
  text-decoration: none;
  text-transform: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  vertical-align: text-bottom;
  line-height: 1;

  &:before {
    content: "\\e${props => props.content}";
  }
`;

function Icon({ name, size, ...other }) {
  const iconName = `im-icon-${name}`;

  const styledIconProps = {
    content: regular[iconName],
    fontFamily: 'indv-mkt-icons',
    fontSize: size !== undefined ? `${size}px` : 'inherit'
  };

  return <StyledIcon aria-hidden {...styledIconProps} {...other} />;
}

Icon.propTypes = {
  /** Name of the icon to display */
  name: PropTypes.string.isRequired,
  /** Specify icon size in pixels */
  size: PropTypes.number
};

Icon.defaultProps = {
  size: undefined
};

export default Icon;

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

import { regular } from './icon-definitions';

const StyledIcon = styled.i`
  display: inline-block;
  font-family: ${props => props.fontFamily} !important;
  font-size: ${props => props.fontSize};
  font-style: normal;
  font-weight: inherit;
  font-variant: normal;
  line-height: 1;
  speak: none;
  text-decoration: none;
  text-transform: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  vertical-align: text-bottom;

  &:before {
    content: "\\e${props => props.content}";
  }
`;

function Icon({ name, size, ...other }) {
  const iconName = `im-icon-${name}`;

  const styledIconProps = {
    content: regular[iconName],
    fontFamily: 'indv-mkt-icons',
    fontSize: size !== undefined ? `${size}px` : 'inherit'
  };

  return <StyledIcon aria-hidden {...styledIconProps} {...other} />;
}

Icon.propTypes = {
  /** Name of the icon to display */
  name: PropTypes.string.isRequired,
  /** Specify icon size in pixels */
  size: PropTypes.number
};

Icon.defaultProps = {
  size: undefined
};

export default Icon;

Move line-height property to correct location


describe('Chosen options: inherit-select-classes', function () {
  beforeEach(function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: true,
      'class': 'inherited-class'
    });
  });

  it('passes classes from the select tag to the chosen container', function () {
    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeTruthy();
  });
});

describe('Chosen options: inherit-select-classes', function () {

  it('passes classes from the select tag to the chosen container', function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: true,
      'class': 'inherited-class'
    });

    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeTruthy();
  });

  it('passes classes from the select tag to the chosen container', function () {
    chosenSelectHelper.compileWithAttributes({
      inheritSelectClasses: false,
      'class': 'inherited-class'
    });

    expect(chosenSelectHelper.chosenContainer().hasClass('inherited-class')).toBeFalsy();
  });
});

Make sure false don't add the class


import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
});

import React from 'react';

import { shallow, mount } from 'enzyme';
import Immutable from 'immutable';
import {expect} from 'chai';

import CodeCell from '../../../../src/notebook/components/cell/code-cell';
import * as commutable from 'commutable';
import { displayOrder, transforms } from 'transformime-react';

const sharedProps = { displayOrder, transforms };
describe('CodeCell', () => {
  it('can be rendered', () => {
    const cell = shallow(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell).to.not.be.null;
  });
  it('creates an editor', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}/>
    );
    expect(cell.find('.input').length).to.be.greaterThan(0);
  });
  it('creates a pager', () => {
    const cell = mount(
      <CodeCell cell={commutable.emptyCodeCell} {...sharedProps}
      cellStatus={Immutable.Map({'outputHidden': false, 'inputHidden': false})}
      pagers={Immutable.Map({'content':
        Immutable.Map({'data': Immutable.Map({'text/plain': 'one'})})})}/>
    );
    expect(cell.find('.pagers').length).to.be.greaterThan(0);
  });

});

Add more tests for CodeCell


describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  // describe('Arrow Functions', function() {
  //
  // })
});

describe("About Functions", function () {

  describe("Default Parameters", function () {

    it("should understand default parameters basic usage", function () {

      function greeting(string = 'party') {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greeting()).toEqual(FILL_ME_IN);
      expect(greeting('get together')).toEqual(FILL_ME_IN);
      expect(undefined).toEqual(FILL_ME_IN);
      expect(null).toEqual(FILL_ME_IN);

      function getDefaultValue() {
        return 'party';
      }

      function greetingAgain(string = getDefaultValue()) {
        return 'Welcome to the ' + string + ' pal!';
      }

      expect(greetingAgain()).toEqual(FILL_ME_IN);
    });
  });

  // describe('Rest Paramaters', function() {
  //
  // })
  //
  // describe('Spread Parameters', function() {
  //
  // })
  //
  describe('Arrow Functions', function() {

    it('shound understand basic usage', function() {

      var arrowFn = () => { 'hello world!'; };

      expect(arrowFn()).toEqual(FILL_ME_IN);

      var arrowFnTwo = (number) => { number * 2; }

      expect(arrowFnTwo(4)).toEqual(FILL_ME_IN);

    })

  })
});

Add initial arrow function test


'use strict'

// Code thanks to MDN
export function storageAvailable (type) {
  try {
    let storage = window[type]
    let x = '__storage_test__'
    storage.setItem(x, x)
    storage.removeItem(x)
    return true
  } catch (e) {
    let storage = window[type]
    return e instanceof DOMException && (
      // everything except Firefox
      e.code === 22 ||
      // Firefox
      e.code === 1014 ||
      // test name field too, because code might not be present
      // everything except Firefox
      e.name === 'QuotaExceededError' ||
      // Firefox
      e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
      // acknowledge QuotaExceededError only if there's something already stored
      storage.length !== 0
  }
}

function storagePopulated () {
  if (Storage.length !== 0) {
    console.log('populated')
    return true
  }
}

'use strict'

// Code thanks to MDN
export function storageAvailable (type) {
  try {
    let storage = window[type]
    let x = '__storage_test__'
    storage.setItem(x, x)
    storage.removeItem(x)
    return true
  } catch (e) {
    let storage = window[type]
    return e instanceof DOMException && (
      // everything except Firefox
      e.code === 22 ||
      // Firefox
      e.code === 1014 ||
      // test name field too, because code might not be present
      // everything except Firefox
      e.name === 'QuotaExceededError' ||
      // Firefox
      e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
      // acknowledge QuotaExceededError only if there's something already stored
      storage.length !== 0
  }
}

export function storagePopulated () {
  if (window.localStorage.length !== 0) {
    console.log('populated')
    return true
  }
}

export function populateStorage () {
  window.localStorage.setItem('pomodoroTime', document.getElementById('pomodoroInput').value)
  window.localStorage.setItem('shortBreakTime', document.getElementById('shortBreakInput').value)
  window.localStorage.setItem('longBreakTime', document.getElementById('longBreakInput').value)
}

Add function to populate storage


import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Box, Tabs, Tab } from 'grommet';
import { Map } from 'react-d3-map';
import { Table } from '../components';
import { deviceActions } from '../actions';

const mapConf = {
  width: 1400,
  height: 600,
  scale: 4000,
  scaleExtent: [1 << 12, 1 << 13],
  center: [111, 36]
};

class Device extends Component {
  componentDidMount() {
    this.props.actions.loadDevices();
  }

  render() {
    const { records } = this.props;
    return (
      <Box pad="small">
        <Tabs>
          <Tab title="Location">
            <Box size="large">
              <Map {...mapConf}/>
            </Box>
            <Table data={records}/>
          </Tab>
          <Tab title="Model" />
          <Tab title="Customer" />
        </Tabs>
      </Box>
    );
  }
}

let mapStateToProps = (state) => {
  return {
    records: state.device.records
  };
};

let mapDispatchProps = (dispatch) => ({
  actions: bindActionCreators(deviceActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchProps)(Device);

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Box, Tabs, Tab } from 'grommet';
import { Map, MarkerGroup } from 'react-d3-map';
import { Table } from '../components';
import { deviceActions } from '../actions';

const mapConf = {
  width: 1400,
  height: 600,
  scale: 4000,
  scaleExtent: [1 << 12, 1 << 13],
  center: [111, 36]
};

class Device extends Component {
  componentDidMount() {
    this.props.actions.loadDevices();
  }

  renderMarkerGroup(records) {
    return records.map((record) => {
      var data = {
          "type": "Feature",
          "properties": {
            "text": record.name
          },
          "geometry": {
              "type": "Point",
              "coordinates": [record.longitude, record.latitude]
          }
      }
      return <MarkerGroup data={data}/>
    });
  }

  render() {
    const { records } = this.props;
    return (
      <Box pad="small">
        <Tabs>
          <Tab title="Location">
            <Box size="large">
              <Map {...mapConf}>
                { this.renderMarkerGroup(records) }
              </Map>
            </Box>
            <Table data={records}/>
          </Tab>
          <Tab title="Model" />
          <Tab title="Customer" />
        </Tabs>
      </Box>
    );
  }
}

let mapStateToProps = (state) => {
  return {
    records: state.device.records
  };
};

let mapDispatchProps = (dispatch) => ({
  actions: bindActionCreators(deviceActions, dispatch)
});

export default connect(mapStateToProps, mapDispatchProps)(Device);

Add MarkerGroup in Map by device geo info


export default {
  'id': -9,
  'title': 'EOC Pre Campaign',
  'dashboardType': 'EocCampaign',
  'builtin': true,
  'charts': [
    {
      'title': 'tableData',
      'type': 'TableChart',
      'indicators': [2, 5, 6, 11, 12, 18, 22, 24, 25, 26, 28, 1],
      'groupBy': 'indicator',
      'timeRange': {
        months: 0
      }
      // 'yFormat': ',.0f',
      // 'xFormat': ',.0f'
    }, {
      'title': 'trendData',
      'type': 'LineChart',
      'indicators': [21],
      'timeRange': {
        months: 12
      }
    }, {
      'title': 'mapData',
      'type': 'ChoroplethMap',
      'locations': 'sublocations',
      'timeRange': 0,
      'indicators': [21]
    }
  ]
}

export default {
  'id': -9,
  'title': 'EOC Pre Campaign',
  'dashboardType': 'EocCampaign',
  'builtin': true,
  'charts': [
    {
      'title': 'tableData',
      'type': 'TableChart',
      'indicators': [28, 1, 2, 5, 6, 11, 12, 18, 22, 24, 25, 26],
      'groupBy': 'indicator',
      'timeRange': {
        months: 0
      }
      // 'yFormat': ',.0f',
      // 'xFormat': ',.0f'
    }, {
      'title': 'trendData',
      'type': 'LineChart',
      'indicators': [21],
      'timeRange': {
        months: 12
      }
    }, {
      'title': 'mapData',
      'type': 'ChoroplethMap',
      'locations': 'sublocations',
      'timeRange': 0,
      'indicators': [21]
    }
  ]
}

Put LPD status as first in pre-campaign dashboard


import Em from 'ember';

export default Em.Component.extend({
  classNames: ['button-select'],
  buttons: null,
  selected: null,

  setup: Em.on('init', function() {
    this.buttons = [];
  }),

  actions: {
    select: function(button) {
      this.set('selected', button.get('value'));
    },

    register: function(button) {
      this.buttons.addObject(button);
    },

    unregister: function(button) {
      // how should we handle the case where the object being destroyed
      // is the selected button?
      this.buttons.removeObject(button);
    }
  }
});

import Em from 'ember';

export default Em.Component.extend({
  classNames: ['button-select'],
  buttons: null,
  selected: null,

  setup: Em.on('init', function() {
    this.buttons = Em.A();
  }),

  actions: {
    select: function(button) {
      this.set('selected', button.get('value'));
    },

    register: function(button) {
      this.buttons.addObject(button);
    },

    unregister: function(button) {
      // how should we handle the case where the object being destroyed
      // is the selected button?
      this.buttons.removeObject(button);
    }
  }
});

Use Em.A rather than [] to allow for unextended prototypes


var express = require('express');

var app = express();

app.use(express.static('resources'));

app.get('/', function (request, response) {
    response.send('Hello, world.');
})

var server = app.listen(8080, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log('Server listening on http://%s:%s', host, port);
})

var express = require('express');

var app = express();

app.use('/static', express.static('resources'));

app.get('/', function (request, response) {
    response.send('Hello, world.');
})

var server = app.listen(8080, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log('Server listening on http://%s:%s', host, port);
})

Create virtual path for static resources


var OpenLayersPlusLegend = function(opts) {
    var self = this;
    this.map = $(opts[0]).data('map');

    this.setLegend = function(layer) {
        // The layer param may vary based on the context from which we are called.
        layer = layer.object ? layer.object : layer;
        if ('legend' in layer) {
            var legend_content = layer.legend || 'your mother';
            var legends = $('div.openlayers-legends', self.map.div);
            if (layer.visibility && !('legendDiv' in layer)) {
                layer.legendDiv = $("

").append(legend_content);
                legends.append(layer.legendDiv);
            }
            else if (!layer.visibility && ('legendDiv' in layer)) {
                layer.legendDiv.remove();
                delete layer.legendDiv;
            }
        }
    };

    for (i in this.map.layers) {
        var layer = this.map.layers[i];
        if (!$('div.openlayers-legends', self.map.div).size()) {
            $(self.map.div).append("

");
        }
        layer.events.register('visibilitychanged', layer, self.setLegend);
        self.setLegend(layer);
    }
};

var OpenLayersPlusLegend = function(opts) {
    if (opts == null) {
        return;
    }
    var self = this;
    this.map = $(opts).data('map');

    this.setLegend = function(layer) {
        // The layer param may vary based on the context from which we are called.
        layer = layer.object ? layer.object : layer;
        if ('legend' in layer) {
            var legend_content = layer.legend || 'your mother';
            var legends = $('div.openlayers-legends', self.map.div);
            if (layer.visibility && !('legendDiv' in layer)) {
                layer.legendDiv = $("

").append(legend_content);
                legends.append(layer.legendDiv);
            }
            else if (!layer.visibility && ('legendDiv' in layer)) {
                layer.legendDiv.remove();
                delete layer.legendDiv;
            }
        }
    };

    for (i in this.map.layers) {
        var layer = this.map.layers[i];
        if (!$('div.openlayers-legends', self.map.div).size()) {
            $(self.map.div).append("

");
        }
        layer.events.register('visibilitychanged', layer, self.setLegend);
        self.setLegend(layer);
    }
};

Allow legend behavior to be called without arguments.


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

import '@instructure/canvas-theme'

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

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

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

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

export const decorators = [canvasThemeProvider]

Add high contrast to storybook


BEST.module('arkady.pevzner:timeline-example', {
    behaviors: {
        '#ui-el' : {
            size: [100, 100],
            style:  function(myStyle) {
                return myStyle;
            },
            position: function(myTimeline, time) {
                return BEST.helpers.timeline(myTimeline)(time);
            }
        }
    },
    events: {
        '#ui-el' : {
            'famous:events:click' : function($state) {
                $state.set('time', 2000, {duration: 2000});
            }
        }
    },
    states: {
        time: 0,
        myTimeline: [
            [0, [100, 100], 'outExo'],
            [500, [600, 100], 'inExpo'],
            [1000, [600, 600], 'inOutBack'],
            [2000, [100, 100]]
        ],
        myStyle : {
            'background-color' : 'black',
            'text-align' : 'center',
            'line-height' : '100px',
            'color' : 'white',
            'cursor' : 'pointer'
        }
    },
    tree: 'timeline-example.html',
})
.config({
    imports: {
        'famous:core': ['ui-element']
    }
});

BEST.module('arkady.pevzner:timeline-example', {
    behaviors: {
        '#ui-el' : {
            size: [100, 100],
            style:  function(myStyle) {
                return myStyle;
            },
            position: function(myTimeline, time) {
                return BEST.helpers.piecewise(myTimeline)(time);
            }
        }
    },
    events: {
        '#ui-el' : {
            'famous:events:click' : function($state) {
                $state.set('time', 2000, {duration: 2000});
            }
        }
    },
    states: {
        time: 0,
        myTimeline: [
            [0, [100, 100], 'outExo'],
            [500, [600, 100], 'inExpo'],
            [1000, [600, 600], 'inOutBack'],
            [2000, [100, 100]]
        ],
        myStyle : {
            'background-color' : 'black',
            'text-align' : 'center',
            'line-height' : '100px',
            'color' : 'white',
            'cursor' : 'pointer'
        }
    },
    tree: 'timeline-example.html',
})
.config({
    imports: {
        'famous:core': ['ui-element']
    }
});

Convert a module's call to BEST.helpers.timeline -> BEST.helpers.piecewise


/*
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

import Vue from 'vue'
import App from './App'
import BootstrapVue from 'bootstrap-vue'
import '@/common/assets/css/bootstrap-theme.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import store from './store'
import ApiService from '@/common/services/ApiService.js'

ApiService.init()

Vue.use(BootstrapVue)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

/*
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

import 'babel-polyfill'
import Vue from 'vue'
import App from './App'
import BootstrapVue from 'bootstrap-vue'
import '@/common/assets/css/bootstrap-theme.min.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import store from './store'
import ApiService from '@/common/services/ApiService.js'

ApiService.init()

Vue.use(BootstrapVue)

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

Add babel-polyfill to aquifers SPA


function getOpeningHoursMessage(status) {
  if (status.isOpen) {
    const closedNext = status.until;
    const closedTime = closedNext.format('h:mm a');
    const closedDay = closedNext.calendar(status.moment, {
      sameDay: '[today]',
      nextDay: '[tomorrow]',
      nextWeek: 'dddd',
      lastDay: '[yesterday]',
      lastWeek: '[last] dddd',
      sameElse: 'DD/MM/YYYY',
    });
    return (
      ((closedDay === 'tomorrow' && closedTime === '12:00 am')
        || (closedDay === 'today' && closedTime === '11:59 pm')) ?
        'Open until midnight' :
        `Open until ${closedTime} ${closedDay}`);
  }
  const openNext = status.until;
  if (!openNext) {
    return 'Call for opening times.';
  }
  const timeUntilOpen = openNext.diff(status.moment, 'minutes');
  const openDay = openNext.calendar(status.moment, {
    sameDay: '[today]',
    nextDay: '[tomorrow]',
    nextWeek: 'dddd',
    lastDay: '[yesterday]',
    lastWeek: '[last] dddd',
    sameElse: 'DD/MM/YYYY',
  });
  return (
    (timeUntilOpen <= 60) ?
      `Opening in ${timeUntilOpen} minutes` :
      `Closed until ${openNext.format('h:mm a')} ${openDay}`);
}

module.exports = getOpeningHoursMessage;

function closesAtMidnightToday(closedDay, closedTime) {
  return (
    (closedDay === 'tomorrow' && closedTime === '12:00 am')
    || (closedDay === 'today' && closedTime === '11:59 pm')
  );
}

function getOpeningHoursMessage(status) {
  if (status.isOpen) {
    const closedNext = status.until;
    const closedTime = closedNext.format('h:mm a');
    const closedDay = closedNext.calendar(status.moment, {
      sameDay: '[today]',
      nextDay: '[tomorrow]',
      nextWeek: 'dddd',
      lastDay: '[yesterday]',
      lastWeek: '[last] dddd',
      sameElse: 'DD/MM/YYYY',
    });
    return (
      closesAtMidnightToday(closedDay, closedTime) ?
        'Open until midnight' :
        `Open until ${closedTime} ${closedDay}`);
  }
  const openNext = status.until;
  if (!openNext) {
    return 'Call for opening times.';
  }
  const timeUntilOpen = openNext.diff(status.moment, 'minutes');
  const openDay = openNext.calendar(status.moment, {
    sameDay: '[today]',
    nextDay: '[tomorrow]',
    nextWeek: 'dddd',
    lastDay: '[yesterday]',
    lastWeek: '[last] dddd',
    sameElse: 'DD/MM/YYYY',
  });
  return (
    (timeUntilOpen <= 60) ?
      `Opening in ${timeUntilOpen} minutes` :
      `Closed until ${openNext.format('h:mm a')} ${openDay}`);
}

module.exports = getOpeningHoursMessage;

Add method for describing intent


var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: isTestling ? location.origin : remoteSignaller
};

var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: require('./signaling-server') // isTestling ? location.origin : remoteSignaller
};

Connect to the testing signaling server


const knex = require('knex')
const config = require('server/services/config')

var defaults = {
  pool: {
    min: 2,
    max: 10
  },
  migrations: {
    tableName: 'schema_migrations',
    directory: '/'
  },
  seeds: {
    directory: '/'
  }
}

module.exports = knex({
  ...defaults,
  ...config.database
})

const knex = require('knex')
const config = require('server/services/config')

var defaults = {
  pool: {
    min: 2,
    max: 10
  },
  migrations: {
    tableName: 'schema_migrations',
    directory: '/'
  },
  seeds: {
    directory: '/'
  }
}

module.exports = knex(Object.assign({}, defaults, config.database))

Replace object spread operator with Object.assign


import * as types from './actionTypes';
import GithubAPI from '../api/githubAPI';

export function oauthTempCodeReceived(tempCode) {
    return {type: types.OAUTH_TEMP_CODE_RECEIVED, tempCode};
}

export function oauthTokenReceived(token) {
  return {type: types.OAUTH_TOKEN_RECEIVED, token};
}

export function oauthTokenDestroyed() {
  return {type: types.OAUTH_TOKEN_DESTROYED};
}

export function oauthAuthUserLoaded(user) {
  return {type: types.OAUTH_AUTH_USER_LOADED, user};
}

export function storeOAuthTempCode(tempCode) {
  return function (dispatch){
    dispatch(oauthTempCodeReceived(tempCode));
  };
}

export function storeOAuthToken(token) {
  return function (dispatch){
    dispatch(oauthTokenReceived(token));
  };
}

export function destroyOAuthToken() {
  return function (dispatch){
    dispatch(oauthTokenDestroyed());
  };
}

export function loadAuthenticatedUser() {
  return function (dispatch){
    return GithubAPI.getAuthenticatedUser().then(user => {
      dispatch(oauthAuthUserLoaded(user));
    }).catch(error => {
      //TODO: Improve error handling instead of re-throwing error
      throw(error);
    });
  };
}

import * as types from './actionTypes';
import GithubAPI from '../api/githubAPI';

export function oauthTempCodeReceived(tempCode) {
    return {type: types.OAUTH_TEMP_CODE_RECEIVED, tempCode};
}

export function oauthTokenReceived(token) {
  return {type: types.OAUTH_TOKEN_RECEIVED, token};
}

export function oauthTokenDestroyed() {
  return {type: types.OAUTH_TOKEN_DESTROYED};
}

export function oauthAuthUserLoaded(user) {
  return {type: types.OAUTH_AUTH_USER_LOADED, user};
}

export function storeOAuthTempCode(tempCode) {
  return function (dispatch){
    dispatch(oauthTempCodeReceived(tempCode));
  };
}

export function storeOAuthToken(token) {
  return function (dispatch){
    dispatch(oauthTokenReceived(token));
  };
}

export function destroyOAuthToken() {
  return function (dispatch){
    return GithubAPI.removeTokenFromOcto().then(() => {
      dispatch(oauthTokenDestroyed());
    }).catch(error => {
      //TODO: Improve error handling instead of re-throwing error
      throw(error);
    });
  };
}

export function loadAuthenticatedUser() {
  return function (dispatch){
    return GithubAPI.getAuthenticatedUser().then(user => {
      dispatch(oauthAuthUserLoaded(user));
    }).catch(error => {
      //TODO: Improve error handling instead of re-throwing error
      throw(error);
    });
  };
}

Make destroyOAuthToken action destroy & recreate Octokat instance


(function (GOVUK) {
  "use strict";

  var sendVirtualPageView = function() {
    var $element = $(this);
    var url = $element.data('url');
    if (GOVUK.analytics  && url){
      GOVUK.analytics.trackPageview(url);
    }
  };

  GOVUK.GDM.analytics.virtualPageViews = function() {
    var $flashMessage;

    $('[data-analytics=trackPageView]').each(sendVirtualPageView);
    if (GOVUK.GDM.analytics.location.pathname().match(/^\/suppliers\/opportunities\/\d+\/ask-a-question/) !== null) {
      $flashMessage = $('.banner-success-without-action .banner-message');

      if ($flashMessage.text().match(/^Your question has been sent/) !== null) {
        GOVUK.analytics.trackPageview(GOVUK.GDM.analytics.location.href() + '?submitted=true');
      }
    }
  };

})(window.GOVUK);

(function (GOVUK) {
  "use strict";

  var sendVirtualPageView = function() {
    var $element = $(this);
    var url = $element.data('url');
    if (GOVUK.analytics  && url){
      GOVUK.analytics.trackPageview(url);
    }
  };

  GOVUK.GDM.analytics.virtualPageViews = function() {
    var $flashMessage;

    $('[data-analytics=trackPageView]').each(sendVirtualPageView);
    if (GOVUK.GDM.analytics.location.pathname().match(/^\/suppliers\/opportunities\/\d+\/ask-a-question/) !== null) {
      $flashMessage = $('.banner-success-without-action .banner-message');

      if ($flashMessage.text().replace(/^\s+|\s+$/g, '').match(/^Your question has been sent/) !== null) {
        GOVUK.analytics.trackPageview(GOVUK.GDM.analytics.location.href() + '?submitted=true');
      }
    }
  };

})(window.GOVUK);

Trim whitespace from banner text before checking


import { behavior, scroll, scrollStep } from './utils'

export function scrollDown (event) {
  scroll(event.repeat, scrollStep, 'scrollTop')
}

export function scrollUp (event) {
  scroll(event.repeat, -scrollStep, 'scrollTop')
}

export function scrollLeft (event) {
  scroll(event.repeat, -scrollStep, 'scrollLeft')
}

export function scrollRight (event) {
  scroll(event.repeat, scrollStep, 'scrollLeft')
}

export function scrollPageDown () {
  scrollBy({
    top: innerHeight * 0.9,
    behavior
  })
}

export function scrollPageUp () {
  scrollBy({
    top: -innerHeight * 0.9,
    behavior
  })
}

export function scrollHalfPageDown () {
  scrollTo({
    top: window.pageYOffset + window.innerHeight / 2,
    left: 0,
    behavior: behavior
  })
}

export function scrollHalfPageUp () {
  scrollTo({
    top: window.pageYOffset - window.innerHeight / 2,
    left: 0,
    behavior: behavior
  })
}

export function scrollToBottom () {
  scrollTo({
    top: document.documentElement.scrollHeight,
    behavior
  })
}

export function scrollToTop () {
  scrollTo({
    top: 0,
    behavior
  })
}

export function scrollToLeft () {
  scrollTo({
    left: 0,
    behavior
  })
}

export function scrollToRight () {
  scrollTo({
    left: document.documentElement.scrollWidth,
    behavior
  })
}

import { behavior, scroll, scrollStep } from './utils'

export function scrollDown (event) {
  scroll(event.repeat, scrollStep, 'scrollTop')
}

export function scrollUp (event) {
  scroll(event.repeat, -scrollStep, 'scrollTop')
}

export function scrollLeft (event) {
  scroll(event.repeat, -scrollStep, 'scrollLeft')
}

export function scrollRight (event) {
  scroll(event.repeat, scrollStep, 'scrollLeft')
}

export function scrollPageDown () {
  scrollBy({
    top: innerHeight * 0.9,
    behavior
  })
}

export function scrollPageUp () {
  scrollBy({
    top: -innerHeight * 0.9,
    behavior
  })
}

export function scrollHalfPageDown () {
  scrollTo({
    top: window.pageYOffset + window.innerHeight / 2,
    behavior: behavior
  })
}

export function scrollHalfPageUp () {
  scrollTo({
    top: window.pageYOffset - window.innerHeight / 2,
    behavior: behavior
  })
}

export function scrollToBottom () {
  scrollTo({
    top: document.documentElement.scrollHeight,
    behavior
  })
}

export function scrollToTop () {
  scrollTo({
    top: 0,
    behavior
  })
}

export function scrollToLeft () {
  scrollTo({
    left: 0,
    behavior
  })
}

export function scrollToRight () {
  scrollTo({
    left: document.documentElement.scrollWidth,
    behavior
  })
}

Remove scrolling to left 0 on half page up or down


import path from 'path';
import UglifyJSPlugin from 'uglifyjs-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

const config = [
  {
    name: 'react-redux-spinner',
    entry: [
      './src/nprogress.css',
      './src/index.js'
    ],
    externals: {
      react: 'react',
      'react-dom': 'ReactDOM'
    },
    output: {
      path: path.join(__dirname, '..', 'dist'),
      filename: 'react-redux-spinner.js',
      library: 'react-redux-spinner',
      libraryTarget: 'umd',
      publicPath: '/dist/'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: [ __dirname ],
          loader: 'babel-loader'
        },
        {
          test: /\.css$/,
          use: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                minimize: true
              }
            }
          })
        }
      ]
    },
    plugins: [
      new UglifyJSPlugin(),
      new ExtractTextPlugin('react-redux-spinner.css')
    ]
  }
];

module.exports = config;

import path from 'path';
import UglifyJSPlugin from 'uglifyjs-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

const config = [
  {
    name: 'react-redux-spinner',
    entry: [
      './src/nprogress.css',
      './src/index.js'
    ],
    externals: {
      react: 'react',
      'react-dom': 'ReactDOM',
      'prop-types': 'PropTypes'
    },
    output: {
      path: path.join(__dirname, '..', 'dist'),
      filename: 'react-redux-spinner.js',
      library: 'react-redux-spinner',
      libraryTarget: 'umd',
      publicPath: '/dist/'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          include: [ __dirname ],
          loader: 'babel-loader'
        },
        {
          test: /\.css$/,
          use: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                minimize: true
              }
            }
          })
        }
      ]
    },
    plugins: [
      new UglifyJSPlugin(),
      new ExtractTextPlugin('react-redux-spinner.css')
    ]
  }
];

module.exports = config;

Add prop-types as a peer dependency


var config =  require('../config.js');
var nano = require('nano')(config.couchAuthDbURL);
var maindb = nano.use('main');
var uuid = require('node-uuid');
var recordsToUpdate = [];

maindb.list({startkey: 'visit_', endkey: 'visit_\uffff', include_docs: true}, function(err, results) {
  if (!err) {
    results.rows.forEach(function(result) {
      var visitDoc = result.doc.data;
      if (visitDoc !== null && visitDoc.notes && !visitDoc.patientNotes) {
        var noteUuid = uuid.v4();
        var visitId = result.doc._id.substr(8);
        var patientNote = {
          _id: 'patientNote_2_' + noteUuid,
          data: {
            content: visitDoc.notes,
            createdBy: 'system',
            date: new Date(),
            noteType: 'General',
            patient: visitDoc.patient,
            visit: visitId
          }
        };
        recordsToUpdate.push(patientNote);
        result.doc.data.patientNotes = [noteUuid];
        recordsToUpdate.push(result.doc);
      }
    });
    if (recordsToUpdate.length > 0) {
      maindb.bulk({ docs: recordsToUpdate}, function(err, results) {
        if (err) {
          console.log('Error updating patient notes records.', err);
        } else {
          console.log('Success updating patient notes records.', JSON.stringify(results, null, 2));
        }
      });
    }
  } else {
    console.log('ERROR fetching visit records', err);
  }
});

var config =  require('../config.js');
var nano = require('nano')(config.couchAuthDbURL);
var maindb = nano.use('main');
var uuid = require('node-uuid');
var recordsToUpdate = [];

maindb.list({startkey: 'visit_', endkey: 'visit_\uffff', include_docs: true}, function(err, results) {
  if (!err) {
    results.rows.forEach(function(result) {
      var visitDoc = result.doc.data;
      if (visitDoc !== null && visitDoc.notes && !visitDoc.patientNotes) {
        var noteUuid = uuid.v4();
        var visitId = result.doc._id.substr(8);
        var patientNote = {
          _id: 'patientNote_2_' + noteUuid,
          data: {
            content: visitDoc.notes,
            createdBy: visitDoc.examiner || '',
            date: visitDoc.startDate,
            noteType: 'General',
            patient: visitDoc.patient,
            visit: visitId
          }
        };
        recordsToUpdate.push(patientNote);
        result.doc.data.patientNotes = [noteUuid];
        recordsToUpdate.push(result.doc);
      }
    });
    if (recordsToUpdate.length > 0) {
      maindb.bulk({ docs: recordsToUpdate}, function(err, results) {
        if (err) {
          console.log('Error updating patient notes records.', err);
        } else {
          console.log('Success updating patient notes records.', JSON.stringify(results, null, 2));
        }
      });
    }
  } else {
    console.log('ERROR fetching visit records', err);
  }
});

Update notes migration to use examiner and startDate


import PropTypes from 'prop-types';
import React, { Fragment } from 'react';
import AUtextInput from '@gov.au/text-inputs';

/**
 * The Search Results component
 *
 * @disable-docs
 */
const SearchResults = ( page ) => {

	return (

{ page.heading }

 results for [query]

								<form className="search__searchbox" role="search" autoComplete="off" action="/search" method="get">
									<input type="search" className="au-text-input round--left" name="q" id="search-input" placeholder="Digital Guides"/>
									<button type="submit" className="au-btn icon icon--search--dark round--right" id="search-btn">Search</button>
								</form>

	);
}

SearchResults.propTypes = {
	/**
	 * _body: (partials)(4)
	 */
	_body: PropTypes.node.isRequired,
};

SearchResults.defaultProps = {};

export default SearchResults;

import PropTypes from 'prop-types';
import React, { Fragment } from 'react';
import AUtextInput from '@gov.au/text-inputs';

/**
 * The Search Results component
 *
 * @disable-docs
 */
const SearchResults = ( page ) => {

	return (

{ page.heading }

 results for

								<form className="search__searchbox" role="search" autoComplete="off" action="/search" method="get">
									<input type="search" className="au-text-input round--left" name="q" id="search-input" placeholder="Digital Guides"/>
									<button type="submit" className="au-btn icon icon--search--dark round--right" id="search-btn">Search</button>
								</form>

	);
}

SearchResults.propTypes = {
	/**
	 * _body: (partials)(4)
	 */
	_body: PropTypes.node.isRequired,
};

SearchResults.defaultProps = {};

export default SearchResults;

Add new span for displaying search query


import { InsertInlineNodeCommand as SubstanceInsertInlineNodeCommand } from 'substance'

/**
  Reusable command implementation for inserting inline nodes.

  @class InsertInlineNodeCommand

  @example

  Define a custom command.

  ```
  class AddXRefCommand extends InsertInlineNodeCommand {
    createNode(tx) {
      let refType = this.config.refType
      let xref = tx.createElement('xref').attr('publication-type', 'journal')
      xref.attr('ref-type', refType)
      xref.attr('rid', '')
      return xref
    }
  }
  ```

  Register it in your app using the configurator.

  ```
  config.addCommand('add-xref', AddXRefCommand, { nodeType: 'xref' })
  ```
*/

class InsertInlineNodeCommand extends SubstanceInsertInlineNodeCommand {

  /**
    Insert new inline node at the current selection
  */
  execute(params) {
    let state = this.getCommandState(params)
    if (state.disabled) return
    let editorSession = this._getEditorSession(params)
    editorSession.transaction((tx) => {
      let node = this.createNode(tx, params)
      tx.insertInlineNode(node)
    })
  }

  createNode(tx) { // eslint-disable-line
    throw new Error('This method is abstract')
  }

}

export default InsertInlineNodeCommand

import { InsertInlineNodeCommand as SubstanceInsertInlineNodeCommand } from 'substance'

/**
  Reusable command implementation for inserting inline nodes.

  @class InsertInlineNodeCommand

  @example

  Define a custom command.

  ```
  class AddXRefCommand extends InsertInlineNodeCommand {
    createNode(tx) {
      let refType = this.config.refType
      let xref = tx.createElement('xref').attr('publication-type', 'journal')
      xref.attr('ref-type', refType)
      xref.attr('rid', '')
      return xref
    }
  }
  ```

  Register it in your app using the configurator.

  ```
  config.addCommand('add-xref', AddXRefCommand, { nodeType: 'xref' })
  ```
*/

class InsertInlineNodeCommand extends SubstanceInsertInlineNodeCommand {

  /**
    Insert new inline node at the current selection
  */
  execute(params) {
    let state = this.getCommandState(params)
    if (state.disabled) return
    let editorSession = this._getEditorSession(params)
    editorSession.transaction((tx) => {
      let node = this.createNode(tx, params)
      tx.insertInlineNode(node)
      this.setSelection(tx, node)
    })
  }

  createNode(tx) { // eslint-disable-line
    throw new Error('This method is abstract')
  }

  setSelection(tx, node) {
    if(node.isPropertyAnnotation()) {
      tx.selection = {
        type: 'property',
        path: node.getPath(),
        startOffset: node.startOffset,
        endOffset: node.endOffset
      }
    }
  }

}

export default InsertInlineNodeCommand

Improve UI flow for adding cross references.


import { isString, isEmpty } from 'lodash'
import Expression from './base'

/**
 * @class SubQueryExpression
 */
export default class SubQuery extends Expression {

  /**
   *
   * @param {Query|Literal} query
   * @constructor
   */
  constructor(query) {
    super()

    this.columns = []
    this.query = query
    this._isCTE = false
  }

  /**
   *
   * @param {String} table
   * @param {Array} columns
   */
  as(table, ...columns) {
    this.columns = columns

    return super.as(table)
  }

  /**
   *
   * @param {Boolean} flag
   * @returns {SubQuery}
   */
  isCTE(flag = true) {
    this._isCTE = flag
    return this
  }

  /**
   *
   * @param {Compiler} compiler
   * @returns {String}
   */
  compile(compiler) {
    let table = compiler.quote(this.alias)
    let query = `(${this.query.compile(compiler)})`

    if (! isEmpty(this.columns) ) {
      let columns = this.columns.map(value => compiler.quote(value))

      table += `(${columns.join(', ')})`
    }

    return this._isCTE ? `${table} as ${query}` : `${query} as ${table}`
  }

  /**
   *
   * @param {Any} expr
   * @returns {Boolean}
   */
  isEqual(expr) {
    if ( isString(expr) )
      return expr === this.alias

    return super.isEqual() || (
      expr instanceof SubQuery && expr.alias === this.alias
    )
  }

}

import { isString, isEmpty } from 'lodash'
import Expression from './base'

/**
 * @class SubQueryExpression
 */
export default class SubQuery extends Expression {

  /**
   *
   * @param {Query|Literal} query
   * @constructor
   */
  constructor(query) {
    super()

    this.columns = []
    this.query = query
    this._isCTE = false
  }

  /**
   *
   * @param {String} name
   * @param {Array} columns
   */
  as(name, ...columns) {
    this.columns = columns

    return super.as(name)
  }

  /**
   *
   * @param {Boolean} flag
   * @returns {SubQuery}
   */
  isCTE(flag = true) {
    this._isCTE = flag
    return this
  }

  /**
   *
   * @param {Compiler} compiler
   * @returns {String}
   */
  compile(compiler) {
    let query = `(${this.query.compile(compiler)})`

    if (! this.alias ) return query

    let name = compiler.quote(this.alias)

    if (! isEmpty(this.columns) ) {
      let columns = this.columns.map(value => compiler.quote(value))

      name += `(${columns.join(', ')})`
    }

    return this._isCTE ? `${name} as ${query}` : `${query} as ${name}`
  }

  /**
   *
   * @param {Any} expr
   * @returns {Boolean}
   */
  isEqual(expr) {
    if ( isString(expr) )
      return expr === this.alias

    return super.isEqual() || (
      expr instanceof SubQuery && expr.alias === this.alias
    )
  }

}

Fix displaying the sub query name if not available


var through = require( 'through2' );
var logger = require( 'pelias-logger' ).get( 'openaddresses' );
var peliasAdminLookup = require( 'pelias-wof-admin-lookup' );

function createAdminLookupStream(enable, peliasConfig) {
  if (enable) {
    logger.info( 'Setting up admin value lookup stream.' );
    var pipResolver = peliasAdminLookup.createWofPipResolver(peliasConfig.imports.adminLookup.url);
    return peliasAdminLookup.createLookupStream(pipResolver);
  } else {
    return through.obj(function (doc, enc, next) {
      next(null, doc);
    });
  }
}

module.exports = {
  create: createAdminLookupStream
};

var through = require( 'through2' );
var logger = require( 'pelias-logger' ).get( 'openaddresses' );
var peliasAdminLookup = require( 'pelias-wof-admin-lookup' );

function createAdminLookupStream(enable) {
  if (enable) {
    logger.info( 'Setting up admin value lookup stream.' );
    var pipResolver = peliasAdminLookup.createLocalWofPipResolver();
    return peliasAdminLookup.createLookupStream(pipResolver);
  } else {
    return through.obj(function (doc, enc, next) {
      next(null, doc);
    });
  }
}

module.exports = {
  create: createAdminLookupStream
};

Use local WOF PIP service


/**
 * @fileoverview WebGL.js - Set up WebGL context
 * @author Ben Brooks (beb12@aber.ac.uk)
 * @version 1.0
 */

/**
 * Initialise the WebGL context and return it
 * @params canvas - HTML5 Canvas element
 */
function initWebGL(canvas) {

  var devicePixelRatio = window.devicePixelRatio || 1;
  var width = canvas.clientWidth;
  var height = canvas.clientHeight;

  // set the display size of the canvas.
  canvas.style.width  = width  + "px";
  canvas.style.height = height + "px";

  // set the size of the drawingBuffer
  canvas.width  = width  * devicePixelRatio;
  canvas.height = height * devicePixelRatio;

  glContext = canvas.getContext("webgl") ||
              canvas.getContext("experimental-webgl");

  if (!glContext) {
    alert("Unable to initialise WebGL. Your browser may not support it.");
  }

  return glContext;

}

/**
 * @fileoverview WebGL.js - Set up WebGL context
 * @author Ben Brooks (beb12@aber.ac.uk)
 * @version 1.0
 */

/**
 * Initialise the WebGL context and return it
 * @params canvas - HTML5 Canvas element
 */
function initWebGL(canvas) {

  var devicePixelRatio = window.devicePixelRatio || 1;
  var width  = canvas.clientWidth;
  var height = canvas.clientHeight;

  // set the display size of the canvas.
  canvas.style.width  = width  + "px";
  canvas.style.height = height + "px";

  // set the size of the drawingBuffer
  canvas.width  = width  * devicePixelRatio;
  canvas.height = height * devicePixelRatio;

  glContext = canvas.getContext("webgl") ||
              canvas.getContext("experimental-webgl");

  glContext.viewportWidth  = canvas.width;
  glContext.viewportHeight = canvas.height;

  if (!glContext) {
    alert("Unable to initialise WebGL. Your browser may not support it.");
  }

  return glContext;

}

Add canvas width height vars


module.exports = {
  rules: {
    // Disallow dangling commas
    'comma-dangle': [2, 'never'],

    // Allow anonymous functions
    'func-names': [0],

    // Allow short identifiers
    'id-length': [0],

    // Enforce curly spacing in object literals and destructuring
    'object-curly-spacing': [2, 'always'],

    // Allow padded blocks
    'padded-blocks': [0]
  }
};

module.exports = {
  rules: {
    // Disallow dangling commas
    'comma-dangle': [2, 'never'],

    // Allow anonymous functions
    'func-names': [0],

    // Allow short identifiers
    'id-length': [0],

    // Enforce indentation of switch/case statements
    // See: https://github.com/airbnb/javascript/issues/470#issuecomment-145066890
    'indent': [2, 2, { SwitchCase: 1 }],

    // Enforce curly spacing in object literals and destructuring
    'object-curly-spacing': [2, 'always'],

    // Allow padded blocks
    'padded-blocks': [0]
  }
};


// https://leetcode.com/problems/subsets/description/

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function (nums) {
  let result = [[]];
  for (let num of nums) {
    let len = result.length;
    for (let i = 0; i < len; i++) {
      let temp = [].concat(result[i]);
      temp.push(num);
      result.push(temp);
    }
  }
  return result;
};

var nums = [1, 2, 3];
console.log(subsets(nums));

var nums = [1, 2, 3, 4];
console.log(subsets(nums));

// https://leetcode.com/problems/subsets/description/

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets2 = function (nums) {
  let result = [[]];
  for (let num of nums) {
    let len = result.length;
    for (let i = 0; i < len; i++) {
      let temp = [].concat(result[i]);
      temp.push(num);
      result.push(temp);
    }
  }
  return result;
};

var subsets = function (nums) {
  let dfs = (tempRes, nums, start) => {
    if (start === nums.length) return [];
    let result = [];
    for (let i = start; i < nums.length; i++) {
      let temp = [].concat(tempRes);
      temp.push(nums[i]);
      result.push(temp);
      result = result.concat(dfs(temp, nums, i + 1));
    }
    return result;
  }

  let result = [[]].concat(dfs([], nums, 0));
  return result;
};

var nums = [1, 2, 3];
console.log(subsets(nums));

var nums = [1, 2, 3, 4];
console.log(subsets(nums));

Add second solution for subsets


var gulp = require('gulp');

gulp.task('default', function(done){
    gulp.src(__dirname + '/template/**')
        .pipe(gulp.dest('./'))
        .on('end', function () {
            done();
        });
});

var gulp = require('gulp');

gulp.task('default', function(done){
    gulp.src(__dirname + '/template/**', {dot: true})
        .pipe(gulp.dest('./'))
        .on('end', function () {
            done();
        });
});

Fix for .bowerrc not being copied


window.onerror = function () {
  var words = 'aie ouille hurt ouch oops boo cry rah huh oh ahen wow poop'.split(' ');
  var rand = Math.floor((Math.random() * words.length));

  console.log(words[rand]);
};

Add real behavior of the library


import secrets from './secrets' // See README

export default {
  apiKey: secrets.apiKey,
  baseUrl: 'http://10.0.2.2:5000/api/v1/', // AVD,
  // baseUrl: 'http://192.168.56.1:5000/api/v1/' // Genymotion,
  // baseUrl: 'http://www.jumu-nordost.eu/api/v1/' // Production
}

import secrets from './secrets' // See README

export default {
  apiKey: secrets.apiKey,
  // baseUrl: 'http://localhost:5000/api/v1/', // iOS,
  baseUrl: 'http://10.0.2.2:5000/api/v1/', // AVD,
  // baseUrl: 'http://192.168.56.1:5000/api/v1/' // Genymotion,
  // baseUrl: 'http://www.jumu-nordost.eu/api/v1/' // Production
}

Add local base URL for iOS


var http = require('http');
http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify({
        message: req.headers.host + ' answered a ' + req.method + ' request.'
    }));
}).listen(80, '0.0.0.0');
console.log('Server running at http://127.0.0.1:1337/');

var http = require('http');
http.createServer(function (req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.writeHead(200, {'Content-Type': 'application/json'});
    res.end(JSON.stringify({
        message: req.headers.host + ' answered a ' + req.method + ' request.'
    }));
}).listen(80, '0.0.0.0');
console.log('Server running at http://127.0.0.1:1337/');

Add PUT and DELETE to Access-Control-Allow-Methods.


import React from 'react';
import ReactDomServer from 'react-dom/server';
import msonZoo from 'mson-zoo';
import jsBeautify from 'js-beautify';
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import dedent from 'dedent';

import parseMson from '../playground/parseMson';
import { Attributes } from '../dist/attributes-kit-server';

describe('Comparision with reference fixtures', () => {

  msonZoo.samples.forEach((sample) => {
    let renderedElement = null;
    let htmlString = null;

    describe(`If I render ${sample.name} on the server`, (done) => {
      let header = '# Data Structures';

      parseMson(`${header}\n${sample.fileContent}`, (err, dataStructureElements) => {
        if (err) {
          return done(err);
        }

        renderedElement = React.createElement(Attributes, {
          element: dataStructureElements[0],
          collapseByDefault: false,
          maxInheritanceDepth: undefined,
          includedProperties: 'show',
          inheritedProperties: 'show',
        });

        htmlString = jsBeautify.html(ReactDomServer.renderToStaticMarkup(renderedElement));
      });

      describe('And I compare that with the reference fixture', () => {
        const reference = fs.readFileSync(path.join('./fixtures', `${sample.name}`));

        it('They should be equal', () => {
          assert.equal(htmlString, reference);
        });
      });
    });
  });
});

import React from 'react';
import ReactDomServer from 'react-dom/server';
import msonZoo from 'mson-zoo';
import jsBeautify from 'js-beautify';
import fs from 'fs';
import path from 'path';
import assert from 'assert';
import dedent from 'dedent';

import parseMson from '../playground/parseMson';
import { Attributes } from '../dist/attributes-kit-server';

describe('Comparision with reference fixtures', () => {

  msonZoo.samples.forEach((sample) => {
    let renderedElement = null;
    let htmlString = null;

    describe(`If I render ${sample.fileName} on the server`, (done) => {
      let header = '# Data Structures';

      parseMson(`${header}\n${sample.fileContent}`, (err, dataStructureElements) => {
        if (err) {
          return done(err);
        }

        renderedElement = React.createElement(Attributes, {
          element: dataStructureElements[0],
          dataStructures: dataStructureElements,
          collapseByDefault: false,
          maxInheritanceDepth: undefined,
          includedProperties: 'show',
          inheritedProperties: 'show',
        });

        htmlString = jsBeautify.html(ReactDomServer.renderToStaticMarkup(renderedElement));
      });

      describe('And I compare that with the reference fixture', () => {
        const reference = fs.readFileSync(
          path.join('./fixtures', `${sample.fileName}`),
          'utf8'
        );

        it('They should be equal', () => {
          assert.equal(htmlString, reference);
        });
      });
    });
  });
});

Fix samples comparison after upgrading to MSON Zoo 3.0.


angular.module('mwUI.UiComponents')
//TODO rename
  .directive('mwTabsPane', function () {
    return {
      scope: {
        title: '@mwTabsPane',
        id: '@',
        icon: '@',
        tooltip: '@',
        isInvalid: '=',
        badge: '@'
      },
      transclude: true,
      replace: true,
      require: '^mwTabs',
      templateUrl: 'uikit/mw-ui-components/directives/templates/mw_tab_pane.html',
      controller: function ($scope) {
        var selected = false;
        $scope.getId = function () {
          return $scope.id || $scope.$id;
        };

        $scope.deselect = function () {
          selected = false;
        };

        $scope.select = function () {
          selected = true;
        };

        $scope.isSelected = function () {
          return selected;
        };
      },
      link: function (scope, el, attr, mwTabsCtrl) {
        mwTabsCtrl.registerPane(scope);

        scope.$on('$destroy', function () {
          el.remove();
          scope.deselect();
          mwTabsCtrl.unRegisterPane(scope);
        });
      }
    };
  });

angular.module('mwUI.UiComponents')
//TODO rename
  .directive('mwTabsPane', function () {
    return {
      scope: {
        title: '@mwTabsPane',
        id: '@?',
        icon: '@?',
        tooltip: '@?',
        badge: '@?',
        isInvalid: '=?'
      },
      transclude: true,
      replace: true,
      require: ['^mwTabs', '^?form'],
      templateUrl: 'uikit/mw-ui-components/directives/templates/mw_tab_pane.html',
      controller: function ($scope) {
        var selected = false;
        $scope.getId = function () {
          return $scope.id || $scope.$id;
        };

        $scope.deselect = function () {
          selected = false;
        };

        $scope.select = function () {
          selected = true;
        };

        $scope.isSelected = function () {
          return selected;
        };
      },
      link: function (scope, el, attr, mwTabsCtrl) {
        mwTabsCtrl.registerPane(scope);

        scope.$on('$destroy', function () {
          el.remove();
          scope.deselect();
          mwTabsCtrl.unRegisterPane(scope);
        });
      }
    };
  });

Set scope attrs to optional


var
    gulp    = require('gulp'),
    babel   = require('gulp-babel'),
    watch   = require('gulp-watch'),
    uglify   = require('gulp-uglify'),
    strip   = require('gulp-strip-comments'),
    rename  = require('gulp-rename');

// Transpile ES6 app to ES5 using babel
gulp.task('transpile', function () {
  return gulp.src('app/main.es6.js')
    .pipe(strip())                       // Strip comments
    .pipe(babel())                       // Pipe the file into babel
    .pipe(uglify())                       // Pipe the file into babel
    .pipe(rename('main.dist.js'))             // rename to main.js
    .pipe(gulp.dest('app'))              // Save it in the same directory
});

// // Watch the main.es6.js file for changes and transpile automatically
// gulp.task('watch', function () {
//   watch('app/main.es6.js', ['transpile']);
// })

gulp.task('default', ['transpile'])

var
    gulp    = require('gulp'),
    babel   = require('gulp-babel'),
    watch   = require('gulp-watch'),
    uglify  = require('gulp-uglify'),
    strip   = require('gulp-strip-comments'),
    rename  = require('gulp-rename');

// Transpile ES6 app to ES5 using babel
gulp.task('transpile', function () {
  return gulp.src('app/main.es6.js')
    .pipe(strip())                       // Strip comments
    .pipe(babel())                       // Pipe the file into babel
    .pipe(uglify())                       // Pipe the file into babel
    .pipe(rename('main.dist.js'))             // rename to main.js
    .pipe(gulp.dest('app'))              // Save it in the same directory
});

// Transpile general ES6 javascripts
gulp.task('transpile-scripts', function () {
  return gulp.src('browser/src/es6-js/**.es6.js')
    .pipe(strip())                       // Strip comments
    .pipe(babel())                       // Babel ES5 -> ES6
    .pipe(uglify())                      // uglify
    .pipe(rename(function (path) {       // Rename files so .es6.js -> .js
      path.basename = path.basename.replace('.es6', '');
    }))
    .pipe(gulp.dest('browser/build/js'))
});

gulp.task('default', ['transpile'])

Add task to transpile all general es6 scripts


(function(once) { if(once) return function() {};
  global.itBehavesLikeAnAdapter = require('./adapter_helper');
  global._specHelper = true;

  beforeEach(function() {
  });

  afterEach(function() {
    Document.Adapter.clear();
  });

  module.exports = global._specHelper
})(global._specHelper);

Make sure the default adapter is cleared before every test run.


import {expect} from 'chai'
import {ENV_NAME, TEST_SERVICE_NAME} from '../../environment'
import Automator from '../../util/cli-automator'

export default function () {

  this.When(/I get the internal port for port ([0-9]*) for the service using the CLI$/, async function (port) {
    this.port = await Automator.spawn(['-e', ENV_NAME, 'port', TEST_SERVICE_NAME, port], {
      env: {
        NAVY_DEBUG: 'null',
      },
    }).waitForDone()
  })

  this.Then(/I should see the port using the CLI$/, function () {
    expect(this.port).to.be.a.number
  })

  this.Then(/I shouldn't see the port using the CLI$/, function () {
    expect(this.port).to.equal('')
  })

}

import {expect} from 'chai'
import {ENV_NAME, TEST_SERVICE_NAME} from '../../environment'
import Automator from '../../util/cli-automator'

export default function () {

  this.When(/I get the internal port for port ([0-9]*) for the service using the CLI$/, async function (port) {
    this.port = await Automator.spawn(['-e', ENV_NAME, 'port', TEST_SERVICE_NAME, port], {
      env: {
        ...process.env,
        NAVY_DEBUG: 'null',
      },
    }).waitForDone()
  })

  this.Then(/I should see the port using the CLI$/, function () {
    expect(this.port).to.be.a.number
  })

  this.Then(/I shouldn't see the port using the CLI$/, function () {
    expect(this.port).to.equal('')
  })

}

Fix port cli integration test


var $$ = React.createElement;

var stateHandlers = {

  handleSelectionChange: function(writerCtrl, sel) {
    var surface = writerCtrl.getSurface();
    if (surface.name !== "content") return;

    if (sel.isNull() || !sel.isPropertySelection()) return;

    var range = sel.getTextRange();
    var annotations = writerCtrl.doc.annotationIndex.get(sel.getPath(), range[0], range[1]);

    // Switch to a neutral state if no annotation matches have been found
    if (annotations.length === 0 && writerCtrl.state.contextId !== "editSubjectReference") {
      var prevContextId = writerCtrl.state.contextId;
      var nextContextId = "entities";

      if (prevContextId === "editSubjectReference" || prevContextId === "subjects") {
        nextContextId = "subjects";
      }

      writerCtrl.replaceState({
        contextId: nextContextId
      });
      return true;
    }
  }
};

module.exports = stateHandlers;

var $$ = React.createElement;

var stateHandlers = {

  handleSelectionChange: function(writerCtrl, sel) {
    var surface = writerCtrl.getSurface();
    if (surface.name !== "content") return;

    if (sel.isNull() || !sel.isPropertySelection()) return;

    var range = sel.getTextRange();
    var annotations = writerCtrl.doc.annotationIndex.get(sel.getPath(), range[0], range[1], "reference");

    // Switch to a neutral state if no annotation matches have been found
    if (annotations.length === 0 && writerCtrl.state.contextId !== "editSubjectReference") {
      var prevContextId = writerCtrl.state.contextId;
      var nextContextId = "entities";

      if (prevContextId === "editSubjectReference" || prevContextId === "subjects") {
        nextContextId = "subjects";
      }

      writerCtrl.replaceState({
        contextId: nextContextId
      });
      return true;
    }
  }
};

module.exports = stateHandlers;

Check for reference handlers only when resetting to default state.


'use strict';

module.exports =

/*@ngInject*/
function GrammarActivitiesCreateCmsCtrl (
  $scope, _, GrammarActivity
) {
  $scope.grammarActivity = {};
  $scope.grammarActivity.question_set = [{}];

  function buildConcepts(set) {
    return _.chain(set)
      .map(function (s) {
        return [s.concept_level_0.$id, {
          quantity: s.number_of_questions,
          ruleNumber: s.concept_level_0.ruleNumber
        }];
      })
      .object()
      .value();
  }

  $scope.processGrammarActivityForm = function () {
    console.log('Processing ', $scope.grammarActivity);
    var ga = $scope.grammarActivity;
    var newGrammarActivity = {
      title: ga.title,
      description: ga.description,
      concepts: buildConcepts(ga.question_set)
    };

    console.log(newGrammarActivity);

    GrammarActivity.addToFB(newGrammarActivity).then(function () {
      console.log('Added', newGrammarActivity);
    });
  };
};

'use strict';

module.exports =

/*@ngInject*/
function GrammarActivitiesCreateCmsCtrl (
  $scope, _, GrammarActivity, $state
) {
  $scope.grammarActivity = {};
  $scope.grammarActivity.question_set = [{}];

  function buildConcepts(set) {
    return _.chain(set)
      .map(function (s) {
        return [s.concept_level_0.$id, {
          quantity: s.number_of_questions,
          ruleNumber: s.concept_level_0.ruleNumber
        }];
      })
      .object()
      .value();
  }

  $scope.processGrammarActivityForm = function () {
    var ga = $scope.grammarActivity;
    var newGrammarActivity = {
      title: ga.title,
      description: ga.description,
      concepts: buildConcepts(ga.question_set),
      standard: ga.standard,
      standard_level: ga.standard_level,
      theme: ga.theme
    };

    GrammarActivity.addToFB(newGrammarActivity).then(function () {
      $state.go('cms-grammar-activities');
    });
  };
};

Add standard, standard_level, theme to ga's


#!/usr/bin/env seed

Seed.import_namespace("Gtk");
Gtk.init(null, null);

BrowserToolbar = new GType({
    parent: Gtk.HBox.type,
    name: "BrowserToolbar",
    instance_init: function(klass)
    {
        var url_bar = new Gtk.Entry();

        var back_button = new Gtk.ToolButton({stock_id:"gtk-go-back"});
        var forward_button = new Gtk.ToolButton({stock_id:"gtk-go-forward"});
        var refresh_button = new Gtk.ToolButton({stock_id:"gtk-refresh"});

        var back = function ()
        {
            Seed.print("back");
        };

        var forward = function ()
        {
            Seed.print("forward");
        };

        var refresh = function ()
        {
            Seed.print("refresh");
        };

        var browse = function ()
        {
            Seed.print("browse");
        };

        back_button.signal.clicked.connect(back);
        forward_button.signal.clicked.connect(forward);
        refresh_button.signal.clicked.connect(refresh);
        url_bar.signal.activate.connect(browse);

        this.pack_start(back_button);
        this.pack_start(forward_button);
        this.pack_start(refresh_button);

        this.pack_start(url_bar, true, true);
    }
});

window = new Gtk.Window({title: "Browser"});
window.signal.hide.connect(function () { Gtk.main_quit(); });
window.add(new BrowserToolbar());
window.show_all();

Gtk.main();

#!/usr/bin/env seed

Seed.import_namespace("Gtk");
Gtk.init(null, null);

BrowserToolbar = new GType({
    parent: Gtk.HBox.type,
    name: "BrowserToolbar",
    instance_init: function (klass)
    {
        // Private
        var url_bar = new Gtk.Entry();

        var back_button = new Gtk.ToolButton({stock_id:"gtk-go-back"});
        var forward_button = new Gtk.ToolButton({stock_id:"gtk-go-forward"});
        var refresh_button = new Gtk.ToolButton({stock_id:"gtk-refresh"});

        var back = function ()
        {
            Seed.print("Go Back");
        };

        var forward = function ()
        {
            Seed.print("Go Forward");
        };

        var refresh = function ()
        {
            Seed.print("Refresh");
        };

        var browse = function (url)
        {
            Seed.print("Navigate to: " + url.text);
        };

        // Implementation
        back_button.signal.clicked.connect(back);
        forward_button.signal.clicked.connect(forward);
        refresh_button.signal.clicked.connect(refresh);
        url_bar.signal.activate.connect(browse);

        this.pack_start(back_button);
        this.pack_start(forward_button);
        this.pack_start(refresh_button);
        this.pack_start(url_bar, true, true);
    }
});

window = new Gtk.Window({title: "Browser"});
window.signal.hide.connect(function () { Gtk.main_quit(); });

toolbar = new BrowserToolbar();
window.add(toolbar);
window.show_all();

Gtk.main();

Break back into three parts.


var gulp = require('gulp');
var inject = require('gulp-inject');
var path = require('path');
var join = path.join;

module.exports = function(config) {
  gulp.task('dist:index', function() {
      gulp.src(config.paths.app.index, {'base': 'src'})
          .pipe(inject(gulp.src(config.paths.lib.styles.prod, {'read': false}), {
              'ignorePath': 'bower_components',
              'addRootSlash': false,
              name: 'lib'
          }))
          .pipe(inject(gulp.src(join(config.distDir, '/all.css'), {'read': false}), {'ignorePath': config.distDir, 'addRootSlash': false}))
          .pipe(inject(gulp.src(join(config.distDir, '/all.js'), {'read': false}), {'ignorePath': config.distDir, 'addRootSlash': false}))
          .pipe(gulp.dest(config.distDir))
  });
};

var gulp = require('gulp');
var inject = require('gulp-inject');
var path = require('path');
var join = path.join;

module.exports = function(config) {
    gulp.task('dist:index', function() {
        gulp.src(config.paths.app.index, {'base': 'src'})
            .pipe(inject(gulp.src(config.paths.lib.styles.prod, {'read': false}), {
                'ignorePath': 'bower_components',
                'addRootSlash': false,
                name: 'lib',
                'transform': function(filepath) {
                    return '<link data-spark-injected rel="stylesheet" href="' + filepath + '">'
                }
            }))
            .pipe(inject(gulp.src(join(config.distDir, '/all.css'), {'read': false}), {
                'ignorePath': config.distDir,
                'addRootSlash': false,
                'transform': function(filepath) {
                    return '<link data-spark-injected rel="stylesheet" href="' + filepath + '">'
                }
            }))
            .pipe(inject(gulp.src(join(config.distDir, '/all.js'), {'read': false}), {
                'ignorePath': config.distDir,
                'addRootSlash': false,
                'transform': function (filepath) {
                    return '<script data-spark-injected src="' + filepath + '"></script>';
                }
            }))
            .pipe(gulp.dest(config.distDir))
    });
};

Mark injected resources with attribute 'data-spark-injected' for further processing on the server. This is required for the resource caching feature of spark.


(function(window, $) {
    var document = window.document;
    var preview_iframe;

    var iframe_name = 'live_preview';   // Name (and id) to give to the preview iframe
    var preview_url = 'preview';        // Relative url to send the POST data to
    var keypress_delay = 200;

    $(document).ready(function() {
        var initial_url = $('.viewsitelink').attr('href');
        var preview = '

';
        var form = $('#cmspage_form');

        // Insert the iframe into the document:
        $('#id_content').after(preview);
        preview_iframe = $('#live_preview');

        // Set the main form's target to the preview iframe, and POST to our
        // preview url to update the iframe's content.
        // TODO: throttle updates
        $('#id_content').keyup(function(e) {
            setTimeout(function() {
                form.attr('target', iframe_name)
                    .attr('action', preview_url)
                    .submit()
                    .attr('target', '')     // Reset target and action after submitting
                    .attr('action', '');
            }, keypress_delay);
        });
    })
}(window, django.jQuery));

(function(window, $) {
    var document = window.document;

    var iframe_name = 'live_preview';   // Name (and id) to give to the preview iframe
    var preview_url = 'preview';        // Relative url to send the POST data to
    var keypress_delay = 400;           // Number of milliseconds to wait before acting on keypress
    var latest_keypress = new Date();

    $(document).ready(function() {
        var initial_url = $('.viewsitelink').attr('href');
        var preview = '

';
        var form = $('#cmspage_form');

        // Insert the iframe into the document:
        $('#id_content').after(preview);

        // Set the main form's target to the preview iframe, and POST to our
        // preview url to update the iframe's content.
        $('#id_content').keyup(function(e) {
            var timestamp = new Date();
            latest_keypress = timestamp;

            setTimeout(function() {
                // If no keypresses have occurred since this one, refresh the
                // iframe using the main form's data:
                var another_key_was_pressed = (latest_keypress > timestamp);
                if (!another_key_was_pressed) {
                    form.attr('target', iframe_name)
                        .attr('action', preview_url)
                        .submit()
                        .attr('target', '')     // Reset target and action after submitting
                        .attr('action', '');
                }
            }, keypress_delay);
        });
    })
}(window, django.jQuery));

Implement simple throttling for preview iframe refreshes.


Meteor.methods({
  addSingleUserPermissions(userId, groupIds) {
    // Remove existing user permissions
    Permissions.remove({ userId });

    // add new permissions, if any group IDs provided
    if (groupIds.length > 0) {
      groupIds.forEach(groupId => {
        Permissions.insert({ userId, groupId });
      });
    }

    return true;
  }
});

Meteor.methods({
  addSingleUserPermissions(userId, groupIds) {
    // Remove existing user permissions
    Permissions.remove({ userId });

    // add new permissions, if any group IDs provided
    if (groupIds.length > 0) {
      groupIds.forEach(groupId => {
        Permissions.insert({ userId, groupId });
      });
    }

    return true;
  },
  getSingleUserGroupIds(userId) {
    // Check for existing permissions
    const existingUserPermissions = Permissions.find({ userId }).fetch();

    return existingUserPermissions.map(permission => permission.groupId);
  }
});

Add method to get single user group IDs


Package.describe({
  name: "shell-server",
  version: "0.2.3",
  summary: "Server-side component of the `meteor shell` command.",
  documentation: "README.md"
});

Package.onUse(function(api) {
  api.use("ecmascript@0.5.7", "server");
  api.mainModule("main.js", "server");
});

Package.describe({
  name: "shell-server",
  version: "0.2.3",
  summary: "Server-side component of the `meteor shell` command.",
  documentation: "README.md"
});

Package.onUse(function(api) {
  api.use("ecmascript", "server");
  api.mainModule("main.js", "server");
});

Remove version constraint from `ecmascript` in `shell-server`.


var app = angular.module('simpleapp', ['ng', 'ngRoute']);

app.controller(
    'simpleController',
    function ($scope, $location, $http) {
        $scope.currentUrl = $location.absUrl();
    }
);

app.config(
    function ($routeProvider, $locationProvider) {
        $locationProvider.html5Mode(true);
        $routeProvider.when(
            '/weather',
            {
                template: '
Weather for {{weather.name}}

{{weather | json}}
',
                resolve: {
                    weatherData: function ($http) {
                        return $http.get(
                            'http://api.openweathermap.org/data/2.5/weather?q=San%20Francisco,%20CA,%20US'
                        ).then(function (resp) { return resp.data; });
                    }
                },
                controller: function (weatherData, $scope) {
                    $scope.weather = weatherData;
                }
            }
        );
        $routeProvider.otherwise(
            {
                template: 'Weather'
            }
        );
    }
);

var app = angular.module('simpleapp', ['ng', 'ngRoute']);

app.controller(
    'simpleController',
    function ($scope, $location, $http) {
        $scope.currentUrl = $location.absUrl();
    }
);

app.controller(
    'weatherController',
    function (weatherData, $scope) {
        $scope.weather = weatherData;
    }
);

app.config(
    function ($routeProvider, $locationProvider) {
        $locationProvider.html5Mode(true);
        $routeProvider.when(
            '/weather',
            {
                template: '
Weather for {{weather.name}}

{{weather | json}}
',
                resolve: {
                    weatherData: function ($http) {
                        return $http.get(
                            'http://api.openweathermap.org/data/2.5/weather?q=San%20Francisco,%20CA,%20US'
                        ).then(function (resp) { return resp.data; });
                    }
                },
                controller: 'weatherController'
            }
        );
        $routeProvider.otherwise(
            {
                template: 'Weather'
            }
        );
    }
);

Make the weather page use a named controller.


import $ from 'jquery';

import Scene from 'Scene';
import bedroom from 'scenes/bedroom';
import homeHallway from 'scenes/homeHallway';

import Door from 'entities/Door';
import cellPhone from 'entities/cellPhone';
import garbage from 'entities/homeKitchen/garbage';
import fridge from 'entities/homeKitchen/fridge';
import cupboards from 'entities/homeKitchen/cupboards';

export class HomeKitchen extends Scene {
  descriptionMessage() {
    const hallwayDoor = new Door(homeHallway);
    const message = `
      You are in the kitchen. It smells
      <% if (state.garbageOutside) { %>
        okay.
      <% } else { %>
        awful, because you haven't taken your <%= entity("garbage") %> out
        since before last week's crazy bender.
      <% } %>
      The <%= entity("fridge") %> is to your right, surrounded by some
      <%= entity("cupboards") %>.
      <br>
      The <%= entity("hallwayDoor", "door back to the hallway") %> is behind you.
      <% if (!isInInventory("cellphone")) { %>
        <%= entity("cellPhone", "Your cellphone") %> is on the counter.
      <% } %>
    `;

    return this.insertEntities(message, {garbage, fridge, cupboards, hallwayDoor, cellPhone});
  }
}

export default new HomeKitchen();

import $ from 'jquery';

import Scene from 'Scene';
import bedroom from 'scenes/bedroom';
import homeHallway from 'scenes/homeHallway';

import Door from 'entities/Door';
import cellPhone from 'entities/cellPhone';
import garbage from 'entities/homeKitchen/garbage';
import fridge from 'entities/homeKitchen/fridge';
import cupboards from 'entities/homeKitchen/cupboards';

export class HomeKitchen extends Scene {
  descriptionMessage() {
    const hallwayDoor = new Door(homeHallway);
    const message = `
      You are in the kitchen. It smells
      <% if (state.garbageOutside) { %>
        okay.
      <% } else { %>
        awful, because you haven't taken your <%= entity("garbage") %> out
        since before last week's crazy bender.
      <% } %>
      The <%= entity("fridge") %> is to your right, surrounded by some
      <%= entity("cupboards") %>.
      <br>
      The <%= entity("hallwayDoor", "door back to the hallway") %> is behind you.
      <% if (!isInInventory("cellphone")) { %>
        <%= entity("cellPhone", "Your cellphone") %>
        <% if (state.mirrorSmashed && state.alarmRinging && isInInventory("granolaBar") && !state.doom.started) { %>
          glows an eerie red.
        <% } else { %>
          is on the counter.
        <% } %>
      <% } %>
    `;

    return this.insertEntities(message, {garbage, fridge, cupboards, hallwayDoor, cellPhone});
  }
}

export default new HomeKitchen();

Add hint to check phone


'use strict';

var webspeechapi, vibrateWebApi, DerReader;

const env = require('./env/env.json').env;

if (env === 'dev') {
  webspeechapi = require('./../modules/tts.webapi/tts.webapi.js');
  vibrateWebApi = require('./../modules/vibrate.webapi/vibrate.webapi.js');
  DerReader = require('./../modules/der-reader/der-reader.js');
} else {
  webspeechapi = require('tts.webapi');
  vibrateWebApi = require('vibrate.webapi');
  DerReader = require('der-reader');
}

var remote = require('electron').remote;

window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;

function exit() {
  var window = remote.getCurrentWindow();
  window.close();
}

DerReader.init({
  container: 'der-reader',
  derFile: null,
  tts: webspeechapi,
  vibrate: vibrateWebApi,
  defaultMode: 0,
  format: 'A3',
  exit: exit
});

window.app = {
  env: env,
  version: process.env.npm_package_version
};

'use strict';

var webspeechapi, vibrateWebApi, DerReader;

const env = require('./env/env.json').env;

if (env === 'dev') {
  webspeechapi = require('../modules/tts.webapi/tts.webapi.js');
  vibrateWebApi = require('../modules/vibrate.webapi/vibrate.webapi.js');
  DerReader = require('../modules/der-reader/dist/der-reader.js');
} else {
  webspeechapi = require('tts.webapi');
  vibrateWebApi = require('vibrate.webapi');
  DerReader = require('der-reader');
}

var remote = require('electron').remote;

window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;

function exit() {
  var window = remote.getCurrentWindow();
  window.close();
}

DerReader.init({
  container: 'der-reader',
  derFile: null,
  tts: webspeechapi,
  vibrate: vibrateWebApi,
  defaultMode: 0,
  format: 'A3',
  exit: exit
});

window.app = {
  env: env,
  version: process.env.npm_package_version
};

Fix electron file to der-reader dist


import React from 'react'
import ReactDOM from 'react-dom'

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import pipelines from 'sajari-react/api/reducers/pipeline'

import App from './App'

function sjsi(config) {
  if (!config) {
    console.error('global value "_sjsi" not found, it is needed for Sajari Search Interface')
    return
  }

  const store = createStore(
    combineReducers({ pipelines }),
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunkMiddleware)
  )

  ReactDOM.render(
    <Provider store={store}>
      <App config={config}/>
    </Provider>,
    config.attachTarget
  )
}

sjsi(window._sjsi)

import React from 'react'
import ReactDOM from 'react-dom'

import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import pipelines from 'sajari-react/api/reducers/pipeline'
import search from 'sajari-react/ui/reducers/Search'
import { resetPipelinePageMiddleware, searchPipelineMiddleware } from 'sajari-react/ui/middleware/Search'
import GoogleAnalytics from 'sajari-react/ui/Analytics/ga'
import AnalyticsEvents from 'sajari-react/ui/middleware/Events'

import App from './App'

function sjsi(config) {
  if (!config) {
    console.error('global value "_sjsi" not found, it is needed for Sajari Search Interface')
    return
  }

  const store = createStore(
    combineReducers({ pipelines, search }),
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
    applyMiddleware(thunkMiddleware, resetPipelinePageMiddleware, searchPipelineMiddleware, AnalyticsEvents(new GoogleAnalytics()))
  )

  ReactDOM.render(
    <Provider store={store}>
      <App config={config}/>
    </Provider>,
    config.attachTarget
  )
}

sjsi(window._sjsi)

Add search middleware and GA to site search integration example


const assign = require('object-assign')
const validate = require('../validators')

function DatasetsClient(client) {
  this.request = client.request.bind(client)
}

assign(DatasetsClient.prototype, {
  create(name) {
    return this._modify('PUT', name)
  },

  delete(name) {
    return this._modify('DELETE', name)
  },

  list() {
    return this.request({uri: '/datasets'})
  },

  _modify(method, name) {
    validate.dataset(name)
    return this.request({method, uri: `/datasets/${name}`})
  }
})

module.exports = DatasetsClient

const assign = require('object-assign')
const validate = require('../validators')

function DatasetsClient(client) {
  this.request = client.request.bind(client)
}

assign(DatasetsClient.prototype, {
  create(name, options) {
    return this._modify('PUT', name, options)
  },

  delete(name) {
    return this._modify('DELETE', name)
  },

  list() {
    return this.request({uri: '/datasets'})
  },

  _modify(method, name, body) {
    validate.dataset(name)
    return this.request({method, uri: `/datasets/${name}`, body})
  }
})

module.exports = DatasetsClient

Allow specifying a request body for dataset creation requests


var slice = [].slice
var eject = require('eject')

// todo: create strings parser.
function Interrupt () {
    this._types = {}
}

Interrupt.prototype._populate = function (error, type, vargs) {
    this._types[type] || (this._types[type] = {})
    error.message = error.type = type
    error.typeIdentifier = this._types[type]
    var context = error.context = {}
    vargs.forEach(function (values) {
        for (var key in values) {
            context[key] = values[key]
        }
    })
    error.context = context
    return error
}

Interrupt.prototype.error = function (error, type) {
    return this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.panic = function (error, type) {
    throw this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.type = function (error) {
    if (error.type
        && this._types[error.type]
        && this._types[error.type] === error.typeIdentifier
    ) {
        return error.type
    }
    return null
}

Interrupt.prototype.rescue = function (catcher, callback) {
    callback || (callback = eject)
    return function (error) {
        if (error) {
            if (this.type(error) != null) {
                catcher(error)
            } else {
                callback(error)
            }
        }
    }.bind(this)
}

module.exports = function (messages) {
    return new Interrupt
}

var slice = [].slice
var eject = require('eject')

// todo: create strings parser.
function Interrupt () {
    this._types = {}
}

Interrupt.prototype._populate = function (error, type, vargs) {
    this._types[type] || (this._types[type] = {})
    error.message = error.type = type
    error.typeIdentifier = this._types[type]
    var context = error.context = {}
    vargs.forEach(function (values) {
        for (var key in values) {
            context[key] = values[key]
        }
    })
    error.context = context
    return error
}

Interrupt.prototype.error = function (error, type) {
    return this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.panic = function (error, type) {
    throw this._populate(error, type, slice.call(arguments, 2))
}

Interrupt.prototype.type = function (error) {
    if (error.type
        && this._types[error.type]
        && this._types[error.type] === error.typeIdentifier
    ) {
        return error.type
    }
    return null
}

Interrupt.prototype.rescue = function (catcher, callback) {
    callback || (callback = eject)
    return function (error) {
        if (error) {
            if (this.type(error) != null) {
                return catcher(error, callback)
            } else {
                callback(error)
            }
        }
    }.bind(this)
}

module.exports = function (messages) {
    return new Interrupt
}

Return catcher value, give catcher the callback.


'use strict';

var events = require('events'),
    process = require('process'),
    util = require('util');

// This only works on the raspberry pi.
try {
  var onoff = require('onoff');
} catch (e) {}

var Panel = function() {
  this.cleanup = function() {
    activate && activate.unexport();
    reset && reset.unexport();
  }

  this.reset = function() {
    console.info('Received reset signal.');
    this.emit('reset');
  }

  this.activate = function() {
    console.info('Received activate signal.');
    this.emit('activate');
  }

  // Listen for signals (on non-raspberry pi platorms).
  process.on('SIGUSR1', this.activate.bind(this));
  process.on('SIGUSR2', this.reset.bind(this));

  // TODO: Make sure this works on Raspberry pi.
  if (onoff) {
    // Pin 6
    var activate = new onoff.Gpio(17, 'in', 'rising');
    activate.watch(this.reset.bind(this));

    // Pin 8
    var reset = new onoff.Gpio(22, 'in', 'rising');
    reset.watch(this.reset.bind(this));
  }
}
util.inherits(Panel, events.EventEmitter);

module.exports = Panel;

'use strict';

var events = require('events'),
    debug = require('debug')('panel'),
    process = require('process'),
    util = require('util');

// This only works on the raspberry pi.
try {
  var onoff = require('onoff');
} catch (e) {}

var debounceThresholdMillis = 200;

var Panel = function() {
  this.lastCmd = new Date();

  this.cleanup = function() {
    activate && activate.unexport();
    reset && reset.unexport();
  }

  this.reset = function() {
    if (new Date() - this.lastCmd < debounceThresholdMillis) {
      debug('Debounced reset signal.');
      return;
    }
    debug('Received reset signal.');
    this.lastCmd = new Date();
    this.emit('reset');
  }

  this.activate = function() {
    if (new Date() - this.lastCmd < debounceThresholdMillis) {
      debug('Debounced activate signal.');
      return;
    }
    debug('Received activate signal.');
    this.lastCmd = new Date();
    this.emit('activate');
  }

  // Listen for signals (on non-raspberry pi platorms).
  process.on('SIGUSR1', this.activate.bind(this));
  process.on('SIGUSR2', this.reset.bind(this));

  // TODO: Make sure this works on Raspberry pi.
  if (onoff) {
    // Pin 6
    var activate = new onoff.Gpio(17, 'in', 'rising');
    activate.watch(this.activate.bind(this));

    // Pin 8
    var reset = new onoff.Gpio(22, 'in', 'rising');
    reset.watch(this.reset.bind(this));
  }
}
util.inherits(Panel, events.EventEmitter);

module.exports = Panel;

Add debouncing for hardware switches, fix bug where both buttons were mapped to reset.