const { IntentDialog, DialogAction, EntityRecognizer } = require('botbuilder');
const consts = require('../helpers/consts');
const config = require('../../config');
const utils = require('../helpers/utils');
const { witRecognizer } = require('../helpers/witRecognizer');

module.exports = new IntentDialog({ recognizers: [witRecognizer] })
    .matches('set_timezone', DialogAction.beginDialog('/setTimezone'))
    .matches('show_reminders', DialogAction.beginDialog('/showReminders'))
    .matches('show_timezone', DialogAction.beginDialog('/showTimezone'))
    .onDefault((session, args) => {
        const message = session.message.text;
        const { entities } = args;

        // Extract all the useful entities.
        const reminder = EntityRecognizer.findEntity(entities, 'reminder');
        const datetime = EntityRecognizer.findEntity(entities, 'datetime');
        const greeting = EntityRecognizer.findEntity(entities, 'greeting');

        // The user wants to set a new reminder.
        // If datetime is undefined, the bot will prompt the user to choose one.
        if (reminder && message.match(/remind me/i))
            session.beginDialog('/newReminder', { reminder, datetime, message });
        // If the user just sends a greeting, send a greeting back and show an example of how to set a reminder.
        else if (greeting && !reminder && !datetime)
            session.endDialog(consts.Messages.GREETING_RESPONSE, utils.getRandomGreeting(), utils.getRandomReminder());
        // Send a default response
        else session.endDialog(consts.Messages.DEFAULT_RESPONSE, utils.getRandomReminder());
    });

const { IntentDialog, DialogAction, EntityRecognizer } = require('botbuilder');
const consts = require('../helpers/consts');
const config = require('../../config');
const utils = require('../helpers/utils');
const { witRecognizer } = require('../helpers/witRecognizer');

module.exports = new IntentDialog({ recognizers: [witRecognizer] })
    .matches('set_timezone', DialogAction.beginDialog('/setTimezone'))
    .matches('show_reminders', DialogAction.beginDialog('/showReminders'))
    .matches('show_timezone', DialogAction.beginDialog('/showTimezone'))
    .onDefault((session, args) => {
        const message = session.message.text;
        const { entities } = args;

        // Extract all the useful entities.
        const reminder = EntityRecognizer.findEntity(entities, 'reminder');
        const datetime = EntityRecognizer.findEntity(entities, 'datetime');
        const greeting = EntityRecognizer.findEntity(entities, 'greeting');

        // The user wants to set a new reminder.
        // If datetime is undefined, the bot will prompt the user to choose one.
        if (reminder) {
            session.beginDialog('/newReminder', { reminder, datetime, message });
        }
        // If the user just sends a greeting, send a greeting back and show an example of how to set a reminder.
        else if (greeting && !reminder && !datetime) {
            session.endDialog(consts.Messages.GREETING_RESPONSE, utils.getRandomGreeting(), utils.getRandomReminder());
        }
        // Send a default response
        else {
            session.endDialog(consts.Messages.DEFAULT_RESPONSE, utils.getRandomReminder());
        }
    });

Remove regular expression as condition for new reminder


// convert current notebook to html by calling "ipython nbconvert" and open static html file in new tab
"using strict";

nbconvertPrintView = function(){
    var kernel = IPython.notebook.kernel;
    var name = IPython.notebook.notebook_name;

    var path = IPython.notebook.notebook_path;
    if (path.length > 0) { path = path.concat('/'); }

    var command = 'ip=get_ipython(); import os; os.system(\"ipython nbconvert --profile=%s --to html ' + name + '\" % ip.profile)';

    function callback(out_type, out_data)
        {
        var url = '/files/' + path + name.split('.ipynb')[0] + '.html';
        var win=window.open(url, '_blank');
        win.focus();
        }
    kernel.execute(command, { shell: { reply : callback } });
};

IPython.toolbar.add_buttons_group([
    {
        id : 'doPrintView',
        label : 'Create static print view',
        icon : 'fa-print',
        callback : nbconvertPrintView
    }
]);

// convert current notebook to html by calling "ipython nbconvert" and open static html file in new tab

define([
    'base/js/namespace',
    'jquery',
], function(IPython, $) {
    "use strict";
    if (IPython.version[0] != 3) {
        console.log("This extension requires IPython 3.x")
        return
    }

	var nbconvertPrintView = function () {
		var kernel = IPython.notebook.kernel;
		var path = IPython.notebook.notebook_path;
		var command = 'ip=get_ipython(); import os; os.system(\"ipython nbconvert --profile=%s --to html ' + name + '\" % ip.profile)';

		function callback(out_type, out_data) {
			var url = '/files/' + path.split('.ipynb')[0] + '.html';
			var win=window.open(url, '_blank');
			win.focus();
		}
		kernel.execute(command, { shell: { reply : callback } });
	};

	IPython.toolbar.add_buttons_group([
		{
		id : 'doPrintView',
		label : 'Create static print view',
		icon : 'fa-print',
		callback : nbconvertPrintView
		}
	])
})

Update to current master. `IPython.notebook.notebook_path` now returns the complete path including the notebook name.


describe('the Basic example page', function () {
  var basicExamplePage = require('./page.js');

  beforeEach(function () {
    basicExamplePage.get();
  });

  describe('rendering of the tree data', function () {
    it('should render a tree with 3 root nodes', function () {
      basicExamplePage.rootNodes.count().then(function (meetupCount) {
        expect(meetupCount).toEqual(3);
      });
    });

    it('should show 2 subnodes for the first root node', function () {
      basicExamplePage.firstSubNodes.count().then(function (meetupCount) {
        expect(meetupCount).toEqual(2);
      });
    });

    it('should show the correct text for the subnodes', function () {
      var subnodes = basicExamplePage.firstSubNodes;

      subnodes.get(0).all(by.css('.tree-node-content')).first().getText().then(function (text) {
        expect(text).toEqual('node1.1');
      });

      subnodes.get(1).all(by.css('.tree-node-content')).first().getText().then(function (text) {
        expect(text).toEqual('node1.2');
      });
    });
  });
});

describe('the Basic example page', function () {
  var basicExamplePage = require('./page.js');

  beforeEach(function () {
    basicExamplePage.get();
  });

  describe('rendering of the tree data', function () {
    it('should render a tree with 3 root nodes', function () {
      basicExamplePage.rootNodes.count().then(function (meetupCount) {
        expect(meetupCount).toEqual(3);
      });
    });

    it('should show 2 subnodes for the first root node', function () {
      basicExamplePage.firstSubNodes.count().then(function (meetupCount) {
        expect(meetupCount).toEqual(2);
      });
    });

    it('should show the correct text for the subnodes', function () {
      var subnodes = basicExamplePage.firstSubNodes;

      subnodes.get(0).all(by.css('.tree-node-content')).first().getText().then(function (text) {
        expect(text).toEqual('node1.1');
      });

      subnodes.get(1).all(by.css('.tree-node-content')).first().getText().then(function (text) {
        expect(text).toEqual('node1.2');
      });
    });
  });

  describe('the dragging and dropping of nodes', function () {
    it('should allow moving a node below another node', function () {
      // TODO
    });

    it('should allow adding a node to another node to make it a child-node', function () {
      // TODO
    });

    it('should allow removing a child-node from a node to put it on the same tree level', function () {
      // TODO
    });
  });
});

Add example tests for the drag and drop behaviour


class PGroup {
    constructor(members) {
        this.members = members;
    }

    add(elem) {
        if (this.has(elem)) return this;
        return new PGroup(this.members.concat(elem));
    }

    delete(elem) {
        if (!this.has(elem)) return this;
        return new PGroup(this.members.filter(e => e !== elem));
    }

    has(elem) {
        return this.members.includes(elem);
    }
}

PGroup.empty = new PGroup([]);

class PGroup {
	constructor(array) {
		this.members = array;
	}

	add(element) {
		return new PGroup(this.members.slice().push(element));
	}

	delete(element) {
		return new PGroup(this.members.filter(e => e !== element));
	}

	has(element) {
		return this.members.includes(element);
	}
}

PGroup.empty = new PGroup([]);

Add chapter 7, exercise 3


exports.update = {
	entryFile: 'index.html',
	headers: {
		'x-request-from': 'cordova-code-swap'
	},
	timeout: 30000
};

exports.instance = {
	backupCount: 1,
	iframe: false,
	debug: {
		reloadServer: '',
		onIframeUpdate: function(){},
		preserveBreakpoints: false
	}
};

exports.update = {
	entryFile: 'index.html',
	headers: {
		'x-request-from': 'cordova-code-swap',
		'If-Modified-Since': 'Thu, 01 Jan 1970 00:00:01 GMT'
	},
	timeout: 30000
};

exports.instance = {
	backupCount: 1,
	iframe: false,
	debug: {
		reloadServer: '',
		onIframeUpdate: function(){},
		preserveBreakpoints: false
	}
};

Set default headers to not use cache when performing requests.


var Version = require('node-version-assets');
var criticalCSS = require('critical');
var smushit = require('node-smushit');

var versionInstance = new Version({
  assets: ['build/styles/main.css', 'build/scripts/bundle.js'],
  grepFiles: ['build/index.html']
});

// Inline critical CSS
criticalCSS.generate({
  inline: true,
  base: 'build/',
  src: 'index.html',
  dest: 'build/index.html',
  dimensions: [{
    height: 640,
    width: 360
  }, {
    height: 1080,
    width: 1920
  }],
  minify: true
}).then(function (output) {
  // Create files version
  versionInstance.run();
}).error(function (err) {
  console.log(err);
});

// Image optimization
smushit.smushit('build/images', {recursive: true});

var Version = require('node-version-assets');
var criticalCSS = require('critical');

var versionInstance = new Version({
  assets: ['build/styles/main.css', 'build/scripts/bundle.js'],
  grepFiles: ['build/index.html']
});

// Inline critical CSS
criticalCSS.generate({
  inline: true,
  base: 'build/',
  src: 'index.html',
  dest: 'build/index.html',
  dimensions: [{
    height: 640,
    width: 360
  }, {
    height: 1080,
    width: 1920
  }],
  minify: true
}).then(function (output) {
  // Create files version
  versionInstance.run();
}).error(function (err) {
  console.log(err);
});

Remove smushit from production build


var fmt = require('fmt');
var awssum = require('awssum');
var amazon = awssum.load('amazon/amazon');
var S3 = awssum.load('amazon/s3').S3;

var env             = process.env;
var accessKeyId     = env.ACCESS_KEY_ID;
var secretAccessKey = env.SECRET_ACCESS_KEY;
var awsAccountId    = env.AWS_ACCOUNT_ID;

var s3 = new S3({
    'accessKeyId' : accessKeyId,
    'secretAccessKey' : secretAccessKey,
    'region' : amazon.US_EAST_1
});

fmt.field('Region', s3.region() );
fmt.field('EndPoint', s3.host() );
fmt.field('AccessKeyId', s3.accessKeyId() );
fmt.field('SecretAccessKey', s3.secretAccessKey().substr(0, 3) + '...' );
fmt.field('AwsAccountId', s3.awsAccountId() );

var options = {
    BucketName : 'pie-18',
    ObjectName : 'copy-of-test-object.txt',
    SourceBucket : 'pie-18',
    SourceObject : 'test-object.txt',
};

s3.CopyObject(options, function(err, data) {
    fmt.msg("copying one object to another - expecting success");
    fmt.dump(err, 'Error');
    fmt.dump(data, 'Data');
});

