/**
 * Jatabase
 *
 * @author Gabriel Jacinto <gamjj74@hotmail.com>
 * @license MIT License
 * @package jatabase
 */

'use strict';

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

module.exports = function (Model) {
  return function (where) {
    let db = require(Model.file),
      collection = db[Model.collection];

    if (typeof where == 'object') {
      if (!utils.object.size(where)) {
        collection = [];
      } else {
        if (Model._validateFields(where)) {
          if (!collection.length) {
            return false;
          }

          let equals = [];

          for (let k in collection) {
            if (collection.hasOwnProperty(k)) {
              if (utils.object.propertiesEqualsTo(collection[k], where)) {
                equals.push(k);
              }
            }
          }

          let newCollection = [];

          for (let k in collection) {
            if (collection.hasOwnProperty(k)) {
              if (!utils.array.contains(equals, k)) {
                newCollection.push(collection[k]);
              }
            }
          }

          collection = newCollection;
        }
      }
    } else {
      let index;

      for (let k in collection) {
        if (collection[k].id == where) {
          index = k;
        }
      }

      if (typeof index === 'undefined') {
        return false;
      }

      collection.splice(index, 1);
    }

    Model._saveModificationOnKey(Model.collection, collection);
  }
};

/**
 * Jatabase
 *
 * @author Gabriel Jacinto <gamjj74@hotmail.com>
 * @license MIT License
 * @package jatabase
 */

'use strict';

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

module.exports = function (Model) {
  return function (where) {
    let db = require(Model.file),
      collection = db[Model.collection];

    if (typeof where == 'object') {
      if (!utils.object.size(where)) {
        collection = [];
      } else {
        if (Model._validateFields(where)) {
          if (!collection.length) {
            return false;
          }

          let equals = [];

          for (let k in collection) {
            if (collection.hasOwnProperty(k)) {
              if (utils.object.propertiesEqualsTo(collection[k], where)) {
                equals.push(k);
              }
            }
          }

          let newCollection = [];

          for (let k in collection) {
            if (collection.hasOwnProperty(k)) {
              if (!utils.array.contains(equals, k)) {
                newCollection.push(collection[k]);
              }
            }
          }

          collection = newCollection;
        }
      }
    } else {
      let index;

      for (let k in collection) {
        if (collection[k].id == where) {
          index = k;
        }
      }

      if (typeof index === 'undefined') {
        throw Error('Record with ID "' + where + '" was not found');
      }

      collection.splice(index, 1);
    }

    Model._saveModificationOnKey(Model.collection, collection);
  }
};

Add exception for unregistred IDs


import gravity from '../../lib/loaders/gravity';
import { GraphQLString, GraphQLBoolean } from 'graphql';
import { mutationWithClientMutationId } from 'graphql-relay';
import { artworkFields } from '../artwork/index';

export default mutationWithClientMutationId({
  name: 'SaveArtwork',
  decription: 'Save (or remove) an artwork to (from) a users default collection.',
  inputFields: {
    artwork_id: {
      type: GraphQLString,
    },
    remove: {
      type: GraphQLBoolean,
    },
  },
  outputFields: artworkFields(),
  mutateAndGetPayload: ({
    artwork_id,
    remove,
  }, request, { rootValue: { accessToken, userID } }) => {
    if (!accessToken) return null;
    const saveMethod = remove ? 'DELETE' : 'POST';
    return gravity.with(accessToken, {
      method: saveMethod,
    })(`/collection/saved-artwork/artwork/${artwork_id}`, {
      user_id: userID,
    }).then(() => gravity(`artwork/${artwork_id}`));
  },
});

import gravity from '../../lib/loaders/gravity';
import { GraphQLString, GraphQLBoolean } from 'graphql';
import { mutationWithClientMutationId } from 'graphql-relay';
import { artworkFields } from '../artwork/index';

export default mutationWithClientMutationId({
  name: 'SaveArtwork',
  decription: 'Save (or remove) an artwork to (from) a users default collection.',
  inputFields: {
    artwork_id: {
      type: GraphQLString,
    },
    remove: {
      type: GraphQLBoolean,
    },
  },
  outputFields: artworkFields(),
  mutateAndGetPayload: ({
    artwork_id,
    remove,
  }, request, { rootValue: { accessToken, userID } }) => {
    if (!accessToken) return new Error('You need to be signed in to perform this action');
    const saveMethod = remove ? 'DELETE' : 'POST';
    return gravity.with(accessToken, {
      method: saveMethod,
    })(`/collection/saved-artwork/artwork/${artwork_id}`, {
      user_id: userID,
    }).then(() => gravity(`artwork/${artwork_id}`));
  },
});

Throw an error instead of returning null


// server.js

"use strict";

const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('db.json')
const middlewares = jsonServer.defaults();

const path = require("path");

const PORT = 3000;
const HOST_NAME = "localhost";
const HOST = `${HOST_NAME}:${PORT}`;

// To handle POST, PUT and PATCH you need to use a body-parser
// You can use the one used by JSON Server
server.use(jsonServer.bodyParser);

// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);

// Use default router
server.use("/api", router);

// Add custom routes before JSON Server router
server.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, "index.html"));
})

server.listen(PORT, () => {
  console.log(`JSON Server is running on ${HOST}`);
});

// server.js

"use strict";

const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router('app/db.json')
const middlewares = jsonServer.defaults();

const path = require("path");

const PORT = 3000;
const HOST_NAME = "localhost";
const HOST = `${HOST_NAME}:${PORT}`;

// To handle POST, PUT and PATCH you need to use a body-parser
// You can use the one used by JSON Server
server.use(jsonServer.bodyParser);

// Set default middlewares (logger, static, cors and no-cache)
server.use(middlewares);

// Use default router
server.use("/api", router);

// Add custom routes before JSON Server router
server.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, "app/index.html"));
})

server.listen(PORT, () => {
  console.log(`JSON Server is running on ${HOST}`);
});

Edit path to DB, index.html


import React from "react"
import { shallow } from "enzyme"
import { expect } from "chai"
import sinon from "sinon"

import Room from "../../web/static/js/components/room"
import CategoryColumn from "../../web/static/js/components/category_column"

describe("Room component", () => {
  const mockRetroChannel = { push: sinon.spy(), on: () => {} }

  describe(".handleIdeaSubmission", () => {
    it("pushes the idea to the room channel", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)

      roomComponent
        .instance()
        .handleIdeaSubmission({ category: "sad", body: "we don't use our linter" })

      expect(
        mockRetroChannel.push.calledWith("new_idea", { category: "sad", body: "we don't use our linter" }),
      ).to.equal(true)
    })
  })

  describe("Action item column", () => {
    it("is not visible on render", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)
      expect(roomComponent.containsMatchingElement(
        <CategoryColumn category="action-item" ideas={[]}/>
      )).to.equal(false)
    })
  })
})

import React from "react"
import { shallow } from "enzyme"
import { expect } from "chai"
import sinon from "sinon"

import Room from "../../web/static/js/components/room"
import CategoryColumn from "../../web/static/js/components/category_column"

describe("Room component", () => {
  const mockRetroChannel = { push: sinon.spy(), on: () => {} }

  describe(".handleIdeaSubmission", () => {
    it("pushes the idea to the room channel", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)

      roomComponent
        .instance()
        .handleIdeaSubmission({ category: "sad", body: "we don't use our linter" })

      expect(
        mockRetroChannel.push.calledWith("new_idea", { category: "sad", body: "we don't use our linter" }),
      ).to.equal(true)
    })
  })

  describe("Action item column", () => {
    it("is not visible on render", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)

      expect(roomComponent.containsMatchingElement(
        <CategoryColumn category="action-item" ideas={[]}/>
      )).to.equal(false)
    })

    it("becomes visible when showActionItem is true", () => {
      const roomComponent = shallow(<Room retroChannel={mockRetroChannel} users={[]} />)
      roomComponent.setState({ showActionItem: true })

      expect(roomComponent.containsMatchingElement(
        <CategoryColumn category="action-item" ideas={[]}/>
      )).to.equal(true)
    })
  })
})

Add test to check that Action Item column is displayed on toggle


(function() {
  'use strict';

  angular
    .module('afterHeap')
    .config(routerConfig);

  /** @ngInject */
  function routerConfig($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('city', {
        abstract: true,
        url: '/:cityId',
        template: '<ui-view/>'
      })
      .state('city.highlights', {
        url: '/',
        templateUrl: 'app/highlights/highlights.html',
        controller: 'HighlightsController',
        controllerAs: 'highlightsController',
        resolve: {
          highlights: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res) {
              return processResponse.createTopSuggestions(res);
            })
          }
        }this_should_break_the_build
      })
      .state('city.feed', {
        url: '/feed',
        templateUrl: 'app/activityfeed/activityfeed.html',
        controller: 'ActivityFeedController',
        controllerAs: 'activityFeed',
        resolve: {
          checkins: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res){
              return processResponse.removeBlackListed(res);
            });
          }
        }
      });

    $urlRouterProvider.otherwise('/Tampere/');
  }

})();

(function() {
  'use strict';

  angular
    .module('afterHeap')
    .config(routerConfig);

  /** @ngInject */
  function routerConfig($stateProvider, $urlRouterProvider) {
    $stateProvider
      .state('city', {
        abstract: true,
        url: '/:cityId',
        template: '<ui-view/>'
      })
      .state('city.highlights', {
        url: '/',
        templateUrl: 'app/highlights/highlights.html',
        controller: 'HighlightsController',
        controllerAs: 'highlightsController',
        resolve: {
          highlights: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res) {
              return processResponse.createTopSuggestions(res);
            })
          }
        }
      })
      .state('city.feed', {
        url: '/feed',
        templateUrl: 'app/activityfeed/activityfeed.html',
        controller: 'ActivityFeedController',
        controllerAs: 'activityFeed',
        resolve: {
          checkins: function(api, processResponse, $stateParams){
            return api.getCityFeed($stateParams.cityId).then(function(res){
              return processResponse.removeBlackListed(res);
            });
          }
        }
      });

    $urlRouterProvider.otherwise('/Tampere/');
  }

})();

Revert "Test that typo in sources break the CI build"


var zoomextent = require('../lib/zoomextent'),
    qs = require('../lib/querystring');

module.exports = function(context) {

    d3.select(window).on('unload', onunload);
    context.dispatch.on('change', onchange);

    var query = qs.stringQs(location.hash.split('#')[1] || '');

    if (location.hash !== '#new' && !query.id && !query.data) {
        var rec = context.storage.get('recover');
        if (rec && confirm('recover your map from the last time you edited?')) {
            context.data.set(rec);
            zoomextent(context);
        } else {
            context.storage.remove('recover');
        }
    }

    function onunload() {
        if (context.data.get('type') === 'local' && context.data.hasFeatures()) {
            context.storage.set('recover', context.data.all());
        } else {
            context.storage.remove('recover');
        }
    }

    function onchange() {
        if (context.data.get('type') !== 'local') {
            context.storage.remove('recover');
        }
    }
};

var zoomextent = require('../lib/zoomextent'),
    qs = require('../lib/querystring');

module.exports = function(context) {

    d3.select(window).on('unload', onunload);
    context.dispatch.on('change', onchange);

    var query = qs.stringQs(location.hash.split('#')[1] || '');

    if (location.hash !== '#new' && !query.id && !query.data) {
        var rec = context.storage.get('recover');
        if (rec && confirm('recover your map from the last time you edited?')) {
            context.data.set(rec);
            zoomextent(context);
        } else {
            context.storage.remove('recover');
        }
    }

    function onunload() {
        if (context.data.get('type') === 'local' && context.data.hasFeatures()) {
            try {
                context.storage.set('recover', context.data.all());
            } catch(e) {
                // QuotaStorageExceeded
            }
        } else {
            context.storage.remove('recover');
        }
    }

    function onchange() {
        if (context.data.get('type') !== 'local') {
            context.storage.remove('recover');
        }
    }
};

