Fix missing methods for displaying tags on archived toggles

Fixes #233

--- src/component/archive/view-container.js
+++ src/component/archive/view-container.js
@@ -2,6 +2,7 @@
 import { fetchArchive, revive } from './../../store/archive/actions';
 import ViewToggleComponent from './../feature/view/view-component';
 import { hasPermission } from '../../permissions';
+import { fetchTags } from '../../store/feature-tags/actions';

 export default connect(
     (state, props) => ({
@@ -10,11 +11,14 @@
             .get('list')
             .toArray()
             .find(toggle => toggle.name === props.featureToggleName),
+        tagTypes: state.tagTypes.toJS(),
+        featureTags: state.featureTags.toJS(),
         activeTab: props.activeTab,
         hasPermission: hasPermission.bind(null, state.user.get('profile')),
     }),
     {
         fetchArchive,
         revive,
+        fetchTags,
     }
 )(ViewToggleComponent);

<|original_code|>
import { connect } from 'react-redux';
import { fetchArchive, revive } from './../../store/archive/actions';
import ViewToggleComponent from './../feature/view/view-component';
import { hasPermission } from '../../permissions';

export default connect(
    (state, props) => ({
        features: state.archive.get('list').toArray(),
        featureToggle: state.archive
            .get('list')
            .toArray()
            .find(toggle => toggle.name === props.featureToggleName),
        activeTab: props.activeTab,
        hasPermission: hasPermission.bind(null, state.user.get('profile')),
    }),
    {
        fetchArchive,
        revive,
    }
)(ViewToggleComponent);

<|edits_diff|>
--- src/component/archive/view-container.js
+++ src/component/archive/view-container.js
@@ -2,6 +2,7 @@
 import { fetchArchive, revive } from './../../store/archive/actions';
 import ViewToggleComponent from './../feature/view/view-component';
 import { hasPermission } from '../../permissions';
+import { fetchTags } from '../../store/feature-tags/actions';

 export default connect(
     (state, props) => ({
@@ -10,6 +11,8 @@
             .get('list')
             .toArray()
             .find(toggle => toggle.name === props.featureToggleName),
+        tagTypes: state.tagTypes.toJS(),
+        featureTags: state.featureTags.toJS(),
         activeTab: props.activeTab,
         hasPermission: hasPermission.bind(null, state.user.get('profile')),
     }),
<|current_version|>
import { connect } from 'react-redux';
import { fetchArchive, revive } from './../../store/archive/actions';
import ViewToggleComponent from './../feature/view/view-component';
import { hasPermission } from '../../permissions';
import { fetchTags } from '../../store/feature-tags/actions';

export default connect(
    (state, props) => ({
        features: state.archive.get('list').toArray(),
        featureToggle: state.archive
            .get('list')
            .toArray()
            .find(toggle => toggle.name === props.featureToggleName),
        tagTypes: state.tagTypes.toJS(),
        featureTags: state.featureTags.toJS(),
        activeTab: props.activeTab,
        hasPermission: hasPermission.bind(null, state.user.get('profile')),
    }),
    {
        fetchArchive,
        revive,
    }
)(ViewToggleComponent);

<|next_version|>
import { connect } from 'react-redux';
import { fetchArchive, revive } from './../../store/archive/actions';
import ViewToggleComponent from './../feature/view/view-component';
import { hasPermission } from '../../permissions';
import { fetchTags } from '../../store/feature-tags/actions';

export default connect(
    (state, props) => ({
        features: state.archive.get('list').toArray(),
        featureToggle: state.archive
            .get('list')
            .toArray()
            .find(toggle => toggle.name === props.featureToggleName),
        tagTypes: state.tagTypes.toJS(),
        featureTags: state.featureTags.toJS(),
        activeTab: props.activeTab,
        hasPermission: hasPermission.bind(null, state.user.get('profile')),
    }),
    {
        fetchArchive,
        revive,
        fetchTags,
    }
)(ViewToggleComponent);


Add ensure_logged_in to onboarding middleware stack

--- apps/onboarding/index.js
+++ apps/onboarding/index.js
@@ -2,6 +2,7 @@
 import SearchBlocks from '../../collections/search_blocks';
 import { extend } from 'underscore';
 import apolloMiddleware from 'react/apollo/middleware';
+import ensureLoggedInMiddleware from 'lib/middleware/ensure_logged_in.coffee';
 import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

 const app = express();
@@ -14,6 +15,7 @@
 };

 const middlewareStack = [
+  ensureLoggedInMiddleware,
   apolloMiddleware,
   setOnboardingComponentMiddleware
 ];

<|original_code|>
import express from 'express';
import SearchBlocks from '../../collections/search_blocks';
import { extend } from 'underscore';
import apolloMiddleware from 'react/apollo/middleware';
import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

const app = express();

app.set('views', `${__dirname}/templates`);
app.set('view engine', 'jade');

const renderOnboarding = (req, res, next) => {
  res.render('index');
};

const middlewareStack = [
  apolloMiddleware,
  setOnboardingComponentMiddleware
];

app.get('/welcome', ...middlewareStack, renderOnboarding);

module.exports = app;

<|edits_diff|>
--- apps/onboarding/index.js
+++ apps/onboarding/index.js
@@ -2,6 +2,7 @@
 import SearchBlocks from '../../collections/search_blocks';
 import { extend } from 'underscore';
 import apolloMiddleware from 'react/apollo/middleware';
+import ensureLoggedInMiddleware from 'lib/middleware/ensure_logged_in.coffee';
 import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

 const app = express();
<|current_version|>
import express from 'express';
import SearchBlocks from '../../collections/search_blocks';
import { extend } from 'underscore';
import apolloMiddleware from 'react/apollo/middleware';
import ensureLoggedInMiddleware from 'lib/middleware/ensure_logged_in.coffee';
import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

const app = express();

app.set('views', `${__dirname}/templates`);
app.set('view engine', 'jade');

const renderOnboarding = (req, res, next) => {
  res.render('index');
};

const middlewareStack = [
  apolloMiddleware,
  setOnboardingComponentMiddleware
];

app.get('/welcome', ...middlewareStack, renderOnboarding);

module.exports = app;

<|next_version|>
import express from 'express';
import SearchBlocks from '../../collections/search_blocks';
import { extend } from 'underscore';
import apolloMiddleware from 'react/apollo/middleware';
import ensureLoggedInMiddleware from 'lib/middleware/ensure_logged_in.coffee';
import setOnboardingComponentMiddleware from 'apps/onboarding/middleware/setOnboardingComponent';

const app = express();

app.set('views', `${__dirname}/templates`);
app.set('view engine', 'jade');

const renderOnboarding = (req, res, next) => {
  res.render('index');
};

const middlewareStack = [
  ensureLoggedInMiddleware,
  apolloMiddleware,
  setOnboardingComponentMiddleware
];

app.get('/welcome', ...middlewareStack, renderOnboarding);

module.exports = app;


Create End point for items list

Create End point for items list

--- server/main.js
+++ server/main.js
@@ -1,6 +1,7 @@
 'use strict';

 const express = require('express');
+const bodyParser = require('body-parser');

 const app = express();

@@ -8,4 +9,10 @@
     res.render('./../app/index.ejs');
 });
 app.use(express.static(__dirname + '/../tmp'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({
+    extended: false
+}));
+
 app.listen(7777);
+require('./routes/items')(app);

<|original_code|>
'use strict';

const express = require('express');

const app = express();

app.get('/', (req, res) => {
    res.render('./../app/index.ejs');
});
app.use(express.static(__dirname + '/../tmp'));
app.listen(7777);

<|edits_diff|>
--- server/main.js
+++ server/main.js
@@ -1,6 +1,7 @@
 'use strict';

 const express = require('express');
+const bodyParser = require('body-parser');

 const app = express();

@@ -8,4 +9,9 @@
     res.render('./../app/index.ejs');
 });
 app.use(express.static(__dirname + '/../tmp'));
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({
+    extended: false
+}));
+
 app.listen(7777);
<|current_version|>
'use strict';

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.get('/', (req, res) => {
    res.render('./../app/index.ejs');
});
app.use(express.static(__dirname + '/../tmp'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));

app.listen(7777);

<|next_version|>
'use strict';

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.get('/', (req, res) => {
    res.render('./../app/index.ejs');
});
app.use(express.static(__dirname + '/../tmp'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: false
}));

app.listen(7777);
require('./routes/items')(app);


Call engine.resize() after stylesheets load.

Otherwise engine would look pixely sometimes.

--- client/react-babylonjs.js
+++ client/react-babylonjs.js
@@ -15,10 +15,15 @@
     this.props.onEngineCreated(this.engine);
     this.handleWindowResize = () => this.engine.resize();
     window.addEventListener('resize', this.handleWindowResize);
+    // Stylesheets which would result in resizing of the canvas may
+    // still be loading. If we don’t call engine.resize() after, the
+    // engine will render pixelated.
+    window.addEventListener('load', this.handleWindowResize);
   }

   componentWillUnmount() {
     window.removeEventListener('resize', this.handleWindowResize);
+    window.removeEventListener('load', this.handleWindowResize);
     this.handleWindowResize = null;
     this.props.onEngineAbandoned(this.engine);
   }

<|original_code|>
import React from 'react';
import BABYLON from 'babylonjs';

export class BabylonJS extends React.Component {
  get defaultProps() {
    return {
      antialias: true,
      onEngineCreated: () => {},
      onEngineAbandoned: () => {},
    };
  }

  componentDidMount() {
    this.engine = new BABYLON.Engine(this.canvas, this.props.antialias);
    this.props.onEngineCreated(this.engine);
    this.handleWindowResize = () => this.engine.resize();
    window.addEventListener('resize', this.handleWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleWindowResize);
    this.handleWindowResize = null;
    this.props.onEngineAbandoned(this.engine);
  }

  render() {
    return <canvas ref={canvas => this.canvas = canvas} />;
  }
}

<|edits_diff|>
--- client/react-babylonjs.js
+++ client/react-babylonjs.js
@@ -15,6 +15,10 @@
     this.props.onEngineCreated(this.engine);
     this.handleWindowResize = () => this.engine.resize();
     window.addEventListener('resize', this.handleWindowResize);