var fmt = require('fmt');
var awssum = require('awssum');
var amazon = awssum.load('amazon/amazon');
var S3 = awssum.load('amazon/s3').S3;

var env             = process.env;
var accessKeyId     = env.ACCESS_KEY_ID;
var secretAccessKey = env.SECRET_ACCESS_KEY;
var awsAccountId    = env.AWS_ACCOUNT_ID;

var s3 = new S3({
    'accessKeyId' : accessKeyId,
    'secretAccessKey' : secretAccessKey,
    'region' : amazon.US_EAST_1
});

fmt.line();
fmt.title('s3.CopyObject');

fmt.field('Region', s3.region() );
fmt.field('EndPoint', s3.host() );
fmt.field('AccessKeyId', s3.accessKeyId() );
fmt.field('SecretAccessKey', s3.secretAccessKey().substr(0, 3) + '...' );
fmt.field('AwsAccountId', s3.awsAccountId() );

fmt.line();

var options = {
    BucketName : 'pie-18',
    ObjectName : 'copy-of-test-object.txt',
    SourceBucket : 'pie-18',
    SourceObject : 'test-object.txt',
};

s3.CopyObject(options, function(err, data) {
    fmt.msg("copying one object to another - expecting success");
    fmt.dump(err, 'Error');
    fmt.dump(data, 'Data');
    fmt.line();
});

Make the Amazon:S3:CopyObject example nicer


/**
 * Feature flags.
 *
 * Site Kit by Google, Copyright 2021 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
 *
 *     https://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.
 */

export const enabledFeatures = global?._googlesitekitBaseData?.enabledFeatures || [];

/**
 * Returns true if a feature is enabled; false otherwise.
 *
 * @since 1.25.0
 *
 * @param {string} feature          The name of the feature to check.
 * @param {?Array} _enabledFeatures Optional. The list of enabled features. Uses `enabledFeatures` set by the server in a global JS variable, by default.
 * @return {boolean} `true` if a feature is enabled; `false` otherwise.
 */
export const isFeatureEnabled = ( feature, _enabledFeatures = enabledFeatures ) => {
	return _enabledFeatures.includes( feature );
};

/**
 * Feature flags.
 *
 * Site Kit by Google, Copyright 2021 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
 *
 *     https://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.
 */

export const enabledFeatures = new Set( global?._googlesitekitBaseData?.enabledFeatures || [] );

/**
 * Returns true if a feature is enabled; false otherwise.
 *
 * @since 1.25.0
 *
 * @param {string} feature            The name of the feature to check.
 * @param {Set}    [_enabledFeatures] Optional. The set of enabled features. Uses `enabledFeatures` set by the server in a global JS variable, by default.
 * @return {boolean} `true` if a feature is enabled; `false` otherwise.
 */
export const isFeatureEnabled = ( feature, _enabledFeatures = enabledFeatures ) => {
	return _enabledFeatures.has( feature );
};

Update enabledFeatures to use a Set.


"use strict";

(function (app) {

    app.HttpService = ng.core
        .Class({
            constructor: function () {},
            http: function () {
                return new ng.http.Http(
                        new ng.http.XHRBackend(
                            new ng.http.BrowserXhr()),
                    new ng.http.RequestOptions());
            }
        });

}(window.app || (window.app = {})));

(function (app) {

    app.HelloComponent = ng.core
        .Component({
            selector: "hello",
            bindings: [app.HttpService]
        })
        .View({
            template: "{{ greeting }} world! {{ greetingFromJSON }}"
            // templateUrl: "hello.html"
        })
        .Class({
            constructor: [app.HttpService, function Hello(httpService) {
                var vm = this;

                vm.greeting = "Hello";
                vm.greetingFromJSON = "n.a";

                activate();

                function activate() {
                    var rx = httpService.http()
                                .get("/src/client/app/greetings.json").share();

                    rx.subscribe(function (res) {
                        var greetings = res.json();

                        vm.greetingFromJSON = greetings.hello;
                    });
                }
            }]
        });

}(window.app || (window.app = {})));

"use strict";

(function (app) {

    app.HttpService = ng.core
        .Class({
            constructor: function () {},
            http: function () {
                return new ng.http.Http(
                        new ng.http.XHRBackend(
                            new ng.http.BrowserXhr()),
                    new ng.http.RequestOptions());
            }
        });

}(window.app || (window.app = {})));

(function (app) {

    app.HelloComponent = ng.core
        .Component({
            selector: "hello",
            bindings: [app.HttpService]
        })
        .View({
            template: "<p>{{ greeting }} world! {{ greetingFromJSON }}</p>"
        })
        .Class({
            constructor: [app.HttpService, function Hello(httpService) {
                var vm = this;

                vm.greeting = "Hello";
                vm.greetingFromJSON = "n.a";

                activate();

                function activate() {
                    var rx = httpService.http()
                                .get("/src/client/app/greetings.json").share();

                    rx.subscribe(function (res) {
                        var greetings = res.json();

                        vm.greetingFromJSON = greetings.hello;
                    });
                }
            }]
        });

}(window.app || (window.app = {})));

Add paragraph tag in hello template.


import React from 'react';

function Navigator({ children, ...props }) {
  return (
    <nav data-testid={ props.testid } className="flex items-center p1 bg-white border-bottom">
      { children }
    </nav>
  );
}

Navigator.propTypes = {
  children: React.PropTypes.node,
  testid: React.PropTypes.string,
};

export default Navigator;

import React from 'react';

function Navigator({ children, isVisible, ...props }) {
  if (!isVisible) {
    return null;
  }

  return (
    <nav data-testid={ props.testid } className="flex items-center p1 bg-white border-bottom">
      { children }
    </nav>
  );
}

Navigator.propTypes = {
  children: React.PropTypes.node,
  isVisible: React.PropTypes.bool,
  testid: React.PropTypes.string,
};

export default Navigator;

Remove the Rangle logo from menu


/* eslint-env jest */
import React from 'react';
import { shallow } from 'enzyme';

import { <%= componentName %> }  from './Component';

describe('<%= componentName %>', () => {
  it('should render equal to the snapshot', () => {
    const wrapper = shallow(<<%= componentName %> />);

    expect(wrapper).toMatchSnapshot();
  });
});

/* eslint-env jest */
import React from 'react';
import { shallow } from 'enzyme';

import { Component } from './Component';

describe('<%= componentName %>', () => {
  it('should render equal to the snapshot', () => {
    const wrapper = shallow(<Component />);

    expect(wrapper).toMatchSnapshot();
  });
});

Fix Component import on test


/**
 * Created by Dylan on 3/31/2015.
 */

'use strict';

var app = angular.module('courses');
app.directive('studentQuestionView', function () {
    return {
        restrict: 'AE',
        templateUrl: '/modules/courses/views/Quiz/student-quiz-question.html'
    };
});

/**
 * Created by Dylan on 3/31/2015.
 */

'use strict';

var app = angular.module('courses');
app.directive('studentQuestionView', function () {
    return {
        restrict: 'AE',
        //eff u and ur capital Q's dilly
        templateUrl: '/modules/courses/views/quiz/student-quiz-question.html'
    };
});

Fix capital Q issue with directive


import React from 'react'

export default class GiftAidButton extends React.Component {
  constructor (props) {
    super (props)
    this.state = { confirmation: false }
  }

  cancel () {
    this.props.update_member_user({ gift_aid_signed: false, gift_aid_cancelled: true })
  }

  attempt_cancel () {
    this.setState({ confirmation: true })
  }

  reset () {
    this.setState({ confirmation: false })
  }

  which_cancel () {
    return this.state.confirmation ? this.cancel.bind(this) : this.attempt_cancel.bind(this)
  }

  which_text () {
    return this.state.confirmation ? 'Confirm' : 'Cancel Gift Aid'
  }

  render () {
    return (

Would you like to cancel your Gift Aid?

        <button onClick={this.which_cancel()} className={this.state.confirmation ? 'red' : ''}>
          {this.which_text()}
        </button>
        {this.state.confirmation && <button onClick={this.reset.bind(this)} className='green' >Keep Gift Aid</button>}

    )
  }
}

import React from 'react'

export default class GiftAidButton extends React.Component {
  constructor (props) {
    super (props)
    this.state = { confirmation: false }
  }

  cancel () {
    this.props.update_member_user(
      { gift_aid_cancelled: true
      , date_gift_aid_cancelled: new Date().toISOString()
      , gift_aid_signed: false
      , date_gift_aid_signed: null
      }
    )
  }

  attempt_cancel () {
    this.setState({ confirmation: true })
  }

  reset () {
    this.setState({ confirmation: false })
  }

  which_cancel () {
    return this.state.confirmation ? this.cancel.bind(this) : this.attempt_cancel.bind(this)
  }

  which_text () {
    return this.state.confirmation ? 'Confirm' : 'Cancel Gift Aid'
  }

  render () {
    return (

Would you like to revoke your Gift Aid declaration?

        <button onClick={this.which_cancel()} className={this.state.confirmation ? 'red' : ''}>
          {this.which_text()}
        </button>
        {this.state.confirmation && <button onClick={this.reset.bind(this)} className='green' >Keep Gift Aid</button>}

    )
  }
}

Make accompanying changes to DB when cancelling Gift Aid declaration.


'use strict';

angular.module('sgApp')
  .controller('AppCtrl', function($scope, ngProgress) {

      // ngProgress do not respect styles assigned via CSS if we do not pass empty parameters
      // See: https://github.com/VictorBjelkholm/ngProgress/issues/33
      ngProgress.height('');
      ngProgress.color('');

      // Scroll top when page is changed
      $scope.$on('$viewContentLoaded', function() {
        window.scrollTo(0, 0);
      });

      $scope.$on('progress start', function() {
        ngProgress.start();
      });

      $scope.$on('progress end', function() {
        ngProgress.complete();
      });

      // Reload styles when server notifies about the changes
      // Add cache buster to every stylesheet on the page forcing them to reload
      $scope.$on('styles changed', function() {
        var links = Array.prototype.slice.call(document.getElementsByTagName('link'));
        links.forEach(function(link) {
          if (typeof link === 'object' && link.getAttribute('type') === 'text/css') {
            link.href = link.href.split('?')[0] + '?id=' + new Date().getTime();
          }
        });
      });

      $scope.$on('socket connected', function() {
        console.log('Socket connection established');
        //TODO: enable Designer Tool 'save changes' button?
      });

      $scope.$on('socket disconnected', function() {
        console.error('Socket connection dropped');
        //TODO: disable Designer Tool 'save changes' button?
      });

      $scope.$on('socket error', function(err) {
        console.error('Socket error:', err);
      });

    });

'use strict';

angular.module('sgApp')
  .controller('AppCtrl', function($scope, ngProgress) {

      // ngProgress do not respect styles assigned via CSS if we do not pass empty parameters
      // See: https://github.com/VictorBjelkholm/ngProgress/issues/33
      ngProgress.height('');
      ngProgress.color('');

      // Scroll top when page is changed
      $scope.$on('$viewContentLoaded', function() {
        window.scrollTo(0, 0);
      });

      $scope.$on('progress start', function() {
        ngProgress.start();
      });

      $scope.$on('progress end', function() {
        ngProgress.complete();
      });

      // Reload styles when server notifies about the changes
      // Add cache buster to every stylesheet on the page forcing them to reload
      $scope.$on('styles changed', function() {
        var links = Array.prototype.slice.call(document.getElementsByTagName('link'));
        links.forEach(function(link) {
          if (typeof link === 'object' && link.getAttribute('type') === 'text/css') {
            link.href = link.href.split('?')[0] + '?id=' + new Date().getTime();
          }
        });
      });

      $scope.$on('socket connected', function() {
        console.log('Socket connection established');
        //TODO: enable Designer Tool 'save changes' button?
      });

      $scope.$on('socket disconnected', function() {
        console.error('Socket connection dropped');
        //TODO: disable Designer Tool 'save changes' button?
        ngProgress.reset();
      });

      $scope.$on('socket error', function(err) {
        console.error('Socket error:', err);
      });

    });

