// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// This file must be webpacked because it contains .css imports

// Load jquery and jquery-ui
var $ = require('jquery');
require('jquery-ui');
window.$ = window.jQuery = $;
require('jquery-ui');

// Load styling
require('jquery-ui/themes/smoothness/jquery-ui.min.css');
require('../css/widgets.min.css');

const widgets = require('./index');
console.info('jupyter-js-widgets loaded successfully');

const manager = new widgets.EmbedManager();

// Magic global widget rendering function:
function renderInlineWidgets(element) {
  var element = element || document;
  var tags = element.querySelectorAll('script.jupyter-widgets');
  for (var i=0; i!=tags.length; ++i) {
    var tag = tags[i];
    var widgetStateObject = JSON.parse(tag.innerHTML);
    var widgetContainer = document.createElement('div');
    widgetContainer.className = 'widget-area';
    manager.display_widget_state(widgetStateObject, widgetContainer).then(function() {
        tag.parentElement.insertBefore(widgetContainer, tag);
    });
  }
}

// Module exports
exports.manager = manager;
exports.renderInlineWidgets = renderInlineWidgets;

// Set window globals
window.manager = manager;

window.onload = renderInlineWidgets;

// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.

// This file must be webpacked because it contains .css imports

// Load jquery and jquery-ui
var $ = require('jquery');
require('jquery-ui');
window.$ = window.jQuery = $;
require('jquery-ui');

// Load styling
require('jquery-ui/themes/smoothness/jquery-ui.min.css');
require('../css/widgets.min.css');

const widgets = require('./index');
console.info('jupyter-js-widgets loaded successfully');

const manager = new widgets.EmbedManager();

// Magic global widget rendering function:
function renderInlineWidgets(event) {
  var element = event.target || document;
  var tags = element.querySelectorAll('script.jupyter-widgets');
  for (var i=0; i!=tags.length; ++i) {
    var tag = tags[i];
    var widgetStateObject = JSON.parse(tag.innerHTML);
    var widgetContainer = document.createElement('div');
    widgetContainer.className = 'widget-area';
    manager.display_widget_state(widgetStateObject, widgetContainer).then(function() {
        tag.parentElement.insertBefore(widgetContainer, tag);
    });
  }
}

// Module exports
exports.manager = manager;
exports.renderInlineWidgets = renderInlineWidgets;

// Set window globals
window.manager = manager;

window.addEventListener('load', renderInlineWidgets);

Use addEventListener('load' instead of onload


var angular = require('angular');

angular.module('DashCtrl', ['chart.js']).controller('DashController', ['$scope', $scope => {
    const colorScheme = ['#2a9fd6'];

    let weekDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    for(let i = 0; i < 6 - (new Date()).getDay(); i++) {
        const back = weekDays.pop();
        weekDays.unshift(back);
    }

    $scope.uploadColors = colorScheme;
    $scope.uploadLabels = weekDays;
    $scope.uploadSeries = ['Uploads'];
    $scope.uploadData = [[10, 20, 30, 20, 15, 20, 45]];
    $scope.uploadOptions = {
        title: {
            display: true,
            text: 'Historical Uploads'
        },
    };

    $scope.viewColors = colorScheme;
    $scope.viewLabels = weekDays;
    $scope.viewSeries = ['Views'];
    $scope.viewData = [[5, 11, 4, 3, 7, 9, 21]];
    $scope.viewOptions = {
        title: {
            display: true,
            text: 'Historical Views'
        }
    };
}]);
{
}

var angular = require('angular');

angular.module('DashCtrl', ['chart.js']).controller('DashController', ['$scope', $scope => {
    const colorScheme = ['#2a9fd6'];

    // Calculate and format descending dates back one week
    const currentDate = new Date();
    const oneDay = 1000 * 60 * 60 * 24;
    let dates = Array.from({length: 7}, (date, i) => new Date(currentDate - oneDay * (6 - i)));
    let labels = dates.map(date => date.toISOString().substr(5, 5));

    $scope.uploadColors = colorScheme;
    $scope.uploadLabels = labels;
    $scope.uploadSeries = ['Uploads'];
    $scope.uploadData = [[10, 20, 30, 20, 15, 20, 45]];
    $scope.uploadOptions = {
        title: {
            display: true,
            text: 'Historical Uploads'
        },
    };

    $scope.viewColors = colorScheme;
    $scope.viewLabels = labels;
    $scope.viewSeries = ['Views'];
    $scope.viewData = [[5, 11, 4, 3, 7, 9, 21]];
    $scope.viewOptions = {
        title: {
            display: true,
            text: 'Historical Views'
        }
    };
}]);
{
}

Fix date and label calculation


$(document).ready(function()
{
    $("#invite-form").on('submit', function()
    {
        var inviteeEmail = $("#email").val();
        console.log(inviteeEmail)

        if (!inviteeEmail || !inviteeEmail.length) {
            alert('Please enter an email');
            return false;
        }

        $.ajax({
          type: "POST",
          url: "/",
          headers: {
            'X-CSRFToken': csrfToken,
            },
          data: { email: inviteeEmail},
          success: function(data) {
            handleResponse(data);
          }
        });
        return false;
    });
});

function handleResponse(data) {
    document.data = data
    if (data.status === 'success') {
      console.log('success')
    }
    if (data.status === 'fail') {
      console.log('failed')
    }
}

$(document).ready(function()
{
    $("#invite-form").on('submit', function()
    {
        var inviteeEmail = $("#email").val();
        console.log(inviteeEmail)

        if (!inviteeEmail || !inviteeEmail.length) {
            alert('Please enter an email');
            return false;
        }

        $.ajax({
          type: "POST",
          url: "/",
          headers: {
            'X-CSRFToken': csrfToken,
            },
          data: { email: inviteeEmail},
          success: function(data) {
            handleResponse(data);
          }
        });
        return false;
    });
});

function handleResponse(data) {
    document.data = data
    if (data.status === 'success') {
      console.log('success')
      Materialize.toast('Success!', 4000)
    }
    if (data.status === 'fail') {
      Materialize.toast(data.error, 4000)
    }
}

Use Materialize toasts to show the status


'use strict';

var url = require('url');

var getHost = require('./get-host');

module.exports = function redirectToOrganization(req, res, organization) {
  res.redirect(req.protocol + '://' + organization.nameKey + '.' + getHost(req) + url.parse(req.url).pathname);
};

'use strict';

var url = require('url');

var getHost = require('./get-host');

module.exports = function redirectToOrganization(req, res, organization) {
  var uri = req.protocol
    + '://' + organization.nameKey
    + '.'
    + getHost(req)
    + url.parse(req.url).pathname;

  var queryParams = Object.keys(req.query);

  if (queryParams.length > 0) {
    uri += '?' + queryParams.map(function (param) {
      return encodeURIComponent(param)
        + '='
        + encodeURIComponent(req.query[param]);
    }).join('&');
  }

  res.redirect(uri);
};

Fix behaviour for redirect with query params


'use strict';

function JobQueue(metadataBackend, jobPublisher) {
    this.metadataBackend = metadataBackend;
    this.jobPublisher = jobPublisher;
}

module.exports = JobQueue;

var QUEUE = {
    DB: 5,
    PREFIX: 'batch:queue:'
};
module.exports.QUEUE = QUEUE;

JobQueue.prototype.enqueue = function (user, jobId, callback) {
    var self = this;

    this.metadataBackend.redisCmd(QUEUE.DB, 'LPUSH', [ QUEUE.PREFIX + user, jobId ], function (err) {
        if (err) {
            return callback(err);
        }

        self.jobPublisher.publish(user);
        callback();
    });
};

JobQueue.prototype.size = function (user, callback) {
    this.metadataBackend.redisCmd(QUEUE.DB, 'LLEN', [ QUEUE.PREFIX + user ], callback);
};

JobQueue.prototype.dequeue = function (user, callback) {
    this.metadataBackend.redisCmd(QUEUE.DB, 'RPOP', [ QUEUE.PREFIX + user ], callback);
};

JobQueue.prototype.enqueueFirst = function (user, jobId, callback) {
    this.metadataBackend.redisCmd(QUEUE.DB, 'RPUSH', [ QUEUE.PREFIX + user, jobId ], callback);
};

'use strict';

var debug = require('./util/debug')('queue');

function JobQueue(metadataBackend, jobPublisher) {
    this.metadataBackend = metadataBackend;
    this.jobPublisher = jobPublisher;
}

module.exports = JobQueue;

var QUEUE = {
    DB: 5,
    PREFIX: 'batch:queue:'
};
module.exports.QUEUE = QUEUE;

JobQueue.prototype.enqueue = function (user, jobId, callback) {
    debug('JobQueue.enqueue user=%s, jobId=%s', user, jobId);
    this.metadataBackend.redisCmd(QUEUE.DB, 'LPUSH', [ QUEUE.PREFIX + user, jobId ], function (err) {
        if (err) {
            return callback(err);
        }

        this.jobPublisher.publish(user);
        callback();
    }.bind(this));
};

JobQueue.prototype.size = function (user, callback) {
    this.metadataBackend.redisCmd(QUEUE.DB, 'LLEN', [ QUEUE.PREFIX + user ], callback);
};

JobQueue.prototype.dequeue = function (user, callback) {
    this.metadataBackend.redisCmd(QUEUE.DB, 'RPOP', [ QUEUE.PREFIX + user ], function(err, jobId) {
        debug('JobQueue.dequeued user=%s, jobId=%s', user, jobId);
        return callback(err, jobId);
    });
};

JobQueue.prototype.enqueueFirst = function (user, jobId, callback) {
    debug('JobQueue.enqueueFirst user=%s, jobId=%s', user, jobId);
    this.metadataBackend.redisCmd(QUEUE.DB, 'RPUSH', [ QUEUE.PREFIX + user, jobId ], callback);
};

Add debug information in Jobs Queue


import React from 'react';
import Playground from './index.js';
import express from 'express';
import ReactDOMServer from 'react-dom/server';

const app = express();
const port = 8000;

app.get('/', function(req, resp) {
  const html = ReactDOMServer.renderToString(
    React.createElement(Playground)
  );
  resp.send(html);
});

app.listen(port, function() {
  console.log(`http://localhost:'${port}`); // eslint-disable-line no-console
});

import React from 'react';
import Playground from './index.js';
import express from 'express';
import ReactDOMServer from 'react-dom/server';

const app = express();
const port = 8000;

app.get('/', function(req, resp) {
  const html = ReactDOMServer.renderToString(
    React.createElement(Playground)
  );
  resp.send(html);
});

app.listen(port, function() {
  console.log(`http://localhost:${port}`); // eslint-disable-line no-console
});

Fix typo in playground listen log


var renderPng = require('../../app/render_png');
describe('renderPng', function () {
  describe('getScreenshotPath', function () {

    it('creates the URL for the screenshot service call', function () {
      renderPng.screenshotServiceUrl = 'http://screenshotservice';
      renderPng.screenshotTargetUrl = 'http://spotlight';
      var url = '/test/path.png';
      var screenshotPath = renderPng.getScreenshotPath(url);
      expect(screenshotPath).toEqual('http://screenshotservice?readyExpression=!!document.querySelector(".loaded")&forwardCacheHeaders=true&clipSelector=.visualisation&url=http://spotlight/test/path?raw');
    });
  });
});

var renderPng = require('../../app/render_png');
describe('renderPng', function () {
  describe('getScreenshotPath', function () {

    it('creates the URL for the screenshot service call', function () {
      renderPng.screenshotServiceUrl = 'http://screenshotservice';
      renderPng.screenshotTargetUrl = 'http://spotlight';
      var url = '/test/path.png';
      var screenshotPath = renderPng.getScreenshotPath({
        url: url,
        query: {}
      });
      expect(screenshotPath).toEqual('http://screenshotservice?readyExpression=!!document.querySelector(".loaded")&forwardCacheHeaders=true&clipSelector=.visualisation-inner figure&url=http://spotlight/test/path');
    });
  });
});

Fix qs params in screenshot url tests


import React, {PropTypes} from 'react'
import resolveRefType from './resolveRefType'
import {resolver as previewResolver} from 'part:@sanity/base/preview'
export default class SanityPreview extends React.PureComponent {

  static propTypes = {
    value: PropTypes.object,
    type: PropTypes.object.isRequired
  };

  state = {
    loading: true,
    materialized: null,
    refType: null
  }

  resolveRefType(value, type) {
    resolveRefType(value, type).then(refType => {
      this.setState({
        refType
      })
    })
  }

  componentDidMount() {
    const {type, value} = this.props
    this.resolveRefType(value, type)
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.type !== nextProps.type || this.props.value !== nextProps.value) {
      this.resolveRefType(nextProps.value, nextProps.type)
    }
  }

  render() {
    const {refType} = this.state

    if (!refType) {
      return null
    }

    const Preview = previewResolver(refType)
    return (
      <Preview {...this.props} type={refType} />
    )
  }
}