+    // Stylesheets which would result in resizing of the canvas may
+    // still be loading. If we don’t call engine.resize() after, the
+    // engine will render pixelated.
+    window.addEventListener('load', this.handleWindowResize);
   }

   componentWillUnmount() {
<|current_version|>
import React from 'react';
import BABYLON from 'babylonjs';

export class BabylonJS extends React.Component {
  get defaultProps() {
    return {
      antialias: true,
      onEngineCreated: () => {},
      onEngineAbandoned: () => {},
    };
  }

  componentDidMount() {
    this.engine = new BABYLON.Engine(this.canvas, this.props.antialias);
    this.props.onEngineCreated(this.engine);
    this.handleWindowResize = () => this.engine.resize();
    window.addEventListener('resize', this.handleWindowResize);
    // Stylesheets which would result in resizing of the canvas may
    // still be loading. If we don’t call engine.resize() after, the
    // engine will render pixelated.
    window.addEventListener('load', this.handleWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleWindowResize);
    this.handleWindowResize = null;
    this.props.onEngineAbandoned(this.engine);
  }

  render() {
    return <canvas ref={canvas => this.canvas = canvas} />;
  }
}

<|next_version|>
import React from 'react';
import BABYLON from 'babylonjs';

export class BabylonJS extends React.Component {
  get defaultProps() {
    return {
      antialias: true,
      onEngineCreated: () => {},
      onEngineAbandoned: () => {},
    };
  }

  componentDidMount() {
    this.engine = new BABYLON.Engine(this.canvas, this.props.antialias);
    this.props.onEngineCreated(this.engine);
    this.handleWindowResize = () => this.engine.resize();
    window.addEventListener('resize', this.handleWindowResize);
    // Stylesheets which would result in resizing of the canvas may
    // still be loading. If we don’t call engine.resize() after, the
    // engine will render pixelated.
    window.addEventListener('load', this.handleWindowResize);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.handleWindowResize);
    window.removeEventListener('load', this.handleWindowResize);
    this.handleWindowResize = null;
    this.props.onEngineAbandoned(this.engine);
  }

  render() {
    return <canvas ref={canvas => this.canvas = canvas} />;
  }
}


Reset lastVotes state when new issue is opened

--- client/src/features/lastVotes/reducer.js
+++ client/src/features/lastVotes/reducer.js
@@ -1,7 +1,11 @@
+import { OPEN_ISSUE } from 'common/actionTypes/issues';
 import { RECEIVE_VOTE } from 'common/actionTypes/voting';

 const lastVotes = (state = [], action) => {
   switch (action.type) {
+    case OPEN_ISSUE: {
+      return [];
+    }
     case RECEIVE_VOTE: {
       return [...state.slice(-5), action.data.id];
     }

<|original_code|>
import { RECEIVE_VOTE } from 'common/actionTypes/voting';

const lastVotes = (state = [], action) => {
  switch (action.type) {
    case RECEIVE_VOTE: {
      return [...state.slice(-5), action.data.id];
    }

    default:
      return state;
  }
};

export default lastVotes;

<|edits_diff|>
--- client/src/features/lastVotes/reducer.js
+++ client/src/features/lastVotes/reducer.js
@@ -1,3 +1,4 @@
+import { OPEN_ISSUE } from 'common/actionTypes/issues';
 import { RECEIVE_VOTE } from 'common/actionTypes/voting';

 const lastVotes = (state = [], action) => {
<|current_version|>
import { OPEN_ISSUE } from 'common/actionTypes/issues';
import { RECEIVE_VOTE } from 'common/actionTypes/voting';

const lastVotes = (state = [], action) => {
  switch (action.type) {
    case RECEIVE_VOTE: {
      return [...state.slice(-5), action.data.id];
    }

    default:
      return state;
  }
};

export default lastVotes;

<|next_version|>
import { OPEN_ISSUE } from 'common/actionTypes/issues';
import { RECEIVE_VOTE } from 'common/actionTypes/voting';

const lastVotes = (state = [], action) => {
  switch (action.type) {
    case OPEN_ISSUE: {
      return [];
    }
    case RECEIVE_VOTE: {
      return [...state.slice(-5), action.data.id];
    }

    default:
      return state;
  }
};

export default lastVotes;


Disable voting if already voted

Disable voting if already voted

--- assets/star-ratings.js
+++ assets/star-ratings.js
@@ -16,9 +16,14 @@
                 }
             });

+        if (element.data('voted')) {
+            options.readOnly = true;
+        }
+
         if (options.readOnly) {
             element.addClass('disabled');
         }
+
         element.starRating(options);
     });
 });

<|original_code|>
jQuery(function() {
    jQuery('[data-star-rating]').each(function(index, element) {
        element = $(element);

        var data = element.data('star-rating'),
            globalOptions = window.StarRatingsOptions ? window.StarRatingsOptions : {},
            options = jQuery.extend(data.options, globalOptions, {
                callback: function(currentRating, element) {
                    $.post(data.uri, { id: data.id, rating: currentRating })
                     .done(function() {
                        console.log('success');
                     })
                     .fail(function() {
                        console.log('fail');
                     });
                }
            });

        if (options.readOnly) {
            element.addClass('disabled');
        }
        element.starRating(options);
    });
});

<|edits_diff|>
--- assets/star-ratings.js
+++ assets/star-ratings.js
@@ -16,6 +16,10 @@
                 }
             });

+        if (element.data('voted')) {
+            options.readOnly = true;
+        }
+
         if (options.readOnly) {
             element.addClass('disabled');
         }
<|current_version|>
jQuery(function() {
    jQuery('[data-star-rating]').each(function(index, element) {
        element = $(element);

        var data = element.data('star-rating'),
            globalOptions = window.StarRatingsOptions ? window.StarRatingsOptions : {},
            options = jQuery.extend(data.options, globalOptions, {
                callback: function(currentRating, element) {
                    $.post(data.uri, { id: data.id, rating: currentRating })
                     .done(function() {
                        console.log('success');
                     })
                     .fail(function() {
                        console.log('fail');
                     });
                }
            });

        if (element.data('voted')) {
            options.readOnly = true;
        }

        if (options.readOnly) {
            element.addClass('disabled');
        }
        element.starRating(options);
    });
});

<|next_version|>
jQuery(function() {
    jQuery('[data-star-rating]').each(function(index, element) {
        element = $(element);

        var data = element.data('star-rating'),
            globalOptions = window.StarRatingsOptions ? window.StarRatingsOptions : {},
            options = jQuery.extend(data.options, globalOptions, {
                callback: function(currentRating, element) {
                    $.post(data.uri, { id: data.id, rating: currentRating })
                     .done(function() {
                        console.log('success');
                     })
                     .fail(function() {
                        console.log('fail');
                     });
                }
            });

        if (element.data('voted')) {
            options.readOnly = true;
        }

        if (options.readOnly) {
            element.addClass('disabled');
        }

        element.starRating(options);
    });
});


Add size loging for vendor.js file

Add size loging for vendor.js file

--- tasks/js-vendor.js
+++ tasks/js-vendor.js
@@ -3,6 +3,7 @@
     var uglify = require('gulp-uglify'),
       gulpif = require('gulp-if'),
       insert = require('gulp-insert'),
+      size = require('gulp-size'),
       concat = require('gulp-concat');

     return gulp.src(speck.config.vendorJS)
@@ -15,6 +16,7 @@
         '* Don\'t edit this file directly.\n' +
         '*/\n'
       )))
+      .pipe(size({gzip: true, title: 'vendor.js'}))
       .pipe(gulp.dest(speck.assets.build.js));
   });
 };

<|original_code|>
module.exports = function(gulp, speck) {
  return gulp.task('js:vendor', function() {
    var uglify = require('gulp-uglify'),
      gulpif = require('gulp-if'),
      insert = require('gulp-insert'),
      concat = require('gulp-concat');

    return gulp.src(speck.config.vendorJS)
      .pipe(gulpif(speck.build.env.optimise, uglify()))
      .pipe(concat('vendor.js'))
      .pipe(gulpif(speck.build.env.optimise, insert.prepend(
        '/*\n' +
        '* ' + speck.config.name + ', ' + speck.config.version + ' (' + speck.config.currentRevision + ')\n' +
        '* ' + 'vendor.js built ' + new Date().toISOString().substring(0, 10) + '\n' +
        '* Don\'t edit this file directly.\n' +
        '*/\n'
      )))
      .pipe(gulp.dest(speck.assets.build.js));
  });
};

<|edits_diff|>
--- tasks/js-vendor.js
+++ tasks/js-vendor.js
@@ -3,6 +3,7 @@
     var uglify = require('gulp-uglify'),
       gulpif = require('gulp-if'),
       insert = require('gulp-insert'),
+      size = require('gulp-size'),
       concat = require('gulp-concat');

     return gulp.src(speck.config.vendorJS)
<|current_version|>
module.exports = function(gulp, speck) {
  return gulp.task('js:vendor', function() {
    var uglify = require('gulp-uglify'),
      gulpif = require('gulp-if'),
      insert = require('gulp-insert'),
      size = require('gulp-size'),
      concat = require('gulp-concat');

    return gulp.src(speck.config.vendorJS)
      .pipe(gulpif(speck.build.env.optimise, uglify()))
      .pipe(concat('vendor.js'))
      .pipe(gulpif(speck.build.env.optimise, insert.prepend(
        '/*\n' +
        '* ' + speck.config.name + ', ' + speck.config.version + ' (' + speck.config.currentRevision + ')\n' +
        '* ' + 'vendor.js built ' + new Date().toISOString().substring(0, 10) + '\n' +
        '* Don\'t edit this file directly.\n' +
        '*/\n'
      )))
      .pipe(gulp.dest(speck.assets.build.js));
  });
};

<|next_version|>
module.exports = function(gulp, speck) {
  return gulp.task('js:vendor', function() {
    var uglify = require('gulp-uglify'),
      gulpif = require('gulp-if'),
      insert = require('gulp-insert'),
      size = require('gulp-size'),
      concat = require('gulp-concat');

    return gulp.src(speck.config.vendorJS)
      .pipe(gulpif(speck.build.env.optimise, uglify()))
      .pipe(concat('vendor.js'))
      .pipe(gulpif(speck.build.env.optimise, insert.prepend(
        '/*\n' +
        '* ' + speck.config.name + ', ' + speck.config.version + ' (' + speck.config.currentRevision + ')\n' +
        '* ' + 'vendor.js built ' + new Date().toISOString().substring(0, 10) + '\n' +
        '* Don\'t edit this file directly.\n' +
        '*/\n'
      )))
      .pipe(size({gzip: true, title: 'vendor.js'}))
      .pipe(gulp.dest(speck.assets.build.js));
  });
};


Add support for Bower to Webpack

Add support for Bower to Webpack

--- webpack.config.js
+++ webpack.config.js
@@ -1,9 +1,11 @@
+var webpack = require("webpack");
 var path = require("path")

 const PROJECT_ROOT = path.resolve(__dirname)

 module.exports = {
   entry: [
+    "./bower_components/underscore/underscore.js",
     "./scripts/dash-spinner/bar.js",
     "./scripts/dash-spinner/foo.js",
     "./scripts/dash-spinner/foo.coffee"
@@ -31,6 +33,11 @@
       }
     ]
   },