Hide progress bar when socket connection is lost


// This file could have a better name

function rand(min = 0, max = 1) {
  return min + Math.random() * (max - min);
}

function randStart(rangeX, rangeY, rangeZ) {
  return {
    x: rand(-rangeX/2, rangeX/2),
    y: rand(-rangeY/2, rangeY/2),
    z: rand(-rangeZ/2, rangeZ/2),
  };
}

module.exports = {
  rand,
  randStart,
}

// This file could have a better name

function rand(min, max) {
  min = min || 0;
  max = max || 1;
  return min + Math.random() * (max - min);
}

function randStart(rangeX, rangeY, rangeZ) {
  return {
    x: rand(-rangeX/2, rangeX/2),
    y: rand(-rangeY/2, rangeY/2),
    z: rand(-rangeZ/2, rangeZ/2),
  };
}

module.exports = {
  rand,
  randStart,
}

Fix mobile by removing default param values from vectorHelper


'use strict';

/**
 * Module dependencies.
 */
var path = require('path'),
  db = require(path.resolve('./config/lib/sequelize')),
  passport = require('passport'),
  LocalStrategy = require('passport-local').Strategy;

module.exports = function() {
  // Use local strategy
  passport.use(new LocalStrategy({
    usernameField: 'username',
    passwordField: 'password'
  },
  function(username, password, done) {
    db.User.findOne({
      where: {
        username: username
      }
    })
    .then(function(user) {
      if (!user || !user.authenticate(user, password)) {
        return done(null, false, {
          message: 'Invalid username or password'
        });
      }

      done(null, user);

      return null;
    })
    .catch(function(err) {
      done(err);
    });
  }));
};

'use strict';

/**
 * Module dependencies.
 */
var path = require('path'),
  db = require(path.resolve('./config/lib/sequelize')),
  passport = require('passport'),
  LocalStrategy = require('passport-local').Strategy;

module.exports = function() {
  // Use local strategy
  passport.use(new LocalStrategy({
    usernameField: 'username',
    passwordField: 'password'
  },
  function(username, password, done) {
    db.User.findOne({
      where: {
        username: username
      }
    })
    .then(function(user) {
      if (!user || !user.authenticate(user, password)) {
        done(null, false, {
          message: 'Invalid username or password'
        });

        return null;
      }

      done(null, user);

      return null;
    })
    .catch(function(err) {
      done(err);
    });
  }));
};

Fix "signin" promise return warning


'use strict';

var Test = require('tape');
var Path = require('path');
var Fs = require('fs');

process.env.BEST_ASSET_READ_HOST = 'http://localhost:3000';
process.env.BEST_BLOCKS_FOLDER = Path.join(__dirname, '..', 'public');

// This removes any previously built versions from the local file system so we
// don't end up creating a bundle that draws from non-fixture components.
// This DUMB HACK almost certainly warrants a factoring-out
if (Fs.existsSync(process.env.BEST_BLOCKS_FOLDER)) {
    Fs.rmdirSync(process.env.BEST_BLOCKS_FOLDER);
}

var Builder = require('./../lib/builder');
var Assistant = require('./../../../local/assistant/lib/assistant');

Test('builder', function(t) {
    t.plan(3);
    t.ok(Builder, 'exports');
    t.ok(new Builder(), 'instance');

    var baseDir = Path.join(__dirname, 'fixtures');
    var subDir = Path.join('a');
    var buildResult = Fs.readFileSync(Path.join(__dirname, 'fixtures', '__data', 'a-build.js'), {encoding:'utf8'});

    var assistant = new Assistant({
        builderOptions: {
            doSkipAssetSaveStep: true,
            doSkipBundleSaveStep: true,
            localRawSourceFolder: baseDir
        }
    });

    assistant.buildSingle(baseDir, subDir, function(err, result) {
        if (err) {
            console.error(err);
        }
        else {
            t.equals(result.bundleString, buildResult, 'freshly generated bundle string matches a previously generated build result');
            // console.log(result.bundleString);
        }
    });
});

'use strict';

var Test = require('tape');
var Path = require('path');
var Fs = require('fs');

process.env.BEST_ASSET_READ_HOST = 'http://localhost:3000';
process.env.BEST_BLOCKS_FOLDER = Path.join(__dirname, '..', 'public');
process.env.BEST_VERSION_INFO_HOST = 'https://api-te.famo.us/codemanager';

var Builder = require('./../lib/builder');
var Assistant = require('./../../../local/assistant/lib/assistant');

Test('builder', function(t) {
    t.plan(3);
    t.ok(Builder, 'exports');
    t.ok(new Builder(), 'instance');

    var baseDir = Path.join(__dirname, 'fixtures');
    var subDir = Path.join('a');
    var buildResult = Fs.readFileSync(Path.join(__dirname, 'fixtures', '__data', 'a-build.js'), {encoding:'utf8'});

    var assistant = new Assistant({
        builderOptions: {
            doSkipAssetSaveStep: true,
            doSkipBundleSaveStep: true,
            localRawSourceFolder: baseDir
        }
    });

    assistant.buildSingle(baseDir, subDir, function(err, result) {
        if (err) {
            console.error(err);
        }
        else {
            t.equals(result.bundleString, buildResult, 'freshly generated bundle string matches a previously generated build result');
            // console.log(result.bundleString);
        }
    });
});

Remove block of code that doesn't um actually work


import React from 'react'

const Details = ({ match }) => (

Details - {match.params.id}

)

export default Details

import React from 'react'
import AccountForm from '../InitialSetup/AccountForm'

const Details = ({ match }) => (
  <div className="container-raised-desktop">
    Details - {match.params.id}
    <AccountForm />
  </div>
)

export default Details

Add form to account detail


import { applyMiddleware, compose, createStore } from 'redux';
import rootReducer  from '../reducers';
import { devTools } from 'redux-devtools';
import thunk        from 'redux-thunk';
import routes       from '../routes';
import { reduxReactRouter } from 'redux-router';
import createHistory from 'history/lib/createBrowserHistory';

export default function configureStore (initialState, debug = false) {
  let createStoreWithMiddleware;

  const middleware = applyMiddleware(thunk);

  if (debug) {
    createStoreWithMiddleware = compose(middleware, reduxReactRouter({ routes, createHistory }), devTools());
  } else {
    createStoreWithMiddleware = compose(middleware, reduxReactRouter({ routes, createHistory }));
  }

  const store = createStoreWithMiddleware(createStore)(
    rootReducer, initialState
  );
  if (module.hot) {
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers/index');

      store.replaceReducer(nextRootReducer);
    });
  }
  return store;
}

import { applyMiddleware, compose, createStore } from 'redux';
import rootReducer  from '../reducers';
import { devTools } from 'redux-devtools';
import thunk        from 'redux-thunk';
import routes       from '../routes';
import { reduxReactRouter } from 'redux-router';
import createHistory from 'history/lib/createHashHistory';

export default function configureStore (initialState, debug = false) {
  let createStoreWithMiddleware;

  const middleware = applyMiddleware(thunk);

  if (debug) {
    createStoreWithMiddleware = compose(middleware, reduxReactRouter({ routes, createHistory }), devTools());
  } else {
    createStoreWithMiddleware = compose(middleware, reduxReactRouter({ routes, createHistory }));
  }

  const store = createStoreWithMiddleware(createStore)(
    rootReducer, initialState
  );
  if (module.hot) {
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers/index');

      store.replaceReducer(nextRootReducer);
    });
  }
  return store;
}

Switch to hash-based routing so that it works with firebase. Fine for dev and home screened app won't see


//= require ./redactor
//= require ./config-setup
//= require refinery-redactor/config
//= require ./boot

//= require refinery-redactor/redactor
//= require ./config-setup
//= require refinery-redactor/config
//= require ./boot

Load redactor relatively so it can be included in app/assets


import { Vue, mergeData } from '../../vue'
import { omit } from '../../utils/object'
import { kebabCase, pascalCase, trim } from '../../utils/string'
import { BVIconBase, props as BVIconBaseProps } from './icon-base'

/**
 * Icon component generator function
 *
 * @param {string} icon name (minus the leading `BIcon`)
 * @param {string} raw `innerHTML` for SVG
 * @return {VueComponent}
 */
export const makeIcon = (name, content) => {
  // For performance reason we pre-compute some values, so that
  // they are not computed on each render of the icon component
  const kebabName = kebabCase(name)
  const iconName = `BIcon${pascalCase(name)}`
  const iconNameClass = `bi-${kebabName}`
  const iconTitle = kebabName.replace(/-/g, ' ')
  const svgContent = trim(content || '')

  return /*#__PURE__*/ Vue.extend({
    name: iconName,
    functional: true,
    props: omit(BVIconBaseProps, ['content']),
    render(h, { data, props }) {
      return h(
        BVIconBase,
        mergeData(
          // Defaults
          {
            props: { title: iconTitle },
            attrs: { 'aria-label': iconTitle }
          },
          // User data
          data,
          // Required data
          {
            staticClass: iconNameClass,
            props: { ...props, content: svgContent }
          }
        )
      )
    }
  })
}

import { Vue, mergeData } from '../../vue'
import { omit } from '../../utils/object'
import { kebabCase, pascalCase, trim } from '../../utils/string'
import { BVIconBase, props as BVIconBaseProps } from './icon-base'

const iconProps = omit(BVIconBaseProps, ['content'])

/**
 * Icon component generator function
 *
 * @param {string} icon name (minus the leading `BIcon`)
 * @param {string} raw `innerHTML` for SVG
 * @return {VueComponent}
 */
export const makeIcon = (name, content) => {
  // For performance reason we pre-compute some values, so that
  // they are not computed on each render of the icon component
  const kebabName = kebabCase(name)
  const iconName = `BIcon${pascalCase(name)}`
  const iconNameClass = `bi-${kebabName}`
  const iconTitle = kebabName.replace(/-/g, ' ')
  const svgContent = trim(content || '')

  return /*#__PURE__*/ Vue.extend({
    name: iconName,
    functional: true,
    props: iconProps,
    render(h, { data, props }) {
      return h(
        BVIconBase,
        mergeData(
          // Defaults
          {
            props: { title: iconTitle },
            attrs: { 'aria-label': iconTitle }
          },
          // User data
          data,
          // Required data
          {
            staticClass: iconNameClass,
            props: { ...props, content: svgContent }
          }
        )
      )
    }
  })
}

Speed up icons module import


import React from 'react'
import { inject, observer } from 'mobx-react'
import Toolbar from './Toolbar'
import ToolbarIndicator from './ToolbarIndicator'

@inject('userStore')
@observer
export default class ToolbarWrapper extends React.Component {
  render () {
    const {
      lazyHideToolbar, showToolbar
    } = this.props.userStore
    return (
      <div onMouseEnter={showToolbar}
        onMouseLeave={lazyHideToolbar}
        style={{
          display: 'flex',
          width: 'fit-content',
          position: 'fixed',
          bottom: 0,
          right: 0
        }}
      >
        <Toolbar />
        <ToolbarIndicator />
      </div>
    )
  }
}