import React, {PropTypes} from 'react'
import resolveRefType from './resolveRefType'
import {resolver as previewResolver} from 'part:@sanity/base/preview'
export default class SanityPreview extends React.PureComponent {

  static propTypes = {
    value: PropTypes.object,
    type: PropTypes.object.isRequired
  };

  state = {
    loading: true,
    materialized: null,
    refType: null
  }

  componentDidMount() {
    const {type, value} = this.props
    this.subscribeRefType(value, type)
  }

  componentWillUnmount() {
    this.unsubscribe()
  }

  subscribeRefType(value, type) {
    this.unsubscribe()
    this.subscription = resolveRefType(value, type)
      .subscribe(refType => {
        this.setState({refType})
      })
  }

  unsubscribe() {
    if (this.subscription) {
      this.subscription.unsubscribe()
      this.subscription = null
    }
  }

  componentWillReceiveProps(nextProps) {
    if (this.props.type !== nextProps.type || this.props.value !== nextProps.value) {
      this.subscribeRefType(nextProps.value, nextProps.type)
    }
  }

  render() {
    const {refType} = this.state

    if (!refType) {
      return null
    }

    const Preview = previewResolver(refType)
    return (
      <Preview {...this.props} type={refType} />
    )
  }
}

Fix wrong resolving of referenced type


import _ from 'lodash';
import React from 'react';
import Settings from '@folio/stripes-components/lib/Settings';

import PermissionSets from './permissions/PermissionSets';
import PatronGroupsSettings from './PatronGroupsSettings';
import AddressTypesSettings from './AddressTypesSettings';

const pages = [
  {
    route: 'perms',
    label: 'Permission sets',
    component: PermissionSets,
    perm: 'ui-users.editpermsets',
  },
  {
    route: 'groups',
    label: 'Patron groups',
    component: PatronGroupsSettings,
    // No perm needed yet
  },

  {
    route: 'addresstypes',
    label: 'Address Types',
    component: AddressTypesSettings,
    // No perm needed yet
  },
];

export default props => <Settings {...props} pages={_.sortBy(pages, ['label'])} paneTitle="Users" />;

import _ from 'lodash';
import React from 'react';
import Settings from '@folio/stripes-components/lib/Settings';

import PermissionSets from './permissions/PermissionSets';
import PatronGroupsSettings from './PatronGroupsSettings';
import AddressTypesSettings from './AddressTypesSettings';

const pages = [
  {
    route: 'perms',
    label: 'Permission sets',
    component: PermissionSets,
    perm: 'ui-users.editpermsets',
  },
  {
    route: 'groups',
    label: 'Patron groups',
    component: PatronGroupsSettings,
    // perm: 'settings.usergroups.all',
  },

  {
    route: 'addresstypes',
    label: 'Address Types',
    component: AddressTypesSettings,
    // perm: 'settings.addresstypes.all',
  },
];

export default props => <Settings {...props} pages={_.sortBy(pages, ['label'])} paneTitle="Users" />;

Add commented-out permission guards. Part of UIU-197.


module.exports = function (grunt) {
  var allJSFilesInJSFolder = "js/**/*.js";

  var distFolder = '../wikipedia/assets/';

  grunt.loadNpmTasks( 'grunt-browserify' );
  grunt.loadNpmTasks( 'gruntify-eslint' );
  grunt.loadNpmTasks( 'grunt-contrib-copy' );
  grunt.loadNpmTasks( 'grunt-contrib-less' );

  grunt.initConfig( {

    browserify: {
      distMain: {
        src: ["index-main.js", allJSFilesInJSFolder, "!preview-main.js"],
        dest: distFolder + "index.js"
      },
      distPreview: {
        src: ["preview-main.js", "js/utilities.js"],
        dest: distFolder + "preview.js"
      }
    },

    less: {
      all: {
        options: {
          compress: true,
          yuicompress: true,
          optimization: 2
        },
        files: [
          { src: ["less/**/*.less", "node_modules/wikimedia-page-library/build/wikimedia-page-library-transform.css"], dest: distFolder + "styleoverrides.css"}
        ]
      }
    },

    eslint: {
        src: [allJSFilesInJSFolder]
    },

    copy: {
      main: {
        files: [{
          src: ["*.html",
                "*.css",
                "ios.json",
                "languages.json",
                "mainpages.json",
                "*.png"],
          dest: distFolder
        }]
      }
    }
  } );

  grunt.registerTask('default', ['eslint', 'browserify', 'less', 'copy']);
};

module.exports = function (grunt) {
  var allJSFilesInJSFolder = "js/**/*.js";

  var distFolder = '../wikipedia/assets/';

  grunt.loadNpmTasks( 'grunt-browserify' );
  grunt.loadNpmTasks( 'gruntify-eslint' );
  grunt.loadNpmTasks( 'grunt-contrib-copy' );
  grunt.loadNpmTasks( 'grunt-contrib-less' );

  grunt.initConfig( {

    browserify: {
      distMain: {
        src: ["index-main.js", allJSFilesInJSFolder, "!preview-main.js"],
        dest: distFolder + "index.js"
      },
      distPreview: {
        src: ["preview-main.js", "js/utilities.js"],
        dest: distFolder + "preview.js"
      }
    },

    less: {
      all: {
        options: {
          compress: true,
          yuicompress: true,
          optimization: 2
        },
        files: [
          { src: ["less/**/*.less", "node_modules/wikimedia-page-library/build/wikimedia-page-library-transform.css"], dest: distFolder + "styleoverrides.css"}
        ]
      }
    },

    eslint: {
        src: [allJSFilesInJSFolder],
        options: {
          fix: false
        }
    },

    copy: {
      main: {
        files: [{
          src: ["*.html",
                "*.css",
                "ios.json",
                "languages.json",
                "mainpages.json",
                "*.png"],
          dest: distFolder
        }]
      }
    }
  } );

  grunt.registerTask('default', ['eslint', 'browserify', 'less', 'copy']);
};

Add eslint 'fix' flag to config.


#!/usr/bin/env node

const generate = require('./generator')
const program = require('commander')

program
    .version('1.0.0')
    .option('-w, --words [num]', 'number of words [2]', 2)
    .option('-n, --numbers', 'use numbers')
    .option('-a, --alliterative', 'use alliterative')
    .option('-o --output [output]', 'output type', /^(raw|dashed|spaced)$/i)
    .parse(process.argv)

let project_name = generate({words: program.words, number: program.numbers, alliterative: program.alliterative});

if (program.output == "dashed"){
    console.log(project_name.dashed);
} else if (program.output == "raw") {
    console.log(project_name.raw);
} else if (program.output == "spaced") {
    console.log(project_name.spaced);
} else {
    console.log(project_name);
}

#!/usr/bin/env node

const generate = require('./generator')
const program = require('commander')

program
    .version('1.0.0')
    .option('-w, --words [num]', 'number of words [2]', 2)
    .option('-n, --numbers', 'use numbers')
    .option('-a, --alliterative', 'use alliterative')
    .option('-o, --output [output]', 'output type [raw|dashed|spaced]', /^(raw|dashed|spaced)$/i)
    .parse(process.argv)

let project_name = generate({words: program.words, number: program.numbers, alliterative: program.alliterative});

if (program.output == "dashed"){
    console.log(project_name.dashed);
} else if (program.output == "raw") {
    console.log(project_name.raw);
} else if (program.output == "spaced") {
    console.log(project_name.spaced);
} else {
    console.log(project_name);
}

Update README for CLI options.


function dform(formElement) {
  var eventEmitter = {
    on: function (name, handler) {
      this[name] = this[name] || [];
      this[name].push(handler);
      return this;
    },
    trigger: function (name, event) {
      if (this[name]) {
        this[name].map(function (handler) {
          handler(event);
        });
      }
      return this;
    }
  };
  var submitElement = formElement.find('[type=submit]');
  var inputElements = formElement.find('input, select');
  formElement.submit(function (event) {
    event.preventDefault();
    var formData = {};
    $.each(inputElements, function (_, element) {
      element = $(element);
      var name = element.attr('name');
      if (name) {
        formData[name] = element.val();
      }
    });
    submitElement.attr('disabled', 'disabled');
    eventEmitter.trigger('submit');
    $[formElement.attr('method')](formElement.attr('action'), formData)
      .always(function () {
        submitElement.removeAttr('disabled', 'disabled');
      })
      .done(function (data) {
        eventEmitter.trigger('done', data);
      })
      .fail(function (jqXHR) {
        eventEmitter.trigger('fail', jqXHR);
      });
  });
  submitElement.removeAttr('disabled', 'disabled');
  return eventEmitter;
}

function dform(formElement) {
  var eventEmitter = {
    on: function (name, handler) {
      this[name] = this[name] || [];
      this[name].push(handler);
      return this;
    },
    trigger: function (name, event) {
      if (this[name]) {
        this[name].map(function (handler) {
          handler(event);
        });
      }
      return this;
    }
  };
  var submitElement = formElement.find('[type=submit]');
  var inputElements = formElement.find('input, select');
  formElement.submit(function (event) {
    event.preventDefault();
    submitElement.attr('disabled', 'disabled');
    var formData = {};
    $.each(inputElements, function (_, element) {
      element = $(element);
      var name = element.attr('name');
      if (name) {
        formData[name] = element.val();
      }
    });
    eventEmitter.trigger('submit');
    $[formElement.attr('method')](formElement.attr('action'), formData)
      .always(function () {
        submitElement.removeAttr('disabled', 'disabled');
      })
      .done(function (responseData) {
        eventEmitter.trigger('done', responseData);
      })
      .fail(function (jqXHR) {
        eventEmitter.trigger('fail', jqXHR);
      });
  });
  submitElement.removeAttr('disabled', 'disabled');
  return eventEmitter;
}