+  plugins: [
+    new webpack.ResolverPlugin(
+      new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(".bower.json", ["main"])
+    )
+  ],
   resolve: {
     alias: {
       "dash_spinner": path.join(PROJECT_ROOT, "scripts", "dash-spinner")

<|original_code|>
var path = require("path")

const PROJECT_ROOT = path.resolve(__dirname)

module.exports = {
  entry: [
    "./scripts/dash-spinner/bar.js",
    "./scripts/dash-spinner/foo.js",
    "./scripts/dash-spinner/foo.coffee"
  ],
  output: {
    path: "./dist",
    filename: "new-spinner.js"
  },
  module: {
    loaders:[
      {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel-loader",
        test: /\.js$/
      }, {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel!coffee",
        test: /\.coffee$/
      }
    ]
  },
  resolve: {
    alias: {
      "dash_spinner": path.join(PROJECT_ROOT, "scripts", "dash-spinner")
    }
  }
}

<|edits_diff|>
--- webpack.config.js
+++ webpack.config.js
@@ -1,9 +1,11 @@
+var webpack = require("webpack");
 var path = require("path")

 const PROJECT_ROOT = path.resolve(__dirname)

 module.exports = {
   entry: [
+    "./bower_components/underscore/underscore.js",
     "./scripts/dash-spinner/bar.js",
     "./scripts/dash-spinner/foo.js",
     "./scripts/dash-spinner/foo.coffee"
<|current_version|>
var webpack = require("webpack");
var path = require("path")

const PROJECT_ROOT = path.resolve(__dirname)

module.exports = {
  entry: [
    "./bower_components/underscore/underscore.js",
    "./scripts/dash-spinner/bar.js",
    "./scripts/dash-spinner/foo.js",
    "./scripts/dash-spinner/foo.coffee"
  ],
  output: {
    path: "./dist",
    filename: "new-spinner.js"
  },
  module: {
    loaders:[
      {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel-loader",
        test: /\.js$/
      }, {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel!coffee",
        test: /\.coffee$/
      }
    ]
  },
  resolve: {
    alias: {
      "dash_spinner": path.join(PROJECT_ROOT, "scripts", "dash-spinner")
    }
  }
}

<|next_version|>
var webpack = require("webpack");
var path = require("path")

const PROJECT_ROOT = path.resolve(__dirname)

module.exports = {
  entry: [
    "./bower_components/underscore/underscore.js",
    "./scripts/dash-spinner/bar.js",
    "./scripts/dash-spinner/foo.js",
    "./scripts/dash-spinner/foo.coffee"
  ],
  output: {
    path: "./dist",
    filename: "new-spinner.js"
  },
  module: {
    loaders:[
      {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel-loader",
        test: /\.js$/
      }, {
        include: [
          path.join(PROJECT_ROOT, "scripts"),
          path.join(PROJECT_ROOT, "spec")
        ],
        loader: "babel!coffee",
        test: /\.coffee$/
      }
    ]
  },
  plugins: [
    new webpack.ResolverPlugin(
      new webpack.ResolverPlugin.DirectoryDescriptionFilePlugin(".bower.json", ["main"])
    )
  ],
  resolve: {
    alias: {
      "dash_spinner": path.join(PROJECT_ROOT, "scripts", "dash-spinner")
    }
  }
}


Fix error if relation empty

Fix error if relation empty

--- lib/deserializer.js
+++ lib/deserializer.js
@@ -15,12 +15,18 @@
         var fkSuffix = null;
         var fk = null;
         if (_.isArray(clientRelation.data)) {
+          if (_.isEmpty(clientRelation.data)) {
+            return;
+          }
           relationType = _.result(_.find(clientRelation.data, 'type'), 'type');
           relationValue = _.map(clientRelation.data, function (val) {
             return val.id;
           });
           fkSuffix = 'Ids';
         } else {
+          if (clientRelation.data === null) {
+            return;
+          }
           relationType = clientRelation.data.type;
           relationValue = clientRelation.data.id;
           fkSuffix = 'Id';

<|original_code|>
var _ = require('lodash');
var inflection = require('inflection');

module.exports = function Deserializer (data, serverRelations) {

  var clientRelations = data.data.relationships;
  var result = data.data.attributes || {};
  if (_.isPlainObject(clientRelations)) {
    _.each(clientRelations, function (clientRelation, clientRelationName) {
      var serverRelation = serverRelations[clientRelationName];

      if (_.isPlainObject(serverRelation)) {
        var relationType = null;
        var relationValue = null;
        var fkSuffix = null;
        var fk = null;
        if (_.isArray(clientRelation.data)) {
          relationType = _.result(_.find(clientRelation.data, 'type'), 'type');
          relationValue = _.map(clientRelation.data, function (val) {
            return val.id;
          });
          fkSuffix = 'Ids';
        } else {
          relationType = clientRelation.data.type;
          relationValue = clientRelation.data.id;
          fkSuffix = 'Id';
        }

        relationType = inflection.singularize(relationType);

        if (relationType === serverRelation.model) {
          if (serverRelation.foreignKey) {
            fk = serverRelation.foreignKey;
          } else {

            relationType = inflection.camelize(relationType, true);
            fk = relationType + fkSuffix;
          }
          result[fk] = relationValue;
        }
      }
    });
  }

  return result;

};

<|edits_diff|>
--- lib/deserializer.js
+++ lib/deserializer.js
@@ -15,6 +15,9 @@
         var fkSuffix = null;
         var fk = null;
         if (_.isArray(clientRelation.data)) {
+          if (_.isEmpty(clientRelation.data)) {
+            return;
+          }
           relationType = _.result(_.find(clientRelation.data, 'type'), 'type');
           relationValue = _.map(clientRelation.data, function (val) {
             return val.id;
<|current_version|>
var _ = require('lodash');
var inflection = require('inflection');

module.exports = function Deserializer (data, serverRelations) {

  var clientRelations = data.data.relationships;
  var result = data.data.attributes || {};
  if (_.isPlainObject(clientRelations)) {
    _.each(clientRelations, function (clientRelation, clientRelationName) {
      var serverRelation = serverRelations[clientRelationName];

      if (_.isPlainObject(serverRelation)) {
        var relationType = null;
        var relationValue = null;
        var fkSuffix = null;
        var fk = null;
        if (_.isArray(clientRelation.data)) {
          if (_.isEmpty(clientRelation.data)) {
            return;
          }
          relationType = _.result(_.find(clientRelation.data, 'type'), 'type');
          relationValue = _.map(clientRelation.data, function (val) {
            return val.id;
          });
          fkSuffix = 'Ids';
        } else {
          relationType = clientRelation.data.type;
          relationValue = clientRelation.data.id;
          fkSuffix = 'Id';
        }

        relationType = inflection.singularize(relationType);

        if (relationType === serverRelation.model) {
          if (serverRelation.foreignKey) {
            fk = serverRelation.foreignKey;
          } else {

            relationType = inflection.camelize(relationType, true);
            fk = relationType + fkSuffix;
          }
          result[fk] = relationValue;
        }
      }
    });
  }

  return result;

};

<|next_version|>
var _ = require('lodash');
var inflection = require('inflection');

module.exports = function Deserializer (data, serverRelations) {

  var clientRelations = data.data.relationships;
  var result = data.data.attributes || {};
  if (_.isPlainObject(clientRelations)) {
    _.each(clientRelations, function (clientRelation, clientRelationName) {
      var serverRelation = serverRelations[clientRelationName];

      if (_.isPlainObject(serverRelation)) {
        var relationType = null;
        var relationValue = null;
        var fkSuffix = null;
        var fk = null;
        if (_.isArray(clientRelation.data)) {
          if (_.isEmpty(clientRelation.data)) {
            return;
          }
          relationType = _.result(_.find(clientRelation.data, 'type'), 'type');
          relationValue = _.map(clientRelation.data, function (val) {
            return val.id;
          });
          fkSuffix = 'Ids';
        } else {
          if (clientRelation.data === null) {
            return;
          }
          relationType = clientRelation.data.type;
          relationValue = clientRelation.data.id;
          fkSuffix = 'Id';
        }

        relationType = inflection.singularize(relationType);

        if (relationType === serverRelation.model) {
          if (serverRelation.foreignKey) {
            fk = serverRelation.foreignKey;
          } else {

            relationType = inflection.camelize(relationType, true);
            fk = relationType + fkSuffix;
          }
          result[fk] = relationValue;
        }
      }
    });
  }

  return result;

};


Set content type of api response to JSON.

--- src/web/routes/api/v1/users.js
+++ src/web/routes/api/v1/users.js
@@ -20,6 +20,7 @@
             res.status(500).end();
           } else {

+            res.set('Content-Type', 'application/json');
             res.end(JSON.stringify(users));

           }
@@ -54,6 +55,7 @@
             user.getCards().success(function(cards) {

               if ( cards ) {
+                res.set('Content-Type', 'application/json');
                 res.end(JSON.stringify(cards));
               } else {
                 res.status(500).end();

<|original_code|>
var helpers = require('./helpers');
var db = helpers.db;

/* /users */
exports.getAllUsers = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
      console.log("getAllUsers:", err);
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        helpers.getAllUsers(function(err, users) {
          if ( err ) {
            res.status(500).end();
          } else {

            res.end(JSON.stringify(users));

          }
        });

      }

    }

  });
};

/* /user/:id/cards */
exports.getCardsOfUser = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        var id = req.params.id;
        helpers.getUser({
          id: id
        }, function(err, user) {

          if ( user ) {
            user.getCards().success(function(cards) {

              if ( cards ) {
                res.end(JSON.stringify(cards));
              } else {
                res.status(500).end();
              }

            });
          }

        });

      }

    }

  });
};

<|edits_diff|>
--- src/web/routes/api/v1/users.js
+++ src/web/routes/api/v1/users.js
@@ -20,6 +20,7 @@
             res.status(500).end();
           } else {

+            res.set('Content-Type', 'application/json');
             res.end(JSON.stringify(users));

           }
<|current_version|>
var helpers = require('./helpers');
var db = helpers.db;

/* /users */
exports.getAllUsers = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
      console.log("getAllUsers:", err);
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        helpers.getAllUsers(function(err, users) {
          if ( err ) {
            res.status(500).end();
          } else {

            res.set('Content-Type', 'application/json');
            res.end(JSON.stringify(users));

          }
        });

      }

    }

  });
};

/* /user/:id/cards */
exports.getCardsOfUser = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        var id = req.params.id;
        helpers.getUser({
          id: id
        }, function(err, user) {

          if ( user ) {
            user.getCards().success(function(cards) {

              if ( cards ) {
                res.end(JSON.stringify(cards));
              } else {
                res.status(500).end();
              }

            });
          }

        });

      }

    }

  });
};

<|next_version|>
var helpers = require('./helpers');
var db = helpers.db;

/* /users */
exports.getAllUsers = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
      console.log("getAllUsers:", err);
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        helpers.getAllUsers(function(err, users) {
          if ( err ) {
            res.status(500).end();
          } else {

            res.set('Content-Type', 'application/json');
            res.end(JSON.stringify(users));

          }
        });

      }

    }

  });
};

/* /user/:id/cards */
exports.getCardsOfUser = function(req, res) {
  helpers.getRequestingUser(req, function(err, user) {

    if ( err ) {
      res.status(500).end();
    } else {

      if ( !user ) {
        res.status(403).end();
      } else {

        // TODO filter for type
        var id = req.params.id;
        helpers.getUser({
          id: id
        }, function(err, user) {

          if ( user ) {
            user.getCards().success(function(cards) {

              if ( cards ) {
                res.set('Content-Type', 'application/json');
                res.end(JSON.stringify(cards));
              } else {
                res.status(500).end();
              }

            });
          }

        });

      }

    }

  });
};