Handle quota storage exceeded error


(function() {
  'use strict';

  angular.module('app', [
    'app.config',
    'app.core',
    'app.feature'
  ]);

  angular.module('app').config([
    '$logProvider', 'LOG', '$locationProvider',
    function($logProvider, log, $locationProvider) {
      // Enable or disable debug logging
      $logProvider.debugEnabled(log.ENABLE_DEBUG);

      $locationProvider.html5Mode(false);
    }
  ]);

  // CONFIG SETTINGS
  angular.module('app.config', [])
    // Log settings
    .constant('LOG', {
      ENABLE_DEBUG: true
    })
    .contant('WEATHER_SETTINGS', {
      OPEN_WEATHER_MAP: {
        BASE_URI: 'http://api.openweathermap.org/data/2.5',
        API_KEY: '<api-key>',
        CITY_ID: '2925550',
        LANGUAGE_ID: 'de',
        UNITS: 'metric'
      }
    });
})();

(function() {
  'use strict';

  angular.module('app', [
    'app.config',
    'app.core',
    'app.feature'
  ]);

  angular.module('app').config([
    '$logProvider', 'LOG', '$locationProvider',
    function($logProvider, log, $locationProvider) {
      // Enable or disable debug logging
      $logProvider.debugEnabled(log.ENABLE_DEBUG);

      $locationProvider.html5Mode(false);
    }
  ]);

  // CONFIG SETTINGS
  angular.module('app.config', [])
    // Log settings
    .constant('LOG', {
      ENABLE_DEBUG: true
    })
    .constant('WEATHER_SETTINGS', {
      OPEN_WEATHER_MAP: {
        BASE_URI: 'http://api.openweathermap.org/data/2.5',
        API_KEY: '<api-key>',
        CITY_ID: '2925550',
        LANGUAGE_ID: 'de',
        UNITS: 'metric'
      }
    });
})();

Correct misspelled method name 'contant' to 'constant'


/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80; */
"use strict";

var mona = require("./mona");

var parsers = {
  dialogue: require("./parsers/dialogue")()
};

function parse(type, content) {
  return parsers[type] && mona.run(parsers[type], content).val;
}

module.exports.parse = parse;

/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80; */
"use strict";

var mona = require("./mona");

var parsers = {
  dialogue: require("./parsers/dialogue")()
};

function parse(type, content) {
  var parser = parsers[type];
  if (!parser) { throw new Error("No parser for type "+type); }
  return mona.run(parser, content).val;
}

module.exports.parse = parse;

Throw an error if an invalid input type is getting parsed


const webpack = require('webpack');
const env = process.env.NODE_ENV;

const config = {
  devtool: 'source-map',
  output: {
    libraryTarget: 'umd',
    library: 'Pic'
  },
  plugins: [],
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM'
  }
};

if (env === 'production') {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    })
  );
}

module.exports = config;

const webpack = require('webpack');
const env = process.env.NODE_ENV;

const config = {
  devtool: 'source-map',
  output: {
    libraryTarget: 'umd',
    library: 'Pic'
  },
  plugins: [],
  module: {
    loaders: [
      {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react']
        }
      }
    ]
  },
  externals: {
    'react': {
      root: 'React',
      amd: 'react',
      commonjs2: 'react',
      commonjs: 'react'
    },
    'react-dom': {
      root: 'ReactDOM',
      amd: 'react-dom',
      commonjs2: 'react-dom',
      commonjs: 'react-dom'
    }
  }
};

if (env === 'production') {
  config.plugins.push(
    new webpack.optimize.UglifyJsPlugin({
      compressor: {
        warnings: false
      }
    })
  );
}

module.exports = config;

Remove react warnings by properly defining externals


// configure the openui5 connect server
module.exports = function(grunt, config) {

	// libraries are sorted alphabetically
	var aLibraries = config.allLibraries.slice();
	aLibraries.sort(function(a, b) {
		return a.name.localeCompare(b.name);
	});

	var openui5_connect = {

		options: {

			contextpath: config.testsuite.name,
			proxypath: 'proxy',
			cors: {
				origin: "*"
			}

		},

		src: {

			options: {

				appresources: config.testsuite.path + '/src/main/webapp',

				resources: aLibraries.map(function(lib) {
					return lib.path + '/src';
				}),

				testresources: aLibraries.map(function(lib) {
					return lib.path + '/test';
				})

			}

		},

		target: {

			options: {

				appresources: 'target/openui5-testsuite',

				resources: aLibraries.map(function(lib) {
					return 'target/openui5-' + lib.name + '/resources';
				}),

				testresources: aLibraries.map(function(lib) {
					return 'target/openui5-' + lib.name + '/test-resources';
				})

			}

		}

	};

	return openui5_connect;

};

// configure the openui5 connect server
module.exports = function(grunt, config) {

	// libraries are sorted alphabetically
	var aLibraries = config.allLibraries.slice();
	aLibraries.sort(function(a, b) {
		return a.name.localeCompare(b.name);
	});

	var openui5_connect = {

		options: {

			contextpath: config.testsuite.name,
			proxypath: 'proxy',
			proxyOptions: {
				secure: false
			},
			cors: {
				origin: "*"
			}

		},

		src: {

			options: {

				appresources: config.testsuite.path + '/src/main/webapp',

				resources: aLibraries.map(function(lib) {
					return lib.path + '/src';
				}),

				testresources: aLibraries.map(function(lib) {
					return lib.path + '/test';
				})

			}

		},

		target: {

			options: {

				appresources: 'target/openui5-testsuite',

				resources: aLibraries.map(function(lib) {
					return 'target/openui5-' + lib.name + '/resources';
				}),

				testresources: aLibraries.map(function(lib) {
					return 'target/openui5-' + lib.name + '/test-resources';
				})

			}

		}

	};

	return openui5_connect;

};

Disable proxy "secure" option for better local testing


var utils    = require("radiodan-client").utils,
    settingsRoutes = require("../settings/routes"),
    logger   = utils.logger(__filename);

module.exports = routes;

function routes(app, states, Settings) {
  var settings = Settings.build(
        "announcer",
        { speak: false, announcer: "marvin" }
      );

  app.get("/", index);
  app.use(settingsRoutes(settings));

  return app;

  function index(req, res) {
    res.json({page: "Announcer"});
  }
}

var utils    = require('radiodan-client').utils,
    settingsRoutes = require('../settings/routes'),
    logger   = utils.logger(__filename);

module.exports = routes;

function routes(app, states, Settings) {
  var settings = Settings.build(
        'announcer',
        { speak: false, announcer: 'marvin' }
      );

  app.get('/', index);
  app.get('/state.json', state);
  app.post('/', announce);
  app.delete('/', cancel);

  app.use(settingsRoutes(settings));

  return app;

  function index(req, res) {
    res.json({page: 'Announcer'});
  }

  function state(req, res) {
    res.json({
      isAnnouncing: (states.state === 'announcing')
    });
  }

  function announce(req, res) {
    settings.get().then(function(settings) {
      states.handle('startAnnouncing', settings);
      res.redirect('./');
    }).then(null, utils.failedPromiseHandler(logger));
  }

  function cancel(req, res) {
    states.handle('stopAnnouncing');
    res.redirect('./');
  }
}

Add boilerplate for announcer route


import { NodeComponent } from 'substance'
import { getXrefLabel, getXrefTargets } from '../../editor/util/xrefHelpers'

export default class ReaderXrefComponent extends NodeComponent {

  render($$) {
    let node = this.props.node

    let refType = node.getAttribute('ref-type')
    let label = getXrefLabel(node)
    let el = $$('span').addClass('sc-xref sm-'+refType).append(label)

    // Add a preview if refType is bibr
    if (refType === 'bibr') {
      el.append(
        this._renderPreview($$)
      )
    }
    return el
  }

  /*
    Only for references
  */
  _renderPreview($$) {
    let xrefTargets = getXrefTargets(this.props.node)
  }
}

import { NodeComponent } from 'substance'
import { getXrefLabel, getXrefTargets } from '../../editor/util/xrefHelpers'

export default class ReaderXrefComponent extends NodeComponent {

  render($$) {
    let node = this.props.node
    let refType = node.getAttribute('ref-type')
    let label = getXrefLabel(node)
    let el = $$('span').addClass('sc-reader-xref sm-'+refType).append(label)
    // Add a preview if refType is bibr
    if (refType === 'bibr') {
      el.append(
        this._renderPreview($$)
      )
    }
    return el
  }

  /*
    Render preview only for references.
  */
  _renderPreview($$) {
    let references = this.context.api.getReferences()

    let el = $$('div').addClass('se-preview')
    let xrefTargets = getXrefTargets(this.props.node)
    xrefTargets.forEach(refId => {
      let label = references.getLabel(refId)
      let html = references.renderReference(refId)
      el.append(
        $$('div').addClass('se-ref').append(
          $$('div').addClass('se-label').append(label),
          $$('div').addClass('se-text').html(html)
        ).attr('data-id', refId)
      )
    })
    return el

  }
}

Implement xref tooltip using ReferencesAPI.


import React, { Component } from 'react';
import PropTypes from 'prop-types';
// import LinkButton from 'shared/components/linkButton/linkButton';
import styles from './imageCard.css';

class ImageCard extends Component {
  render() {
    return (

{this.props.title}:

{this.props.cardText}

    );
  }
}

// TODO - AFTER MVP:
// Add within cardText div under the paragraph tag once links are generated
// <LinkButton text={this.props.buttonText} link={this.props.link} />

ImageCard.propTypes = {
  image: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  cardText: PropTypes.string.isRequired,
  // buttonText: PropTypes.string.isRequired,
  // link: PropTypes.string.isRequired
};

export default ImageCard;

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import LinkButton from 'shared/components/linkButton/linkButton';
import styles from './imageCard.css';

class ImageCard extends Component {
  render() {
    return (

{this.props.title}:

{this.props.cardText}

            {this.props.link && <LinkButton text={this.props.buttonText} link={this.props.link} />}

    );
  }
}

ImageCard.propTypes = {
  image: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  cardText: PropTypes.string.isRequired,
  buttonText: PropTypes.string,
  link: PropTypes.string
};

ImageCard.defaultProps = {
  link: null,
  buttonText: null
}

// TODO: Uncomment buttons as routes are implemented
// NOTE: ImageCard dynamically handles button-links as optionally, so no further work required

export default ImageCard;

Implement optional button-link functionality for ImageCards


var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {
  context: __dirname,
  entry: './scrappyr/static/js/index',
  output: {
      path: path.resolve('./scrappyr/static/webpack_bundles/'),
      filename: "[name]-[hash].js"
  },
  plugins: [
    new BundleTracker({filename: './webpack-stats.json'}),
    new webpack.NoErrorsPlugin()
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets:['es2015', 'es2017', 'react']
        },
        include: path.join(__dirname, 'scrappyr/static/js')
      }
    ]
  }
}

var path = require('path');
var webpack = require('webpack');
var BundleTracker = require('webpack-bundle-tracker');