import React from 'react'
import { inject, observer } from 'mobx-react'
import Toolbar from './Toolbar'
import ToolbarIndicator from './ToolbarIndicator'

@inject('userStore')
@observer
export default class ToolbarWrapper extends React.Component {
  render () {
    const {
      lazyHideToolbar, showToolbar
    } = this.props.userStore
    return (
      <div onMouseEnter={showToolbar}
        onMouseLeave={lazyHideToolbar}
        style={{
          display: 'flex',
          width: 'fit-content',
          position: 'fixed',
          bottom: 0,
          right: 0,
          backgroundColor: 'rgba(255, 255, 255, 0.8)'
        }}
      >
        <Toolbar />
        <ToolbarIndicator />
      </div>
    )
  }
}

Add background color for Toolbar


import window from 'global';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// this component renders an iframe, which gets updates via post-messages
export class IFrame extends Component {
  iframe = null;

  componentDidMount() {
    const { id } = this.props;
    this.iframe = window.document.getElementById(id);
  }

  shouldComponentUpdate(nextProps) {
    const { scale } = this.props;
    return scale !== nextProps.scale;
  }

  componentDidUpdate(prevProps) {
    const { scale } = this.props;
    if (scale !== prevProps.scale) {
      this.setIframeBodyStyle({
        width: `${scale * 100}%`,
        height: `${scale * 100}%`,
        transform: `scale(${1 / scale})`,
        transformOrigin: 'top left',
      });
    }
  }

  setIframeBodyStyle(style) {
    return Object.assign(this.iframe.contentDocument.body.style, style);
  }

  render() {
    const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
    return (
      <iframe
        scrolling="yes"
        id={id}
        title={title}
        src={src}
        allowFullScreen={allowFullScreen}
        {...rest}
      />
    );
  }
}
IFrame.propTypes = {
  id: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  allowFullScreen: PropTypes.bool.isRequired,
  scale: PropTypes.number.isRequired,
};

import window from 'global';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

// this component renders an iframe, which gets updates via post-messages
export class IFrame extends Component {
  iframe = null;

  componentDidMount() {
    const { id } = this.props;
    this.iframe = window.document.getElementById(id);
  }

  shouldComponentUpdate(nextProps) {
    const { scale, src } = this.props;
    return scale !== nextProps.scale || src !== nextProps.src;
  }

  componentDidUpdate(prevProps) {
    const { scale } = this.props;
    if (scale !== prevProps.scale) {
      this.setIframeBodyStyle({
        width: `${scale * 100}%`,
        height: `${scale * 100}%`,
        transform: `scale(${1 / scale})`,
        transformOrigin: 'top left',
      });
    }
  }

  setIframeBodyStyle(style) {
    return Object.assign(this.iframe.contentDocument.body.style, style);
  }

  render() {
    const { id, title, src, allowFullScreen, scale, ...rest } = this.props;
    return (
      <iframe
        scrolling="yes"
        id={id}
        title={title}
        src={src}
        allowFullScreen={allowFullScreen}
        {...rest}
      />
    );
  }
}
IFrame.propTypes = {
  id: PropTypes.string.isRequired,
  title: PropTypes.string.isRequired,
  src: PropTypes.string.isRequired,
  allowFullScreen: PropTypes.bool.isRequired,
  scale: PropTypes.number.isRequired,
};

Fix regression with deep linking


import moment from 'moment';
import 'moment/locale/fr';
import 'moment-timezone';

moment.locale('fr');

export const monthDayYear = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('MMMM Do, YYYY') :
    moment(timestamp).tz(timezone).format('MMMM Do, YYYY')
);

export const dayMonthYearAtTime = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('Do MMMM YYYY [à] hh:mm') :
    moment(timestamp).tz(timezone).format('Do MMMM YYYY [à] hh:mm')
);

export const timeago = (timestamp, timezone) => (
  !timezone ? moment(timestamp).fromNow() :
    moment(timestamp).tz(timezone).fromNow()
);

export const add = (timestamp, amount, range, timezone) => (
  !timezone ? moment(timestamp).add(amount, range).format() :
    moment(timestamp).tz(timezone).add(amount, range).format()
);

export const year = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('YYYY') :
    moment(timestamp).tz(timezone).format('YYYY')
);

import moment from 'moment';
import 'moment/locale/fr';
import 'moment-timezone';

moment.locale('fr');

export const dayMonthYear = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('Do MMMM YYYY') :
    moment(timestamp).tz(timezone).format('Do MMMM YYYY')
);

export const dayMonthYearAtTime = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('Do MMMM YYYY [à] HH:mm') :
    moment(timestamp).tz(timezone).format('Do MMMM YYYY [à] HH:mm')
);

export const timeago = (timestamp, timezone) => (
  !timezone ? moment(timestamp).fromNow() :
    moment(timestamp).tz(timezone).fromNow()
);

export const add = (timestamp, amount, range, timezone) => (
  !timezone ? moment(timestamp).add(amount, range).format() :
    moment(timestamp).tz(timezone).add(amount, range).format()
);

export const year = (timestamp, timezone) => (
  !timezone ? moment(timestamp).format('YYYY') :
    moment(timestamp).tz(timezone).format('YYYY')
);

Update Dates library, fix hour format and rename 'monthDayYear' to 'dayMonthYear'


Travis.Controllers.Builds.List = SC.ArrayProxy.extend({
  parent: null,
  repositoryBinding: 'parent.repository',
  contentBinding: 'parent.repository.builds',

  init: function() {
    SC.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);

    this.view = Travis.View.create({
      builds: this,
      repositoryBinding: 'builds.repository',
      templateName: 'app/templates/builds/list'
    })
  },

  destroy: function() {
    // console.log('destroying list in: ' + this.selector + ' .details')
    if(this.view) {
      this.view.$().remove();
      this.view.destroy();
    }
  },

  updateTimes: function() {
    var builds  = this.get('builds');
    if(builds) $.each(builds, function(ix, build) { build.updateTimes() }.bind(this));
    SC.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
  }
});

Travis.Controllers.Builds.List = SC.ArrayProxy.extend({
  parent: null,
  repositoryBinding: 'parent.repository',
  contentBinding: 'parent.repository.builds',

  init: function() {
    SC.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);

    this.view = Travis.View.create({
      builds: this,
      repositoryBinding: 'builds.repository',
      templateName: 'app/templates/builds/list'
    });
  },

  destroy: function() {
    // console.log('destroying list in: ' + this.selector + ' .details')
    if(this.view) {
      this.view.$().remove();
      this.view.destroy();
    }
  },

  updateTimes: function() {
    var builds  = this.get('builds');
    if(builds) {
      $.each(builds, function(ix, build) { build.updateTimes(); }.bind(this));
    }
    SC.run.later(this.updateTimes.bind(this), Travis.UPDATE_TIMES_INTERVAL);
  }
});

Add missing semicolons in Travis.Controllers.Builds.List


/**
 * Lullaby - Songza connector
 * by Matt Runkle
 *
 * https://github.com/runkalicious/lullaby
 *
 * Use of this source code is governed by the MIT license
 * that can be found in the LICENSE file.
 *
 */

var PLAYER = 'div.sz-player';
var PAUSE = 'li.player-pause';
var PLAYING = 'sz-player-state-play'

function l_isPlaying() {
	// Songza toggles a class tag on the player div
	return $(PLAYER).hasClass(PLAYING);
}

function l_pause() {
	console.log("Pausing Songza");

	var pauseButton = $(PAUSE)[0];
	$(pauseButton).click();
}

/**
 * Lullaby - Songza connector
 * by Matt Runkle
 *
 * https://github.com/runkalicious/lullaby
 *
 * Use of this source code is governed by the MIT license
 * that can be found in the LICENSE file.
 *
 */

var PLAYER = 'div.player-wrapper';
var PAUSE = 'div.miniplayer-control-play-pause';
var PLAYING = 'player-state-play'

function l_isPlaying() {
	// Songza toggles a class tag on the player div
	return $(PLAYER).hasClass(PLAYING);
}

function l_pause() {
	console.log("Pausing Songza");

	var pauseButton = $(PAUSE)[0];
	$(pauseButton).click();
}

Update Songza connector to work with new site layout changes.


/* eslint-env node, mocha */
const expect = require('chai').expect
import accountStatistics from '../src/index'

describe('statistics', () => {
  it('can calculate all statistics', () => {
    const keys = Object.keys(accountStatistics({}))
    expect(keys.length).to.be.above(0)
  })
})

/* eslint-env node, mocha */
const expect = require('chai').expect
import accountStatistics from '../src/index'

describe('statistics', () => {
  it('can calculate all statistics', () => {
    const statistics = accountStatistics({})
    expect(statistics).to.contain.all.keys(['brokenSpoons', 'totalAuras'])
  })
})

Improve test for aggregated statistics


import React, { PropTypes } from 'react';

const UnsupportedFile = ({ name }) => (

    {name}

);

UnsupportedFile.propTypes = {
  name: PropTypes.string
};

export default UnsupportedFile;

Make unsupported files have lower opacity


var kindleMyClippings = require('kindle-my-clippings');
var fs = require('fs');

var options = {
  file: 'public/data/MyClippings.txt',
  format: 'json'
};

kindleMyClippings.init(options, function (data) {
    // data is an object with parsed clippings
    fs.writeFile('clippings.json', data, function(err) {
      if(err) {
        return console.log(err);
      }
      console.log('File Saved!');
    });
});

var kindleMyClippings = require('kindle-my-clippings');
var fs = require('fs');

var options = {
  file: 'My\ Clippings.txt',
  format: 'json'
};

kindleMyClippings.init(options, function (data) {
    // data is an object with parsed clippings
    fs.writeFile('clippings.json', data, function(err) {
      if(err) {
        return console.log(err);
      }
      console.log('File Saved!');
    });
});

Fix path for clipping script


import { Meteor } from 'meteor/meteor';
import moment from 'moment';

import { sendReports } from '../../lib/imports/sendReports';

const CHECK_INTERVAL = {
  minutes: 1,
};

function autoSendReports() {
  const now = moment();
  const gameState = Gamestate.findOne();
  const lastAutoReportSend = moment(gameState.lastAutoReportSend || moment(now).subtract(2, 'day'));
  const waitingUntil = moment(lastAutoReportSend).add(1, 'day');

  Meteor.logger.info(`Checking to send auto reports. now: ${now} .. waiting for ${waitingUntil}`);

  if (now.isAfter(waitingUntil)) {
    const sendTime = moment(now).startOf('hour');
    Meteor.logger.info(`Sending auto reports at ${sendTime} to: ${gameState.sendReportsTo}`);
    sendReports(gameState.sendReportsTo);
    Gamestate.update({ _id: gameState._id}, { $set: { lastAutoReportSend: sendTime }});
  }
}

Meteor.startup(() => {
  // On Startup, init Interval for puzzle timeout watcher.
  const interval = moment.duration(CHECK_INTERVAL).asMilliseconds();
  Meteor.setInterval(autoSendReports, interval);
});

import { Meteor } from 'meteor/meteor';
import moment from 'moment';

import { sendReports } from '../../lib/imports/sendReports';

const CHECK_INTERVAL = {
  minutes: 5,
};