Add react-dom to the devDependency list for React Native projects

--- lib/cli/generators/REACT_NATIVE/index.js
+++ lib/cli/generators/REACT_NATIVE/index.js
@@ -19,8 +19,14 @@

   const packageJson = helpers.getPackageJson();

+  packageJson.dependencies = packageJson.dependencies || {};
   packageJson.devDependencies = packageJson.devDependencies || {};
+
   packageJson.devDependencies['@storybook/react-native'] = `^${version}`;
+
+  if (!packageJson.dependencies['react-dom'] && !packageJson.devDependencies['react-dom']) {
+    packageJson.devDependencies['react-dom'] = '^15.5.4';
+  }

   packageJson.scripts = packageJson.scripts || {};
   packageJson.scripts['storybook'] = 'storybook start -p 7007';

<|original_code|>
const mergeDirs = require('merge-dirs').default;
const helpers = require('../../lib/helpers');
const path = require('path');
const shell = require('shelljs');
const latestVersion = require('latest-version');

module.exports = latestVersion('@storybook/react-native').then(version => {
  // copy all files from the template directory to project directory
  mergeDirs(path.resolve(__dirname, 'template/'), '.', 'overwrite');

  // set correct project name on entry files if possible
  const dirname = shell.ls('-d', 'ios/*.xcodeproj').stdout;
  const projectName =
    dirname && dirname.slice('ios/'.length, dirname.length - '.xcodeproj'.length - 1);
  if (projectName) {
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.ios.js');
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.android.js');
  }

  const packageJson = helpers.getPackageJson();

  packageJson.devDependencies = packageJson.devDependencies || {};
  packageJson.devDependencies['@storybook/react-native'] = `^${version}`;

  packageJson.scripts = packageJson.scripts || {};
  packageJson.scripts['storybook'] = 'storybook start -p 7007';

  helpers.writePackageJson(packageJson);
});

<|edits_diff|>
--- lib/cli/generators/REACT_NATIVE/index.js
+++ lib/cli/generators/REACT_NATIVE/index.js
@@ -19,7 +19,9 @@

   const packageJson = helpers.getPackageJson();

+  packageJson.dependencies = packageJson.dependencies || {};
   packageJson.devDependencies = packageJson.devDependencies || {};
+
   packageJson.devDependencies['@storybook/react-native'] = `^${version}`;

   packageJson.scripts = packageJson.scripts || {};
<|current_version|>
const mergeDirs = require('merge-dirs').default;
const helpers = require('../../lib/helpers');
const path = require('path');
const shell = require('shelljs');
const latestVersion = require('latest-version');

module.exports = latestVersion('@storybook/react-native').then(version => {
  // copy all files from the template directory to project directory
  mergeDirs(path.resolve(__dirname, 'template/'), '.', 'overwrite');

  // set correct project name on entry files if possible
  const dirname = shell.ls('-d', 'ios/*.xcodeproj').stdout;
  const projectName =
    dirname && dirname.slice('ios/'.length, dirname.length - '.xcodeproj'.length - 1);
  if (projectName) {
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.ios.js');
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.android.js');
  }

  const packageJson = helpers.getPackageJson();

  packageJson.dependencies = packageJson.dependencies || {};
  packageJson.devDependencies = packageJson.devDependencies || {};

  packageJson.devDependencies['@storybook/react-native'] = `^${version}`;

  packageJson.scripts = packageJson.scripts || {};
  packageJson.scripts['storybook'] = 'storybook start -p 7007';

  helpers.writePackageJson(packageJson);
});

<|next_version|>
const mergeDirs = require('merge-dirs').default;
const helpers = require('../../lib/helpers');
const path = require('path');
const shell = require('shelljs');
const latestVersion = require('latest-version');

module.exports = latestVersion('@storybook/react-native').then(version => {
  // copy all files from the template directory to project directory
  mergeDirs(path.resolve(__dirname, 'template/'), '.', 'overwrite');

  // set correct project name on entry files if possible
  const dirname = shell.ls('-d', 'ios/*.xcodeproj').stdout;
  const projectName =
    dirname && dirname.slice('ios/'.length, dirname.length - '.xcodeproj'.length - 1);
  if (projectName) {
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.ios.js');
    shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.android.js');
  }

  const packageJson = helpers.getPackageJson();

  packageJson.dependencies = packageJson.dependencies || {};
  packageJson.devDependencies = packageJson.devDependencies || {};

  packageJson.devDependencies['@storybook/react-native'] = `^${version}`;

  if (!packageJson.dependencies['react-dom'] && !packageJson.devDependencies['react-dom']) {
    packageJson.devDependencies['react-dom'] = '^15.5.4';
  }

  packageJson.scripts = packageJson.scripts || {};
  packageJson.scripts['storybook'] = 'storybook start -p 7007';

  helpers.writePackageJson(packageJson);
});


Add spinner to external preview

Add spinner to external preview

--- app/assets/javascripts/protocols/external_protocols_tab.js
+++ app/assets/javascripts/protocols/external_protocols_tab.js
@@ -8,15 +8,18 @@
         protocol_source: $(this).data('protocol-source'),
         protocol_id: $(this).data('show-protocol-id')
       },
+      beforeSend: animateSpinner($('.protocol-preview-panel'), true),
       success: function(data) {
         $('.empty-preview-panel').hide();
         $('.full-preview-panel').show();
         $('.preview-iframe').contents().find('body').html(data.html);
+        animateSpinner($('.protocol-preview-panel'), false);
       },
       error: function(_error) {
         // TODO: we should probably show some alert bubble
         $('.empty-preview-panel').show();
         $('.full-preview-panel').hide();
+        animateSpinner($('.protocol-preview-panel'), false);
       }
     });
     e.preventDefault();

<|original_code|>
function applyClickCallbackOnProtocolCards() {
  $('.protocol-card').off('click').on('click', function(e) {
    $.ajax({
      url: $(this).data('show-url'),
      type: 'GET',
      dataType: 'json',
      data: {
        protocol_source: $(this).data('protocol-source'),
        protocol_id: $(this).data('show-protocol-id')
      },
      success: function(data) {
        $('.empty-preview-panel').hide();
        $('.full-preview-panel').show();
        $('.preview-iframe').contents().find('body').html(data.html);
      },
      error: function(_error) {
        // TODO: we should probably show some alert bubble
        $('.empty-preview-panel').show();
        $('.full-preview-panel').hide();
      }
    });
    e.preventDefault();
    return false;
  });
}

applyClickCallbackOnProtocolCards();

<|edits_diff|>
--- app/assets/javascripts/protocols/external_protocols_tab.js
+++ app/assets/javascripts/protocols/external_protocols_tab.js
@@ -8,10 +8,12 @@
         protocol_source: $(this).data('protocol-source'),
         protocol_id: $(this).data('show-protocol-id')
       },