module.exports = {
  context: __dirname,
  entry: './scrappyr/static/js/index',
  output: {
      path: path.resolve('./scrappyr/static/webpack_bundles/'),
      filename: "[name]-[hash].js"
  },
  devtool: 'source-map',
  plugins: [
    new BundleTracker({filename: './webpack-stats.json'}),
    new webpack.NoErrorsPlugin()
  ],
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets:['es2015', 'es2017', 'react']
        },
        include: path.join(__dirname, 'scrappyr/static/js')
      }
    ]
  }
}

Add sourcemaps for easier debugging


'use strict';

let webpack = require('webpack');
let ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  devtool: 'source-map',
  entry: [`${__dirname}/src/main.js`],
  output: {
    path: `${__dirname}/dist`,
    publicPath: '/dist/',
    filename: 'all.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets: ['es2015']
        }
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('css?sourceMap!autoprefixer!sass?sourceMap')
      },
      {
        test: /\.(ttf|eot|svg|woff)(\?[a-z0-9#-]+)?$/,
        loader: 'file-loader'
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new ExtractTextPlugin('all.css', {
      allChunks: true
    })
  ]
};

'use strict';

let webpack = require('webpack');
let ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  devtool: 'source-map',
  entry: [`${__dirname}/src/main.js`],
  output: {
    path: `${__dirname}/dist`,
    publicPath: '/dist/',
    filename: 'all.js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: 'babel',
        query: {
          presets: ['es2015']
        }
      },
      {
        test: /\.scss$/,
        loader: ExtractTextPlugin.extract('css?sourceMap!autoprefixer!sass?sourceMap')
      },
      {
        test: /\.(ttf|eot|svg|woff)(\?[a-z0-9#-]+)?$/,
        loader: 'file-loader?name=[name]-[sha512:hash:base64:7].[ext]'
      }
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new ExtractTextPlugin('all.css', {
      allChunks: true
    })
  ]
};

Change asset naming of Webpack


import m from 'mithril';
import attributes from '../attributes';

export let Header = {
	view(ctrl, args, ...children) {
		args = args || {};
		let attr = attributes(args);
		let {scroll, waterfall, transparent} = args;

		attr.class.push('mdl-layout__header');

		if(scroll) attr.class.push('mdl-layout__header--scroll');
		if(waterfall) attr.class.push('mdl-layout__header--waterfall');
		if(transparent) attr.class.push('mdl-layout__header--transparent');

		return {children};
	}
};

export let HeaderRow = {
	view(ctrl, args, ...children) {
		args = args || {};
		let attr = attributes(args);
		attr.class.push('mdl-layout__header-row');

		return
{children}
;
	}
};

import m from 'mithril';
import attributes from '../attributes';

export let Header = {
	view(ctrl, args, ...children) {
		args = args || {};
		let attr = attributes(args);
		let {scroll, waterfall, transparent, castShadow} = args;

		attr.class.push('mdl-layout__header');

		if(scroll) attr.class.push('mdl-layout__header--scroll');
		if(waterfall) attr.class.push('mdl-layout__header--waterfall');
		if(transparent) attr.class.push('mdl-layout__header--transparent');
		if(castShadow) attr.class.push('is-casting-shadow');

		return {children};
	}
};

export let HeaderRow = {
	view(ctrl, args, ...children) {
		args = args || {};
		let attr = attributes(args);
		attr.class.push('mdl-layout__header-row');

		return
{children}
;
	}
};

Add is-casting-shadow to Layout Header.


(function ($) {

	var Sinistrocular = {
		'common': {
			init: function () {
				console.log('common.init');

        Utility.header.init();

        Animation.init();
				Video.init();
        Nav.init();
			},
			finalize: function () {
			}
		},
		'page': {
			init: function () {
				console.log('the body has class `page`');
			}
		}
	};

	// The routing fires all common scripts, followed by the page specific scripts.
	var UTIL = {
		fire: function (func, funcname, args) {
			var fire;
			var namespace = Sinistrocular;
			funcname = (funcname === undefined) ? 'init' : funcname;
			fire = func !== '';
			fire = fire && namespace[func];
			fire = fire && typeof namespace[func][funcname] === 'function';

			if (fire) {
				namespace[func][funcname](args);
			}
		},
		loadEvents: function () {
			UTIL.fire('common');

			// Fire page-specific init JS, and then finalize JS
			$.each(document.body.className.replace(/-/g, '_').split(/\s+/), function (i, classnm) {
				UTIL.fire(classnm);
				UTIL.fire(classnm, 'finalize');
			});

			UTIL.fire('common', 'finalize');
		}
	};
	$(document).ready(UTIL.loadEvents);

})(jQuery);

(function ($) {

	var Sinistrocular = {
		'common': {
			init: function () {
				console.log('common.init');

        Utility.header.init();

        Animation.init();
				Video.init();
        Nav.init();

        $(document.links).filter(function() {
          return this.hostname != window.location.hostname;
        }).attr('target', '_blank');
			},
			finalize: function () {
			}
		},
		'page': {
			init: function () {
				console.log('the body has class `page`');
			}
		}
	};

	// The routing fires all common scripts, followed by the page specific scripts.
	var UTIL = {
		fire: function (func, funcname, args) {
			var fire;
			var namespace = Sinistrocular;
			funcname = (funcname === undefined) ? 'init' : funcname;
			fire = func !== '';
			fire = fire && namespace[func];
			fire = fire && typeof namespace[func][funcname] === 'function';

			if (fire) {
				namespace[func][funcname](args);
			}
		},
		loadEvents: function () {
			UTIL.fire('common');

			// Fire page-specific init JS, and then finalize JS
			$.each(document.body.className.replace(/-/g, '_').split(/\s+/), function (i, classnm) {
				UTIL.fire(classnm);
				UTIL.fire(classnm, 'finalize');
			});

			UTIL.fire('common', 'finalize');
		}
	};
	$(document).ready(UTIL.loadEvents);

})(jQuery);

Add target="_blank" to external links with JS


import { execFile } from 'child_process'

export class YoutubeDownloader {
  constructor({downloadPath}){
    this.options = { cwd: downloadPath };
  }

  download(link){
    return new Promise((resolve, reject) => {
      execFile('youtube-dl', ['-f', 'bestaudio[ext=m4a]/bestaudio', link], this.options, (error, stdout, stderr) => {
        if (error || stderr) reject(error, stderr);
        resolve(stdout);
      })
    })
  }
}

import { execFile } from 'child_process'

export class YoutubeDownloader {
  constructor({downloadPath}){
    this.options = { cwd: downloadPath };
  }

  download(link){
    return new Promise((resolve, reject) => {
      execFile('youtube-dl', ['-f', 'bestaudio[ext=m4a]/bestaudio', link], this.options, (error, stdout, stderr) => {
        if (error || stderr) reject({error, stderr});
        resolve(stdout);
      })
    })
  }
}

Fix promise returning more than one value


/* eslint-disable no-unused-vars */

type ConnectMessage = {
	type: 'CONNECT',
	chorusClientId: string,
	description: string,
}

type PublishStepMessage = {
	type: 'PUBLISH_STEP',
	chorusClientId: string,
	stepId: string,
	pattern: string,
	pendingMessage?: string,
	technicalDescription?: string,
}

type StepsAlignedMessage = {
	type: 'STEPS_ALIGNED',
	chorusClientId: string,
}

type StepSucceededMessage = {
	type: 'STEP_SUCCEEDED',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	result?: number | string | void,
	contextVariables: Object,
}

type StepFailedMessage = {
	type: 'STEP_FAILED',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	description?: string,
	errorText?: string,
}

type OutgoingMessage =
	ConnectMessage |
	PublishStepMessage |
	StepsAlignedMessage |
	StepSucceededMessage |
	StepFailedMessage;

type ExecuteStepMessage = {
	type: 'EXECUTE_STEP',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	pattern: string,
	arguments: Array<string>,
	timeoutPeriodSeconds: number,
	contextVariables: Object,
}

/* eslint-disable no-unused-vars */

type ConnectMessage = {|
	type: 'CONNECT',
	chorusClientId: string,
	description: string,
|}

type PublishStepMessage = {|
	type: 'PUBLISH_STEP',
	chorusClientId: string,
	stepId: string,
	pattern: string,
	pendingMessage?: string,
	technicalDescription?: string,
|}

type StepsAlignedMessage = {|
	type: 'STEPS_ALIGNED',
	chorusClientId: string,
|}

type StepSucceededMessage = {|
	type: 'STEP_SUCCEEDED',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	result?: number | string | void,
	contextVariables: Object,
|}

type StepFailedMessage = {|
	type: 'STEP_FAILED',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	description?: string,
	errorText?: string,
|}

type OutgoingMessage =
	ConnectMessage |
	PublishStepMessage |
	StepsAlignedMessage |
	StepSucceededMessage |
	StepFailedMessage;

type ExecuteStepMessage = {|
	type: 'EXECUTE_STEP',
	chorusClientId: string,
	stepId: string,
	executionId: string,
	pattern: string,
	arguments: Array<string>,
	timeoutPeriodSeconds: number,
	contextVariables: Object,
|}

Make message object types strict


var assert = require("assert");

var documents = require("../lib/documents");
var DocumentConverter = documents.DocumentConverter;
var test = require("./testing").test;

describe('DocumentConverter', function() {
    test('should convert document containing one paragraph to single p element', function() {
        var document = new documents.Document([
            paragraphOfText("Hello.")
        ]);
        var converter = new DocumentConverter();
        return converter.convertToHtml(document).then(function(result) {
            assert.equal("
Hello.
", result.html);
        });
    })
});

describe('DocumentConverter', function() {
    test('should convert document containing multiple paragraphs to multiple p elements', function() {
        var document = new documents.Document([
            paragraphOfText("Hello."),
            paragraphOfText("Goodbye.")
        ]);
        var converter = new DocumentConverter();
        return converter.convertToHtml(document).then(function(result) {
            assert.equal("
Hello.

Goodbye.
", result.html);
        });
    })
});

function paragraphOfText(text) {
    var run = runOfText(text);
    return new documents.Paragraph([run]);
}

function runOfText(text) {
    var textElement = new documents.Text(text);
    return new documents.Run([textElement]);
}

var assert = require("assert");

var documents = require("../lib/documents");
var DocumentConverter = documents.DocumentConverter;
var test = require("./testing").test;

describe('DocumentConverter', function() {
    test('should convert document containing one paragraph to single p element', function() {
        var document = new documents.Document([
            paragraphOfText("Hello.")
        ]);
        var converter = new DocumentConverter();
        return converter.convertToHtml(document).then(function(result) {
            assert.equal("
Hello.
", result.html);
        });
    })

    test('should convert document containing multiple paragraphs to multiple p elements', function() {
        var document = new documents.Document([
            paragraphOfText("Hello."),
            paragraphOfText("Goodbye.")
        ]);
        var converter = new DocumentConverter();
        return converter.convertToHtml(document).then(function(result) {
            assert.equal("
Hello.

Goodbye.
", result.html);
        });
    })
});

function paragraphOfText(text) {
    var run = runOfText(text);
    return new documents.Paragraph([run]);
}

function runOfText(text) {
    var textElement = new documents.Text(text);
    return new documents.Run([textElement]);
}

Remove duplicate usage of describe


import React, { Component } from 'react';

import SearchActions from '../actions/SearchActions.js';

class Run extends Component {
  componentDidMount() {
    SearchActions.nsearch(this.props.namespace);
  }

  componentWillUnmount() {
    SearchActions.nsearch(this.props.namespace);
  }

  render() {
    return null;
  }
}

Run.propTypes = {
  namespace: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.arrayOf(React.PropTypes.string),
  ]),
};

export default Run;

import React, { Component } from 'react';

import SearchActions from '../actions/SearchActions.js';

class Run extends Component {
  componentDidMount() {
    SearchActions.nsearch(this.props.namespace || 'default');
  }

  componentWillUnmount() {
    SearchActions.nsearch(this.props.namespace || 'default');
  }

  render() {
    return null;
  }
}

Run.propTypes = {
  namespace: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.arrayOf(React.PropTypes.string),
  ]),
};

export default Run;

Make run default to default namespace


import { hierarchy } from "d3-hierarchy";

const JSON_PATH = "http://rawgit.com/interlockjs/interlock/master/docs/compilation.json";

function orderChildren (node, anscestors) {
  if (node.children) {
    node.children = node.children.map((child, idx) => {
      const childAnscestors = anscestors.concat(node);
      child = orderChildren(child, childAnscestors);
      child.position = idx;
      return child;
    });
  }

  node.anscestors = anscestors;
  return node;
}

module.exports = () => fetch(JSON_PATH)
  .then(response => response.json())
  .then(data =>
    orderChildren(hierarchy(data), [])
      .sort((a, b) => b.data.position - a.data.position)
      .sum(d => d.size)
  );

import { hierarchy } from "d3-hierarchy";

const JSON_PATH = "http://rawgit.com/interlockjs/interlock/master/docs/compilation.json";

function orderChildren (datum) {
  if (datum.children) {
    datum.children = datum.children.map((child, idx) => {
      child = orderChildren(child);
      child.position = idx;
      return child;
    });
  }
  return datum;
}

function addAnscestors (node, anscestors) {
  if (node.children) {
    node.children = node.children.map((child, idx) => {
      const childAnscestors = anscestors.concat(node);
      child = addAnscestors(child, childAnscestors);
      return child;
    });
  }

  node.anscestors = anscestors;
  return node;
}

module.exports = () => fetch(JSON_PATH)
  .then(response => response.json())
  .then(data => {
    data = orderChildren(data);
    return addAnscestors(hierarchy(data), [])
      .sort((a, b) => b.data.position > a.data.position)
      .sum(d => d.size)
  });

Fix ordering and breadcrumb generation for interactive doc.


/*
 * debugger.io: An interactive web scripting sandbox
 */

(function() {
  'use strict';

  var SRC = '../../src';
  var TEST = '../../test'; // relative to SRC

  require([SRC + '/js/require.config'], function() {
    requirejs.config({
      baseUrl: SRC + '/js',
      urlArgs: ('v=' + (new Date()).getTime()),

      paths: {
        jasminejs: '//cdn.jsdelivr.net/jasmine/2.0.0/jasmine',
        jasmine_html: '//cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html',
        jasmine_boot: '//cdn.jsdelivr.net/jasmine/2.0.0/boot',
        jasmine_jquery: '//cdn.jsdelivr.net/jasmine.jquery/2.0.3/jasmine-jquery.min',

        jasmine: TEST + '/js/libs/jasmine',
        matchers: TEST + '/js/matchers'
      },

      shim: {
        jasminejs: { exports: 'jasmine' },
        jasmine_html: { deps: ['jasminejs'] },
        jasmine_boot: { deps: ['jasminejs', 'jasmine_html'] },
        jasmine_jquery: { deps: ['jquery', 'jasmine'] }
      }
    });

    require(['jquery', 'underscore', 'jasmine', 'matchers'],
    function($, _, jasmine, matchers) {

      var specs = _.map([
        'config', 'utils'
      ], function(module) {
        return _.sprintf('%s/spec/%s_spec', TEST, module);
      });

      $(function() {
        require(specs, function() {
          matchers.addMatchers();
          jasmine.onJasmineLoad();
        });
      });
    });
  });

})();

/*
 * debugger.io: An interactive web scripting sandbox
 */

(function() {
  'use strict';

  var SRC = '../../src';
  var TEST = '../../test'; // relative to SRC

  require([SRC + '/js/require.config'], function() {
    requirejs.config({
      baseUrl: SRC + '/js',
      urlArgs: ('v=' + (new Date()).getTime()),

      paths: {
        jasminejs: '//cdn.jsdelivr.net/jasmine/2.0.0/jasmine',
        jasmine_html: '//cdn.jsdelivr.net/jasmine/2.0.0/jasmine-html',
        jasmine_boot: '//cdn.jsdelivr.net/jasmine/2.0.0/boot',
        jasmine_jquery: '//cdn.jsdelivr.net/jasmine.jquery/2.0/jasmine-jquery.min',

        jasmine: TEST + '/js/libs/jasmine',
        matchers: TEST + '/js/matchers'
      },

      shim: {
        jasminejs: { exports: 'jasmine' },
        jasmine_html: { deps: ['jasminejs'] },
        jasmine_boot: { deps: ['jasminejs', 'jasmine_html'] },
        jasmine_jquery: { deps: ['jquery', 'jasmine'] }
      }
    });

    require(['jquery', 'underscore', 'jasmine', 'matchers'],
    function($, _, jasmine, matchers) {

      var specs = _.map([
        'config', 'utils'
      ], function(module) {
        return _.sprintf('%s/spec/%s_spec', TEST, module);
      });

      $(function() {
        require(specs, function() {
          matchers.addMatchers();
          jasmine.onJasmineLoad();
        });
      });
    });
  });

})();

Use version alias for jasmine-jquery


require('core.string');
import('helma.system', 'system');

system.addHostObject(org.helma.web.Request);
system.addHostObject(org.helma.web.Session);

var log = require('helma.logging').getLogger(__name__);

(function() {

    function cachedGetter(name, fn) {
        return function() {
            // make sure to never set anything on request prototype
            if (this != Request.prototype) {
                var cache = this.__property_cache__;
                if (!cache) {
                    cache = this.__property_cache__ = {};
                }
                if (!cache[name]) {
                    cache[name] = fn.apply(this);
                }
                return cache[name];
            }
        };
    }

    function ParameterGroup(path, map) {

        for (var i in map) {
            if (i.startsWith(path)) {
                var dot = i.indexOf('.', path.length);
                var key, value;
                if (dot > -1) {
                    key = i.slice(path.length, dot);
                    value = new ParameterGroup(i.slice(0, dot + 1), map);
                } else {
                    key = i.slice(path.length);
                    value = map[i];
                }
                if (!this.hasOwnProperty(key)) {
                    this[key] = value[0];
                }
            }
        }

        return this;
    }

    /**
     * Return true if this is a HTTP POST request.
     */
    this.isPost = function() {
        return this.method == "POST";
    }

    /**
     * Return true if this is a HTTP GET request.
     */
    this.isGet = function() {
        return this.method == "GET";
    }

    /* this.__defineGetter__("cookies", cachedGetter('cookies',
        function() {
            var cookies = {};
            for each (var cookie in this.getCookies()) {
                cookies[cookie.name] = cookie.value;
            }
            return cookies;
        })
    ); */

    /* this.__defineGetter__("params", cachedGetter('params', function() {
            return new ParameterGroup("", this.getParameterMap());
        })
    ); */

    this.__defineGetter__("data", cachedGetter('data', function() {
            return new ParameterGroup("", this.getParameterMap());
        })
    );

}).apply(Request.prototype);

require('core.string');
import('helma.system', 'system');

system.addHostObject(org.helma.web.Request);
system.addHostObject(org.helma.web.Session);

var log = require('helma.logging').getLogger(__name__);

(function() {

    /**
     * Return true if this is a HTTP POST request.
     */
    this.isPost = function() {
        return this.method == "POST";
    }

    /**
     * Return true if this is a HTTP GET request.
     */
    this.isGet = function() {
        return this.method == "GET";
    }

}).apply(Request.prototype);

Remove complex getter code - this stuff is now implemented in Java (ParameterMap)


'use strict';

/**
 * @ngdoc directive
 * @name fiAngulartjeApp.directive:FiProduct
 * @description
 * # FiProduct
 */
angular.module('fiAngulartjeApp')
  .directive('fiproduct', function () {
    return {
      templateUrl: 'views/directives/fiproduct.html',
      restrict: 'E',
      scope: {
      	product: '@product'
      },
      controller: function($scope) {
      	var ingredientsarray = $scope.product.composition.concat($scope.product.sauces);
      	$scope.ingredients = ingredientsarray.join(', ');
      	$scope.price = '€ ' + $scope.product.price;
      }
    };
  });

'use strict';

/**
 * @ngdoc directive
 * @name fiAngulartjeApp.directive:FiProduct
 * @description
 * # FiProduct
 */
angular.module('fiAngulartjeApp')
  .directive('fiproduct', function () {
    return {
      templateUrl: 'views/directives/fiproduct.html',
      restrict: 'E',
      scope: {
      	product: '=product'
      },
      controller: function($scope) {
      	var ingredientsarray = $scope.product.composition.concat($scope.product.sauces);
      	$scope.ingredients = ingredientsarray.join(', ');
      	$scope.price = '€ ' + $scope.product.price;
      }
    };
  });

Revert "@ because of 1-way binding!"


var animationDuration = 1000;
var fadeInDelay = animationDuration;
var openFirstCategoryDelay = 2*fadeInDelay;

jQuery.fn.extend({
  fadeInOpacity: function() {
    $( this ).animate( { opacity: 1 }, animationDuration );
  }
});

$( document ).ready( function() {
  // Fade in the whole shebang after the DOM is ready
  $( '#wrapper ').fadeIn( animationDuration, function() {
    setTimeout( function() {
      $( '.category-item' ).first().click();
    }, openFirstCategoryDelay);
  });

  $( '.category-item' ).click( function( e ) {
    $( this ).toggleClass( 'open closed' );
    $( this ).find( '.category-text' ).stop().slideToggle();
    $( this ).find( '.read-more-icon' ).toggleClass( 'fa-chevron-circle-down fa-times-circle' );
  });

  // Set copyright year to current year
  $( '#current-year' ).text( (new Date).getFullYear() );
})

// $(window).on( 'load' , function() {
//   $( '.contact-item' ).each( function( i ) {
//     var item = $( this );
//     setTimeout( function() {
//       console.log(i)
//       item.fadeInOpacity();
//     }, i * fadeInDelay);
//   });
// });

var animationDuration = 800;
var fadeInDelay = animationDuration;

jQuery.fn.extend({
  fadeInOpacity: function() {
    $( this ).animate( { opacity: 1 }, animationDuration );
  }
});

$( document ).ready( function() {
  // Fade in the whole shebang after the DOM is ready
  $( '#wrapper ').fadeIn( animationDuration, function() {

    var numberOfContactItems = 0;
    $( '.contact-item' ).each( function( i ) {
      var item = $( this );
      setTimeout( function() {
        console.log(i)
        item.fadeInOpacity();
      }, i * fadeInDelay);
      numberOfContactItems += 1;
    });

    setTimeout( function() {
      $( '.category-item' ).first().click();
    }, numberOfContactItems * fadeInDelay);
  });

  $( '.category-item' ).click( function( e ) {
    $( this ).toggleClass( 'open closed' );
    $( this ).find( '.category-text' ).stop().slideToggle();
    $( this ).find( '.read-more-icon' ).toggleClass( 'fa-chevron-circle-down fa-times-circle' );
  });

  // Set copyright year to current year
  $( '#current-year' ).text( (new Date).getFullYear() );
})

Make more generic to allow easily adding more contact-items in the future


'use strict';

module.exports = {

  'serverport': 3000,

  'styles': {
    'src' : 'app/styles/**/*.scss',
    'dest': 'build/css'
  },

  'scripts': {
    'src' : 'app/js/**/*.js',
    'dest': 'build/js'
  },

  'images': {
    'src' : 'app/images/**/*',
    'dest': 'build/images'
  },

  'views': {
    'watch': [
      'app/index.html',
      'app/views/**/*.html'
    ],
    'src': 'app/views/**/*.html',
    'dest': 'app/js'
  },

  'dist': {
    'root'  : 'build'
  },

  'browserify': {
    'entries'   : ['./app/js/main.js'],
    'bundleName': 'main.js',
    'sourcemap' : true
  },

  'test': {
    'karma': 'test/karma.conf.js',
    'protractor': 'test/protractor.conf.js'
  }

};

'use strict';

module.exports = {

  'serverport': 4000,

  'styles': {
    'src' : 'app/styles/**/*.scss',
    'dest': 'build/css'
  },

  'scripts': {
    'src' : 'app/js/**/*.js',
    'dest': 'build/js'
  },

  'images': {
    'src' : 'app/images/**/*',
    'dest': 'build/images'
  },

  'views': {
    'watch': [
      'app/index.html',
      'app/views/**/*.html'
    ],
    'src': 'app/views/**/*.html',
    'dest': 'app/js'
  },

  'dist': {
    'root'  : 'build'
  },

  'browserify': {
    'entries'   : ['./app/js/main.js'],
    'bundleName': 'main.js',
    'sourcemap' : true
  },

  'test': {
    'karma': 'test/karma.conf.js',
    'protractor': 'test/protractor.conf.js'
  }

};

Change server port to 4000


'use strict';
var path = require('path');

var DEFAULT_EXCLUDE_PATTERN = "^(node_modules|bower_components|\\.imdone|target|build|dist|logs)[\\/\\\\]?|\\.(git|svn|hg|npmignore)|\\~$|\\.(jpg|png|gif|swp|ttf|otf)$";
var CONFIG_DIR = ".imdone";
module.exports = {
  ASYNC_LIMIT: 512,
  CONFIG_DIR: CONFIG_DIR,
  CONFIG_FILE: path.join(CONFIG_DIR,"config.json"),
  IGNORE_FILE: '.imdoneignore',
  DEFAULT_FILE_PATTERN: "^(readme\\.md|home\\.md|readme\\.\w+|home\\.\w+)$",
  DEFAULT_EXCLUDE_PATTERN: DEFAULT_EXCLUDE_PATTERN,
  DEFAULT_CONFIG: {
    exclude: [DEFAULT_EXCLUDE_PATTERN],
    watcher: true,
    keepEmptyPriority: false,
    code: {
      include_lists:[
        "TODO", "DOING", "DONE", "PLANNING", "FIXME", "ARCHIVE", "HACK", "CHANGED", "XXX", "IDEA", "NOTE", "REVIEW"
      ]
    },
    lists: [],
    marked : {
      gfm: true,
      tables: true,
      breaks: false,
      pedantic: false,
      smartLists: true,
      langPrefix: 'language-' }
  },
  ERRORS: {
    NOT_A_FILE: "file must be of type File",
    CALLBACK_REQUIRED: "Last paramter must be a callback function",
    NO_CONTENT: "File has no content"
  }
};

'use strict';
var path = require('path');

var DEFAULT_EXCLUDE_PATTERN = "^(node_modules|bower_components|\\.imdone|target|build|dist|logs|flow-typed)[\\/\\\\]?|\\.(git|svn|hg|npmignore)|\\~$|\\.(jpg|png|gif|swp|ttf|otf)$";
var CONFIG_DIR = ".imdone";
module.exports = {
  ASYNC_LIMIT: 512,
  CONFIG_DIR: CONFIG_DIR,
  CONFIG_FILE: path.join(CONFIG_DIR,"config.json"),
  IGNORE_FILE: '.imdoneignore',
  DEFAULT_FILE_PATTERN: "^(readme\\.md|home\\.md|readme\\.\w+|home\\.\w+)$",
  DEFAULT_EXCLUDE_PATTERN: DEFAULT_EXCLUDE_PATTERN,
  DEFAULT_CONFIG: {
    exclude: [DEFAULT_EXCLUDE_PATTERN],
    watcher: true,
    keepEmptyPriority: false,
    code: {
      include_lists:[
        "TODO", "DOING", "DONE", "PLANNING", "FIXME", "ARCHIVE", "HACK", "CHANGED", "XXX", "IDEA", "NOTE", "REVIEW"
      ]
    },
    lists: [],
    marked : {
      gfm: true,
      tables: true,
      breaks: false,
      pedantic: false,
      smartLists: true,
      langPrefix: 'language-' }
  },
  ERRORS: {
    NOT_A_FILE: "file must be of type File",
    CALLBACK_REQUIRED: "Last paramter must be a callback function",
    NO_CONTENT: "File has no content"
  }
};

Exclude the `flow-typed` directory by default


$(function() {
  // move form to header
  $('form:last')
    .css({ 'color': 'black', 'position': 'absolute', 'right': '400px' })
    .prependTo('body');
});

$(function() {
  // move form to header
  $('span.yclinks').parent().find('form')
    .css({ 'color': 'black', 'position': 'absolute', 'right': '400px' })
    .prependTo('body');
});

Make sure it's the search form and not any other.


import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss';
import uglify from 'rollup-plugin-uglify';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import pkg from './package.json';

const transformStyles = postcss({
  extract: 'dist/aos.css',
  plugins: [
    autoprefixer,
    cssnano,
  ]
});

const input = 'src/js/aos.js';

export default [
	{
		input,
		output: {
      file: pkg.browser,
      name: 'AOS',
      format: 'umd',
    },
		plugins: [
      transformStyles,
      resolve(),
			commonjs(),
			babel({
        exclude: ['node_modules/**']
      }),
      uglify(),
		],
	},
	{
		input,
		external: Object.keys(pkg.dependencies),
		output: [
			{ file: pkg.main, format: 'cjs' },
			{ file: pkg.module, format: 'es' },
		],
		plugins: [
      transformStyles,
			babel({
				exclude: ['node_modules/**']
      }),
		],
	},
];

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss';
import uglify from 'rollup-plugin-uglify';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import pkg from './package.json';

const transformStyles = postcss({
  extract: 'dist/aos.css',
  plugins: [autoprefixer, cssnano]
});

const input = 'src/js/aos.js';

export default [
  {
    input,
    output: {
      file: pkg.browser,
      name: 'AOS',
      format: 'umd'
    },
    sourceMap: process.env.NODE_ENV === 'dev',
    plugins: [
      transformStyles,
      resolve(),
      commonjs(),
      babel({
        exclude: ['node_modules/**']
      }),
      uglify()
    ]
  },
  {
    input,
    external: Object.keys(pkg.dependencies),
    output: [
      { file: pkg.main, format: 'cjs' },
      { file: pkg.module, format: 'es' }
    ],
    plugins: [
      transformStyles,
      babel({
        exclude: ['node_modules/**']
      })
    ]
  }
];

Format rollbar, add sourcemap in DEV env


function toArray (obj) {
  return typeof obj === 'object' ? Object.keys(obj) : []
}

module.exports = function loadPlugins (pkg) {
  if (!pkg || !pkg.packageJson) return []

  let list = toArray(pkg.packageJson.dependencies)
    .concat(toArray(pkg.packageJson.devDependencies))
    .filter(i => i.startsWith('@size-limit/'))
    .reduce((all, i) => all.concat(require(require.resolve(i, {
      paths: [process.cwd()]
    }))), [])

  return {
    list,
    isEmpty: list.length === 0,
    has (type) {
      return this.list.some(i => i.name === `@size-limit/${ type }`)
    }
  }
}

function toArray (obj) {
  return typeof obj === 'object' ? Object.keys(obj) : []
}

class Plugins {
  constructor (list) {
    this.list = list
    this.isEmpty = list.length === 0
  }

  has (type) {
    return this.list.some(i => i.name === `@size-limit/${ type }`)
  }
}

module.exports = function loadPlugins (pkg) {
  if (!pkg || !pkg.packageJson) return []

  let list = toArray(pkg.packageJson.dependencies)
    .concat(toArray(pkg.packageJson.devDependencies))
    .filter(i => i.startsWith('@size-limit/'))
    .reduce((all, i) => all.concat(require(require.resolve(i, {
      paths: [process.cwd()]
    }))), [])

  return new Plugins(list)
}

module.exports.Plugins = Plugins

Add class for plugin list


/**
 * To retrieve the picture of the day for a given date.
 */

'use strict';

const BBPromise = require('bluebird');
const dateUtil = require('../dateUtil');
const sUtil = require('../util');
const imageinfo = require('../imageinfo');
const HTTPError = sUtil.HTTPError;

/**
 * Get imageinfo data for featured image (Picture of the day)
 * @param  {Object} app      App object
 * @param  {Object} req      req Server request
 * @param  {Object} siteinfo Site info object
 * @return {Object}          featured image response
 */
function promise(req, siteinfo) {
    const aggregated = !!req.query.aggregated;

    if (!dateUtil.validate(dateUtil.hyphenDelimitedDateString(req))) {
        if (aggregated) {
            return BBPromise.resolve({ meta: {} });
        }
        dateUtil.throwDateError();
    }

    return imageinfo.requestPictureOfTheDay(req, dateUtil.getRequestedDate(req), siteinfo)
    .catch((err) => {
        if (aggregated && err.status === 504) {
            return BBPromise.resolve({ meta: {} });
        }
        throw err;
    });
}

module.exports = {
    promise,
};

/**
 * To retrieve the picture of the day for a given date.
 */

'use strict';

const BBPromise = require('bluebird');
const dateUtil = require('../dateUtil');
const imageinfo = require('../imageinfo');

/**
 * Get imageinfo data for featured image (Picture of the day)
 * @param  {Object} req      req Server request
 * @param  {Object} siteinfo Site info object
 * @return {Object}          featured image response
 */
function promise(req, siteinfo) {
    const aggregated = !!req.query.aggregated;

    if (!dateUtil.validate(dateUtil.hyphenDelimitedDateString(req))) {
        if (aggregated) {
            return BBPromise.resolve({ meta: {} });
        }
        dateUtil.throwDateError();
    }

    return imageinfo.requestPictureOfTheDay(req, dateUtil.getRequestedDate(req), siteinfo)
    .catch((err) => {
        if (aggregated) {
            return BBPromise.resolve({ meta: {} });
        }
        throw err;
    });
}

module.exports = {
    promise,
};

Stop limiting error handling for aggregated requests


/* global interfaceConfig */

import { parseURIString, safeDecodeURIComponent } from '../base/util';

/**
 * Transforms the history list to a displayable list.
 *
 * @private
 * @param {Array<Object>} recentList - The recent list form the redux store.
 * @returns {Array<Object>}
 */
export function toDisplayableList(recentList) {
    return (
        recentList.reverse()
            .map(item => {
                return {
                    date: item.date,
                    duration: item.duration,
                    time: [ item.date ],
                    title: safeDecodeURIComponent(parseURIString(item.conference).room),
                    url: item.conference
                };
            }));
}

/**
 * Returns <tt>true</tt> if recent list is enabled and <tt>false</tt> otherwise.
 *
 * @returns {boolean} <tt>true</tt> if recent list is enabled and <tt>false</tt>
 * otherwise.
 */
export function isRecentListEnabled() {
    return interfaceConfig.RECENT_LIST_ENABLED;
}

/* global interfaceConfig */

import { parseURIString, safeDecodeURIComponent } from '../base/util';

/**
 * Transforms the history list to a displayable list.
 *
 * @private
 * @param {Array<Object>} recentList - The recent list form the redux store.
 * @returns {Array<Object>}
 */
export function toDisplayableList(recentList) {
    return (
        [ ...recentList ].reverse()
            .map(item => {
                return {
                    date: item.date,
                    duration: item.duration,
                    time: [ item.date ],
                    title: safeDecodeURIComponent(parseURIString(item.conference).room),
                    url: item.conference
                };
            }));
}

/**
 * Returns <tt>true</tt> if recent list is enabled and <tt>false</tt> otherwise.
 *
 * @returns {boolean} <tt>true</tt> if recent list is enabled and <tt>false</tt>
 * otherwise.
 */
export function isRecentListEnabled() {
    return interfaceConfig.RECENT_LIST_ENABLED;
}

Order recents by last used


import _ from 'lodash';
import Type from './type.js';
import Validators from '../validators/validators.js';

const enumDefinitionPattern = {
  name: String,
  options: Object
};

const Enum = {
  create(definition) {
    check(definition, enumDefinitionPattern);

    // Get options.
    const options = definition.options;
    const values = _.values(options);
    // Create a new Enum constructor.
    const Enum = function Enum(identifier) {
      return Enum[identifier];
    };
    // Set options properties in the class.
    _.each(options, (value, name) => {
      if (Object.defineProperty) {
        Object.defineProperty(Enum, name, {
          writable: false,
          enumerable: true,
          value
        });
      }
      else {
        Enum[name] = value;
      }
    });
    // Create type definition for the given enum.
    Type.create({
      name: definition.name,
      class: Enum,
      validate(args) {
        args.param = values;
        Validators.choice(args);
      }
    });
    // Store enum in the enums list.
    this.enums[definition.name] = Enum;

    return Enum;
  },
  enums: {}
};

export default Enum;

import _ from 'lodash';
import Type from './type.js';
import Validators from '../validators/validators.js';
import { check, Match } from 'meteor/check';

const enumDefinitionPattern = {
  name: String,
  identifiers: Match.OneOf(Array, Object)
};

const Enum = {
  create(definition) {
    check(definition, enumDefinitionPattern);

    // Get identifiers and values.
    let identifiers;
    if (Match.test(definition.identifiers, Array)) {
      identifiers = _.zipObject(
        definition.identifiers, _.range(definition.identifiers.length)
      );
    }
    else if (Match.test(definition.identifiers, Object)) {
      identifiers = definition.identifiers;
      let i = 0;
      _.each(identifiers, function(value, key) {
        if (_.isNil(value)) {
          identifiers[key] = i;
          i++;
        }
        else if (_.isNumber(value)) {
          i = value + 1;
        }
      });
    }
    const values = _.values(identifiers);
    const keys = _.keys(identifiers);
    // Create a new Enum constructor.
    const Enum = function Enum(identifier) {
      return Enum[identifier];
    };
    Enum.getIdentifiers = function() {
      return keys;
    };
    Enum.getIdentifier = function(value) {
      const index = _.indexOf(values, value);
      return keys[index];
    };
    // Set identifiers properties in the class.
    _.each(identifiers, (value, name) => {
      if (Object.defineProperty) {
        Object.defineProperty(Enum, name, {
          writable: false,
          enumerable: true,
          value
        });
      }
      else {
        Enum[name] = value;
      }
    });
    // Create type definition for the given enum.
    Type.create({
      name: definition.name,
      class: Enum,
      validate(args) {
        args.param = values;
        Validators.choice(args);
      }
    });
    // Store enum in the enums list.
    this.enums[definition.name] = Enum;

    return Enum;
  },
  enums: {}
};

export default Enum;

Improve ENUMs - getIdentifier(s) methods


const fs = require('fs');

export const makeAsyncCallback = (callbackValue) => {
  let promiseResolve;
  const promise = new Promise((resolve) => {
    promiseResolve = resolve;
  });
  const func = jest.fn(
    callbackValue
      ? () => promiseResolve(callbackValue)
      : (...args) => promiseResolve(args.length === 1 ? args[0] : args),
  );

  return { promise, func };
};

export const loadPDF = (path) => {
  const raw = fs.readFileSync(path);
  const arrayBuffer = raw.buffer;

  return {
    raw,
    arrayBuffer,
    get blob() {
      return new Blob([arrayBuffer], { type: 'application/pdf' });
    },
    get data() {
      return new Uint8Array(raw);
    },
    get dataURI() {
      return `data:application/pdf;base64,${raw.toString('base64')}`;
    },
    get file() {
      return new File([arrayBuffer], { type: 'application/pdf' });
    },
  };
};

export const muteConsole = () => {
  global.consoleBackup = global.console;

  global.console = {
    log: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
  };
};

export const restoreConsole = () => {
  global.console = global.consoleBackup;
};

const fs = require('fs');

export const makeAsyncCallback = (callbackValue) => {
  let promiseResolve;
  const promise = new Promise((resolve) => {
    promiseResolve = resolve;
  });
  const func = jest.fn(
    callbackValue
      ? () => promiseResolve(callbackValue)
      : (...args) => promiseResolve(args.length === 1 ? args[0] : args),
  );

  return { promise, func };
};

export const loadPDF = (path) => {
  const raw = fs.readFileSync(path);
  const arrayBuffer = raw.buffer;

  return {
    raw,
    arrayBuffer,
    get blob() {
      return new Blob([arrayBuffer], { type: 'application/pdf' });
    },
    get data() {
      return new Uint8Array(raw);
    },
    get dataURI() {
      return `data:application/pdf;base64,${raw.toString('base64')}`;
    },
    get file() {
      return new File([arrayBuffer], { type: 'application/pdf' });
    },
  };
};

export const muteConsole = () => {
  jest.spyOn(global.console, 'log').mockImplementation(() => {});
  jest.spyOn(global.console, 'error').mockImplementation(() => {});
  jest.spyOn(global.console, 'warn').mockImplementation(() => {});
};

export const restoreConsole = () => {
  global.console.log.mockClear();
  global.console.error.mockClear();
  global.console.warn.mockClear();
};

Update muteConsole & restoreConsole functions


const http = require("http");

/**
 * Sends an HTTP request to the server and returns a Promise which resolve
 * with the HTTP response containing the body too.
 *
 * @param  {String} host
 * @param  {String} port
 * @param  {String} method
 * @param  {String} path
 * @return {Promise}
 */
module.exports.request = (host, port, method, path) => {
    return new Promise((resolve, reject) => {
        const req = http.request({ host: host, port: port, method: method, path: path }, (res) => {
            res.body = "";

            res.on("data", (chunk) => {
                res.body += chunk;
            });

            res.on("end", () => {
                resolve(res);
            });
        });

        req.on("error", (err) => {
            reject(err);
        });

        req.end();
    });
};

const http = require("http");

/**
 * Sends an HTTP request to the server and returns a Promise which resolve
 * with the HTTP response containing the body too.
 *
 * @param  {String} host
 * @param  {String} port
 * @param  {String} method
 * @param  {String} path
 * @return {Promise}
 */
module.exports.request = (host, port, method, path, headers, body) => {
    return new Promise((resolve, reject) => {
        const req = http.request({ host: host, port: port, method: method, headers: headers, path: path }, (res) => {
            res.body = "";

            res.on("data", (chunk) => {
                res.body += chunk;
            });

            res.on("end", () => {
                resolve(res);
            });
        });

        req.on("error", (err) => {
            reject(err);
        });

        if (body) {
            req.write(body);
        }

        req.end();
    });
};

Allow to set body and headers


require('normalize.css');
require('styles/App.css');

import React from 'react';

let someUnusedStuff = 'hello';
let yeomanImage = require('../images/yeoman.png');
const colors = ['red', 'white', 'green'];
let currentIndex = 0;
function nextColor() {
  let color = colors[currentIndex % colors.length];
  currentIndex = currentIndex + 1;
  return color;
}

const one = 1;
const moreStuff = [{}]
console.info([1,2,3].map(console.info));

class AppComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = { background: nextColor() };
  }

  componentDidMount() {
    let self = this;
    setInterval(function () {
      self.setState({background: nextColor()});
      }, 3000);
  }

  render() {
    return (

Please edit src/components/Main.js to get started!

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;

require('normalize.css');
require('styles/App.css');

import React from 'react';

let someUnusedStuff = 'hello';
let yeomanImage = require('../images/yeoman.png');
const colors = ['red', 'white', 'green'];
let currentIndex = 0;
function nextColor() {
  let color = colors[currentIndex % colors.length];
  currentIndex = currentIndex + 1;
  return color;
}

const one = 1;
const moreStuff = [{}]

class AppComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = { background: nextColor() };
  }

  componentDidMount() {
    let self = this;
    this.interval = setInterval(()  => self.setState({background: nextColor()}), 3000);
  }

  componentDidUnmount() {
    clearInterval(this.interval);
  }

  render() {
    return (

Please edit src/components/Main.js to get started!

    );
  }
}