function autoSendReports() {
  const now = moment();
  const gameState = Gamestate.findOne();
  const lastAutoReportSend = moment(gameState.lastAutoReportSend || moment(now).subtract(2, 'day'));
  const waitingUntil = moment(lastAutoReportSend).add(1, 'day');

  Meteor.logger.info(`Checking to send auto reports. now: ${now} .. waiting for ${waitingUntil}`);

  if (now.isAfter(waitingUntil)) {
    const sendTime = moment(now).startOf('hour');
    Meteor.logger.info(`Sending auto reports at ${sendTime} to: ${gameState.sendReportsTo}`);
    sendReports(gameState.sendReportsTo);
    Gamestate.update({ _id: gameState._id}, { $set: { lastAutoReportSend: sendTime }});
  }
}

Meteor.startup(() => {
  // On Startup, init Interval for puzzle timeout watcher.
  const interval = moment.duration(CHECK_INTERVAL).asMilliseconds();
  Meteor.setInterval(autoSendReports, interval);
});

Check for auto reports send ever 5 min


var wompt = require("../includes"),
mongoose = wompt.mongoose,
ObjectId = mongoose.Schema.ObjectId;

var Account = new mongoose.Schema({
	'display_name'    : String
	,'name'           : String
	,'owner_ids'      : [ObjectId]
});

// number defines index sort order (1=asc)
Account.index({name: 1});

// Model name, Schema, collection name
mongoose.model('Account', Account, 'accounts');
module.exports = mongoose.model('Account');

var wompt = require("../includes"),
mongoose = wompt.mongoose,
ObjectId = mongoose.Schema.ObjectId;

var Account = new mongoose.Schema({
	'display_name'    : String
	,'name'           : String
	,'owner_ids'      : [ObjectId]
	,'secret'         : {type: String, 'default': generateSecret}
});

// number defines index sort order (1=asc)
Account.index({name: 1});

Account.static({
	generateSecret: generateSecret
});

// Model name, Schema, collection name
mongoose.model('Account', Account, 'accounts');
module.exports = mongoose.model('Account');

function generateSecret(){
	return wompt.util.randomString(20);
}

Add Account.secret field to mongodb


(function($) {
    $.fn.pagemore = function(options) {
        var settings = $.extend( {
            containerClass: 'pagemore-container',
            paginatorClass: 'pagemore-paginator',
            pageRequested: function(e) {
                e.paginator.hide();
            },
            pageCompleted: function(e) {
                e.paginator.toggle(! e.lastPage);
            }
        }, options);
        return this.each(function() {
            var container = $("." + settings.containerClass);
            var paginator = $(this);
            paginator.click(function() {
                var event = { paginator: paginator };
                settings.pageRequested(event);
                $.get($(this).attr('href'),
                  function(resp) {
                      $(resp).find(".pagemore-container").children().appendTo(container);
                      var newPaginator = $(resp).find(".pagemore-paginator");
                      var lastPage = ! newPaginator.length;
                      var completedEvent = $.extend({}, event, {
                          lastPage: lastPage });
                      if (! lastPage) {
                        paginator.attr('href',
                         newPaginator.attr('href'));
                    }
                    settings.pageCompleted(completedEvent);
                }
                );
                return false;
            });

        });
    };
})(jQuery);

(function($) {
    $.fn.pagemore = function(options) {
        var settings = $.extend( {
            containerClass: 'pagemore-container',
            paginatorClass: 'pagemore-paginator',
            pageRequested: function(e) {
                e.paginator.hide();
            },
            pageCompleted: function(e) {
                e.paginator.toggle(! e.lastPage);
            }
        }, options);
        return this.each(function() {
            var container = $("." + settings.containerClass);
            var paginator = $(this);
            paginator.click(function() {
                var event = { paginator: paginator };
                settings.pageRequested(event);
                $.get($(this).attr('href'),
                  function(resp) {
                      $(resp).find("." + settings.containerClass).children().appendTo(container);
                      var newPaginator = $(resp).find("." + settings.paginatorClass);
                      var lastPage = ! newPaginator.length;
                      var completedEvent = $.extend({}, event, {
                          lastPage: lastPage });
                      if (! lastPage) {
                        paginator.attr('href',
                         newPaginator.attr('href'));
                    }
                    settings.pageCompleted(completedEvent);
                }
                );
                return false;
            });

        });
    };
})(jQuery);

Use settings.containerClass and settings.paginatorClass on ajax call


//= require spree/frontend

SpreePaypalExpress = {
  hidePaymentSaveAndContinueButton: function(paymentMethod) {
    if (SpreePaypalExpress.paymentMethodID && paymentMethod.val() == SpreePaypalExpress.paymentMethodID) {
      $('.continue').hide();
    } else {
      $('.continue').show();
    }
  }
}

$(document).ready(function() {
  checkedPaymentMethod = $('div[data-hook="checkout_payment_step"] input[type="radio"]:checked');
  SpreePaypalExpress.hidePaymentSaveAndContinueButton(checkedPaymentMethod);
  paymentMethods = $('div[data-hook="checkout_payment_step"] input[type="radio"]').click(function (e) {
    SpreePaypalExpress.hidePaymentSaveAndContinueButton($(e.target));
  });
})

//= require spree/frontend

SpreePaypalExpress = {
  hidePaymentSaveAndContinueButton: function(paymentMethod) {
    if (!$('#use_existing_card_yes:checked').length && SpreePaypalExpress.paymentMethodID && paymentMethod.val() == SpreePaypalExpress.paymentMethodID) {
      $('.continue').hide();
    } else {
      $('.continue').show();
    }
  },
  checkedPaymentMethod: function() {
    return $('div[data-hook="checkout_payment_step"] input[type="radio"][name="order[payments_attributes][][payment_method_id]"]:checked');
  }
}

$(document).ready(function() {
  SpreePaypalExpress.hidePaymentSaveAndContinueButton(SpreePaypalExpress.checkedPaymentMethod());
  paymentMethods = $('div[data-hook="checkout_payment_step"] input[type="radio"]').click(function (e) {
    SpreePaypalExpress.hidePaymentSaveAndContinueButton(SpreePaypalExpress.checkedPaymentMethod());
  });
})

Fix "Save & Continue" + Payment Profiles.


console.log("Starting Statik Website...");
require("./app.js").listen(process.env.PORT || 3000, function() {
    console.log("Statik Website listening on port " + port + "!");
});

console.log("Starting Statik Website...");
var port = process.env.PORT || 3000;
require("./app.js").listen(port, function() {
    console.log("Statik Website listening on port " + port + "!");
});

Fix entry point and rename Node project


/*global define*/
define(function() {
    "use strict";

    var context2DsByWidthAndHeight = {};

    /**
     * Extract a pixel array from a loaded image.  Draws the image
     * into a canvas so it can read the pixels back.
     *
     * @param {Image} image The image to extract pixels from.
     *
     * @returns {CanvasPixelArray} The pixels of the image.
     */
    var imageToPixels = function(image, width, height) {
        if (typeof width === 'undefined') {
            width = image.width;
        }
        if (typeof height === 'undefined') {
            height = image.height;
        }

        var context2DsByHeight = context2DsByWidthAndHeight[width];
        if (typeof context2DsByHeight === 'undefined') {
            context2DsByHeight = {};
            context2DsByWidthAndHeight[width] = context2DsByHeight;
        }

        var context2d = context2DsByHeight[height];
        if (typeof context2d === 'undefined') {
            var canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            context2d = canvas.getContext('2d');
            context2d.globalCompositeOperation = 'copy';
            context2DsByHeight[height] = context2d;
        }

        context2d.drawImage(image, 0, 0, width, height);
        return context2d.getImageData(0, 0, width, height).data;
    };

    return imageToPixels;
});

/*global define*/
define(function() {
    "use strict";

    var context2DsByWidthAndHeight = {};

    /**
     * Extract a pixel array from a loaded image.  Draws the image
     * into a canvas so it can read the pixels back.
     *
     * @exports getImagePixels
     *
     * @param {Image} image The image to extract pixels from.
     *
     * @returns {CanvasPixelArray} The pixels of the image.
     */
    var getImagePixels = function(image, width, height) {
        if (typeof width === 'undefined') {
            width = image.width;
        }
        if (typeof height === 'undefined') {
            height = image.height;
        }

        var context2DsByHeight = context2DsByWidthAndHeight[width];
        if (typeof context2DsByHeight === 'undefined') {
            context2DsByHeight = {};
            context2DsByWidthAndHeight[width] = context2DsByHeight;
        }

        var context2d = context2DsByHeight[height];
        if (typeof context2d === 'undefined') {
            var canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;
            context2d = canvas.getContext('2d');
            context2d.globalCompositeOperation = 'copy';
            context2DsByHeight[height] = context2d;
        }

        context2d.drawImage(image, 0, 0, width, height);
        return context2d.getImageData(0, 0, width, height).data;
    };

    return getImagePixels;
});

Make function name match the module name.


import React from 'react';
import _ from 'lodash';

function dissoc(obj, key) {
  const newObj = _.clone(obj);
  delete newObj[key];
  return newObj;
}

export default function NodeShapeStack(props) {
  const propsNoHighlight = dissoc(props, 'highlighted');
  const propsOnlyHighlight = Object.assign({}, props, {onlyHighlight: true});

  const Shape = props.shape;
  const [dx, dy] = [0, 4];
  const dsx = (props.size * 2 + (dx * 2)) / (props.size * 2);
  const dsy = (props.size * 2 + (dy * 2)) / (props.size * 2);
  const hls = [dsx, dsy];

  return (
    <g transform={`translate(${dx * -1}, ${dy * -1})`} className="stack">
      <g transform={`scale(${hls})translate(${dx}, ${dy}) `} className="stack">
        <Shape {...propsOnlyHighlight} />
      </g>
      <g transform={`translate(${dx * 2}, ${dy * 2})`}>
        <Shape {...propsNoHighlight} />
      </g>
      <g transform={`translate(${dx * 1}, ${dy * 1})`}>
        <Shape {...propsNoHighlight} />
      </g>
      <Shape {...propsNoHighlight} />
    </g>
  );
}

import React from 'react';
import _ from 'lodash';

function dissoc(obj, key) {
  const newObj = _.clone(obj);
  delete newObj[key];
  return newObj;
}

export default function NodeShapeStack(props) {
  const propsNoHighlight = dissoc(props, 'highlighted');
  const propsOnlyHighlight = Object.assign({}, props, {onlyHighlight: true});

  const Shape = props.shape;
  const [dx, dy] = [0, 4];
  const dsx = (props.size * 2 + (dx * 2)) / (props.size * 2);
  const dsy = (props.size * 2 + (dy * 2)) / (props.size * 2);
  const hls = [dsx, dsy];

  return (
    <g transform={`translate(${dx * -1}, ${dy * -2})`} className="stack">
      <g transform={`scale(${hls})translate(${dx}, ${dy}) `} className="stack">
        <Shape {...propsOnlyHighlight} />
      </g>
      <g transform={`translate(${dx * 2}, ${dy * 2})`}>
        <Shape {...propsNoHighlight} />
      </g>
      <g transform={`translate(${dx * 1}, ${dy * 1})`}>
        <Shape {...propsNoHighlight} />
      </g>
      <Shape {...propsNoHighlight} />
    </g>
  );
}

Increase space between stacked nodes and their labels