+      beforeSend: animateSpinner($('.protocol-preview-panel'), true),
       success: function(data) {
         $('.empty-preview-panel').hide();
         $('.full-preview-panel').show();
         $('.preview-iframe').contents().find('body').html(data.html);
+        animateSpinner($('.protocol-preview-panel'), false);
       },
       error: function(_error) {
         // TODO: we should probably show some alert bubble
<|current_version|>
function applyClickCallbackOnProtocolCards() {
  $('.protocol-card').off('click').on('click', function(e) {
    $.ajax({
      url: $(this).data('show-url'),
      type: 'GET',
      dataType: 'json',
      data: {
        protocol_source: $(this).data('protocol-source'),
        protocol_id: $(this).data('show-protocol-id')
      },
      beforeSend: animateSpinner($('.protocol-preview-panel'), true),
      success: function(data) {
        $('.empty-preview-panel').hide();
        $('.full-preview-panel').show();
        $('.preview-iframe').contents().find('body').html(data.html);
        animateSpinner($('.protocol-preview-panel'), false);
      },
      error: function(_error) {
        // TODO: we should probably show some alert bubble
        $('.empty-preview-panel').show();
        $('.full-preview-panel').hide();
      }
    });
    e.preventDefault();
    return false;
  });
}

applyClickCallbackOnProtocolCards();

<|next_version|>
function applyClickCallbackOnProtocolCards() {
  $('.protocol-card').off('click').on('click', function(e) {
    $.ajax({
      url: $(this).data('show-url'),
      type: 'GET',
      dataType: 'json',
      data: {
        protocol_source: $(this).data('protocol-source'),
        protocol_id: $(this).data('show-protocol-id')
      },
      beforeSend: animateSpinner($('.protocol-preview-panel'), true),
      success: function(data) {
        $('.empty-preview-panel').hide();
        $('.full-preview-panel').show();
        $('.preview-iframe').contents().find('body').html(data.html);
        animateSpinner($('.protocol-preview-panel'), false);
      },
      error: function(_error) {
        // TODO: we should probably show some alert bubble
        $('.empty-preview-panel').show();
        $('.full-preview-panel').hide();
        animateSpinner($('.protocol-preview-panel'), false);
      }
    });
    e.preventDefault();
    return false;
  });
}

applyClickCallbackOnProtocolCards();


Add responseTime to console logs

Add responseTime to console logs

--- src/logging/request_message.js
+++ src/logging/request_message.js
@@ -11,8 +11,11 @@
   error,
   actions = {},
   fullAction,
+  responseTime,
 }) {
   const action = error ? fullAction : Object.keys(actions).join(' ');
+  responseTime = `${Math.round(responseTime)}ms`;
+
   const message = [
     protocolStatus,
     error,
@@ -21,6 +24,7 @@
     protocolMethod,
     path,
     action,
+    responseTime,
   ].filter(val => val)
     .join(' ');
   return message;

<|original_code|>
'use strict';

// Build message of logs of type `request` as:
//  STATUS [ERROR] - PROTOCOL METHOD /PATH ACTION...
const getRequestMessage = function ({
  protocol,
  protocolMethod,
  path,
  protocolStatus,
  error,
  actions = {},
  fullAction,
}) {
  const action = error ? fullAction : Object.keys(actions).join(' ');
  const message = [
    protocolStatus,
    error,
    '-',
    protocol,
    protocolMethod,
    path,
    action,
  ].filter(val => val)
    .join(' ');
  return message;
};

module.exports = {
  getRequestMessage,
};

<|edits_diff|>
--- src/logging/request_message.js
+++ src/logging/request_message.js
@@ -11,8 +11,11 @@
   error,
   actions = {},
   fullAction,
+  responseTime,
 }) {
   const action = error ? fullAction : Object.keys(actions).join(' ');
+  responseTime = `${Math.round(responseTime)}ms`;
+
   const message = [
     protocolStatus,
     error,
<|current_version|>
'use strict';

// Build message of logs of type `request` as:
//  STATUS [ERROR] - PROTOCOL METHOD /PATH ACTION...
const getRequestMessage = function ({
  protocol,
  protocolMethod,
  path,
  protocolStatus,
  error,
  actions = {},
  fullAction,
  responseTime,
}) {
  const action = error ? fullAction : Object.keys(actions).join(' ');
  responseTime = `${Math.round(responseTime)}ms`;

  const message = [
    protocolStatus,
    error,
    '-',
    protocol,
    protocolMethod,
    path,
    action,
  ].filter(val => val)
    .join(' ');
  return message;
};

module.exports = {
  getRequestMessage,
};

<|next_version|>
'use strict';

// Build message of logs of type `request` as:
//  STATUS [ERROR] - PROTOCOL METHOD /PATH ACTION...
const getRequestMessage = function ({
  protocol,
  protocolMethod,
  path,
  protocolStatus,
  error,
  actions = {},
  fullAction,
  responseTime,
}) {
  const action = error ? fullAction : Object.keys(actions).join(' ');
  responseTime = `${Math.round(responseTime)}ms`;

  const message = [
    protocolStatus,
    error,
    '-',
    protocol,
    protocolMethod,
    path,
    action,
    responseTime,
  ].filter(val => val)
    .join(' ');
  return message;
};

module.exports = {
  getRequestMessage,
};


Add hooks to plugin API

Add hooks to plugin API

--- src/plugins.js
+++ src/plugins.js
@@ -1,4 +1,5 @@
 const attachments = require('./data/attachments');
+const { beforeNewThread } = require('./hooks');

 module.exports = {
   getPluginAPI({ bot, knex, config, commands }) {
@@ -17,6 +18,9 @@
         addStorageType: attachments.addStorageType,
         downloadAttachment: attachments.downloadAttachment
       },
+      hooks: {
+        beforeNewThread,
+      },
     };
   },

<|original_code|>
const attachments = require('./data/attachments');

module.exports = {
  getPluginAPI({ bot, knex, config, commands }) {
    return {
      bot,
      knex,
      config,
      commands: {
        manager: commands.manager,
        addGlobalCommand: commands.addGlobalCommand,
        addInboxServerCommand: commands.addInboxServerCommand,
        addInboxThreadCommand: commands.addInboxThreadCommand,
        addAlias: commands.addAlias
      },
      attachments: {
        addStorageType: attachments.addStorageType,
        downloadAttachment: attachments.downloadAttachment
      },
    };
  },

  loadPlugin(plugin, api) {
    plugin(api);
  }
};

<|edits_diff|>
--- src/plugins.js
+++ src/plugins.js
@@ -1,4 +1,5 @@
 const attachments = require('./data/attachments');
+const { beforeNewThread } = require('./hooks');

 module.exports = {
   getPluginAPI({ bot, knex, config, commands }) {
<|current_version|>
const attachments = require('./data/attachments');
const { beforeNewThread } = require('./hooks');

module.exports = {
  getPluginAPI({ bot, knex, config, commands }) {
    return {
      bot,
      knex,
      config,
      commands: {
        manager: commands.manager,
        addGlobalCommand: commands.addGlobalCommand,
        addInboxServerCommand: commands.addInboxServerCommand,
        addInboxThreadCommand: commands.addInboxThreadCommand,
        addAlias: commands.addAlias
      },
      attachments: {
        addStorageType: attachments.addStorageType,
        downloadAttachment: attachments.downloadAttachment
      },
    };
  },

  loadPlugin(plugin, api) {
    plugin(api);
  }
};

<|next_version|>
const attachments = require('./data/attachments');
const { beforeNewThread } = require('./hooks');

module.exports = {
  getPluginAPI({ bot, knex, config, commands }) {
    return {
      bot,
      knex,
      config,
      commands: {
        manager: commands.manager,
        addGlobalCommand: commands.addGlobalCommand,
        addInboxServerCommand: commands.addInboxServerCommand,
        addInboxThreadCommand: commands.addInboxThreadCommand,
        addAlias: commands.addAlias
      },
      attachments: {
        addStorageType: attachments.addStorageType,
        downloadAttachment: attachments.downloadAttachment
      },
      hooks: {
        beforeNewThread,
      },
    };
  },

  loadPlugin(plugin, api) {
    plugin(api);
  }
};


Add option to Petition.js to skip post submit action

--- app/assets/javascripts/member-facing/backbone/petition.js
+++ app/assets/javascripts/member-facing/backbone/petition.js
@@ -14,10 +14,15 @@
   initialize(options = {}) {
     this.followUpUrl = options.followUpUrl;
     this.submissionCallback = options.submissionCallback;
+    this.skipOnSuccessAction = options.skipOnSuccessAction;
     GlobalEvents.bindEvents(this);
   },

   handleSuccess(e, data) {
+    $.publish('petition:submitted');
+    if(this.skipOnSuccessAction) {
+      return;
+    }
     let hasCallbackFunction = (typeof this.submissionCallback === 'function');
     if (hasCallbackFunction) {
       this.submissionCallback(e, data);

<|original_code|>
const GlobalEvents = require('shared/global_events');

const Petition = Backbone.View.extend({

  el: '.petition-bar',

  globalEvents: {
    'form:submitted': 'handleSuccess',
  },

  // options: object with any of the following keys
  //    followUpUrl: the url to redirect to after success
  //    submissionCallback: callback with event and server data for successful submission
  initialize(options = {}) {
    this.followUpUrl = options.followUpUrl;
    this.submissionCallback = options.submissionCallback;
    GlobalEvents.bindEvents(this);
  },

  handleSuccess(e, data) {
    let hasCallbackFunction = (typeof this.submissionCallback === 'function');
    if (hasCallbackFunction) {
      this.submissionCallback(e, data);
    }
    if (data && data.follow_up_url) {
      this.redirectTo(data.follow_up_url);
    } else if (this.followUpUrl) {
      this.redirectTo(this.followUpUrl);
    } else if(!hasCallbackFunction) {
      // only do this option if no redirect or callback supplied
      alert(I18n.t('petition.excited_confirmation'));
    }
  },

  redirectTo(url) {
    window.location.href = url;
  },

});

module.exports = Petition;

<|edits_diff|>
--- app/assets/javascripts/member-facing/backbone/petition.js
+++ app/assets/javascripts/member-facing/backbone/petition.js
@@ -14,6 +14,7 @@
   initialize(options = {}) {
     this.followUpUrl = options.followUpUrl;
     this.submissionCallback = options.submissionCallback;
+    this.skipOnSuccessAction = options.skipOnSuccessAction;
     GlobalEvents.bindEvents(this);
   },

<|current_version|>
const GlobalEvents = require('shared/global_events');

const Petition = Backbone.View.extend({

  el: '.petition-bar',

  globalEvents: {
    'form:submitted': 'handleSuccess',
  },

  // options: object with any of the following keys
  //    followUpUrl: the url to redirect to after success
  //    submissionCallback: callback with event and server data for successful submission
  initialize(options = {}) {
    this.followUpUrl = options.followUpUrl;
    this.submissionCallback = options.submissionCallback;
    this.skipOnSuccessAction = options.skipOnSuccessAction;
    GlobalEvents.bindEvents(this);
  },

  handleSuccess(e, data) {
    let hasCallbackFunction = (typeof this.submissionCallback === 'function');
    if (hasCallbackFunction) {
      this.submissionCallback(e, data);
    }
    if (data && data.follow_up_url) {
      this.redirectTo(data.follow_up_url);
    } else if (this.followUpUrl) {
      this.redirectTo(this.followUpUrl);
    } else if(!hasCallbackFunction) {
      // only do this option if no redirect or callback supplied
      alert(I18n.t('petition.excited_confirmation'));
    }
  },

  redirectTo(url) {
    window.location.href = url;
  },

});

module.exports = Petition;

<|next_version|>
const GlobalEvents = require('shared/global_events');

const Petition = Backbone.View.extend({

  el: '.petition-bar',

  globalEvents: {
    'form:submitted': 'handleSuccess',
  },

  // options: object with any of the following keys
  //    followUpUrl: the url to redirect to after success
  //    submissionCallback: callback with event and server data for successful submission
  initialize(options = {}) {
    this.followUpUrl = options.followUpUrl;
    this.submissionCallback = options.submissionCallback;
    this.skipOnSuccessAction = options.skipOnSuccessAction;
    GlobalEvents.bindEvents(this);
  },

  handleSuccess(e, data) {
    $.publish('petition:submitted');
    if(this.skipOnSuccessAction) {
      return;
    }
    let hasCallbackFunction = (typeof this.submissionCallback === 'function');
    if (hasCallbackFunction) {
      this.submissionCallback(e, data);
    }
    if (data && data.follow_up_url) {
      this.redirectTo(data.follow_up_url);
    } else if (this.followUpUrl) {
      this.redirectTo(this.followUpUrl);
    } else if(!hasCallbackFunction) {
      // only do this option if no redirect or callback supplied
      alert(I18n.t('petition.excited_confirmation'));
    }
  },

  redirectTo(url) {
    window.location.href = url;
  },

});

module.exports = Petition;


Add additional suggested security headers

--- lib/defaultHeadersMiddleware.js
+++ lib/defaultHeadersMiddleware.js
@@ -10,7 +10,13 @@
   res.set({
     // this should come after express.static otherwise it overrides
     'Cache-Control': 'max-age=300',
+
+    // https://observatory.mozilla.org/ for recommended settings
     'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
+    'Referrer-Policy': 'no-referrer',
+    'X-Content-Type-Options': 'nosniff',
+    'X-XSS-Protection': '1; mode=block',
+    'X-Frame-Options': 'SAMEORIGIN'
   });
   next();
 };

<|original_code|>
// sets default headers for all responses

// USAGE
// (should come AFTER any express.static middleware, including
//  react-scripts/lib/assetMiddleware)
//
// app.use(defaultHeadersMiddleware);

module.exports = (req, res, next) => {
  res.set({
    // this should come after express.static otherwise it overrides
    'Cache-Control': 'max-age=300',
    'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
  });
  next();
};

<|edits_diff|>
--- lib/defaultHeadersMiddleware.js
+++ lib/defaultHeadersMiddleware.js
@@ -10,6 +10,8 @@
   res.set({
     // this should come after express.static otherwise it overrides
     'Cache-Control': 'max-age=300',
+
+    // https://observatory.mozilla.org/ for recommended settings
     'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
   });
   next();
<|current_version|>
// sets default headers for all responses

// USAGE
// (should come AFTER any express.static middleware, including
//  react-scripts/lib/assetMiddleware)
//
// app.use(defaultHeadersMiddleware);

module.exports = (req, res, next) => {
  res.set({
    // this should come after express.static otherwise it overrides
    'Cache-Control': 'max-age=300',

    // https://observatory.mozilla.org/ for recommended settings
    'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
  });
  next();
};

<|next_version|>
// sets default headers for all responses

// USAGE
// (should come AFTER any express.static middleware, including
//  react-scripts/lib/assetMiddleware)
//
// app.use(defaultHeadersMiddleware);

module.exports = (req, res, next) => {
  res.set({
    // this should come after express.static otherwise it overrides
    'Cache-Control': 'max-age=300',

    // https://observatory.mozilla.org/ for recommended settings
    'Strict-Transport-Security': 'max-age=15768000; includeSubDomains; preload',
    'Referrer-Policy': 'no-referrer',
    'X-Content-Type-Options': 'nosniff',
    'X-XSS-Protection': '1; mode=block',
    'X-Frame-Options': 'SAMEORIGIN'
  });
  next();
};


Use libjass.min.js for production build.

--- webpack.production.config.js
+++ webpack.production.config.js
@@ -18,6 +18,7 @@
  * limitations under the License.
  */

+var path = require("path");
 var webpack = require("webpack");

 var config = require("./webpack.config");
@@ -30,4 +31,6 @@
 	})
 ];

+config.resolve.alias["libjass"] = path.resolve(require.resolve("libjass"), "..", "libjass.min.js");
+
 module.exports = config;

<|original_code|>
/**
 * libjass-demo-react
 *
 * https://github.com/Arnavion/libjass-demo-react
 *
 * Copyright 2016 Arnav Singh
 *
 * 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.
 */

var webpack = require("webpack");

var config = require("./webpack.config");

config.plugins = [
	new webpack.DefinePlugin({
		"process.env": {
			NODE_ENV: '"production"'
		}
	})
];

module.exports = config;

<|edits_diff|>
--- webpack.production.config.js
+++ webpack.production.config.js
@@ -18,6 +18,7 @@
  * limitations under the License.
  */

+var path = require("path");
 var webpack = require("webpack");

 var config = require("./webpack.config");
<|current_version|>
/**
 * libjass-demo-react
 *
 * https://github.com/Arnavion/libjass-demo-react
 *
 * Copyright 2016 Arnav Singh
 *
 * 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.
 */

var path = require("path");
var webpack = require("webpack");

var config = require("./webpack.config");

config.plugins = [
	new webpack.DefinePlugin({
		"process.env": {
			NODE_ENV: '"production"'
		}
	})
];

module.exports = config;

<|next_version|>
/**
 * libjass-demo-react
 *
 * https://github.com/Arnavion/libjass-demo-react
 *
 * Copyright 2016 Arnav Singh
 *
 * 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.
 */

var path = require("path");
var webpack = require("webpack");

var config = require("./webpack.config");

config.plugins = [
	new webpack.DefinePlugin({
		"process.env": {
			NODE_ENV: '"production"'
		}
	})
];

config.resolve.alias["libjass"] = path.resolve(require.resolve("libjass"), "..", "libjass.min.js");

module.exports = config;


import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';

export default class Compose extends React.PureComponent {

  render () {
    return (
      <div>
        <ComposeFormContainer />
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />
      </div>
    );
  }

}

import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';
import ModalContainer from '../../ui/containers/modal_container';

export default class Compose extends React.PureComponent {

  render () {
    return (
      <div>
        <ComposeFormContainer />
        <NotificationsContainer />
        <ModalContainer />
        <LoadingBarContainer className='loading-bar' />
      </div>
    );
  }

}

Fix visibility modal on standalone compose

--- app/javascript/mastodon/features/standalone/compose/index.js
+++ app/javascript/mastodon/features/standalone/compose/index.js
@@ -2,6 +2,7 @@
 import ComposeFormContainer from '../../compose/containers/compose_form_container';
 import NotificationsContainer from '../../ui/containers/notifications_container';
 import LoadingBarContainer from '../../ui/containers/loading_bar_container';
+import ModalContainer from '../../ui/containers/modal_container';

 export default class Compose extends React.PureComponent {

@@ -10,6 +11,7 @@
       <div>
         <ComposeFormContainer />
         <NotificationsContainer />
+        <ModalContainer />
         <LoadingBarContainer className='loading-bar' />
       </div>
     );

import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';
import ModalContainer from '../../ui/containers/modal_container';

export default class Compose extends React.PureComponent {

  render () {
    return (
      <div>
        <ComposeFormContainer />
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />
      </div>
    );
  }

}

<|original_code|>
import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';

export default class Compose extends React.PureComponent {

  render () {
    return (

        <ComposeFormContainer />
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />

    );
  }

}

<|edits_diff|>
--- app/javascript/mastodon/features/standalone/compose/index.js
+++ app/javascript/mastodon/features/standalone/compose/index.js
@@ -2,6 +2,7 @@
 import ComposeFormContainer from '../../compose/containers/compose_form_container';
 import NotificationsContainer from '../../ui/containers/notifications_container';
 import LoadingBarContainer from '../../ui/containers/loading_bar_container';
+import ModalContainer from '../../ui/containers/modal_container';

 export default class Compose extends React.PureComponent {

<|current_version|>
import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';
import ModalContainer from '../../ui/containers/modal_container';

export default class Compose extends React.PureComponent {

  render () {
    return (

        <ComposeFormContainer />
        <NotificationsContainer />
        <LoadingBarContainer className='loading-bar' />

    );
  }

}

<|next_version|>
import React from 'react';
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../ui/containers/notifications_container';
import LoadingBarContainer from '../../ui/containers/loading_bar_container';
import ModalContainer from '../../ui/containers/modal_container';

export default class Compose extends React.PureComponent {

  render () {
    return (

        <ComposeFormContainer />
        <NotificationsContainer />
        <ModalContainer />
        <LoadingBarContainer className='loading-bar' />

    );
  }

}


Disable scroll on number fields

Disable scroll on number fields

--- cla_frontend/assets-src/javascripts/app/js/controllers/layout.js
+++ cla_frontend/assets-src/javascripts/app/js/controllers/layout.js
@@ -12,8 +12,14 @@
             $window.scrollTo(0,0);
           });