Disable button should be the first thing to do


/*
  Copyright 2022 Google LLC.
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
      http://www.apache.org/licenses/LICENSE-2.0
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

function transform(line) {
  var values = line.split(',');

  var obj = new Object();
  obj.name = values[0];
  obj.surname = values[1];
  obj.timestamp = values[2];
  var jsonString = JSON.stringify(obj);

  return jsonString;
}

Add a license header on code files


const timetrack = require('./timetrack');
const calculateDuration = require('./calculateDuration');

module.exports = (pluginContext) => {
    return (project, env = {}) => {
        return new Promise((resolve, reject) => {
            timetrack.loadData();
            resolve(timetrack.getHistory().map(entry => {
                // get the duration of the entry
                let duration = 'Running';
                if ('stop' in entry) {
                    duration = calculateDuration(entry.start, entry.stop);
                }

                // get the project name
                const project = entry.project || 'No project';

                return {
                    icon: 'fa-clock-o',
                    title: `[${duration}] ${project}`,
                    subtitle: `Nice work bro!`
                }
            }));
        });
    }
}

const timetrack = require('./timetrack');
const Sugar = require('sugar-date');
const calculateDuration = require('./calculateDuration');

module.exports = (pluginContext) => {
    return (project, env = {}) => {
        return new Promise((resolve, reject) => {
            timetrack.loadData();
            resolve(timetrack.getHistory().map(entry => {
                try {
                // get the duration of the entry
                let duration = 'Running';
                if ('stop' in entry) {
                    // sugarjs sets it auto on 1 hour, so i'll just subtract 3600000 for now, fix this later
                    duration = Sugar.Date.format(Sugar.Date.create(entry.stop - entry.start - 3600000), '%H:%M:%S');
                }

                // get the project name
                const project = entry.project || 'No project';

                // get the start date
                const startDate = Sugar.Date.format(Sugar.Date.create(entry.start), '%c')

                return {
                    icon: 'fa-clock-o',
                    title: `[${duration}] ${project}`,
                    subtitle: `started on ${startDate}`
                }
                } catch (e) {
                    alert('fount: ' + e);
                }
            }));
        });
    }
}

Use sugarjs for date parsing


'use strict';

import bunyan from 'bunyan';
import http from 'http';
import { createApp } from './app.js';

const logger = bunyan.createLogger({name: "Graphql-Swapi"});

logger.info('Server initialisation...');
createApp()
	.then((app) => {
		logger.info('Starting HTTP server');
		const server = http.createServer(app);
		server.listen(8000, ()=> { logger.info('Server is up and running'); });
	})
	.catch((err) => { logger.error(err); });

'use strict';

import bunyan from 'bunyan';
import http from 'http';
import { createApp } from './app.js';
import eventsToAnyPromise from 'events-to-any-promise';

const logger = bunyan.createLogger({name: "Graphql-Swapi"});

function startServer(port, app) {
	logger.info('Starting HTTP server');
	const server = http.createServer(app);
	server.listen(port);
	return eventsToAnyPromise(server, 'listening').then(() => {
		logger.info('Server is up and running');
		return server;
	});
}

logger.info('Server initialisation...');
createApp()
	.then((app) => startServer(8000, app))
	.catch((err) => { logger.error(err); });

Use my new lib events-to-any-promise


'use strict';

const childProcess = require('child-process-promise');
const express = require('express');
const path = require('path');
const fs = require('fs');

const configPath = path.resolve(process.cwd(), process.argv.slice().pop());
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const app = express();

async function build (target, args) {
    const commands = args.commands;
    const options = {
        cwd: args.path,
    };

    console.log(`Deploying ${ target }...`);

    for (let i = 0; i < commands.length; i++) {
        const date = (new Date()).toLocaleDateString('sv-SE', {
            hour: 'numeric',
            minute: 'numeric',
            second: 'numeric',
        });

        console.log(`${ date } Running ${ commands[i] }...`);
        await childProcess.exec(commands[i], options);
        console.log('Done');
    }
}

app.post('/', (req, res) => {
    const target = req.query.target;

    if (config.repos[target]) {
        if(req.get('HTTP_X_GITHUB_EVENT') && req.get('HTTP_X_GITHUB_EVENT') === 'push'){
            build(target, config.repos[target]);
            res.status(200).send();
        } else {
            res.status(401).send();
        }
    } else {
        res.status(400).send();
    }
});

app.listen(config.port, config.host);

'use strict';

const childProcess = require('child-process-promise');
const express = require('express');
const path = require('path');
const fs = require('fs');

const configPath = path.resolve(process.cwd(), process.argv.slice().pop());
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const app = express();

async function build (target, args) {
    const commands = args.commands;
    const options = {
        cwd: args.path,
    };

    console.log(`Deploying ${ target }...`);

    for (let i = 0; i < commands.length; i++) {
        const date = (new Date()).toLocaleDateString('sv-SE', {
            hour: 'numeric',
            minute: 'numeric',
            second: 'numeric',
        });

        console.log(`${ date } Running ${ commands[i] }...`);
        await childProcess.exec(commands[i], options);
        console.log('Done');
    }
}

app.post('/', (req, res) => {
    const target = req.query.target;

    if (config.repos[target]) {
        if (req.get('x-github-event') && req.get('x-github-event') === 'push') {
            build(target, config.repos[target]);
            res.status(200).send();
        } else {
            res.status(401).send();
        }
    } else {
        res.status(400).send();
    }
});

app.listen(config.port, config.host);

Use correct GitHub header name when checking requests


import {addLocaleData} from 'react-intl';
import {updateIntl as superUpdateIntl} from 'react-intl-redux';
import languages from '../languages.json';
import messages from '../../locale/messages.json';
import {IntlProvider, intlReducer} from 'react-intl-redux';

Object.keys(languages).forEach(locale => {
    // TODO: will need to handle locales not in the default intl - see www/custom-locales
    // eslint-disable-line import/no-unresolved
    import(`react-intl/locale-data/${locale}`).then(data => addLocaleData(data));
});

const intlInitialState = {
    intl: {
        defaultLocale: 'en',
        locale: 'en',
        messages: messages.en
    }
};

const updateIntl = locale => superUpdateIntl({
    locale: locale,
    messages: messages[locale] || messages.en
});

export {
    intlReducer as default,
    IntlProvider,
    intlInitialState,
    updateIntl
};

import {addLocaleData} from 'react-intl';
import {updateIntl as superUpdateIntl} from 'react-intl-redux';
import languages from '../languages.json';
import messages from '../../locale/messages.json'; // eslint-disable-line import/no-unresolved

import {IntlProvider, intlReducer} from 'react-intl-redux';

Object.keys(languages).forEach(locale => {
    // TODO: will need to handle locales not in the default intl - see www/custom-locales
    import(`react-intl/locale-data/${locale}`).then(data => addLocaleData(data));
});

const intlInitialState = {
    intl: {
        defaultLocale: 'en',
        locale: 'en',
        messages: messages.en
    }
};

const updateIntl = locale => superUpdateIntl({
    locale: locale,
    messages: messages[locale] || messages.en
});

export {
    intlReducer as default,
    IntlProvider,
    intlInitialState,
    updateIntl
};

Fix eslint-disable for unresolved messages line


import React, { Component, PropTypes } from 'react';
import Relay from 'react-relay';

import ProductPrice from './ProductPrice';

class ProductItem extends Component {

  static propTypes = {
    product: PropTypes.object
  };

  render() {
    const { product } = this.props;
    return (

                {product.name}

              <ProductPrice price={product.price} availability={product.availability} />

    );
  }
}

export default Relay.createContainer(ProductItem, {
  fragments: {
    product: () => Relay.QL`
      fragment on ProductType {
        id
        name
        price {
          currency
          gross
          net
        }
        availability {
          ${ProductPrice.getFragment('availability')}
        }
        thumbnailUrl
        url
      }
    `
  }
});

import React, { Component, PropTypes } from 'react';
import Relay from 'react-relay';

import ProductPrice from './ProductPrice';

class ProductItem extends Component {

  static propTypes = {
    product: PropTypes.object
  };

  render() {
    const { product } = this.props;
    return (

                {product.name}

              <ProductPrice price={product.price} availability={product.availability} />

    );
  }
}

export default Relay.createContainer(ProductItem, {
  fragments: {
    product: () => Relay.QL`
      fragment on ProductType {
        id
        name
        price {
          currency
          gross
          grossLocalized
          net
        }
        availability {
          ${ProductPrice.getFragment('availability')}
        }
        thumbnailUrl
        url
      }
    `
  }
});

Add missing field to query


const DEFAULT_IN = 'fadeIn'
const DEFAULT_OUT = 'fadeOut'

class Slide extends BaseElement {
  static get observedAttributes () {
    return ['active', 'in', 'out']
  }

  get active () {
    return this.hasAttribute('active')
  }

  set active (value) {
    value ? this.setAttribute('active', '')
          : this.removeAttribute('active')
  }

  get in () {
    return this.getAttribute('in') || DEFAULT_IN
  }

  get out () {
    return this.getAttribute('out') || DEFAULT_OUT
  }

  onConnect() {
    this.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      `

    this.classList.add('animated')
  }

  renderCallback () {
    if (this.active) {
      this.classList.add(this.in)
      this.classList.remove(this.out)
    } else {
      this.classList.add(this.out)
      this.classList.remove(this.in)
    }
  }
}

customElements.define('x-slide', Slide)

const DEFAULT_IN = 'fadeIn'
const DEFAULT_OUT = 'fadeOut'

class Slide extends BaseElement {
  static get observedAttributes () {
    return ['active', 'in', 'out']
  }

  get active () {
    return this.hasAttribute('active')
  }

  set active (value) {
    value ? this.setAttribute('active', '')
          : this.removeAttribute('active')
  }

  get in () {
    return this.getAttribute('in') || DEFAULT_IN
  }

  get out () {
    return this.getAttribute('out') || DEFAULT_OUT
  }

  onConnect() {
    this.classList.add('animated')
    Object.assign(this.style, this.inlineCSS())
  }

  renderCallback () {
    if (this.active) {
      this.classList.add(this.in)
      this.classList.remove(this.out)
    } else {
      this.classList.add(this.out)
      this.classList.remove(this.in)
    }
  }

  inlineCSS() {
    return {
      position: 'fixed',
      top: 0,
      left: 0,
      display: 'flex',
      width: '100%',
      height: '100%',
      background: 'black',
      fontFamily: 'BlinkMacSystemFont, sans-serif',
      borderWidth: '30px',
      borderStyle: 'solid',
      padding: '100px'
    }
  }
}

customElements.define('x-slide', Slide)

Simplify inline styles for inheritance


angular
  .module('app')
  .directive('projectQuickView', function() {
    return {
      restrict: 'EA',
      replace: false,
      templateUrl: 'app/templates/partials/projectQuickView.html',
      link: function(scope, element, attrs) {
        scope.completed = attrs.completed;

        scope.hasDate = function() {
          return scope.item.end_date && scope.item.end_date !== null;
        };

        scope.isContentFromOldSite = function(item) {
          return scope.item.end_date == "2012-10-20T04:00:00.000Z";
        };

        scope.completedStamp = function(item) {
          return scope.completed && !scope.isContentFromOldSite(item);
        };

      }
    };
  });

angular
  .module('app')
  .directive('projectQuickView', function() {
    return {
      restrict: 'EA',
      replace: false,
      templateUrl: 'app/templates/partials/projectQuickView.html',
      link: function(scope, element, attrs) {
        scope.completed = attrs.completed;

        scope.hasDate = function() {
          return scope.item && scope.item.end_date && scope.item.end_date !== null;
        };

        scope.isContentFromOldSite = function(item) {
          return item && item.end_date == "2012-10-20T04:00:00.000Z";
        };

        scope.completedStamp = function(item) {
          return scope.completed && !scope.isContentFromOldSite(item);
        };

      }
    };
  });

Make a check if end_date exists on the project in the hasDate function


'use strict'

const TelegramBot = require('node-telegram-bot-api');

const config = require('../config');
const utils = require('./utils');
const Kinospartak = require('./Kinospartak/Kinospartak');

const bot = new TelegramBot(config.TOKEN);

const kinospartak = new Kinospartak();

/**
 * updateSchedule - update schedule and push updates to Telegram channel
 *
 * @return {Promise}
 */