define( [ "store/TwitchSerializer" ], function( TwitchSerializer ) {

	return TwitchSerializer.extend({
		primaryKey: "name",

		modelNameFromPayloadKey: function() {
			return "twitchChannel";
		},

		attrs : {
			teams: { deserialize: "records" }
		},

		normalizeResponse: function( store, primaryModelClass, payload, id, requestType ) {
			payload = {
				twitchChannel: payload
			};

			return this._super( store, primaryModelClass, payload, id, requestType );
		},

		normalize: function( modelClass, resourceHash, prop ) {
			// Twitch.tv API bug?
			// Sometimes a user record (/user/:user - model not implemented) is embedded into
			// a stream record instead of a channels record (/channels/:channel - the current model)
			if ( resourceHash ) {
				delete resourceHash.bio;
				delete resourceHash.type;

				// also remove the teams property for now
				delete resourceHash.teams;
			}

			return this._super( modelClass, resourceHash, prop );
		}
	});

});

define( [ "store/TwitchSerializer" ], function( TwitchSerializer ) {

	return TwitchSerializer.extend({
		primaryKey: "name",

		modelNameFromPayloadKey: function() {
			return "twitchChannel";
		},

		normalizeResponse: function( store, primaryModelClass, payload, id, requestType ) {
			payload = {
				twitchChannel: payload
			};

			return this._super( store, primaryModelClass, payload, id, requestType );
		},

		normalize: function( modelClass, resourceHash, prop ) {
			// Twitch.tv API bug?
			// Sometimes a user record (/user/:user - model not implemented) is embedded into
			// a stream record instead of a channels record (/channels/:channel - the current model)
			if ( resourceHash ) {
				delete resourceHash.bio;
				delete resourceHash.type;

				// also remove the teams property for now
				delete resourceHash.teams;
			}

			return this._super( modelClass, resourceHash, prop );
		}
	});

});

Fix warning message caused by d5ee6ed


import EditFormRoute from './edit-form';

export default EditFormRoute.extend({
  model: function() {
    // NOTE: record.id is null.
    var record = this.store.createRecord(this.modelName);
    return record;
  },

  renderTemplate: function(controller, model) {
    this.render(this.modelName, {
      model: model
    });
  },

  deactivate: function() {
    var model = this.get('controller').get('model');
    model.rollback();

    if (model.get('isNew')) {
      model.deleteRecord();
    }
  }
});

import EditFormRoute from './edit-form';

export default EditFormRoute.extend({
  model: function() {
    // NOTE: record.id is null.
    var record = this.store.createRecord(this.modelName);
    return record;
  },

  renderTemplate: function(controller, model) {
    this.render(this.modelName, {
      model: model,
      controller
    });
  },

  deactivate: function() {
    var model = this.get('controller').get('model');
    model.rollback();

    if (model.get('isNew')) {
      model.deleteRecord();
    }
  }
});

Fix using of wrong controller instance


import XFlow              from './xflow';
import XFlowStruct        from './xflow-struct';
import XFlowMutableStruct from './xflow-mutable-struct';
import XFlowDispatcher    from './xflow-dispatcher';
import XFlowFactory       from './xflow-factory';
import XFlowValidator     from './xflow-validator';
import XFlowRunner        from './xflow-runner';

function getXFlowFactory() {
  return (new XFlowFactory(
    new XFlowDispatcher()
  ));
}

function getXFlowRunner() {
  return (new XFlowRunner(
    getXFlowFactory()
  ));
}

export default {
  XFlow              : XFlow,
  XFlowStruct        : XFlowStruct,
  XFlowMutableStruct : XFlowMutableStruct,
  XFlowDispatcher    : XFlowDispatcher,
  XFlowFactory       : XFlowFactory,
  XFlowValidator     : XFlowValidator,
  getXFlowRunner     : getXFlowRunner
};

import XFlow              from './xflow';
import XFlowStruct        from './xflow-struct';
import XFlowMutableStruct from './xflow-mutable-struct';
import XFlowDispatcher    from './xflow-dispatcher-dynamic';
import XFlowFactory       from './xflow-factory';
import XFlowValidator     from './xflow-validator';
import XFlowRunner        from './xflow-runner';
import XFlowHarness       from './xflow-harness';

export default {
  XFlow              : XFlow,
  XFlowStruct        : XFlowStruct,
  XFlowMutableStruct : XFlowMutableStruct,
  XFlowDispatcher    : XFlowDispatcher,
  XFlowFactory       : XFlowFactory,
  XFlowRunner        : XFlowRunner,
  XFlowHarness       : XFlowHarness,
  XFlowValidator     : XFlowValidator
};

Update browser target build with new classes


// (c) Copyright 2002-2012 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$

// JS glue for jCapture feature

function openJCaptureDialog() {

	var $appletDiv = $("#jCaptureAppletDiv");
	if ($appletDiv.length) {
		$appletDiv.remove();
	}

	$appletDiv = $("<div id='jCaptureAppletDiv'> </div>");
	$(document.body).append($appletDiv);

	$.get($.service('jcapture', 'capture'), {
			area: "none",
			page: "test"
	}, function(data){
		$appletDiv.html(data);
	});

//	} else {	TODO later
//		$appletDiv[0].showCaptureFrame();
//	}

}

function insertAtCarret( areaId, dokuTag ) {
	var tag = dokuTag.subString(3, dokuTag.length - 3);
	alert("file : " + tag);
	if (areaId) {
		insertAt( areaId, tag);
	}
}

// (c) Copyright 2002-2012 by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$

// JS glue for jCapture feature

function openJCaptureDialog() {

	var $appletDiv = $("#jCaptureAppletDiv");
	if ($appletDiv.length) {
		$appletDiv.remove();
	}

	$appletDiv = $("<div id='jCaptureAppletDiv' style='width:1px;height:1px;visibility:hidden;'> </div>");
	$(document.body).append($appletDiv);

	$.get($.service('jcapture', 'capture'), {
			area: "none",
			page: "test"
	}, function(data){
		$appletDiv.html(data);
	});

//	} else {	TODO later
//		$appletDiv[0].showCaptureFrame();
//	}

}

function insertAtCarret( areaId, dokuTag ) {
	var tag = dokuTag.substring(3, dokuTag.length - 3);
	alert("file : " + tag);
	if (areaId) {
		insertAt( areaId, tag);
	}
}

Implement token authorisation and reconfigure file data to upload into filegallery


'use strict';

var ascii_art = require('../../ascii-art.js');

function messageListener(db, from, channel, message) {
  var img_regex = /^https?:\/\/[^ ]+\.(png|jpg|gif)$/i;

  if (img_regex.test(message)) {
    var img_url = message;

    return [{
      callback: function(say) {
        ascii_art(img_url, function(art) {
          say(channel, art);
        });
      }
    }];
  }
}

module.exports = messageListener;

'use strict';

var ascii_art = require('../../ascii-art.js');

function messageListener(db, from, channel, message) {
  var img_regex = /^ascii +https?:\/\/[^ ]+\.(png|jpg|gif)$/i;

  if (img_regex.test(message)) {
    var img_url = message.replace(/^ascii +/, '');

    return [{
      callback: function(say) {
        ascii_art(img_url, function(ascii) {
          var lines = ascii.split("\n");

          slowly_say(say, channel, lines);
        });
      }
    }];
  }
}

function slowly_say(say, channel, lines) {
  var line = lines.shift();

  if (line) {
    say(channel, line);
    setTimeout(function() {slowly_say(say, channel, lines);}, 1000);
  }
}

module.exports = messageListener;

Add time delay before each line of ascii art


/* eslint-disable no-var */
var config = window.tresdb.config
var urljoin = require('url-join')

exports.attachmentUrl = function (attachment) {
  return urljoin(config.uploadUrl, attachment.filepath)
}

exports.attachmentThumbUrl = function (attachment) {
  return urljoin(config.uploadUrl, attachment.thumbfilepath)
}

exports.locationTypeToSymbolUrl = function (type) {
  var baseUrl = '/assets/images/markers/symbols/'
  return baseUrl + type + '.png'
}

exports.markerIconUrl = function (templateName, symbolName) {
  var iconName = templateName + '-' + symbolName + '.png'
  return '/api/icons/' + iconName
}

/* eslint-disable no-var */

exports.locationTypeToSymbolUrl = function (type) {
  var baseUrl = '/assets/images/markers/symbols/'
  return baseUrl + type + '.png'
}

exports.markerIconUrl = function (templateName, symbolName) {
  var iconName = templateName + '-' + symbolName + '.png'
  return '/api/icons/' + iconName
}

Remove attachment url methods from tresdb-urls in favour of server-side


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

/**
 *
 */
const DescriptiveButton = (props) => {
  const { imageUrl, label, svgIcon, description, onClick, className, id } = props;
  let visualContent = null;
  if (imageUrl) {
    visualContent = ();
  } else if (svgIcon) {
    visualContent = svgIcon;
  }
  return (
    <div
      id={id}
      className={`descriptive-button ${className || ''}`}
      role="button"
      onClick={onClick}
      tabIndex={0}
      onKeyDown={() => null}
    >

        {visualContent}

      {label}

{description}

  );
};

DescriptiveButton.propTypes = {
  // from parent
  imageUrl: PropTypes.string,
  svgIcon: PropTypes.node,
  label: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired,
  onClick: PropTypes.func.isRequired,
  className: PropTypes.string,
  id: PropTypes.string,
};

export default DescriptiveButton;

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

/**
 *
 */
const DescriptiveButton = (props) => {
  const { imageUrl, label, svgIcon, description, onClick, className, id } = props;
  let visualContent = null;
  if (imageUrl) {
    visualContent = ();
  } else if (svgIcon) {
    visualContent = svgIcon;
  }
  return (
    <div
      id={id}
      className={`descriptive-button ${className || ''}`}
      role="button"
      onClick={onClick}
      tabIndex={0}
      onKeyDown={() => null}
    >

        {visualContent}

      {label}

{description}

  );
};

DescriptiveButton.propTypes = {
  // from parent
  imageUrl: PropTypes.string,
  svgIcon: PropTypes.node,
  label: PropTypes.string.isRequired,
  description: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.node,
  ]).isRequired,
  onClick: PropTypes.func.isRequired,
  className: PropTypes.string,
  id: PropTypes.string,
};

export default DescriptiveButton;

Support html on descriptive buttons


import React from 'react';
import expect from 'expect';
import { mount } from 'enzyme';
import styles from '../../src/chat.scss';
import Message from '../../src/components/messages/Message';

const props = {
  message:
  {
    id: 1,
    name: 'John',
    avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/oagra/128.jpg',
    msg: 'Hello, Marry!',
    time: 1444428192,
    sender: 1
  },
  me: { id: 2 },
  isMine: id => props.me.id === id,
  withPhoto: true
};

describe('Message', () => {
  it('should render message', () => {
    const wrapper = mount(<Message {...props} />);
    expect(wrapper.find('div').at(0).prop('className')).toBe(styles.msgBox);
    const parentDiv = wrapper.find('.' + styles.msgBox).children();
    expect(parentDiv.length).toBe(3);
    expect(wrapper.find('.' + styles.msgBox).childAt(0).prop('className')).toBe(styles.avatar);
  });
});

import React from 'react';
import expect from 'expect';
import { mount } from 'enzyme';
import styles from '../../src/chat.scss';
import Message from '../../src/components/messages/Message';

const props = {
  message:
  {
    id: 1,
    name: 'John',
    avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/oagra/128.jpg',
    msg: 'Hello, Marry!',
    time: 1444428192,
    sender: 1
  },
  me: { id: 2 },
  isMine: id => props.me.id === id,
  withPhoto: true,
  onDelete: (message, success) => {
    success();
  }
};