AppComponent.defaultProps = {
};

export default AppComponent;

Use ecmascript 6, cancel interval


'use strict';

var configuration = require('../config');
var util = require('util');

var databases = {};

/**
 * register database provider
 * @param  {string} name     database provider identifier
 * @param  {constructor} provider database provider constructor function
 */
function register(name, provider) {
  databases[name] = provider;
}
module.exports.register = register;

// Add default providers
register('gss', require('./provider/gss').create);

function* get(name, logger) {
  let cfg = configuration.get();
  let config = cfg.get(util.format('database:%s', name));
  let db = databases[config.provider];
  if (!db) {
    throw new Error(util.format('database provider %s does not exist', config.provider));
  }
  return yield db(config, logger);
}

module.exports.get = get;

'use strict';

var configuration = require('../config');
var util = require('util');

var databases = {};

/**
 * register database provider
 * @param  {string} name     database provider identifier
 * @param  {constructor} provider database provider constructor function
 */
function register(name, provider) {
  databases[name] = provider;
}
module.exports.register = register;

// Add default providers
register('gss', require('./provider/gss').create);

function* get(name, logger) {
  let cfg = configuration.get();
  let config = cfg.get(util.format('database:%s', name));
  if (!config) {
    throw new Error(util.format('could not find database configuration for provider %s', name));
  }
  let db = databases[config.provider];
  if (!db) {
    throw new Error(util.format('database provider %s does not exist', config.provider));
  }
  return yield db(config, logger);
}