function updateSchedule() {
  return kinospartak.getChanges()
    .then(changes =>
      changes.length
        ? utils.formatSchedule(changes)
        : undefined)
    .then(messages =>
      messages
        ? utils.sendInOrder(bot, config.CHANNEL, messages)
        : undefined)
    .then(() => kinospartak.commitChanges())
};

/**
 * updateNews - update news and push updates to Telegram channel
 *
 * @return {Promise}
 */
function updateNews() {
  return kinospartak.getLatestNews()
    .then(news =>
      news.length
        ? utils.formatNews(news)
        : undefined)
    .then(messages =>
      messages
        ? utils.sendInOrder(bot, config.CHANNEL, messages)
        : undefined)
    .then(() => kinospartak.setNewsOffset(new Date().toString()))
};

function update() {
  return Promise.all([
    updateSchedule,
    updateNews
  ]).catch((err) => {
    setTimeout(() => {
        update();
    }, 1000 * 60 * 5);
  })
}

update();

'use strict'

const TelegramBot = require('node-telegram-bot-api');

const config = require('../config');
const utils = require('./utils');
const Kinospartak = require('./Kinospartak/Kinospartak');

const bot = new TelegramBot(config.TOKEN);

const kinospartak = new Kinospartak();

/**
 * updateSchedule - update schedule and push updates to Telegram channel
 *
 * @return {Promise}
 */
function updateSchedule() {
  return kinospartak.getChanges()
    .then(changes =>
      changes.length
        ? utils.formatSchedule(changes)
        : undefined)
    .then(messages =>
      messages
        ? utils.sendInOrder(bot, config.CHANNEL, messages)
        : undefined)
    .then(() => kinospartak.commitChanges())
};

/**
 * updateNews - update news and push updates to Telegram channel
 *
 * @return {Promise}
 */
function updateNews() {
  return kinospartak.getLatestNews()
    .then(news =>
      news.length
        ? utils.formatNews(news)
        : undefined)
    .then(messages =>
      messages
        ? utils.sendInOrder(bot, config.CHANNEL, messages)
        : undefined)
    .then(() => kinospartak.setNewsOffset(new Date().toString()))
};

function update() {
  return Promise.all([
    updateSchedule(),
    updateNews()
  ]).catch((err) => {
    setTimeout(() => {
        update();
    }, 1000 * 60 * 5);
  })
}

update();

Fix channel updating (Well, that was dumb)


var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendfile('index.html');
});

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});

http.listen(1089, function(){
  console.log('listening on *:1089');
});

var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendfile('index.html');
});

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});

http.listen(1091, function(){
  console.log('listening on *:1091');
});

Change port of websocket to 1091


var get = Ember.get,
    set = Ember.set,
    meta = Ember.meta;

Ember.attr = function(type) {
  return Ember.computed(function(key, value) {
    var data = get(this, 'data'),
        dataValue = data && get(data, key),
        beingCreated = meta(this).proto === this;

    if (arguments.length === 2) {
      if (beingCreated) {
        if (!data) {
          data = {};
          set(this, 'data', data);
          data[key] = value;
        }
        return value;
      }

      var isEqual;
      if (type && type.isEqual) {
        isEqual = type.isEqual(dataValue, value);
      } else {
        isEqual = dataValue === value;
      }

      if (!isEqual) {
        if (!this._dirtyAttributes) { this._dirtyAttributes = Ember.A(); }
        this._dirtyAttributes.push(key);
      } else {
        if (this._dirtyAttributes) { this._dirtyAttributes.removeObject(key); }
      }
      return value;
    }

    if (typeof dataValue === 'object') {
      dataValue = Object.create(dataValue);
    }
    return dataValue;
  }).property('data').meta({isAttribute: true, type: type});
};

var get = Ember.get,
    set = Ember.set,
    meta = Ember.meta;

Ember.attr = function(type) {
  return Ember.computed(function(key, value) {
    var data = get(this, 'data'),
        dataValue = data && get(data, key),
        beingCreated = meta(this).proto === this;

    if (arguments.length === 2) {
      if (beingCreated) {
        if (!data) {
          data = {};
          set(this, 'data', data);
          data[key] = value;
        }
        return value;
      }

      var isEqual;
      if (type && type.isEqual) {
        isEqual = type.isEqual(dataValue, value);
      } else {
        isEqual = dataValue === value;
      }

      if (!isEqual) {
        if (!this._dirtyAttributes) { this._dirtyAttributes = Ember.A(); }
        this._dirtyAttributes.push(key);
      } else {
        if (this._dirtyAttributes) { this._dirtyAttributes.removeObject(key); }
      }
      return value;
    }

    if (typeof dataValue === 'object') {
      dataValue = Ember.create(dataValue);
    }
    return dataValue;
  }).property('data').meta({isAttribute: true, type: type});
};

Use Ember.create vs Object.create for polyfill fix


import {VM} from 'vm2'
import SensorData from './SensorData'

export default class Calibrator {
	calibrate (data, cb) {
		if ( ! (data instanceof SensorData)) return cb('Invalid SensorData given.')

		var meta = data.getMetadata()

		meta.getAttributes().forEach(function (attr) {
			attr.getProperties().forEach(function (prop) {
				if (prop.name !== 'calibrate') return

				var vm = new VM({
					timeout: 1000,
					sandbox: {
						val: data.getValue(attr.getName())
					},
				})
				var value = vm.run('(' + decodeURI(prop.value) + ')(val)')
				data.setValue(attr.getName(), value)
			})
		})

		cb()
	}
}

import {VM} from 'vm2'
import SensorData from './SensorData'

export default class Calibrator {
	calibrate (data, cb) {
		if ( ! (data instanceof SensorData)) return cb('Invalid SensorData given.')

		var meta = data.getMetadata()

		meta.getAttributes().forEach(function (attr) {
			attr.getProperties().forEach(function (prop) {
				if (prop.name !== 'calibrate') return

				var vm = new VM({
					timeout: 1000,
					sandbox: {
						Math: Math,
						val: data.getValue(attr.getName())
					},
				})
				var value = vm.run('(' + decodeURI(prop.value.toString()) + ')(val)')
				data.setValue(attr.getName(), value)
			})
		})

		cb()
	}
}

Add Math to calibrator VM and allow use of functions (not only function strings).


(function (env) {
    "use strict";
    env.ddg_spice_arxiv = function(api_result){

        if (!api_result) {
            return Spice.failed('arxiv');
        }

        Spice.add({
            id: "arxiv",

            name: "Reference",
            data: api_result,
            meta: {
                sourceName: "arxiv.org",
                sourceUrl: api_result.feed.entry.link[0].href
            },
            normalize: function(item) {
                return {
                    title: item.feed.entry.title.text,
                    url: item.feed.entry.link[0].href,
                    subtitle: item.feed.entry.author.map( function(e) {
                        return e.name.text;
                    } ).join(', '),
                    description: item.feed.entry.summary.text
                };
            },
            templates: {
                group: 'info',
                options: {
                    moreAt: true
                }
            }
        });
    };
}(this));

(function (env) {
    "use strict";
    env.ddg_spice_arxiv = function(api_result){

        if (!api_result) {
            return Spice.failed('arxiv');
        }

        Spice.add({
            id: "arxiv",

            name: "Reference",
            data: api_result,
            meta: {
                sourceName: "arxiv.org",
                sourceUrl: api_result.feed.entry.link[0].href
            },
            normalize: function(item) {
                return {
                    title: sprintf( "%s (%s)",
                        item.feed.entry.title.text,
                        item.feed.entry.published.text.replace( /^(\d{4}).*/, "$1" )
                    ),
                    url: item.feed.entry.link[0].href,
                    subtitle: item.feed.entry.author.map( function(e) {
                        return e.name.text;
                    } ).join(', '),
                    description: item.feed.entry.summary.text
                };
            },
            templates: {
                group: 'info',
                options: {
                    moreAt: true
                }
            }
        });
    };
}(this));

Add published year to title


function cookieValueHandler() {
    Cookies.set(this.name, this.value, 365)
    window.location.reload()
}

function registerSpecialInputs() {
    $$("input").each(function(input) {
        if (input.getAttribute("special") == "cookie") {
            Event.observe(input, "click", cookieValueHandler)
            if (input.type == "radio" && Cookies.get(input.name) == input.value) {
                input.checked = true
            }
        }
    });
}

Event.observe(window, 'load', registerSpecialInputs)

function cookieValueHandler(event) {
    var src = Event.element(event)
    Cookies.set(src.name, src.value, 365)
    window.location.reload()
}

function registerSpecialInputs() {
    $$("input").each(function(input) {
        if (input.getAttribute("special") == "cookie") {
            Event.observe(input, "click", cookieValueHandler)
            if (input.type == "radio" && Cookies.get(input.name) == input.value) {
                input.checked = true
            }
        }
    });
}

Event.observe(window, 'load', registerSpecialInputs)

Fix cookieValueHandler to work with IE


import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
	state: {},
	mutations: {},
	actions: {},
	modules: {}
});

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
	strict: process.env.NODE_ENV !== "production", //see https://vuex.vuejs.org/guide/strict.html
	state: {},
	mutations: {},
	actions: {},
	modules: {}
});

Enable VueX strict mode in dev


// Override download path to files_sharing/public.php
function fileDownloadPath(dir, file) {
	var url = $('#downloadURL').val();
	if (url.indexOf('&path=') != -1) {
		url += '/'+file;
	}
	return url;
}

$(document).ready(function() {

	if (typeof FileActions !== 'undefined') {
		var mimetype = $('#mimetype').val();
		// Show file preview if previewer is available, images are already handled by the template
		if (mimetype.substr(0, mimetype.indexOf('/')) != 'image') {
			// Trigger default action if not download TODO
			var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ);
			if (typeof action === 'undefined') {
				$('#noPreview').show();
			} else {
				action($('#filename').val());
			}
		}
		FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename) {
			var tr = $('tr').filterAttr('data-file', filename)
			if (tr.length > 0) {
				window.location = $(tr).find('a.name').attr('href');
			}
		});
	}

});

// Override download path to files_sharing/public.php
function fileDownloadPath(dir, file) {
	var url = $('#downloadURL').val();
	if (url.indexOf('&path=') != -1) {
		url += '/'+file;
	}
	return url;
}