describe('Message', () => {
  it('should render message', () => {
    const wrapper = mount(<Message {...props} />);
    expect(wrapper.find('div').at(0).prop('className')).toBe(styles.msgBox);
    const parentDiv = wrapper.find('.' + styles.msgBox).children();
    expect(parentDiv.length).toBe(3);
    expect(wrapper.find('.' + styles.msgBox).childAt(0).prop('className')).toBe(styles.avatar);
  });
});

describe('Message', () => {
  it('should delete message', () => {
    const wrapper = mount(<Message {...props} />);
    expect(wrapper.node.state.deleted).toBe(false);
    wrapper.find('.' + styles.msgOptions).childAt(0).simulate('click');
    expect(wrapper.node.state.deleted).toBe(true);
  });
});

Add test for deleting message


describe('Actor', function() {
    var actor;

    beforeEach(function() {
        actor = new Actor();
    });

    it('Actor 생성 특성', function() {
        expect(actor.getHp()).toBe(100);
        expect(actor.getTeam()).not.toBeDefined();
        expect(actor.getIx()).not.toBeDefined();
        expect(actor.getIy()).not.toBeDefined();
        expect(function() {
            actor.setIx(1);
        }).toThrow(new Error('Not attached to world'));
        expect(function() {
            actor.setIy(2);
        }).toThrow(new Error('Not attached to world'));
    });

    it('낄낄낄', function() {
        fail();
    })
});

describe('Actor', function() {
    var actor;

    beforeEach(function() {
        actor = new Actor();
    });

    it('Actor 생성 특성', function() {
        expect(actor.getHp()).toBe(100);
        expect(actor.getTeam()).not.toBeDefined();
        expect(actor.getIx()).not.toBeDefined();
        expect(actor.getIy()).not.toBeDefined();
        expect(function() {
            actor.setIx(1);
        }).toThrow(new Error('Not attached to world'));
        expect(function() {
            actor.setIy(2);
        }).toThrow(new Error('Not attached to world'));
    });
});

Revert "Travis CI 실패 일어나는 것 테스트를 위해 일부러 실패시킴"


import Ember from 'ember';
import JSONFormatterPkg from 'npm:json-formatter-js';

const { Component, computed, run } = Ember;

// json-formatter-js is packaged in a funny way that ember-cli-browserify
// doesn't unwrap properly.
const { default: JSONFormatter } = JSONFormatterPkg;

export default Component.extend({
  classNames: ['json-viewer'],

  json: null,
  expandDepth: 2,

  formatter: computed('json', 'expandDepth', function() {
    return new JSONFormatter(this.get('json'), this.get('expandDepth'), {
      theme: 'nomad',
    });
  }),

  didReceiveAttrs() {
    const json = this.get('json');
    if (!json) {
      return;
    }

    run.scheduleOnce('afterRender', this, embedViewer);
  },
});

function embedViewer() {
  this.$().empty().append(this.get('formatter').render());
}

import Ember from 'ember';
import JSONFormatterPkg from 'npm:json-formatter-js';

const { Component, computed, run } = Ember;

// json-formatter-js is packaged in a funny way that ember-cli-browserify
// doesn't unwrap properly.
const { default: JSONFormatter } = JSONFormatterPkg;

export default Component.extend({
  classNames: ['json-viewer'],

  json: null,
  expandDepth: Infinity,

  formatter: computed('json', 'expandDepth', function() {
    return new JSONFormatter(this.get('json'), this.get('expandDepth'), {
      theme: 'nomad',
    });
  }),

  didReceiveAttrs() {
    const json = this.get('json');
    if (!json) {
      return;
    }

    run.scheduleOnce('afterRender', this, embedViewer);
  },
});

function embedViewer() {
  this.$()
    .empty()
    .append(this.get('formatter').render());
}

Expand the entire job definition by default


import React from 'react'
import styled from 'styled-components'
import { NavLink as Link } from 'react-router-dom'

const Nav = styled.nav`
  display: flex;
  justify-content: center;
`

const NavLink = styled(Link)`
  margin: 0 12px;
  font-size: 1.8rem;
  text-decoration: none;
  color: var(--gray700);

  &:hover,
  &:focus {
    color: var(--gray900);
  }

  &.${props => props.activeClassName} {
    color: var(--gray900);
    text-decoration: line-through;
  }
`

export default () => (
  <Nav>
    <NavLink exact to="/" activeClassName="active">
      Home
    </NavLink>
    <NavLink to="/about" activeClassName="active">
      About
    </NavLink>
  </Nav>
)

import React from 'react'
import styled from 'styled-components'
import { NavLink as Link } from 'react-router-dom'

const Nav = styled.nav`
  display: flex;
  justify-content: center;
`

const NavLink = styled(Link)`
  margin: 0 12px;
  font-size: 1.8rem;
  text-decoration: none;
  color: var(--gray700);

  &:hover,
  &:focus {
    color: var(--gray900);
  }

  &[aria-current='true'] {
    color: var(--gray900);
    text-decoration: line-through;
  }
`

export default () => (
  <Nav>
    <NavLink exact to="/">
      Home
    </NavLink>
    <NavLink to="/about">About</NavLink>
  </Nav>
)

Use aria-current selector instead of class


import React from 'react'
import PropTypes from 'prop-types'

import Head from 'v2/components/UI/Head'

const Description = ({ children: description }) => (
  <Head>
    <meta property="description" content={description} />
    <meta name="twitter:description" content={description} />
    <meta property="og:description" content={description} />
  </Head>
)

Description.propTypes = {
  children: PropTypes.string,
}

Description.defaultProps = {
  children:
    'Are.na is a social platform for creative and collaborative research.',
}

export default Description

import React from 'react'
import PropTypes from 'prop-types'

import Head from 'v2/components/UI/Head'

const Description = ({ children: description }) => (
  <Head>
    <meta name="description" content={description} />
    <meta name="twitter:description" content={description} />
    <meta property="og:description" content={description} />
  </Head>
)

Description.propTypes = {
  children: PropTypes.string,
}

Description.defaultProps = {
  children:
    'Are.na is a social platform for creative and collaborative research.',
}

export default Description

Change meta attribute to name, not property


include('ringo/unittest');
var changeMe = require('namespace/skeleton');

exports.testSomething = function () {
    assertTrue(true);
};

if (require.main == module.id) {
    require('ringo/unittest').run(exports);
}

Fix test scaffold (missing `include`).


"use strict";

import Controller from "./Controller";
import View from "./View";

// private properties
const _controller = Symbol();

export default class ViewController extends View {
    constructor(options) {
        super(options);
        let {view} = options;
        this[_controller] = new Controller(options);
        if (view) { this.addSubview(view); }
    }

    get controller() {
        return this[_controller];
    }
    get model() {
        return this.controller && this.controller.model;
    }
    get view() {
        return this.controller && this.controller.view;
    }

    destroy() {
        if (this.controller) {
            this.controller.destroy();
        }
        this.controller = null;
        super.destroy();
    }

}

"use strict";

import Controller from "./Controller";
import View from "./View";

// private properties
const _controller = Symbol();

export default class ViewController extends View {
    constructor(options = {}) {
        super(options);
        let {view} = options;
        this[_controller] = new Controller(options);
        if (view) { this.addSubview(view); }
    }

    get controller() {
        return this[_controller];
    }
    get model() {
        return this.controller && this.controller.model;
    }
    get view() {
        return this.controller && this.controller.view;
    }

    destroy() {
        if (this.controller) {
            this.controller.destroy();
        }
        this[_controller] = null;
        super.destroy();
    }

}

Fix no default; .controller is r/o


define( ['backbone', 'backbone.marionette', 'communicator', 'velocity',
         'views/layout/app.layout', 'views/layout/flyouts.layout'],

  function( Backbone, Marionette, Communicator, V,
            AppLayout, FlyoutsLayout ) {
    'use strict';

    var App = new Marionette.Application();

    var container = new Marionette.Region( {
      el: "#erfgeoviewer"
    } );

    App.layout = new AppLayout();
    App.layout.render();
    container.show( App.layout );

    App.flyouts = new FlyoutsLayout();
    App.flyouts.render();
    App.layout.getRegion('flyout').show( App.flyouts );

    Communicator.mediator.on("map:tile-layer-clicked", function() {
      if (!App.flyouts.getRegion('detail').hasView() || !App.flyouts.getRegion('detail').isVisible() ) {
        App.flyouts.getRegion('right').hideFlyout();
      }
      App.flyouts.getRegion('bottom').hideFlyout();
      App.flyouts.getRegion('detail').hideFlyout();
    });

    App.on( "start", function() {
      Backbone.history.start();
    } );

    return App;
  } );

define( ['backbone', 'backbone.marionette', 'communicator', 'velocity',
         'views/layout/app.layout', 'views/layout/flyouts.layout'],

  function( Backbone, Marionette, Communicator, V,
            AppLayout, FlyoutsLayout ) {
    'use strict';

    var App = new Marionette.Application(),
      container = new Marionette.Region( {
        el: "#erfgeoviewer"
      }),
      closeOnClick = true;

    App.layout = new AppLayout();
    App.layout.render();
    container.show( App.layout );

    App.flyouts = new FlyoutsLayout();
    App.flyouts.render();
    App.layout.getRegion('flyout').show( App.flyouts );

    Communicator.mediator.on('map:setCloseOnClick', function(value) {
      closeOnClick = value;
    });

    Communicator.mediator.on("map:tile-layer-clicked", function() {
      if (!closeOnClick) {
        return;
      }

      if (!App.flyouts.getRegion('detail').hasView() || !App.flyouts.getRegion('detail').isVisible() ) {
        App.flyouts.getRegion('right').hideFlyout();
      }
      App.flyouts.getRegion('bottom').hideFlyout();
      App.flyouts.getRegion('detail').hideFlyout();
    });

    App.on( "start", function() {
      Backbone.history.start();
    } );

    return App;
  } );

Add API for preventing close on click behavoir on map.


/**
 * @license
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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() {
  'use strict';

  Polymer({
    is: 'gr-app-p2',
  });
})();

/**
 * @license
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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() {
  'use strict';

  // Disable extra font load from paper-styles
  window.polymerSkipLoadingFontRoboto = true;

  Polymer({
    is: 'gr-app-p2',
  });
})();

Disable the extra font loading for paper-styles


// eslint-disable-next-line
WebFontConfig = {
  typekit: {
    id: ''
  },
  google: {
    families: []
  },
  timeout: 2000,
  active: function() { clearFout(); },
  inactive: function() { clearFout(); }
};

function clearFout() {
  sessionStorage.fonts = true;
}

// Uncomment the lines below and remove line 'clearFout()' once webfonts have been included.

(function(d) {
  // var wf = d.createElement('script'), s = d.scripts[0];
  // wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.16/webfont.js';
  // s.parentNode.insertBefore(wf, s);
  clearFout()
  if(sessionStorage.fonts) {
    d.getElementsByTagName('html')[0].classList.add('wf-active');
  }
})(document);

// eslint-disable-next-line
WebFontConfig = {
  google: {
    families: ['Droid Sans', 'Droid Serif']
  },
  timeout: 2000,
  active: function() { clearFout(); },
  inactive: function() { clearFout(); }
};

function clearFout() {
  sessionStorage.fonts = true;
}

(function(d) {
  var wf = d.createElement('script'), s = d.scripts[0];
  wf.src = 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.16/webfont.js';
  s.parentNode.insertBefore(wf, s);
  if(sessionStorage.fonts) {
    d.getElementsByTagName('html')[0].classList.add('wf-active');
  }
})(document);

Swap out comments in webfont loader for real example


$(document).ready(function() {
  showCurrentUserInfo();
  setImageFormAction();
  populatePrefilled('interests');
  populatePrefilled('skills');
});

/**
 * Populates inputs with current user information.
 */