+          var numberFields = angular.element('body');
+          numberFields.on('mousewheel', ':input[type=number]',function () {
+            angular.element(this).blur();
+          });
+
           $scope.$on('$destroy', function () {
             offStateChange();
+            numberFields.off('mousewheel');
           });

           $rootScope.user = user;

<|original_code|>
(function(){
  'use strict';

  angular.module('cla.controllers')
    .controller('LayoutCtrl',
      ['$rootScope', '$scope', '$window', 'History', 'user', 'hotkeys', 'localStorageService',
        function($rootScope, $scope, $window, History, user, hotkeys, localStorageService){
          var offStateChange = $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams){
            if (from.name === 'case_list') {
              History.caseListStateParams = fromParams;
            }
            $window.scrollTo(0,0);
          });

          $scope.$on('$destroy', function () {
            offStateChange();
          });

          $rootScope.user = user;

          hotkeys.add({
            combo: 's c',
            description: 'Search cases',
            callback: function(e) {
              e.preventDefault();
              angular.element('#search [name="q"]').focus();
            }
          });

          hotkeys.add({
            combo: '$',
            description: 'Show call scripts',
            callback: function(e) {
              e.preventDefault();
              $rootScope.showCallScript = !$rootScope.showCallScript;
              localStorageService.set('showCallScript', $rootScope.showCallScript);
            }
          });
        }
      ]
    );
})();

<|edits_diff|>
--- cla_frontend/assets-src/javascripts/app/js/controllers/layout.js
+++ cla_frontend/assets-src/javascripts/app/js/controllers/layout.js
@@ -10,6 +10,11 @@
               History.caseListStateParams = fromParams;
             }
             $window.scrollTo(0,0);
+          });
+
+          var numberFields = angular.element('body');
+          numberFields.on('mousewheel', ':input[type=number]',function () {
+            angular.element(this).blur();
           });

           $scope.$on('$destroy', function () {
<|current_version|>
(function(){
  'use strict';

  angular.module('cla.controllers')
    .controller('LayoutCtrl',
      ['$rootScope', '$scope', '$window', 'History', 'user', 'hotkeys', 'localStorageService',
        function($rootScope, $scope, $window, History, user, hotkeys, localStorageService){
          var offStateChange = $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams){
            if (from.name === 'case_list') {
              History.caseListStateParams = fromParams;
            }
            $window.scrollTo(0,0);
          });

          var numberFields = angular.element('body');
          numberFields.on('mousewheel', ':input[type=number]',function () {
            angular.element(this).blur();
          });

          $scope.$on('$destroy', function () {
            offStateChange();
          });

          $rootScope.user = user;

          hotkeys.add({
            combo: 's c',
            description: 'Search cases',
            callback: function(e) {
              e.preventDefault();
              angular.element('#search [name="q"]').focus();
            }
          });

          hotkeys.add({
            combo: '$',
            description: 'Show call scripts',
            callback: function(e) {
              e.preventDefault();
              $rootScope.showCallScript = !$rootScope.showCallScript;
              localStorageService.set('showCallScript', $rootScope.showCallScript);
            }
          });
        }
      ]
    );
})();

<|next_version|>
(function(){
  'use strict';

  angular.module('cla.controllers')
    .controller('LayoutCtrl',
      ['$rootScope', '$scope', '$window', 'History', 'user', 'hotkeys', 'localStorageService',
        function($rootScope, $scope, $window, History, user, hotkeys, localStorageService){
          var offStateChange = $rootScope.$on('$stateChangeSuccess', function(event, to, toParams, from, fromParams){
            if (from.name === 'case_list') {
              History.caseListStateParams = fromParams;
            }
            $window.scrollTo(0,0);
          });

          var numberFields = angular.element('body');
          numberFields.on('mousewheel', ':input[type=number]',function () {
            angular.element(this).blur();
          });

          $scope.$on('$destroy', function () {
            offStateChange();
            numberFields.off('mousewheel');
          });

          $rootScope.user = user;

          hotkeys.add({
            combo: 's c',
            description: 'Search cases',
            callback: function(e) {
              e.preventDefault();
              angular.element('#search [name="q"]').focus();
            }
          });

          hotkeys.add({
            combo: '$',
            description: 'Show call scripts',
            callback: function(e) {
              e.preventDefault();
              $rootScope.showCallScript = !$rootScope.showCallScript;
              localStorageService.set('showCallScript', $rootScope.showCallScript);
            }
          });
        }
      ]
    );
})();


Add highlighting for hbs and json

Add highlighting for hbs and json

--- packages/uikit-workshop/src/scripts/components/prism-languages.js
+++ packages/uikit-workshop/src/scripts/components/prism-languages.js
@@ -4,6 +4,7 @@
 import 'prismjs/components/prism-twig';
 import 'prismjs/components/prism-clike';
 import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-json';
 import 'prismjs/components/prism-css';
 import 'prismjs/components/prism-css-extras';
 import 'prismjs/components/prism-scss';
@@ -12,4 +13,9 @@
 import 'prismjs/components/prism-yaml';
 import 'prismjs/components/prism-handlebars';

+if (!Prism.languages.hasOwnProperty('hbs')) {
+  // hbs doesn't exist initially
+  Prism.languages.hbs = Prism.languages.handlebars;
+}
+
 export const PrismLanguages = Prism;

<|original_code|>
import Prism from 'prismjs/components/prism-core';
import 'prismjs/components/prism-markup-templating';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-twig';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-css-extras';
import 'prismjs/components/prism-scss';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-markdown';
import 'prismjs/components/prism-yaml';
import 'prismjs/components/prism-handlebars';