$(document).ready(function() {

	if (typeof FileActions !== 'undefined') {
		var mimetype = $('#mimetype').val();
		// Show file preview if previewer is available, images are already handled by the template
		if (mimetype.substr(0, mimetype.indexOf('/')) != 'image') {
			// Trigger default action if not download TODO
			var action = FileActions.getDefault(mimetype, 'file', OC.PERMISSION_READ);
			if (typeof action === 'undefined') {
				$('#noPreview').show();
				if (mimetype != 'httpd/unix-directory') {
					// NOTE: Remove when a better file previewer solution exists
					$('#content').remove();
					$('table').remove();
				}
			} else {
				action($('#filename').val());
			}
		}
		FileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename) {
			var tr = $('tr').filterAttr('data-file', filename)
			if (tr.length > 0) {
				window.location = $(tr).find('a.name').attr('href');
			}
		});
	}

});

Remove the content and table to prevent covering the download link


import $ from 'jquery'
import Alert from './alert'
import Button from './button'
import Carousel from './carousel'
import Collapse from './collapse'
import Dropdown from './dropdown'
import Modal from './modal'
import Popover from './popover'
import Scrollspy from './scrollspy'
import Tab from './tab'
import Tooltip from './tooltip'
import Util from './util'

/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.0.0-alpha.6): index.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

(($) => {
  if (typeof $ === 'undefined') {
    throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.')
  }

  const version = $.fn.jquery.split(' ')[0].split('.')
  const minMajor = 1
  const ltMajor = 2
  const minMinor = 9
  const minPatch = 1
  const maxMajor = 4

  if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) {
    throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0')
  }
})($)

export {
  Util,
  Alert,
  Button,
  Carousel,
  Collapse,
  Dropdown,
  Modal,
  Popover,
  Scrollspy,
  Tab,
  Tooltip
}

import $ from 'jquery'
import Alert from './alert'
import Button from './button'
import Carousel from './carousel'
import Collapse from './collapse'
import Dropdown from './dropdown'
import Modal from './modal'
import Popover from './popover'
import Scrollspy from './scrollspy'
import Tab from './tab'
import Tooltip from './tooltip'
import Util from './util'

/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.0.0): index.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

(($) => {
  if (typeof $ === 'undefined') {
    throw new TypeError('Bootstrap\'s JavaScript requires jQuery. jQuery must be included before Bootstrap\'s JavaScript.')
  }

  const version = $.fn.jquery.split(' ')[0].split('.')
  const minMajor = 1
  const ltMajor = 2
  const minMinor = 9
  const minPatch = 1
  const maxMajor = 4

  if (version[0] < ltMajor && version[1] < minMinor || version[0] === minMajor && version[1] === minMinor && version[2] < minPatch || version[0] >= maxMajor) {
    throw new Error('Bootstrap\'s JavaScript requires at least jQuery v1.9.1 but less than v4.0.0')
  }
})($)

export {
  Util,
  Alert,
  Button,
  Carousel,
  Collapse,
  Dropdown,
  Modal,
  Popover,
  Scrollspy,
  Tab,
  Tooltip
}

Fix leftover reference to v4.0.0-alpha.6


var gulp = require('gulp');
var $ = require('gulp-load-plugins')({ lazy: false });

var source = require('vinyl-source-stream2');
var browserify = require('browserify');

var path = require('path');

var pkg = require('./package.json');

var banner = ['/**',
' * <%= pkg.name %> - <%= pkg.description %>',
' * @version v<%= pkg.version %>',
' * @author <%= pkg.author %>',
' * @link <%= pkg.homepage %>',
' * @license <%= pkg.license %>',
' */',
''].join('\n');

gulp.task('test', function() {
    return gulp.src('test/**/*.test.js', { read: false })
      .pipe($.mocha({
          reporter: 'mocha-better-spec-reporter'
      }));
});

gulp.task('script', function () {
    var bundleStream = browserify({
        entries: './lib/should.js',
        builtins: ['util', 'assert']
    })
      .bundle({
          insertGlobals: false,
          detectGlobals: false,
          standalone: 'Should'
      });

    return bundleStream
      .pipe(source('should.js'))
      .pipe($.header(banner, { pkg : pkg } ))
      .pipe(gulp.dest('./'))
      .pipe($.uglify())
      .pipe($.header(banner, { pkg : pkg } ))
      .pipe($.rename('should.min.js'))
      .pipe(gulp.dest('./'));
});

var gulp = require('gulp');
var $ = require('gulp-load-plugins')({ lazy: false });

var source = require('vinyl-source-stream2');
var browserify = require('browserify');

var path = require('path');

var pkg = require('./package.json');

var banner = ['/**',
' * <%= pkg.name %> - <%= pkg.description %>',
' * @version v<%= pkg.version %>',
' * @author <%= pkg.author %>',
' * @link <%= pkg.homepage %>',
' * @license <%= pkg.license %>',
' */',
''].join('\n');

gulp.task('test', function() {
    return gulp.src('test/**/*.test.js', { read: false })
      .pipe($.mocha({
          reporter: 'mocha-better-spec-reporter'
      }));
});

gulp.task('script', function () {
    var bundleStream = browserify({
        entries: './lib/should.js',
        builtins: ['util', 'assert'],
	insertGlobals: false,
        detectGlobals: false,
        standalone: 'Should'
    })
      .bundle();

    return bundleStream
      .pipe(source('should.js'))
      .pipe($.header(banner, { pkg : pkg } ))
      .pipe(gulp.dest('./'))
      .pipe($.uglify())
      .pipe($.header(banner, { pkg : pkg } ))
      .pipe($.rename('should.min.js'))
      .pipe(gulp.dest('./'));
});

Fix bundle arguments for latest browserify


'use strict';

var gulp = require('gulp');
var shell = require('gulp-shell')
var sass = require('gulp-sass');
var electron = require('electron-connect').server.create();

gulp.task('serve', function () {

  // Compile the sass
  gulp.start('sass');

  // Start browser process
  electron.start();

  // Restart browser process
  gulp.watch('main.js', electron.restart);

  // Reload renderer process
  gulp.watch(['index.js', 'index.html', 'index.scss'], function(){
    gulp.start('sass');
    electron.reload()
  });
});

gulp.task('build', shell.task([
  'electron-packager . --overwrite --platform=darwin --icon=assets/icons/mac/icon.icns --arch=x64 --prune=true --out=release_builds'
]))