async function showCurrentUserInfo() {
  const response = await fetch('/profile-update');
  const userData = await response.json();
  const userName = userData['name'];

  $('#name').val(userName);
  populateExisting('interests', userData);
  populateExisting('skills', userData);
  populateExistingImage('profile', '#profile-picture');
}

/**
 * Populates inputs with labels that already exist
 */
function populateExisting(className, userData) {
  const existingLabels = userData[className];
  for (const label of existingLabels) {
    $(`#${className}`).tagsinput('add', label);
  }
}

function removeAllExtraInputs() {
  removeExtraInputs('#interests');
  removeExtraInputs('#skills');
}

function getInterests() {
  return getLabels('interests');
}

function getSkills() {
  return getLabels('skills');
}

function getLabels(labelType) {
  let labels = [];
  for (const label of $(`#${labelType}`).children()) {
    labels.push(label.value);
  }
  return labels;
}

$(document).ready(function() {
  showCurrentUserInfo();
  setImageFormAction('profile');
  populatePrefilled('interests');
  populatePrefilled('skills');
});

/**
 * Populates inputs with current user information.
 */
async function showCurrentUserInfo() {
  const response = await fetch('/profile-update');
  const userData = await response.json();
  const userName = userData['name'];

  $('#name').val(userName);
  populateExisting('interests', userData);
  populateExisting('skills', userData);
  populateExistingImage('profile', '#profile-picture');
}

/**
 * Populates inputs with labels that already exist
 */
function populateExisting(className, userData) {
  const existingLabels = userData[className];
  for (const label of existingLabels) {
    $(`#${className}`).tagsinput('add', label);
  }
}

function removeAllExtraInputs() {
  removeExtraInputs('#interests');
  removeExtraInputs('#skills');
}

function getInterests() {
  return getLabels('interests');
}

function getSkills() {
  return getLabels('skills');
}

function getLabels(labelType) {
  let labels = [];
  for (const label of $(`#${labelType}`).children()) {
    labels.push(label.value);
  }
  return labels;
}

Fix profile edit functions for blobstore


$(document).ready(function() {
  showCurrentUserInfo();
  setImageFormAction('profile');
  populatePrefilled('interests');
  populatePrefilled('skills');
});

/**
 * Populates inputs with current user information.
 */
async function showCurrentUserInfo() {
  const response = await fetch('/profile-update');
  const userData = await response.json();
  const userName = userData['name'];

  $('#name').val(userName);
  populateExisting('interests', userData);
  populateExisting('skills', userData);
  populateExistingImage('profile', '#profile-picture');
  populateExistingImage('profile', '#profile-picture-main');
}

/**
 * Populates inputs with labels that already exist
 */
function populateExisting(className, userData) {
  const existingLabels = userData[className];
  for (const label of existingLabels) {
    $(`#${className}`).tagsinput('add', label);
  }
}

function removeAllExtraInputs() {
  removeExtraInputs('#interests');
  removeExtraInputs('#skills');
}

function getInterests() {
  return getLabels('interests');
}

function getSkills() {
  return getLabels('skills');
}

function getLabels(labelType) {
  let labels = [];
  for (const label of $(`#${labelType}`).children()) {
    labels.push(label.value);
  }
  return labels;
}

$(document).ready(function() {
  showCurrentUserInfo();
  setImageFormAction('profile');
  populatePrefilled('interests');
  populatePrefilled('skills');
});

/**
 * Populates inputs with current user information.
 */
async function showCurrentUserInfo() {
  const response = await fetch('/profile-update');
  const userData = await response.json();

  $('#name').val(userData['name']);
  $('#email').text(userData['email']);
  populateExisting('interests', userData);
  populateExisting('skills', userData);
  populateExistingImage('profile', '#profile-picture');
  populateExistingImage('profile', '#profile-picture-main');
}

/**
 * Populates inputs with labels that already exist
 */
function populateExisting(className, userData) {
  const existingLabels = userData[className];
  for (const label of existingLabels) {
    $(`#${className}`).tagsinput('add', label);
  }
}

function removeAllExtraInputs() {
  removeExtraInputs('#interests');
  removeExtraInputs('#skills');
}

function getInterests() {
  return getLabels('interests');
}

function getSkills() {
  return getLabels('skills');
}

function getLabels(labelType) {
  let labels = [];
  for (const label of $(`#${labelType}`).children()) {
    labels.push(label.value);
  }
  return labels;
}

Add e-mail for user profile edit form


$(document).ready(function () {
    google.charts.load('current', { packages: ['corechart', 'bar'] });
    google.charts.setOnLoadCallback(drawChart);

    function drawChart() {
        var elem = $('#pie_chart').get(0);
        var headerParts = elem.dataset.header.split(',');
        var data = [[headerParts[0], headerParts[1]]];
        var values = elem.dataset.values.split(';');
        var names = [];
        values.forEach(function (str, _, _) {
            var all = str.split(',');
            data.push([all[0], parseInt(all[1])]);
            names.push(all[0]);
        });

        var dataTable = google.visualization.arrayToDataTable(data);
        var options = {
            title: elem.dataset.title,
        };

        var chart = new google.visualization.PieChart(elem);
        chart.draw(dataTable, options);

        google.visualization.events.addListener(chart, 'select', function () {
            var selectedItem = chart.getSelection()[0];
            if (selectedItem) {
                var name = names[selectedItem.row];
                $('#filter_assignee').attr('value', name);
                $('#filter_form').submit()
            }
        });
    }
});

$(document).ready(function () {
    google.charts.load('current', { packages: ['corechart', 'bar'] });
    google.charts.setOnLoadCallback(drawChart);

    function drawChart() {
        var elem = $('#pie_chart').get(0);
        var headerParts = elem.dataset.header.split(',');
        var data = [[headerParts[0], headerParts[1]]];
        var values = elem.dataset.values.split(';');
        var names = [];
        values.forEach(function (str, _, _) {
            var all = str.split(',');
            data.push([all[0], parseInt(all[1])]);
            names.push(all[0]);
        });

        var dataTable = google.visualization.arrayToDataTable(data);
        var options = {
            title: elem.dataset.title,
            pieSliceText: 'value',
        };

        var chart = new google.visualization.PieChart(elem);
        chart.draw(dataTable, options);

        google.visualization.events.addListener(chart, 'select', function () {
            var selectedItem = chart.getSelection()[0];
            if (selectedItem) {
                var name = names[selectedItem.row];
                $('#filter_assignee').attr('value', name);
                $('#filter_form').submit()
            }
        });
    }
});

Use numbers instead of percent in chart


import React, { PropTypes } from 'react';

import LanguageLink from '../../components/LanguageLink/LanguageLink';

import './Guide.scss';
import 'highlight.js/styles/default.css';

const Guide = (props) => (

{props.title}

        {props.languageOptions.map(({ language, url }) => (
          <LanguageLink
            key={language}
            language={language}
            url={url}
            isActive={language === props.language}
            isShowEmpty={false}
          />
        ))}

);

Guide.propTypes = {
  title: PropTypes.string,
  docHtml: PropTypes.string,
  languageOptions: PropTypes.array,
  language: PropTypes.string.isRequired,
};

export default Guide;

import React, { PropTypes } from 'react';

import LanguageLink from '../../components/LanguageLink/LanguageLink';

import './Guide.scss';
import 'highlight.js/styles/xcode.css';

const Guide = (props) => (

{props.title}

        {props.languageOptions.map(({ language, url }) => (
          <LanguageLink
            key={language}
            language={language}
            url={url}
            isActive={language === props.language}
            isShowEmpty={false}
          />
        ))}

);

Guide.propTypes = {
  title: PropTypes.string,
  docHtml: PropTypes.string,
  languageOptions: PropTypes.array,
  language: PropTypes.string.isRequired,
};

export default Guide;

Use xcode theme instead of default for highlight.js


#!/usr/bin/env node

var app = require('commander')
var moment = require('moment')
var getLists = require('./lib/get-lists')
var printList = require('./lib/print-list')
var auth = require('./lib/auth')

app
  .description('View tasks due today')
  .parse(process.argv)

function main () {
  getLists(function (err, data) {
    if (err) process.exit(1)

    var today = moment().format('YYYY-MM-DD')

    data.forEach(function (list) {
      list.tasks = list.tasks.filter(function (item) {
        if (!item.due_date) return false
        return item.due_date === today
      })

      printList(list)
    })
  })
}

auth(main)

#!/usr/bin/env node

var app = require('commander')
var moment = require('moment')
var getLists = require('./lib/get-lists')
var printList = require('./lib/print-list')
var auth = require('./lib/auth')

app
  .description('View tasks due today')
  .parse(process.argv)

function main () {
  getLists(function (err, data) {
    if (err) process.exit(1)

    var today = new Date()

    data.forEach(function (list) {
      list.tasks = list.tasks.filter(function (item) {
        if (!item.due_date) return false
        var due = new Date(item.due_date)
        return due.getTime() <= today.getTime()
      })

      printList(list)
    })
  })
}

auth(main)

Include overdue items in the "today" smart list


// @flow

import * as React from "react";

export default ({ App, render }: PhenomicHtmlPropsType) => {
  const { html, Main, State, Script, Style, assets } = render(<App />);

  const styles =
    process.env.PHENOMIC_ENV !== "static" ? (
      <Style />
    ) : (
      <style
        // @idea use https://github.com/addyosmani/critical
        dangerouslySetInnerHTML={{
          __html: Object.keys(assets)
            .reduce((acc, name) => acc.concat(assets[name]), [])
            .filter(asset => asset.endsWith(".css"))
            .map(css =>
              // @todo try to get /dist/ from config somehow
              require("fs").readFileSync("./dist/" + css, { encoding: "utf8" })
            )
            .join("")
            .replace(/\n/, "")
        }}
      />
    );

  return (
    <html>
      <head>{styles}</head>
      <body>
        <Main />
        <State />
        <Script />
      </body>
    </html>
  );
};

// @flow

import fs from "fs";

import * as React from "react";

// memory to avoid reading a css file already read
// @todo try to get ./dist/ from config somehow
const stylesMemory = {};
const readCssFile = css => {
  if (!stylesMemory[css])
    stylesMemory[css] = fs.readFileSync("./dist/" + css, { encoding: "utf8" });
  return stylesMemory[css];
};

export default ({ App, render }: PhenomicHtmlPropsType) => {
  const { html, Main, State, Script, Style, assets } = render(<App />);

  return (
    <html>
      <head>
        {process.env.PHENOMIC_ENV !== "static" ? (
          <Style />
        ) : (
          <style
            // @idea use https://github.com/addyosmani/critical
            dangerouslySetInnerHTML={{
              __html: Object.keys(assets)
                .reduce((acc, name) => acc.concat(assets[name]), [])
                .filter(asset => asset.endsWith(".css"))
                .map(css => readCssFile(css))
                .join("")
            }}
          />
        )}
      </head>
      <body>
        <Main />
        <State />
        <Script />
      </body>
    </html>
  );
};

Enhance css inlined in head example to avoid same file on the fs multiple times