module.exports.get = get;

Check if database config does exist


import test from 'ava';
import sha1 from '..';
import { nativeSha1, randomBytes } from './_helpers';

test('Invalid data should throw type error', t => {
  t.throws(() => sha1('hello there'), TypeError);
});

test('Sample text', t => {
  const buffer = Buffer.from('The quick brown fox jumps over the lazy dog');
  t.is(sha1(buffer), nativeSha1(buffer));
});

for (let i = 0; i < 50; i++) {
  const length = Math.pow(i, 2.5) | 0;
  test(`Random data of length: ${length}`, async t => {
    const buffer = await randomBytes(length);
    t.is(sha1(buffer), nativeSha1(buffer));
  });
}

import test from 'ava';
import sha1 from '..';
import { getUint8Array } from '../utils';
import { nativeSha1, randomBytes } from './_helpers';

test('Invalid data should throw type error', t => {
  t.throws(() => sha1('hello there'), TypeError);
});

test('Sample text', t => {
  const buffer = Buffer.from('The quick brown fox jumps over the lazy dog');
  t.is(sha1(buffer), nativeSha1(buffer));
});

test('getUint8Array', t => {
  t.true(getUint8Array(new Uint8Array(1024)) instanceof Uint8Array);
  t.true(getUint8Array(new ArrayBuffer(1024)) instanceof Uint8Array);
  t.true(getUint8Array(Buffer.alloc(1024)) instanceof Uint8Array);
  t.true(getUint8Array(new Uint32Array(256)) instanceof Uint8Array);
  t.true(getUint8Array(new Float64Array(128)) instanceof Uint8Array);

  t.throws(() => getUint8Array());
  t.throws(() => getUint8Array('hello'));
  t.throws(() => getUint8Array(42));
});