gulp.task('sass', function () {
  return gulp.src('./*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(gulp.dest('./'));
});

'use strict';

var gulp = require('gulp');
var shell = require('gulp-shell')
var sass = require('gulp-sass');
var electron = require('electron-connect').server.create();
var package_info = require('./package.json')

gulp.task('serve', function () {

  // Compile the sass
  gulp.start('sass');

  // Start browser process
  electron.start();

  // Restart browser process
  gulp.watch('main.js', electron.restart);

  // Reload renderer process
  gulp.watch(['index.js', 'index.html', 'index.scss'], function(){
    gulp.start('sass');
    electron.reload()
  });
});

gulp.task('build-all', shell.task([
  'electron-packager . --overwrite --platform=all --arch=all --prune=true --out=release_builds'
]));

gulp.task('build-mac', shell.task([
  'electron-packager . --overwrite --platform=darwin --icon=assets/icons/mac/icon.icns --arch=x64 --prune=true --out=release_builds'
]));

gulp.task('zip', shell.task([
  `zip -FSr release_builds/opsdroid-desktop-darwin-x64/opsdroid-desktop-${package_info.version}-macos-x64.zip release_builds/opsdroid-desktop-darwin-x64/opsdroid-desktop.app`
]));

gulp.task('sass', function () {
  return gulp.src('./*.scss')
    .pipe(sass().on('error', sass.logError))
    .pipe(gulp.dest('./'));
});

Make builds platform specific and add zipping


var gulp       = require('gulp'),
    coffee     = require('gulp-coffee'),
    concat     = require('gulp-concat'),
    uglify     = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    del        = require('del');

var paths = {
  scripts: ['source/**/*.coffee']
};

gulp.task('clean', function (cb) {
  del(['build'], cb);
});

// Compiles coffee to js
gulp.task('scripts', ['clean'], function () {
  return gulp.src(paths.scripts)
    .pipe(sourcemaps.init())
    .pipe(coffee({bare: true}))
    //.pipe(uglify())
    .pipe(concat('all.min.js'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('build'));
});

gulp.task('watch', function () {
  gulp.watch(paths.scripts, ['scripts']);
});

gulp.task('default', ['watch', 'scripts']);

var gulp       = require('gulp'),
    coffee     = require('gulp-coffee'),
    concat     = require('gulp-concat'),
    uglify     = require('gulp-uglify'),
    sourcemaps = require('gulp-sourcemaps'),
    del        = require('del'),
    karma      = require('karma').server;

var paths = {
  scripts: ['source/**/*.coffee']
};

gulp.task('clean', function (cb) {
  del(['build'], cb);
});

// Compiles coffee to js
gulp.task('scripts', ['clean'], function () {
  return gulp.src(paths.scripts)
    .pipe(sourcemaps.init())
    .pipe(coffee({bare: true}))
    //.pipe(uglify())
    .pipe(concat('all.min.js'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('build'));
});

gulp.task('watch', function () {
  gulp.watch(paths.scripts, ['scripts']);
});

// Run test once and exit
gulp.task('test', function (done) {
  karma.start({
    configFile: __dirname + '/karma.conf.js',
    singleRun: true
  }, done);
});

// Watch for file changes and re-run tests on each change
gulp.task('tdd', function (done) {
  karma.start({
    configFile: __dirname + '/karma.conf.js'
  }, done);
});

gulp.task('default', ['tdd']);

Add tasks test and tdd


$(document).ready(function () {
  $.fn.select24entity = function (action) {
    // Create the parameters array with basic values
    var select24entityParam = {
      ajax: {
        data: function (params) {
          return {
            q: params.term
          };
        },
        processResults: function (data) {
          return {
            results: data
          };
        },
        cache: true,
        tags: false
      }
    };

    // Extend the parameters array with the one in arguments
    $.extend(select24entityParam, action);

    // Activate the select2 field
    this.select2(select24entityParam);

    // If we have tag support activated, add a listener to add "new_" in front of new entries.
    if (select24entityParam.tags === true) {
      this.on('select2:selecting', function (e) {
        if (e.params.args.data.id === e.params.args.data.text) {
          e.params.args.data.id = 'new_' + e.params.args.data.id;
        }
      });
    }

    // Return current field
    return this;
  };
});

$(document).ready(function () {
  $.fn.select24entity = function (action) {
    // Create the parameters array with basic values
    var select24entityParam = {
      ajax: {
        data: function (params) {
          return {
            q: params.term
          };
        },
        processResults: function (data) {
          return {
            results: data
          };
        },
        cache: true
      },
      tags: false
    };

    // Extend the parameters array with the one in arguments
    $.extend(select24entityParam, action);

    // Activate the select2 field
    this.select2(select24entityParam);

    // If we have tag support activated, add a listener to add "new_" in front of new entries.
    if (select24entityParam.tags === true) {
      this.on('select2:selecting', function (e) {
        if (e.params.args.data.id === e.params.args.data.text) {
          e.params.args.data.id = 'new_' + e.params.args.data.id;
        }
      });
    }

    // Return current field
    return this;
  };
});

Fix place of "tags" option in the default options


// This is a simple test that is meant to demonstrate the ability
// interact with the 'document' instance from the global scope.
//
// Thus, the line:  `exports.document = document;`
// or something similar should be done in your main module.

module.load('./DOM', function(DOM) {

  // A wrapper for 'document.createElement()'
  var foo = new (DOM.Element)('h1');

  // Sets the 'innerHTML'
  foo.update('It Works! Go Back');

  // Appends to the  tag
  DOM.insert(foo);

});

Make the link work from a "file://" URI.


/*global describe, beforeEach, it*/
'use strict';

var path = require('path');
var assert = require('yeoman-generator').assert;
var helpers = require('yeoman-generator').test;
var os = require('os');

describe('hubot:app', function () {
  before(function (done) {
    helpers.run(path.join(__dirname, '../app'))
      .inDir(path.join(os.tmpdir(), './temp-test'))
      .withOptions({ 'skip-install': true })
      .withPrompt({
        someOption: true
      })
      .on('end', done);
  });

  it('creates files', function () {
    assert.file([
      'bower.json',
      'package.json',
      '.editorconfig',
      '.jshintrc'
    ]);
  });
});

/*global describe, beforeEach, it*/
'use strict';

var path = require('path');
var assert = require('yeoman-generator').assert;
var helpers = require('yeoman-generator').test;
var os = require('os');

describe('hubot:app', function () {
  before(function (done) {
    helpers.run(path.join(__dirname, '../app'))
      .inDir(path.join(os.tmpdir(), './temp-test'))
      .withOptions({ 'skip-install': true })
      .withPrompt({
        someOption: true
      })
      .on('end', done);
  });

  it('creates files', function () {
    assert.file([
      'bin/hubot',
      'bin/hubot.cmd',
      'Procfile',
      'README.md',
      'external-scripts.json',
      'hubot-scripts.json',
      '.gitignore',
      'package.json',
      'scripts/example.coffee',
      '.editorconfig',
    ]);
  });
});

Fix assertion for not existing files


var expr = require('vega-expression'),
    args = ['datum', 'event', 'signals'];

module.exports = expr.compiler(args, {
  idWhiteList: args,
  fieldVar:    args[0],
  globalVar:   args[2],
  functions:   function(codegen) {
    var fn = expr.functions(codegen);
    fn.item   = function() { return 'event.vg.item'; };
    fn.group  = 'event.vg.getGroup';
    fn.mouseX = 'event.vg.getX';
    fn.mouseY = 'event.vg.getY';
    fn.mouse  = 'event.vg.getXY';
    return fn;
  }
});

var expr = require('vega-expression'),
    args = ['datum', 'event', 'signals'];

module.exports = expr.compiler(args, {
  idWhiteList: args,
  fieldVar:    args[0],
  globalVar:   args[2],
  functions:   function(codegen) {
    var fn = expr.functions(codegen);
    fn.eventItem = function() { return 'event.vg.item'; };
    fn.eventGroup = 'event.vg.getGroup';
    fn.eventX = 'event.vg.getX';
    fn.eventY = 'event.vg.getY';
    return fn;
  }
});

Update to revised event methods.


// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview
 * Registers a language handler for Protocol Buffers as described at
 * http://code.google.com/p/protobuf/.
 *
 * Based on the lexical grammar at
 * http://research.microsoft.com/fsharp/manual/spec2.aspx#_Toc202383715
 *
 * @author mikesamuel@gmail.com
 */

PR['registerLangHandler'](PR['sourceDecorator']({
        keywords: (
            'bool bytes default double enum extend extensions false fixed32 '
            + 'fixed64 float group import int32 int64 max message option '
            + 'optional package repeated required returns rpc service '
            + 'sfixed32 sfixed64 sint32 sint64 string syntax to true uint32 '
            + 'uint64'),
        cStyleComments: true
      }), ['proto']);

// Copyright (C) 2006 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @fileoverview
 * Registers a language handler for Protocol Buffers as described at
 * http://code.google.com/p/protobuf/.
 *
 * Based on the lexical grammar at
 * http://research.microsoft.com/fsharp/manual/spec2.aspx#_Toc202383715
 *
 * @author mikesamuel@gmail.com
 */

PR['registerLangHandler'](PR['sourceDecorator']({
        'keywords': (
            'bytes,default,double,enum,extend,extensions,false,'
            + 'group,import,max,message,option,'
            + 'optional,package,repeated,required,returns,rpc,service,'
            + 'syntax,to,true'),
        'types': /^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,
        'cStyleComments': true
      }), ['proto']);

Fix proto lang handler to work minified and to use the type style for types like uint32.


// Api Routes --
// RESTful API cheat sheet : http://ricostacruz.com/cheatsheets/rest-api.html

var express = require('express'),
    router = express.Router(),
    db = require('../modules/database');

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.json({
    message: 'Hello world!'
  });
});

router.get('/users', function(req, res, next) {
  db.select('first_name', 'last_name', 'username').from('User').where({active: true}).all()
    .then(function (users) {
      res.json({
        users
      });
    });
});

module.exports = router;

// Api Routes --
// RESTful API cheat sheet : http://ricostacruz.com/cheatsheets/rest-api.html

var express = require('express'),
    router = express.Router(),
    db = require('../modules/database');

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.json({
    message: 'Hello world!'
  });
});

router.get('/users', function(req, res, next) {
  db.select('first_name', 'last_name', 'username').from('User').where({active: true}).all()
    .then(function (users) {
      res.json({
        users
      });
    });
});

router.get('/users/:username', function (req, res) {
    db.select().from('User').where({active: true, 'username': req.param('username')}).all()
      .then(function (user) {
        res.json({
          user
        });
      });
});

module.exports = router;

Add get one user in API


(function() {
  chrome.browserAction.onClicked.addListener(function() {
    var newURL = "chrome-extension://" + chrome.runtime.id + "/index.html";
    chrome.tabs.create({ url: newURL });
  });
})();

(function() {
  chrome.browserAction.onClicked.addListener(function() {
    var appUrl = chrome.extension.getURL('index.html');
    chrome.tabs.create({ url: appUrl });
  });
})();

Use builtin to build url


export function safariGCBugWorkaround() {
  if (customElements.polyfillWrapFlushCallback === undefined) {
    console.warn('The custom elements polyfill was reinstalled.');
    window.__CE_installPolyfill();
  }
}

/**
 * @license
 * Copyright (c) 2019 The Polymer Project Authors. All rights reserved.
 * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
 * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
 * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
 * Code distributed by Google as part of the polymer project is also
 * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
 */

// Some distributions of Safari 10 and 11 contain a bug where non-native
// properties added to certain globals very early in the lifetime of the page
// are not considered reachable and might be garbage collected. This happens
// randomly and appears to affect not only the `customElements` global but also
// the wrapper functions it attaches to other globals, like `Node.prototype`,
// effectively removing the polyfill. To work around this, this function checks
// if the polyfill is missing before running the tests. If so, it uses a
// special global function added by the polyfill during tests (which doesn't
// get collected) to reinstall it.
//
// https://bugs.webkit.org/show_bug.cgi?id=172575
export function safariGCBugWorkaround() {
  if (customElements.polyfillWrapFlushCallback === undefined) {
    window.__CE_installPolyfill();
    console.warn('The custom elements polyfill was reinstalled.');
  }
}

Add license and comment describing the Safari GC bug workaround.


// based on the qs module, but handles null objects as expected
// fixes by Tomas Pollak.

function stringify(obj, prefix) {
  if (prefix && (obj === null || typeof obj == 'undefined')) {
    return prefix + '=';
  } else if (obj.constructor == Array) {
    return stringifyArray(obj, prefix);
  } else if (obj !== null && typeof obj == 'object') {
    return stringifyObject(obj, prefix);
  } else if (prefix) { // string inside array or hash
    return prefix + '=' + encodeURIComponent(String(obj));
  } else if (String(obj).indexOf('=') !== -1) { // string with equal sign
    return String(obj);
  } else {
    throw new TypeError('Cannot build a querystring out of: ' + obj);
  }
};

function stringifyArray(arr, prefix) {
  var ret = [];

  for (var i = 0, len = arr.length; i < len; i++) {
    if (prefix)
      ret.push(stringify(arr[i], prefix + '[' + i + ']'));
    else
      ret.push(stringify(arr[i]));
  }

  return ret.join('&');
}

function stringifyObject(obj, prefix) {
  var ret = [];

  Object.keys(obj).forEach(function(key) {
    ret.push(stringify(obj[key], prefix
      ? prefix + '[' + encodeURIComponent(key) + ']'
      : encodeURIComponent(key)));
  })

  return ret.join('&');
}

exports.build = stringify;

// based on the qs module, but handles null objects as expected
// fixes by Tomas Pollak.

var toString = Object.prototype.toString;

function stringify(obj, prefix) {
  if (prefix && (obj === null || typeof obj == 'undefined')) {
    return prefix + '=';
  } else if (toString.call(obj) == '[object Array]') {
    return stringifyArray(obj, prefix);
  } else if (toString.call(obj) == '[object Object]') {
    return stringifyObject(obj, prefix);
  } else if (toString.call(obj) == '[object Date]') {
    return obj.toISOString();
  } else if (prefix) { // string inside array or hash
    return prefix + '=' + encodeURIComponent(String(obj));
  } else if (String(obj).indexOf('=') !== -1) { // string with equal sign
    return String(obj);
  } else {
    throw new TypeError('Cannot build a querystring out of: ' + obj);
  }
};

function stringifyArray(arr, prefix) {
  var ret = [];

  for (var i = 0, len = arr.length; i < len; i++) {
    if (prefix)
      ret.push(stringify(arr[i], prefix + '[' + i + ']'));
    else
      ret.push(stringify(arr[i]));
  }

  return ret.join('&');
}

function stringifyObject(obj, prefix) {
  var ret = [];

  Object.keys(obj).forEach(function(key) {
    ret.push(stringify(obj[key], prefix
      ? prefix + '[' + encodeURIComponent(key) + ']'
      : encodeURIComponent(key)));
  })

  return ret.join('&');
}

exports.build = stringify;

Add support for Dates to stringify, also improve stringify Object and Array


import { remote } from 'electron'

export default async function takeScreenshot (html, deviceWidth) {
  return new Promise(resolve => {
    const win = new remote.BrowserWindow({
      width: deviceWidth,
      show: false,
    })

    win.loadURL(`data:text/html, ${html}`)

    win.webContents.on('did-finish-load', () => {
      // Window is not fully loaded after this event, hence setTimeout()...
      win.webContents.executeJavaScript('document.querySelector(\'body\').getBoundingClientRect().height', (height) => {
        win.setSize(deviceWidth, height)
        setTimeout(() => {
          win.webContents.capturePage((img) => { // eslint-disable-line
            win.close()
            resolve(img.toPng())
          })
        }, 500)
      })
    })
  })
}

import { remote } from 'electron'
import path from 'path'
import os from 'os'

import { fsWriteFile } from 'helpers/fs'

const TMP_DIR = os.tmpdir()