export const PrismLanguages = Prism;

<|edits_diff|>
--- packages/uikit-workshop/src/scripts/components/prism-languages.js
+++ packages/uikit-workshop/src/scripts/components/prism-languages.js
@@ -4,6 +4,7 @@
 import 'prismjs/components/prism-twig';
 import 'prismjs/components/prism-clike';
 import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-json';
 import 'prismjs/components/prism-css';
 import 'prismjs/components/prism-css-extras';
 import 'prismjs/components/prism-scss';
<|current_version|>
import Prism from 'prismjs/components/prism-core';
import 'prismjs/components/prism-markup-templating';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-twig';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-css-extras';
import 'prismjs/components/prism-scss';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-markdown';
import 'prismjs/components/prism-yaml';
import 'prismjs/components/prism-handlebars';

export const PrismLanguages = Prism;

<|next_version|>
import Prism from 'prismjs/components/prism-core';
import 'prismjs/components/prism-markup-templating';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-twig';
import 'prismjs/components/prism-clike';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-css-extras';
import 'prismjs/components/prism-scss';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-markdown';
import 'prismjs/components/prism-yaml';
import 'prismjs/components/prism-handlebars';

if (!Prism.languages.hasOwnProperty('hbs')) {
  // hbs doesn't exist initially
  Prism.languages.hbs = Prism.languages.handlebars;
}

export const PrismLanguages = Prism;


Add route towards testing page

Add route towards testing page

--- src/js/modules/comp-lib/v1/routes/index.js
+++ src/js/modules/comp-lib/v1/routes/index.js
@@ -1,10 +1,12 @@
 import Page from './../components/templates/page.jsx';
 import React from 'react';
 import atomicDesign from './atomic-design';
+import testing from './testing';

 export default (injectDeps, context) => {
   const {mount, page} = context;
   const PageCtx = injectDeps(Page);

   atomicDesign(React, PageCtx, page, mount);
+  testing(React, PageCtx, page, mount);
 };

<|original_code|>
import Page from './../components/templates/page.jsx';
import React from 'react';
import atomicDesign from './atomic-design';

export default (injectDeps, context) => {
  const {mount, page} = context;
  const PageCtx = injectDeps(Page);

  atomicDesign(React, PageCtx, page, mount);
};

<|edits_diff|>
--- src/js/modules/comp-lib/v1/routes/index.js
+++ src/js/modules/comp-lib/v1/routes/index.js
@@ -1,6 +1,7 @@
 import Page from './../components/templates/page.jsx';
 import React from 'react';
 import atomicDesign from './atomic-design';
+import testing from './testing';

 export default (injectDeps, context) => {
   const {mount, page} = context;
<|current_version|>
import Page from './../components/templates/page.jsx';
import React from 'react';
import atomicDesign from './atomic-design';
import testing from './testing';

export default (injectDeps, context) => {
  const {mount, page} = context;
  const PageCtx = injectDeps(Page);

  atomicDesign(React, PageCtx, page, mount);
};

<|next_version|>
import Page from './../components/templates/page.jsx';
import React from 'react';
import atomicDesign from './atomic-design';
import testing from './testing';

export default (injectDeps, context) => {
  const {mount, page} = context;
  const PageCtx = injectDeps(Page);

  atomicDesign(React, PageCtx, page, mount);
  testing(React, PageCtx, page, mount);
};


Fix bug where colour on mdast(1) persisted

The symbols used in the report to show the count of warnings, errors,
were coloured even when `color` was disabled on mdast(1).

--- lib/cli/file-set-pipeline/log.js
+++ lib/cli/file-set-pipeline/log.js
@@ -12,6 +12,7 @@
  * Dependencies.
  */

+var chalk = require('chalk');
 var report = require('vfile-reporter');

 /**
@@ -29,6 +30,10 @@
         'silent': context.silent
     });

+    if (!context.color) {
+        diagnostics = chalk.stripColor(diagnostics);
+    }
+
     if (diagnostics) {
         context.stderr(diagnostics);
     }

<|original_code|>
/**
 * @author Titus Wormer
 * @copyright 2015 Titus Wormer
 * @license MIT
 * @module mdast:cli:log
 * @fileoverview Log a file context on successful completion.
 */

'use strict';

/*
 * Dependencies.
 */

var report = require('vfile-reporter');

/**
 * Output diagnostics to stdout(4) or stderr(4).
 *
 * @param {CLI} context - CLI engine.
 */
function log(context) {
    var files = context.files;
    var applicables = files.filter(function (file) {
        return file.namespace('mdast:cli').providedByUser;
    });
    var diagnostics = report(applicables, {
        'quiet': context.quiet,
        'silent': context.silent
    });

    if (diagnostics) {
        context.stderr(diagnostics);
    }
}

/*
 * Expose.
 */

module.exports = log;

<|edits_diff|>
--- lib/cli/file-set-pipeline/log.js
+++ lib/cli/file-set-pipeline/log.js
@@ -12,6 +12,7 @@
  * Dependencies.
  */

+var chalk = require('chalk');
 var report = require('vfile-reporter');

 /**
<|current_version|>
/**
 * @author Titus Wormer
 * @copyright 2015 Titus Wormer
 * @license MIT
 * @module mdast:cli:log
 * @fileoverview Log a file context on successful completion.
 */

'use strict';

/*
 * Dependencies.
 */

var chalk = require('chalk');
var report = require('vfile-reporter');

/**
 * Output diagnostics to stdout(4) or stderr(4).
 *
 * @param {CLI} context - CLI engine.
 */
function log(context) {
    var files = context.files;
    var applicables = files.filter(function (file) {
        return file.namespace('mdast:cli').providedByUser;
    });
    var diagnostics = report(applicables, {
        'quiet': context.quiet,
        'silent': context.silent
    });

    if (diagnostics) {
        context.stderr(diagnostics);
    }
}

/*
 * Expose.
 */

module.exports = log;

<|next_version|>
/**
 * @author Titus Wormer
 * @copyright 2015 Titus Wormer
 * @license MIT
 * @module mdast:cli:log
 * @fileoverview Log a file context on successful completion.
 */

'use strict';

/*
 * Dependencies.
 */

var chalk = require('chalk');
var report = require('vfile-reporter');

/**
 * Output diagnostics to stdout(4) or stderr(4).
 *
 * @param {CLI} context - CLI engine.
 */
function log(context) {
    var files = context.files;
    var applicables = files.filter(function (file) {
        return file.namespace('mdast:cli').providedByUser;
    });
    var diagnostics = report(applicables, {
        'quiet': context.quiet,
        'silent': context.silent
    });

    if (!context.color) {
        diagnostics = chalk.stripColor(diagnostics);
    }

    if (diagnostics) {
        context.stderr(diagnostics);
    }
}

/*
 * Expose.
 */

module.exports = log;


Clean up some linter warnings

Clean up some linter warnings

--- lib/classes/Renderable.js
+++ lib/classes/Renderable.js
@@ -21,8 +21,11 @@
       // Intent of hasOwnProperty check is handled by constructor test.
       // noinspection JSUnfilteredForInLoop
       if (this.data[item].constructor.name === 'Renderable') {
+        //noinspection JSUnfilteredForInLoop
         renders.push(function(item, next) {
+          // noinspection JSUnfilteredForInLoop
           this.data[item].render(function(rendered) {
+            // noinspection JSUnfilteredForInLoop
             this.data[item] = rendered;
             next(false);
           }.bind(this));

<|original_code|>
var hooks = require('hooks');
var async = require('async');

function Renderable(View) {
  this.data = {};
  this.mode = 'main';
  this.view = {};

  if (typeof View === 'function') {
    this.view = new View();
  }

  this.render = function(cb) {
    // recursively render any Renderable objects inside this.data
    // this currently has one flaw, Renderable objects cannot be in nested objects,
    // or you need to render them manually
    var renders = [];
    var item;

    for (item in this.data) {
      // Intent of hasOwnProperty check is handled by constructor test.
      // noinspection JSUnfilteredForInLoop
      if (this.data[item].constructor.name === 'Renderable') {
        renders.push(function(item, next) {
          this.data[item].render(function(rendered) {
            this.data[item] = rendered;
            next(false);
          }.bind(this));
        }.bind(this, item));
      }
    }

    async.parallel(
      renders,
      function(err) {
      if (err) {
        cb(err)
      }
      else {
        this.view.data(this.data).mode(this.mode).render(cb);
      }
    }.bind(this));
  }
}

for (var k in hooks) {
  // Per hooks module documentation
  // noinspection JSUnfilteredForInLoop
  Renderable[k] = Renderable.prototype[k] = hooks[k];
}

module.exports = Renderable;

<|edits_diff|>
--- lib/classes/Renderable.js
+++ lib/classes/Renderable.js
@@ -21,7 +21,9 @@
       // Intent of hasOwnProperty check is handled by constructor test.
       // noinspection JSUnfilteredForInLoop
       if (this.data[item].constructor.name === 'Renderable') {
+        //noinspection JSUnfilteredForInLoop
         renders.push(function(item, next) {
+          // noinspection JSUnfilteredForInLoop
           this.data[item].render(function(rendered) {
             this.data[item] = rendered;
             next(false);
<|current_version|>
var hooks = require('hooks');
var async = require('async');

function Renderable(View) {
  this.data = {};
  this.mode = 'main';
  this.view = {};

  if (typeof View === 'function') {
    this.view = new View();
  }

  this.render = function(cb) {
    // recursively render any Renderable objects inside this.data
    // this currently has one flaw, Renderable objects cannot be in nested objects,
    // or you need to render them manually
    var renders = [];
    var item;

    for (item in this.data) {
      // Intent of hasOwnProperty check is handled by constructor test.
      // noinspection JSUnfilteredForInLoop
      if (this.data[item].constructor.name === 'Renderable') {
        //noinspection JSUnfilteredForInLoop
        renders.push(function(item, next) {
          // noinspection JSUnfilteredForInLoop
          this.data[item].render(function(rendered) {
            this.data[item] = rendered;
            next(false);
          }.bind(this));
        }.bind(this, item));
      }
    }

    async.parallel(
      renders,
      function(err) {
      if (err) {
        cb(err)
      }
      else {
        this.view.data(this.data).mode(this.mode).render(cb);
      }
    }.bind(this));
  }
}

for (var k in hooks) {
  // Per hooks module documentation
  // noinspection JSUnfilteredForInLoop
  Renderable[k] = Renderable.prototype[k] = hooks[k];
}

module.exports = Renderable;

<|next_version|>
var hooks = require('hooks');
var async = require('async');

function Renderable(View) {
  this.data = {};
  this.mode = 'main';
  this.view = {};

  if (typeof View === 'function') {
    this.view = new View();
  }

  this.render = function(cb) {
    // recursively render any Renderable objects inside this.data
    // this currently has one flaw, Renderable objects cannot be in nested objects,
    // or you need to render them manually
    var renders = [];
    var item;

    for (item in this.data) {
      // Intent of hasOwnProperty check is handled by constructor test.
      // noinspection JSUnfilteredForInLoop
      if (this.data[item].constructor.name === 'Renderable') {
        //noinspection JSUnfilteredForInLoop
        renders.push(function(item, next) {
          // noinspection JSUnfilteredForInLoop
          this.data[item].render(function(rendered) {
            // noinspection JSUnfilteredForInLoop
            this.data[item] = rendered;
            next(false);
          }.bind(this));
        }.bind(this, item));
      }
    }

    async.parallel(
      renders,
      function(err) {
      if (err) {
        cb(err)
      }
      else {
        this.view.data(this.data).mode(this.mode).render(cb);
      }
    }.bind(this));
  }
}

for (var k in hooks) {
  // Per hooks module documentation
  // noinspection JSUnfilteredForInLoop
  Renderable[k] = Renderable.prototype[k] = hooks[k];
}

module.exports = Renderable;


Add a throbber for "sending sub request" in UserCard

--- src/redux/reducers/user-views.js
+++ src/redux/reducers/user-views.js
@@ -6,14 +6,17 @@
 export default function userViews(state = {}, action) {
   switch (action.type) {
     case request(ActionTypes.SUBSCRIBE):
+    case request(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
     case request(ActionTypes.UNSUBSCRIBE): {
       const userId = action.payload.id;
       const userView = state[userId];
       return {...state, [userId]: {...userView, isSubscribing: true}};
     }
     case response(ActionTypes.SUBSCRIBE):
+    case response(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
     case response(ActionTypes.UNSUBSCRIBE):
     case fail(ActionTypes.SUBSCRIBE):
+    case fail(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
     case fail(ActionTypes.UNSUBSCRIBE): {
       const userId = action.request.id;
       const userView = state[userId];

<|original_code|>
import * as ActionTypes from '../action-types';
import * as ActionHelpers from '../action-helpers';

const {request, response, fail} = ActionHelpers;

export default function userViews(state = {}, action) {
  switch (action.type) {
    case request(ActionTypes.SUBSCRIBE):
    case request(ActionTypes.UNSUBSCRIBE): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: true}};
    }
    case response(ActionTypes.SUBSCRIBE):
    case response(ActionTypes.UNSUBSCRIBE):
    case fail(ActionTypes.SUBSCRIBE):
    case fail(ActionTypes.UNSUBSCRIBE): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: false}};
    }

    case request(ActionTypes.BAN):
    case request(ActionTypes.UNBAN): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: true}};
    }
    case response(ActionTypes.BAN):
    case response(ActionTypes.UNBAN):
    case fail(ActionTypes.BAN):
    case fail(ActionTypes.UNBAN): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: false}};
    }
  }

  return state;
}

<|edits_diff|>
--- src/redux/reducers/user-views.js
+++ src/redux/reducers/user-views.js
@@ -6,12 +6,14 @@
 export default function userViews(state = {}, action) {
   switch (action.type) {
     case request(ActionTypes.SUBSCRIBE):
+    case request(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
     case request(ActionTypes.UNSUBSCRIBE): {
       const userId = action.payload.id;
       const userView = state[userId];
       return {...state, [userId]: {...userView, isSubscribing: true}};
     }
     case response(ActionTypes.SUBSCRIBE):
+    case response(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
     case response(ActionTypes.UNSUBSCRIBE):
     case fail(ActionTypes.SUBSCRIBE):
     case fail(ActionTypes.UNSUBSCRIBE): {
<|current_version|>
import * as ActionTypes from '../action-types';
import * as ActionHelpers from '../action-helpers';

const {request, response, fail} = ActionHelpers;

export default function userViews(state = {}, action) {
  switch (action.type) {
    case request(ActionTypes.SUBSCRIBE):
    case request(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
    case request(ActionTypes.UNSUBSCRIBE): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: true}};
    }
    case response(ActionTypes.SUBSCRIBE):
    case response(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
    case response(ActionTypes.UNSUBSCRIBE):
    case fail(ActionTypes.SUBSCRIBE):
    case fail(ActionTypes.UNSUBSCRIBE): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: false}};
    }

    case request(ActionTypes.BAN):
    case request(ActionTypes.UNBAN): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: true}};
    }
    case response(ActionTypes.BAN):
    case response(ActionTypes.UNBAN):
    case fail(ActionTypes.BAN):
    case fail(ActionTypes.UNBAN): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: false}};
    }
  }

  return state;
}