for (let i = 0; i < 50; i++) {
  const length = Math.pow(i, 2.5) | 0;
  test(`Random data of length: ${length}`, async t => {
    const buffer = await randomBytes(length);
    t.is(sha1(buffer), nativeSha1(buffer));
  });
}

Add more tests to reach 100% coverage


import Relay from 'react-relay';

export default class extends Relay.Mutation {
  getMutation() {
    return Relay.QL`mutation{ ToggleAchievement }`;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on ToggleAchievementPayload {
        developer {
          id,
          connections,
          achievements,
        }
      }
    `;
  }

  getConfigs() {
    return [
      {
        type: 'RANGE_ADD',
        parentName: 'developer',
        parentID: this.props.developerId,
        connectionName: 'achievements',
        edgeName: 'achievementEdge',
        rangeBehaviors: {
          '': 'append',
          'order(-id)': 'prepend',
        },
      },
    ];
  }

  getVariables() {
    return {
      id: this.props.id,
      selection: this.props.selection,
    };
  }
}

import Relay from 'react-relay';

export default class extends Relay.Mutation {
  getMutation() {
    return Relay.QL`mutation{ ToggleAchievement }`;
  }

  getFatQuery() {
    return Relay.QL`
      fragment on ToggleAchievementPayload {
        developer {
          id,
          achievements,
        },
        connection {
          id,
          imports,
        },
      }
    `;
  }

  getConfigs() {
    return [
      {
        type: 'FIELDS_CHANGE',
        fieldIDs: {
          developer: this.props.developerId,
        },
      },
      {
        type: 'FIELDS_CHANGE',
        fieldIDs: {
          connection: this.props.connectionId,
        },
      },
    ];
  }

  getVariables() {
    return {
      id: this.props.id,
    };
  }
}

Add toggle mutation for adding achievement


'use strict';

import 'source-map-support/register';
import http from 'http';
import express from 'express';
import RED from 'node-red';

// Create an Express app
let app = express();

// Create a server
let server = http.createServer(app);

// Create the settings object - see default settings.js file for other options
let settings = {
  verbose: true,
  disableEditor: false,
  nodesDir: [__dirname + '/nodes'],
  httpAdminRoot: '/red',
  httpNodeRoot: '/api',
  userDir: (process.env.HOME || process.env.USERPROFILE) + '/.node-red',
  functionGlobalContext: { }    // enables global context
};

// Initialise the runtime with a server and settings
RED.init(server, settings);

// Add a simple route for static content served from 'public'
app.use('/', express.static('public'));
if (settings.httpAdminRoot) {
  app.get('/', (_, res) => {
    res.redirect(settings.httpAdminRoot);
  });
}

// Serve the editor UI from /red
app.use(settings.httpAdminRoot, RED.httpAdmin);

// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot, RED.httpNode);

server.listen(8000);

// Start the runtime
RED.start();

'use strict';

import 'source-map-support/register';
import http from 'http';
import express from 'express';
import RED from 'node-red';

// Create an Express app
let app = express();

// Create a server
let server = http.createServer(app);

// Create the settings object - see default settings.js file for other options
let settings = {
  verbose: true,
  disableEditor: false,
  httpAdminRoot: '/red',
  httpNodeRoot: '/api',
  userDir: (process.env.HOME || process.env.USERPROFILE) + '/.node-red',
  functionGlobalContext: { }    // enables global context
};

// Initialise the runtime with a server and settings
RED.init(server, settings);

// Add a simple route for static content served from 'public'
app.use('/', express.static('public'));
if (settings.httpAdminRoot) {
  app.get('/', (_, res) => {
    res.redirect(settings.httpAdminRoot);
  });
}

// Serve the editor UI from /red
app.use(settings.httpAdminRoot, RED.httpAdmin);

// Serve the http nodes UI from /api
app.use(settings.httpNodeRoot, RED.httpNode);

server.listen(8000);

// Start the runtime
RED.start();

Remove nodesDir since plugin nodes loaded from the directories don't handle i18n message resources


import mixins from './mixins/index.js'

import {
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  generateChart
} from './BaseCharts'

const VueCharts = {
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  mixins,
  generateChart
}

export default VueCharts

export {
  VueCharts,
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  mixins,
  generateChart
}

import mixins from './mixins/index.js'

import {
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  generateChart
} from './BaseCharts'

const VueCharts = {
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  mixins,
  generateChart,
  render: () => console.error('[vue-chartjs]: This is not a vue component. It is the whole object containing all vue components. Please import the named export or access the components over the dot notation. For more info visit https://vue-chartjs.org/#/home?id=quick-start')
}

export default VueCharts

export {
  VueCharts,
  Bar,
  HorizontalBar,
  Doughnut,
  Line,
  Pie,
  PolarArea,
  Radar,
  Bubble,
  Scatter,
  mixins,
  generateChart
}

Add fake render method with error message


import webpack from 'webpack'
import FaviconsWebpackPlugin from 'favicons-webpack-plugin'

exports.modifyWebpackConfig = ({ config, stage }, { logo, icons }) => {
  if (stage === 'develop-html' || stage === 'build-html') {
    config.plugin(`Favicon`, FaviconsWebpackPlugin, [
      {
        logo: logo || './src/favicon.png',
        prefix: 'favicons/',
        icons: {
          android: true,
          appleIcon: true,
          appleStartup: true,
          coast: true,
          favicons: true,
          yandex: true,
          windows: true,
          ...icons,
        },
      },
    ])
  }
}

import webpack from 'webpack'
import FaviconsWebpackPlugin from 'favicons-webpack-plugin'

exports.modifyWebpackConfig = ({ config, stage }, { logo, icons, title, background }) => {
  if (stage === 'develop-html' || stage === 'build-html') {
    config.plugin(`Favicon`, FaviconsWebpackPlugin, [
      {
        logo: logo || './src/favicon.png',
        prefix: 'favicons/',
        title,
        background: background || '#fff',
        icons: {
          android: true,
          appleIcon: true,
          appleStartup: true,
          coast: true,
          favicons: true,
          yandex: true,
          windows: true,
          ...icons,
        },
      },
    ])
  }
}

Add background and title to the configuration


import { bool, string } from 'prop-types'
import React from 'react'
import Text from './Text'

const OrderedTagType = {
  [false]: 'ul',
  [true]: 'ol'
}

const List = ({ ordered, type, ...props }) => {
  if (!type) {
    if (ordered) {
      type = 'disc'
    } else {
      type = 'decimal'
    }
  }
  return (
    <Text
      as={OrderedTagType[Boolean(ordered)]}
      block
      css={[{ listStyleType: type }]}
      marginVertical={1}
      {...props}
    />
  )
}

List.propTypes = {
  ordered: bool,
  type: string
}

export default List

import { bool, string } from 'prop-types'
import React from 'react'
import Text from './Text'

const OrderedTagType = {
  [false]: 'ul',
  [true]: 'ol'
}

const List = ({ ordered, type, ...props }) => {
  if (!type) {
    if (ordered) {
      type = 'decimal'
    } else {
      type = 'disc'
    }
  }
  return (
    <Text
      as={OrderedTagType[Boolean(ordered)]}
      block
      css={[{ listStyleType: type }]}
      marginVertical={1}
      {...props}
    />
  )
}

List.propTypes = {
  ordered: bool,
  type: string
}

export default List

Fix bug that swapped default list style types...


/**
 * @module ol/format/filter/Within
 */
import {inherits} from '../../util.js';
import Spatial from '../filter/Spatial.js';

/**
 * @classdesc
 * Represents a `<Within>` operator to test whether a geometry-valued property
 * is within a given geometry.
 *
 * @constructor
 * @param {!string} geometryName Geometry name to use.
 * @param {!module:ol/geom/Geometry} geometry Geometry.
 * @param {string=} opt_srsName SRS name. No srsName attribute will be
 *    set on geometries when this is not provided.
 * @extends {module:ol/format/filter/Spatial}
 * @api
 */
class Within {

  constructor(geometryName, geometry, opt_srsName) {
    Spatial.call(this, 'Within', geometryName, geometry, opt_srsName);
  }

}

inherits(Within, Spatial);

export default Within;

/**
 * @module ol/format/filter/Within
 */
import Spatial from '../filter/Spatial.js';

/**
 * @classdesc
 * Represents a `<Within>` operator to test whether a geometry-valued property
 * is within a given geometry.
 */
class Within extends Spatial {

  /**
   * @param {!string} geometryName Geometry name to use.
   * @param {!module:ol/geom/Geometry} geometry Geometry.
   * @param {string=} opt_srsName SRS name. No srsName attribute will be
   *    set on geometries when this is not provided.
   * @api
   */
  constructor(geometryName, geometry, opt_srsName) {
    super('Within', geometryName, geometry, opt_srsName);
  }

}

export default Within;

Clean up remaining ol/format/filter class


/* NewsSelector.js
 * Use reselect.js library for our seearch functionlity which has two types of selectors: input and memoized
 * Dependencies: ActionTypes
 * Modules: NewsActions, and NewsFeed
 * Author: Tiffany Tse
 * Created: July 29, 2017
 */
//import dependencies
import {createSelector } from 'reselect';
//import modules
import { reshapeNewsData } from '../util/DataTransformations.js';

//pass state  to newsSelector where wstate.news is passed to reshapeNewsData as input
const newsSelector = state => state.news;

/*Return the news portion of our Redux state tree. From there, we create two memoized
selectors–reshapeNewsSelector and allNewsSelector. Memoized selectors take two
arguments. The first is an array of inputs or memoized selectors.*/
const reshapeNewsSelector = createSelector(
    [newsSelector],
    reshapeNewsData
);

//allNewsSelector simply returns the entire transformed list without modification (newsItems => newsItems).
export const allNewsSelector = createSelector(
    [reshapeNewsSelector],
    newsItems => newsItems
);

/* NewsSelector.js
 * Use reselect.js library for our seearch functionlity which has two types of selectors: input and memoized
 * Dependencies: reselect
 * Modules: reshapeNewsData
 * Author: Tiffany Tse
 * Created: August 12, 2017
 */
//import dependencies
import {createSelector } from 'reselect';
//import modules
import { reshapeNewsData } from '../util/DataTransformations.js';

//pass state  to newsSelector where wstate.news is passed to reshapeNewsData as input
const newsSelector = state => state.news;

/*Return the news portion of our Redux state tree. From there, we create two memoized
selectors–reshapeNewsSelector and allNewsSelector. Memoized selectors take two
arguments. The first is an array of inputs or memoized selectors.*/
const reshapeNewsSelector = createSelector(
    [newsSelector],
    reshapeNewsData
);

//allNewsSelector simply returns the entire transformed list without modification (newsItems => newsItems).
export const allNewsSelector = createSelector(
    [reshapeNewsSelector],
    newsItems => newsItems
);

Update header comment with correct date creation, dependencies, and modules needed


import match from './requirejs-finder/match';

const fakeInstance = {
  get _super() {
    return () => {};
  },
  set _super(fn) {},
  execute() {},
};

export default function matchAction(method, url, regex, params) {
  if (url) {
    return match(m =>
      m.prototype &&
        m.prototype.type === method &&
        m.prototype.route === url
    );
  } else if (regex) {
    return match(m => {
      if (!m.prototype || m.prototype.type !== method) {
        return false;
      }

      try {
        m.prototype.init.apply(fakeInstance, params || []);
      } catch (e) {
        return false;
      }

      return fakeInstance.route && (
        typeof regex === 'string'
          ? fakeInstance.route.indexOf(regex) === 0
          : regex.test(fakeInstance.route)
      );
    });
  }
}

import match from './requirejs-finder/match';

export default function matchAction(method, url, regex, params) {
  if (url) {
    return match(m =>
      m.prototype &&
        m.prototype.type === method &&
        m.prototype.route === url
    );
  } else if (regex) {
    return match(m => {
      if (!m.prototype || m.prototype.type !== method) {
        return false;
      }

      const fakeInstance = Object.assign(Object.create(m.prototype), {
        execute() {},
      });
      Object.defineProperty(fakeInstance, '_super', {
        get () {
          return () => {};
        },
        set (fn) {},
      });

      try {
        m.prototype.init.apply(fakeInstance, params || []);
      } catch (e) {
        return false;
      }

      return fakeInstance.route && (
        typeof regex === 'string'
          ? fakeInstance.route.indexOf(regex) === 0
          : regex.test(fakeInstance.route)
      );
    });
  }
}

Fix matching actions with routes on their prototype.


import BoardView from 'app/components/serverboard/board'
import store from 'app/utils/store'
import {
  serverboards_widget_list,
  serverboard_update_widget_catalog,
  board_update_now
  } from 'app/actions/serverboard'

const Board = store.connect({
  state: (state) => ({
    widgets: state.serverboard.widgets,
    widget_catalog: state.serverboard.widget_catalog
  }),
  handlers: (dispatch, prop) => ({
    updateDaterangeNow: () => dispatch( board_update_now() )
  }),
  subscribe: (props) => [
    `serverboard.widget.added[${props.serverboard}]`,
    `serverboard.widget.removed[${props.serverboard}]`,
    `serverboard.widget.updated[${props.serverboard}]`
  ],
  // Update catalog on entry
  store_enter: (props) => [
    () => serverboards_widget_list(props.serverboard.current),
    () => serverboard_update_widget_catalog(props.serverboard.current)
  ],
  store_exit: (props) => [
    () => serverboards_widget_list(null),
    () => serverboard_update_widget_catalog(null),
  ],
  watch: ['serverboard'], // Watch this prop
  loading(state){
    if (!state.serverboard.widget_catalog)
      return "Widget catalog"
    return false
  }
}, BoardView)

export default Board

import BoardView from 'app/components/serverboard/board'
import store from 'app/utils/store'
import {
  serverboards_widget_list,
  serverboard_update_widget_catalog,
  board_update_now
  } from 'app/actions/serverboard'

const Board = store.connect({
  state: (state) => ({
    widgets: state.serverboard.widgets,
    widget_catalog: state.serverboard.widget_catalog
  }),
  handlers: (dispatch, prop) => ({
    updateDaterangeNow: () => dispatch( board_update_now() )
  }),
  subscriptions: (state, props) => {
    return [
      `serverboard.widget.added[${props.serverboard}]`,
      `serverboard.widget.removed[${props.serverboard}]`,
      `serverboard.widget.updated[${props.serverboard}]`
    ]
  },
  // Update catalog on entry
  store_enter: (props) => [
    () => serverboards_widget_list(props.serverboard.current),
    () => serverboard_update_widget_catalog(props.serverboard.current)
  ],
  store_exit: (props) => [
    () => serverboards_widget_list(null),
    () => serverboard_update_widget_catalog(null),
  ],
  watch: ['serverboard'], // Watch this prop
  loading(state){
    if (!state.serverboard.widget_catalog)
      return "Widget catalog"
    return false
  }
}, BoardView)

export default Board

Refresh widget when config changes


Blinky = function () {
	this.sprite = null;
	this.speed = 10;

	this.direction; //LEFT, RIGHT, UP, DOWN
};

Blinky.prototype = {
	preload : function() {
		//Carrega o sprite do fantasminha blinky
		game.load.image('blinky', 'assets/images/blinky_36-36.png');
	},

	create : function() {
		//Adiciona o blinky na tela
		this.sprite = game.add.sprite(game.world.width/2, game.world.height/2, 'blinky');
	},

	update : function() {
		this.move();
	},

	//Move o blinky
	move : function() {
		//Move o blinky para testar (mudar quando tiver definido o movimento dele)
		this.sprite.x += this.speed;
	}
};

Blinky = function () {
	this.sprite = null;
	this.speed = 10;

	this.direction; //LEFT, RIGHT, UP, DOWN
};

Blinky.prototype = {
	preload : function() {
		//Carrega o sprite do fantasminha blinky
		game.load.image('blinky', 'assets/images/blinky_36-36.png');
	},

	create : function() {
		//Adiciona o blinky na tela
		this.sprite = game.add.sprite(game.world.width/2, game.world.height/2, 'blinky');
	},

	update : function() {
		this.moveRandomly();
	},

	//Move o blinky
	moveRandomly : function() {
		//Move o blinky para testar (mudar quando tiver definido o movimento dele)
		this.sprite.x += this.speed;
	}
};

Change function name according to diagram


'use strict';

var pym = require('pym.js');

var register = window.contextVars.share.urls.register;

new pym.Parent(
    'share_registration_iframe',
    register,
    {}
);

'use strict';

var pym = require('pym.js');

var register = window.contextVars.share.urls.register;

var pymParent = new pym.Parent(
    'share_registration_iframe',
    register,
    {}
);

function scrollToTop(data) {
    window.scrollTo(0, 0);
}

pymParent.onMessage('scroll_top_now', scrollToTop)

Add js function to call when scroll message is recieved


document.observe("dom:loaded", function()
{
  function get_index()
  {
    var index = parseInt(location.hash.substr(1));
    if(isNaN(index)) index = 0;
    return index;
  }

  function go_next_page()
  {
    var index = get_index();
    index += 1;

    if(index >= pages.length) index = pages.length - 1;
    location.hash = "#" + index;
  }

  window.onhashchange = function()
  {
    var index = get_index();

    $("image").src = "/images/blank.png";
    window.scrollTo(0, 0);
    $("image").src = pages[index];

    if((index + 1) < pages.length)
    {
      preload = new Image();
      preload.src = pages[index + 1];
    }
  }

  window.onkeydown = function(event)
  {
    if(event.keyCode == 32 && scrollDistanceFromBottom() == 0)
    {
      Event.stop(event);
      go_next_page();
    }
  };

  $("image").onclick = go_next_page;
  location.hash = "#0";
});

document.observe("dom:loaded", function()
{
  function get_index()
  {
    var index = parseInt(location.hash.substr(1));
    if(isNaN(index)) index = 0;
    return index;
  }

  function go_next_page()
  {
    var index = get_index();
    index += 1;

    if(index >= pages.length) index = pages.length - 1;
    location.hash = "#" + index;
  }

  window.onhashchange = function()
  {
    var index = get_index();

    $("image").src = "/images/blank.png";
    window.scrollTo(0, 0);
    $("image").src = pages[index];

    if((index + 1) < pages.length)
    {
      preload = new Image();
      preload.src = pages[index + 1];
    }
  }

  window.onkeydown = function(event)
  {
    if(event.keyCode == 32 && scrollDistanceFromBottom() == 0)
    {
      Event.stop(event);
      go_next_page();
    }
    else if(event.keyCode == 8)
    {
      Event.stop(event);
      history.back();
    }
  };

  $("image").onclick = go_next_page;
  location.hash = "#0";
});

Add back button support back in


import React from 'react';
import { connect } from 'react-redux';
import pieceComponent from '../components/piece';
import { holdPiece, enhanceCanDropPosition } from '../actions';
import store from '../stores/index';

const mapStateToProps = (state) => {
  return {
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onPieceClick: (piece) => {
      const state = store.getState();
      const board = state.shogi.board();

      const holdingPiece = state.shogi.holdingPiece;

      if (!holdingPiece) {
        // FIX: holdingPiece equals piece. It's confusing.
        dispatch(holdPiece(piece));
        dispatch(enhanceCanDropPosition(piece));
        return;
      }
    }
  };
};

const CapturedPiece = connect(
  mapStateToProps,
  mapDispatchToProps
)(pieceComponent);

export default CapturedPiece;

import React from 'react';
import { connect } from 'react-redux';
import pieceComponent from '../components/piece';
import { holdPiece, enhanceCanDropPosition } from '../actions';
import store from '../stores/index';

const mapStateToProps = (state) => {
  return {
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    onPieceClick: (piece) => {
      const state = store.getState();
      const board = state.shogi.board;

      const holdingPiece = state.shogi.holdingPiece;

      if (!holdingPiece) {
        // FIX: holdingPiece equals piece. It's confusing.
        dispatch(holdPiece(piece));
        dispatch(enhanceCanDropPosition(piece));
        return;
      }
    }
  };
};

const CapturedPiece = connect(
  mapStateToProps,
  mapDispatchToProps
)(pieceComponent);

export default CapturedPiece;

Fix bug. board is property. not but function.


import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'

// create the saga middleware
export const sagaMiddleware = createSagaMiddleware()

// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

export default store

import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'

// create the saga middleware
export const sagaMiddleware = createSagaMiddleware()

const composeEnhancers =
  process.env.NODE_ENV !== 'production' &&
  typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__()
    : compose

// mount it on the Store
const store = createStore(
  reducer,
  composeEnhancers(
    applyMiddleware(sagaMiddleware)
  )
)

export default store

Add support for Redux Dev Tools


export default function createDevToolsStore(onDispatch) {
  let currentState = {
    committedState: {},
    stagedActions: [],
    computedStates: [],
    skippedActions: {},
    currentStateIndex: 0
  };
  let listeners = [];
  let initiated = false;

  function dispatch(action) {
    if (action.type[0] !== '@') onDispatch(action);
    return action;
  }

  function getState() {
    return currentState;
  }

  function isSet() {
    return initiated;
  }

  function setState(state) {
    currentState = state;
    listeners.forEach(listener => listener());
    initiated = true;
  }

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  return {
    dispatch,
    getState,
    subscribe,
    liftedStore: {
      dispatch,
      getState,
      setState,
      subscribe,
      isSet
    }
  };
}

export default function createDevToolsStore(onDispatch) {
  let currentState = {
    actionsById: {},
    computedStates: [],
    currentStateIndex: 0,
    monitorState: {},
    nextActionId: 0,
    skippedActionIds: [],
    stagedActionIds: []
  };
  let listeners = [];
  let initiated = false;

  function dispatch(action) {
    if (action.type[0] !== '@') onDispatch(action);
    return action;
  }

  function getState() {
    return currentState;
  }

  function isSet() {
    return initiated;
  }

  function setState(state) {
    currentState = state;
    listeners.forEach(listener => listener());
    initiated = true;
  }

  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  return {
    dispatch,
    getState,
    subscribe,
    liftedStore: {
      dispatch,
      getState,
      setState,
      subscribe,
      isSet
    }
  };
}

Change lifted store structure to fit Redux DevTools 3.0