export default async function takeScreenshot (html, deviceWidth) {
  return new Promise(async resolve => {
    const win = new remote.BrowserWindow({
      width: deviceWidth,
      show: false,
    })

    const tmpFileName = path.join(TMP_DIR, 'tpm-mjml-preview.html')
    await fsWriteFile(tmpFileName, html)

    win.loadURL(`file://${tmpFileName}`)

    win.webContents.on('did-finish-load', () => {
      // Window is not fully loaded after this event, hence setTimeout()...
      win.webContents.executeJavaScript('document.querySelector(\'body\').getBoundingClientRect().height', (height) => {
        win.setSize(deviceWidth, height)
        setTimeout(() => {
          win.webContents.capturePage((img) => { // eslint-disable-line
            win.close()
            resolve(img.toPng())
          })
        }, 500)
      })
    })
  })
}

Use file to generate screenshot


var Struct = require('../base/Struct');
var Data = require('../base/Data');
var Text = require('../base/Text');
var List = require('../base/List');
var AnyPointer = require('../base/AnyPointer');

/*
 * Primary use case is testing.  AnyPointers map to `'[AnyPointer]'` not a nice
 * dump for general use, but good for testing.  Circular reference will bring
 * this crashing down too.
 */
var objectify = function (reader) {
    var object = {};
    for (var key in reader) {
        var v = reader[key];
        if (v instanceof Struct) {
            object[key] = objectify(v);
        } else if (v instanceof List) {
            if (v instanceof Data) {
                object[key] = v.raw;
            } else if (v instanceof Text) {
                object[key] = v.string;
            } else {
                /* TODO: map for list types. */
                object[key] = v.map(objectify);
            }
        } else if (v instanceof AnyPointer) {
            object[key] = '[AnyPointer]';
        } else {
            object[key] = v;
        }
    }

    return object;
};

module.exports = objectify;

var Struct = require('../base/Struct');
var Data = require('./list/Data');
var Text = require('./list/Text');
var List = require('../base/List');
var AnyPointer = require('../base/AnyPointer');

/*
 * Primary use case is testing.  AnyPointers map to `'[AnyPointer]'` not a nice
 * dump for general use, but good for testing.  Circular reference will bring
 * this crashing down too.
 */
var objectify = function (reader) {
    var object = {};
    for (var key in reader) {
        if (key[0] !== '$') continue;

        var v = reader[key];

        if (v instanceof Struct) {
            object[key] = objectify(v);
        } else if (v instanceof List) {
            if (v instanceof Data) {
                object[key] = v.raw;
            } else if (v instanceof Text) {
                object[key] = v.string;
            } else {
                /* TODO: map for list types. */
                object[key] = v.map(objectify);
            }
        } else if (v instanceof AnyPointer) {
            object[key] = '[AnyPointer]';
        } else {
            object[key] = v;
        }
    }
    return object;
};

module.exports = objectify;

Determine enumerability by $ prefix


module.exports = function ({
  returnLink,
  errors = [],
}) {
  return {
    buttonText: 'Continue',
    errors,
    children: [
      {
        macroName: 'MultipleChoiceField',
        type: 'radio',
        label: 'What would you like to record?',
        name: 'kind',
        options: [{
          value: 'interaction',
          label: 'A standard interaction',
          hint: 'For example, an email, phone call or meeting',
        }, {
          value: 'service_delivery',
          label: 'A service that you have provided',
          hint: 'For example, account management, a significant assist or an event',
        },
        /*, {
          value: 'policy_feedback',
          label: 'Policy feedback',
          hint: 'For example, when a company wants to give UK government policy feedback',
        } */
        ],
      },
    ],
  }
}

module.exports = function ({
  returnLink,
  errors = [],
}) {
  return {
    buttonText: 'Continue',
    errors,
    children: [{
      macroName: 'MultipleChoiceField',
      type: 'radio',
      label: 'What would you like to record?',
      name: 'kind',
      options: [{
        value: 'interaction',
        label: 'A standard interaction',
        hint: 'For example, an email, phone call or meeting',
      }, {
        value: 'service_delivery',
        label: 'A service that you have provided',
        hint: 'For example, account management, a significant assist or an event',
      },
      {
        value: 'policy_feedback',
        label: 'Capture policy feedback',
        hint: 'For example, and issue or comment on government policy from a company',
      }],
    }],
  }
}

Enable policy feedback in interaction creation selection


$(document).ready(function(){
	$('.swipeGal').each(function(i, obj){

	});
});

flickrParser = function(json){
	var imageList = []
	$(json.items).each(function(i, image){
		imageList.push({
			'title':image.title,
			'artist':image.author,
			'url':image.link,
			'image':image.media.m});
	})
	return imageList;
}

$(document).ready(function(){
	$('.swipeGal').each(function(i, obj){
		var feedid = $(obj).data('feed-id');
		var url = 'http://ycpi.api.flickr.com/services/feeds/groups_pool.gne?id='+feedid+'&jsoncallback=?';

		$.getJSON(url, {format: "json"})
			.done(function(data){
					parsedImages = flickrParser(data);
					//console.log(parsedImages);
				});
	});
});

Make request to flickr for group images and parse results into a standard json format.


define(['backbone', '../models/dataset/connection'],
        function(Backbone, Connection) {
    'use strict';

    var ConnectionPool = Backbone.Collection.extend({

        model: Connection,

        initialize: function(models, options) {
            this.dataset = options['dataset'];
            this.defaultCut = options['defaultCut'];
        },

        getConnection: function(opts) {
            var id = this.getConnectionId(opts),
                conn = this.get(id);

            if (_.isUndefined(conn)) {
                var defaults = {
                    id: id,
                    dataset: this.dataset,
                    cut: this.defaultCut
                };
                conn = new Connection(_.extend(defaults, opts));
                this.add(conn);
            }

            return conn;
        },

        getConnectionId: function(opts) {
            switch(opts['type']) {
                case 'dimensions':
                    return opts['type'] + ':' + opts['dimension'];

                case 'observations':
                    return opts['type'] + ':' + opts['dimension'] + ':' + opts['measure'];

                default:
                    return _.uniqueId('conn_');
            }
        }

    });

    return ConnectionPool;

});

define(['backbone', '../models/dataset/connection'],
        function(Backbone, Connection) {
    'use strict';

    var ConnectionPool = Backbone.Collection.extend({

        model: Connection,

        initialize: function(models, options) {
            this.dataset = options['dataset'];
            this.defaultCut = options['defaultCut'];
        },

        getConnection: function(opts) {
            var id = this.getConnectionId(opts),
                conn = this.get(id);

            if (_.isUndefined(conn)) {
                var defaults = {
                    id: id,
                    dataset: this.dataset,
                    cut: this.defaultCut
                };
                conn = new Connection(_.extend(defaults, opts));
                this.add(conn);
            }

            return conn;
        },

        getConnectionId: function(opts) {
            switch(opts['type']) {
                case 'dimensions':
                    return opts['type'] + ':' + opts['dimension'];

                case 'observations':
                    return opts['type'] + ':' + opts['dimension'] + ':' + opts['measure'] + ':' + opts['aggregation'];

                default:
                    return _.uniqueId('conn_');
            }
        }

    });

    return ConnectionPool;

});

Use aggregation to namespace the connection id


var $ = require('jquery');
var _ = require('lodash');

var BASE_URL = '/api/todos/';