<|next_version|>
import * as ActionTypes from '../action-types';
import * as ActionHelpers from '../action-helpers';

const {request, response, fail} = ActionHelpers;

export default function userViews(state = {}, action) {
  switch (action.type) {
    case request(ActionTypes.SUBSCRIBE):
    case request(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
    case request(ActionTypes.UNSUBSCRIBE): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: true}};
    }
    case response(ActionTypes.SUBSCRIBE):
    case response(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
    case response(ActionTypes.UNSUBSCRIBE):
    case fail(ActionTypes.SUBSCRIBE):
    case fail(ActionTypes.SEND_SUBSCRIPTION_REQUEST):
    case fail(ActionTypes.UNSUBSCRIBE): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isSubscribing: false}};
    }

    case request(ActionTypes.BAN):
    case request(ActionTypes.UNBAN): {
      const userId = action.payload.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: true}};
    }
    case response(ActionTypes.BAN):
    case response(ActionTypes.UNBAN):
    case fail(ActionTypes.BAN):
    case fail(ActionTypes.UNBAN): {
      const userId = action.request.id;
      const userView = state[userId];
      return {...state, [userId]: {...userView, isBlocking: false}};
    }
  }

  return state;
}


Add setOnChange that allows a controller to set a function to call when the currentTask changes.

--- website/src/app/project/experiments/experiment/components/tasks/current-task.service.js
+++ website/src/app/project/experiments/experiment/components/tasks/current-task.service.js
@@ -1,6 +1,11 @@
 class CurrentTask {
     constructor() {
         this.currentTask = null;
+        this.onChangeFN = null;
+    }
+
+    setOnChange(fn) {
+        this.onChangeFN = fn;
     }

     get() {
@@ -9,6 +14,9 @@

     set(task) {
         this.currentTask = task;
+        if (this.onChangeFN) {
+            this.onChangeFN();
+        }
     }
 }

<|original_code|>
class CurrentTask {
    constructor() {
        this.currentTask = null;
    }

    get() {
        return this.currentTask;
    }

    set(task) {
        this.currentTask = task;
    }
}

angular.module('materialscommons').service('currentTask', CurrentTask);

<|edits_diff|>
--- website/src/app/project/experiments/experiment/components/tasks/current-task.service.js
+++ website/src/app/project/experiments/experiment/components/tasks/current-task.service.js
@@ -1,6 +1,11 @@
 class CurrentTask {
     constructor() {
         this.currentTask = null;
+        this.onChangeFN = null;
+    }
+
+    setOnChange(fn) {
+        this.onChangeFN = fn;
     }

     get() {
<|current_version|>
class CurrentTask {
    constructor() {
        this.currentTask = null;
        this.onChangeFN = null;
    }

    setOnChange(fn) {
        this.onChangeFN = fn;
    }

    get() {
        return this.currentTask;
    }

    set(task) {
        this.currentTask = task;
    }
}

angular.module('materialscommons').service('currentTask', CurrentTask);

<|next_version|>
class CurrentTask {
    constructor() {
        this.currentTask = null;
        this.onChangeFN = null;
    }

    setOnChange(fn) {
        this.onChangeFN = fn;
    }

    get() {
        return this.currentTask;
    }

    set(task) {
        this.currentTask = task;
        if (this.onChangeFN) {
            this.onChangeFN();
        }
    }
}

angular.module('materialscommons').service('currentTask', CurrentTask);


Fix MIME type for whitelist

Fix MIME type for whitelist

--- spider/common/whiteList.js
+++ spider/common/whiteList.js
@@ -21,8 +21,16 @@
     ext: 'xlsx'
   },
   {
+    mime: 'application/x-zip-compressed',
+    ext: 'zip'
+  },
+  {
     mime: 'application/zip',
     ext: 'zip'
+  },
+  {
+    mime: 'application/octet-stream',
+    ext: 'rar'
   },
   {
     mime: 'application/x-rar-compressed',

<|original_code|>
// 允许上传的MIME类型
const mimeWhiteList = [
  {
    mime: 'application/msword',
    ext: 'doc'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ext: 'docx'
  },
  {
    mime: 'application/pdf',
    ext: 'pdf'
  },
  {
    mime: 'application/vnd.ms-excel',
    ext: 'xls'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    ext: 'xlsx'
  },
  {
    mime: 'application/zip',
    ext: 'zip'
  },
  {
    mime: 'application/x-rar-compressed',
    ext: 'rar'
  },
  {
    mime: 'image/jpeg',
    ext: 'jpeg, jpg'
  },
  {
    mime: 'image/gif',
    ext: 'gif'
  },
  {
    mime: 'image/png',
    ext: 'png'
  }
];

module.exports = mimeWhiteList;
<|edits_diff|>
--- spider/common/whiteList.js
+++ spider/common/whiteList.js
@@ -19,6 +19,10 @@
   {
     mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
     ext: 'xlsx'
+  },
+  {
+    mime: 'application/x-zip-compressed',
+    ext: 'zip'
   },
   {
     mime: 'application/zip',
<|current_version|>
// 允许上传的MIME类型
const mimeWhiteList = [
  {
    mime: 'application/msword',
    ext: 'doc'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ext: 'docx'
  },
  {
    mime: 'application/pdf',
    ext: 'pdf'
  },
  {
    mime: 'application/vnd.ms-excel',
    ext: 'xls'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    ext: 'xlsx'
  },
  {
    mime: 'application/x-zip-compressed',
    ext: 'zip'
  },
  {
    mime: 'application/zip',
    ext: 'zip'
  },
  {
    mime: 'application/x-rar-compressed',
    ext: 'rar'
  },
  {
    mime: 'image/jpeg',
    ext: 'jpeg, jpg'
  },
  {
    mime: 'image/gif',
    ext: 'gif'
  },
  {
    mime: 'image/png',
    ext: 'png'
  }
];

module.exports = mimeWhiteList;
<|next_version|>
// 允许上传的MIME类型
const mimeWhiteList = [
  {
    mime: 'application/msword',
    ext: 'doc'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    ext: 'docx'
  },
  {
    mime: 'application/pdf',
    ext: 'pdf'
  },
  {
    mime: 'application/vnd.ms-excel',
    ext: 'xls'
  },
  {
    mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    ext: 'xlsx'
  },
  {
    mime: 'application/x-zip-compressed',
    ext: 'zip'
  },
  {
    mime: 'application/zip',
    ext: 'zip'
  },
  {
    mime: 'application/octet-stream',
    ext: 'rar'
  },
  {
    mime: 'application/x-rar-compressed',
    ext: 'rar'
  },
  {
    mime: 'image/jpeg',
    ext: 'jpeg, jpg'
  },
  {
    mime: 'image/gif',
    ext: 'gif'
  },
  {
    mime: 'image/png',
    ext: 'png'
  }
];

module.exports = mimeWhiteList;