var TodoApi = {
  destroy: function(_id, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      type: 'DELETE',
      dataType: 'json',
      success: function() {
        success();
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },
  getAll: function(success, failure) {
    $.ajax({
      url: BASE_URL,
      dataType: 'json',
      success: function(data) {
        success(data);
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },

  get: function(_id, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      dataType: 'json',
      success: function(data) {
        success(data);
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },

  update: function(_id, props, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      type: 'PUT',
      dataType: 'json',
      data: props,
      success: function() {
        success();
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },
};

module.exports = TodoApi;

var $ = require('jquery');
var _ = require('lodash');

var BASE_URL = '/api/todos/';

var TodoApi = {
  create: function(todo, success, failure) {
    $.ajax({
      url: BASE_URL,
      type: 'POST',
      dataType: 'json',
      data: todo,
      success: function() {
        success();
      },
      error: function() {
        failure();
      }
    });
  },

  destroy: function(_id, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      type: 'DELETE',
      dataType: 'json',
      success: function() {
        success();
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },

  getAll: function(success, failure) {
    $.ajax({
      url: BASE_URL,
      dataType: 'json',
      success: function(data) {
        success(data);
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },

  get: function(_id, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      dataType: 'json',
      success: function(data) {
        success(data);
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },

  update: function(_id, props, success, failure) {
    $.ajax({
      url: BASE_URL + _id,
      type: 'PUT',
      dataType: 'json',
      data: props,
      success: function() {
        success();
      },
      error: function(xhr, status, error) {
        failure(error);
      }
    });
  },
};

module.exports = TodoApi;

Add create method to Todo Api


//
// TODO: Window resizing needs finer-grain control
// TODO: Take into account where user has moved the windows
// TODO: Controls need automatic resizing within their parent windows
//

Remotery = (function()
{
	function Remotery()
	{
		this.WindowManager = new WM.WindowManager();
		this.Server = new WebSocketConnection();

		// Create the console up front as everything reports to it
		this.Console = new Console(this.WindowManager, this.Server);

		// Create required windows
		this.TitleWindow = new TitleWindow(this.WindowManager, this.Server);

		// Kick-off the auto-connect loop
		AutoConnect(this);

		// Hook up resize event handler
		DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
		OnResizeWindow(this);
	}

	function AutoConnect(self)
	{
		// Only attempt to connect if there isn't already a connection or an attempt to connect
		if (!self.Server.Connected())
			self.Server.Connect("ws://127.0.0.1:17815/remotery");

		// Always schedule another check
		window.setTimeout(Bind(AutoConnect, self), 5000);
	}

	function OnResizeWindow(self)
	{
		// Resize windows
		var w = window.innerWidth;
		var h = window.innerHeight;
		self.Console.WindowResized(w, h);
		self.TitleWindow.WindowResized(w, h);
	}

	return Remotery;
})();

//
// TODO: Window resizing needs finer-grain control
// TODO: Take into account where user has moved the windows
// TODO: Controls need automatic resizing within their parent windows
//

Remotery = (function()
{
	function Remotery()
	{
		this.WindowManager = new WM.WindowManager();
		this.Server = new WebSocketConnection();

		// Create the console up front as everything reports to it
		this.Console = new Console(this.WindowManager, this.Server);

		// Create required windows
		this.TitleWindow = new TitleWindow(this.WindowManager, this.Server);

		// Kick-off the auto-connect loop
		AutoConnect(this);

		// Hook up resize event handler
		DOM.Event.AddHandler(window, "resize", Bind(OnResizeWindow, this));
		OnResizeWindow(this);
	}

	function AutoConnect(self)
	{
		// Only attempt to connect if there isn't already a connection or an attempt to connect
		if (!self.Server.Connected())
			self.Server.Connect("ws://127.0.0.1:17815/remotery");

		// Always schedule another check
		window.setTimeout(Bind(AutoConnect, self), 2000);
	}

	function OnResizeWindow(self)
	{
		// Resize windows
		var w = window.innerWidth;
		var h = window.innerHeight;
		self.Console.WindowResized(w, h);
		self.TitleWindow.WindowResized(w, h);
	}

	return Remotery;
})();

Reduce auto-connect loop to 2 seconds.


var domElementValue = require('dom-element-value');
var EventManager = require('dom-event-manager');

var eventManager;

function eventHandler(name, e) {
  var element = e.delegateTarget, eventName = 'on' + name;
  if (!element.domLayerNode || !element.domLayerNode.events || !element.domLayerNode.events[eventName]) {
    return;
  }

  var value;
  if (/^(?:input|select|textarea|button)$/i.test(element.tagName)) {
    value = domElementValue(element);
  }
  return element.domLayerNode.events[eventName](e, value, element.domLayerNode);
}

function getManager() {
  if (eventManager) {
    return eventManager;
  }
  return eventManager = new EventManager(eventHandler);
}

function init() {
  var em = getManager();
  em.bindDefaultEvents();
  return em;
}

module.exports = {
  EventManager: EventManager,
  getManager: getManager,
  init: init
};

var domElementValue = require('dom-element-value');
var EventManager = require('dom-event-manager');

var eventManager;

function eventHandler(name, e) {
  var element = e.delegateTarget;
  if (!element.domLayerNode || !element.domLayerNode.events) {
    return;
  }

  var events = []
  var mouseClickEventName;
  var bail = false;

  if (name.substr(name.length - 5) === 'click') {
    mouseClickEventName = 'onmouse' + name;
    if (element.domLayerNode.events[mouseClickEventName]) {
      events.push(mouseClickEventName);
    }
    // Do not call the `click` handler if it's not a left click.
    if (e.button !== 0) {
      bail = true;
    }
  }

  var eventName;
  if (!bail) {
    eventName = 'on' + name;
    if (element.domLayerNode.events[eventName]) {
      events.push(eventName);
    }
  }

  if (!events.length) {
    return;
  }

  var value;
  if (/^(?:input|select|textarea|button)$/i.test(element.tagName)) {
    value = domElementValue(element);
  }

  for (var i = 0, len = events.length; i < len ; i++) {
    element.domLayerNode.events[events[i]](e, value, element.domLayerNode);
  }
}

function getManager() {
  if (eventManager) {
    return eventManager;
  }
  return eventManager = new EventManager(eventHandler);
}

function init() {
  var em = getManager();
  em.bindDefaultEvents();
  return em;
}

module.exports = {
  EventManager: EventManager,
  getManager: getManager,
  init: init
};

Change the event handler, the `click` handler is only called on left clicks, use the fake `mouseclick` event to catch any kind of click.


module.exports = [
  {name:'C++',        mode:'c_cpp'},
  {name:'CSS',        mode:'css'},
  {name:'HTML',       mode:'html'},
  {name:'JavaScript', mode:'javascript'},
  {name:'Perl',       mode:'perl'},
  {name:'Python',     mode:'python'},
  {name:'Ruby',       mode:'ruby'},
  {name:'Shell',      mode:'sh'},
  {name:'SQL',        mode:'sql'},
  {name:'Text',       mode:'text'}
];

module.exports = [
  {name:'C++',        mode:'c_cpp'},
  {name:'CSS',        mode:'css'},
  {name:'HTML',       mode:'html'},
  {name:'JavaScript', mode:'javascript'},
  {name:'JSON',       mode:'json'},
  {name:'Perl',       mode:'perl'},
  {name:'Python',     mode:'python'},
  {name:'Ruby',       mode:'ruby'},
  {name:'Shell',      mode:'sh'},
  {name:'SQL',        mode:'sql'},
  {name:'Text',       mode:'text'},
  {name:'XML',        mode:'xml'}
];

Add XML and JSON modes


describe('asking if a visible div scrolled', function() {
  require('./fixtures/bootstrap.js');
  beforeEach(h.clean);
  afterEach(h.clean);

  var visible = false;
  var element;
  var watcher;

  beforeEach(function(done) {
    element = h.createTest({
      style: {
        top: '10000px'
      }
    });
    h.insertTest(element);
    watcher = inViewport(element, function() {
      scrolled = true;
      done();
    });
  });

  describe('when the watcher is not active', function() {
    beforeEach(watcher.dispose());
    beforeEach(h.scroller(0, 10000));
    beforeEach(h.scroller(0, 0));

    it('cb not called', function() {
      assert.strictEqual(calls.length, 0);
    });
  });

  describe('when the watcher is active', function() {
    beforeEach(watcher.watch());
    beforeEach(h.scroller(0, 10000));
    beforeEach(h.scroller(0, 0));

    it('cb called', function() {
      assert.strictEqual(calls.length, 1);
    });
  });
});

describe('using the watcher API to dispose and watch again', function() {
  require('./fixtures/bootstrap.js');
  beforeEach(h.clean);
  afterEach(h.clean);

  var visible = false;
  var element;
  var watcher;

  beforeEach(function(done) {
    element = h.createTest({
      style: {
        top: '10000px'
      }
    });
    h.insertTest(element);
    watcher = inViewport(element, function() {
      scrolled = true;
      done();
    });
  });

  describe('when the watcher is not active', function() {
    beforeEach(watcher.dispose());
    beforeEach(h.scroller(0, 10000));
    beforeEach(h.scroller(0, 0));

    it('cb not called', function() {
      assert.strictEqual(calls.length, 0);
    });
  });

  describe('when the watcher is active', function() {
    beforeEach(watcher.watch());
    beforeEach(h.scroller(0, 10000));
    beforeEach(h.scroller(0, 0));

    it('cb called', function() {
      assert.strictEqual(calls.length, 1);
    });
  });
});

Rename the test on watcher API to have something more specific


import manifest from '../../../../package.json';
import filePaths from '../../utils/file-paths';
import Winreg from 'winreg';

import BaseAutoLauncher from './base';

class Win32AutoLauncher extends BaseAutoLauncher {

  static REG_KEY = new Winreg({
    hive: Winreg.HKCU,
    key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
  });

  enable(callback) {
    const updateExePath = filePaths.getSquirrelUpdateExe();
    const cmd = `"${updateExePath}" --processStart ` +
      `"${manifest.productName}.exe" --process-start-args "--os-startup"`;
    log('setting registry key for', manifest.productName, 'value', cmd);
    Win32AutoLauncher.REG_KEY.set(manifest.productName, Winreg.REG_SZ, cmd, callback);
  }

  disable(callback) {
    log('removing registry key for', manifest.productName);
    Win32AutoLauncher.REG_KEY.remove(manifest.productName, callback);
  }

  isEnabled(callback) {
    log('querying registry key for', manifest.productName);
    Win32AutoLauncher.REG_KEY.get(manifest.productName, function(err, item) {
      const enabled = !!item;
      log('registry value for', manifest.productName, 'is', enabled);
      callback(err, enabled);
    });
  }

}

export default Win32AutoLauncher;

import manifest from '../../../../package.json';
import filePaths from '../../utils/file-paths';
import Winreg from 'winreg';

import BaseAutoLauncher from './base';

class Win32AutoLauncher extends BaseAutoLauncher {

  static REG_KEY = new Winreg({
    hive: Winreg.HKCU,
    key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
  });

  enable(callback) {
    const updateExePath = filePaths.getSquirrelUpdateExe();
    const cmd = `"${updateExePath}" --processStart ` +
      `"${manifest.productName}.exe" --process-start-args "--os-startup"`;
    log('setting registry key for', manifest.productName, 'value', cmd);
    Win32AutoLauncher.REG_KEY.set(manifest.productName, Winreg.REG_SZ, cmd, callback);
  }

  disable(callback) {
    log('removing registry key for', manifest.productName);
    Win32AutoLauncher.REG_KEY.remove(manifest.productName, (err) => {
      const notFound = err.message == 'The system was unable to find the specified registry key or value.';
      if (notFound) {
        callback();
      } else {
        callback(err);
      }
    });
  }

  isEnabled(callback) {
    log('querying registry key for', manifest.productName);
    Win32AutoLauncher.REG_KEY.get(manifest.productName, function(err, item) {
      const enabled = !!item;
      log('registry value for', manifest.productName, 'is', enabled);
      callback(err, enabled);
    });
  }

}

export default Win32AutoLauncher;

Handle not found error in win32 auto launcher


var email   = require('emailjs')
  , debug = require('debug')('wifi-chat:mailer')

var config = null
  , server = null

var connect = function() {
  debug('Connecting to mail server', config.email.connection)
  server = email.server.connect(config.email.connection)
}

var setConfig = function(configuration) {
  config = configuration
  connect()
}

var sendMail = function(to, subject, template, substitutions, callback) {
  var content = template.replace(new RegExp('%url%', 'g'), config.url)
  Object.keys(substitutions || {}).forEach(function(key) {
    content = content.replace(new RegExp('%' + key + '%', 'g'), substitutions[key])
  })
  var message = {
    text: content,
    from: config.email.sendAddress,
    to: to,
    subject: subject
  }
  debug('Sending password reset email', message)
  server.send(message, callback)
}

module.exports = {
  setConfig: setConfig,
  sendMail: sendMail
}

var email   = require('emailjs')
  , debug = require('debug')('wifi-chat:mailer')

var config = null
  , server = null

var connect = function() {
  debug('Connecting to mail server', config.email.connection)
  server = email.server.connect(config.email.connection)
}

var setConfig = function(configuration) {
  config = configuration
  connect()
}

var sendMail = function(to, subject, template, substitutions, callback) {
  var content = template.replace(new RegExp('%url%', 'g'), config.url)
  Object.keys(substitutions || {}).forEach(function(key) {
    content = content.replace(new RegExp('%' + key + '%', 'g'), substitutions[key])
  })
  var message = {
    text: content,
    from: config.email.sendAddress,
    to: to,
    subject: subject
  }
  debug('Sending email', message)
  server.send(message, callback)
}

module.exports = {
  setConfig: setConfig,
  sendMail: sendMail
}

Update debug message to be less incorrect


HandlebarsIntl.registerWith(Handlebars);

$(function() {
	window.state = {};
	var source = $("#stats-template").html();
	var template = Handlebars.compile(source);
	refreshStats(template);

	setInterval(function() {
		refreshStats(template);
	}, 5000)
});

function refreshStats(template) {
	$.getJSON("/stats", function(stats) {
		$("#alert").addClass('hide');

		// Sort miners by ID
		if (stats.miners) {
			stats.miners = stats.miners.sort(compare)
		}

		var epochOffset = (30000 - (stats.height % 30000)) * 1000 * 15
		stats.nextEpoch = stats.now + epochOffset

		// Repaint stats
		var html = template(stats);
		$('#stats').html(html);
	}).fail(function() {
		$("#alert").removeClass('hide');
	});
}

function compare(a, b) {
	if (a.name < b.name)
		return -1;
	if (a.name > b.name)
		return 1;
	return 0;
}

HandlebarsIntl.registerWith(Handlebars);

$(function() {
	window.state = {};
	var source = $("#stats-template").html();
	var template = Handlebars.compile(source);
	refreshStats(template);

	setInterval(function() {
		refreshStats(template);
	}, 5000)
});

function refreshStats(template) {
	$.getJSON("/stats", function(stats) {
		$("#alert").addClass('hide');

		// Sort miners by ID
		if (stats.miners) {
			stats.miners = stats.miners.sort(compare)
		}

		var epochOffset = (30000 - (stats.height % 30000)) * 1000 * 14.4
		stats.nextEpoch = stats.now + epochOffset

		// Repaint stats
		var html = template(stats);
		$('#stats').html(html);
	}).fail(function() {
		$("#alert").removeClass('hide');
	});
}

function compare(a, b) {
	if (a.name < b.name)
		return -1;
	if (a.name > b.name)
		return 1;
	return 0;
}

Adjust block time to 14.4 seconds