:zap: Add Clock settings to the list of settings

--- src/containers/Settings.js
+++ src/containers/Settings.js
@@ -2,6 +2,7 @@
 import Layout from 'react-toolbox/lib/layout/Layout';
 import Panel from 'react-toolbox/lib/layout/Panel';
 import { Settings as BackgroundSettings } from '@modules/background';
+import { Settings as ClockSettings } from '@modules/clock';

 class Settings extends Component {
     render() {
@@ -12,6 +13,9 @@

                             <BackgroundSettings />

+

+                            <ClockSettings />
+

                 </Panel>
             </Layout>

<|original_code|>
import React, { Component } from 'react';
import Layout from 'react-toolbox/lib/layout/Layout';
import Panel from 'react-toolbox/lib/layout/Panel';
import { Settings as BackgroundSettings } from '@modules/background';

class Settings extends Component {
    render() {
        return (
            <Layout>
                <Panel>

                            <BackgroundSettings />

                </Panel>
            </Layout>
        );
    }
}

export default Settings

<|edits_diff|>
--- src/containers/Settings.js
+++ src/containers/Settings.js
@@ -2,6 +2,7 @@
 import Layout from 'react-toolbox/lib/layout/Layout';
 import Panel from 'react-toolbox/lib/layout/Panel';
 import { Settings as BackgroundSettings } from '@modules/background';
+import { Settings as ClockSettings } from '@modules/clock';

 class Settings extends Component {
     render() {
<|current_version|>
import React, { Component } from 'react';
import Layout from 'react-toolbox/lib/layout/Layout';
import Panel from 'react-toolbox/lib/layout/Panel';
import { Settings as BackgroundSettings } from '@modules/background';
import { Settings as ClockSettings } from '@modules/clock';

class Settings extends Component {
    render() {
        return (
            <Layout>
                <Panel>

                            <BackgroundSettings />

                </Panel>
            </Layout>
        );
    }
}

export default Settings

<|next_version|>
import React, { Component } from 'react';
import Layout from 'react-toolbox/lib/layout/Layout';
import Panel from 'react-toolbox/lib/layout/Panel';
import { Settings as BackgroundSettings } from '@modules/background';
import { Settings as ClockSettings } from '@modules/clock';

class Settings extends Component {
    render() {
        return (
            <Layout>
                <Panel>

                            <BackgroundSettings />

                            <ClockSettings />

                </Panel>
            </Layout>
        );
    }
}

export default Settings


Add status messages to the headlines script

--- src/get-headlines.js
+++ src/get-headlines.js
@@ -20,6 +20,7 @@
 if (!NEWS_API_KEY) {
     if (MOCK_HEADLINES === '1') {
         fs.copyFileSync(MOCK_HEADLINES_FILE, HEADLINES_FILE);
+        console.log('Using mock headlines');
     } else {
         console.error('The NEWS_API_KEY environment variable is not set.');
         console.error('Use template.env to Create a .env file.');
@@ -29,5 +30,6 @@
 } else {
     getSourcesWithArticles(NEWS_API_KEY).then(headlines => {
         fs.writeFileSync(HEADLINES_FILE, JSON.stringify(headlines));
+        console.log('Retrieved real headlines');
     });
 }

<|original_code|>
/* eslint-env node */
/* eslint-disable no-console */

import fs from 'fs';
import path from 'path';

import dotenv from 'dotenv';
import {getSourcesWithArticles} from './newsapi';

dotenv.config();

const {MOCK_HEADLINES, NEWS_API_KEY} = process.env;

const HEADLINES_FILE = path.resolve(__dirname, '../site/headlines.json');
const MOCK_HEADLINES_FILE = path.resolve(
    __dirname,
    '../site/mock-headlines.json'
);

if (!NEWS_API_KEY) {
    if (MOCK_HEADLINES === '1') {
        fs.copyFileSync(MOCK_HEADLINES_FILE, HEADLINES_FILE);
    } else {
        console.error('The NEWS_API_KEY environment variable is not set.');
        console.error('Use template.env to Create a .env file.');
        console.error('You can also set MOCK_HEADLINES to 1 to use mock data.');
        process.exit(1);
    }
} else {
    getSourcesWithArticles(NEWS_API_KEY).then(headlines => {
        fs.writeFileSync(HEADLINES_FILE, JSON.stringify(headlines));
    });
}

<|edits_diff|>
--- src/get-headlines.js
+++ src/get-headlines.js
@@ -20,6 +20,7 @@
 if (!NEWS_API_KEY) {
     if (MOCK_HEADLINES === '1') {
         fs.copyFileSync(MOCK_HEADLINES_FILE, HEADLINES_FILE);
+        console.log('Using mock headlines');
     } else {
         console.error('The NEWS_API_KEY environment variable is not set.');
         console.error('Use template.env to Create a .env file.');
<|current_version|>
/* eslint-env node */
/* eslint-disable no-console */

import fs from 'fs';
import path from 'path';

import dotenv from 'dotenv';
import {getSourcesWithArticles} from './newsapi';

dotenv.config();

const {MOCK_HEADLINES, NEWS_API_KEY} = process.env;

const HEADLINES_FILE = path.resolve(__dirname, '../site/headlines.json');
const MOCK_HEADLINES_FILE = path.resolve(
    __dirname,
    '../site/mock-headlines.json'
);

if (!NEWS_API_KEY) {
    if (MOCK_HEADLINES === '1') {
        fs.copyFileSync(MOCK_HEADLINES_FILE, HEADLINES_FILE);
        console.log('Using mock headlines');
    } else {
        console.error('The NEWS_API_KEY environment variable is not set.');
        console.error('Use template.env to Create a .env file.');
        console.error('You can also set MOCK_HEADLINES to 1 to use mock data.');
        process.exit(1);
    }
} else {
    getSourcesWithArticles(NEWS_API_KEY).then(headlines => {
        fs.writeFileSync(HEADLINES_FILE, JSON.stringify(headlines));
    });
}

<|next_version|>
/* eslint-env node */
/* eslint-disable no-console */

import fs from 'fs';
import path from 'path';

import dotenv from 'dotenv';
import {getSourcesWithArticles} from './newsapi';

dotenv.config();

const {MOCK_HEADLINES, NEWS_API_KEY} = process.env;

const HEADLINES_FILE = path.resolve(__dirname, '../site/headlines.json');
const MOCK_HEADLINES_FILE = path.resolve(
    __dirname,
    '../site/mock-headlines.json'
);

if (!NEWS_API_KEY) {
    if (MOCK_HEADLINES === '1') {
        fs.copyFileSync(MOCK_HEADLINES_FILE, HEADLINES_FILE);
        console.log('Using mock headlines');
    } else {
        console.error('The NEWS_API_KEY environment variable is not set.');
        console.error('Use template.env to Create a .env file.');
        console.error('You can also set MOCK_HEADLINES to 1 to use mock data.');
        process.exit(1);
    }
} else {
    getSourcesWithArticles(NEWS_API_KEY).then(headlines => {
        fs.writeFileSync(HEADLINES_FILE, JSON.stringify(headlines));
        console.log('Retrieved real headlines');
    });
}


Add organization + 404 pages

Add organization + 404 pages

--- src/App.js
+++ src/App.js
@@ -9,6 +9,8 @@
 import ExperimentsHome from './experiments/Index';
 import Home from './home/Index';
 import HomeHeader from './home/Header';
+import NotFound from './NotFound';
+import OrganizationHome from './organization/Index';
 import TopNav from './patterns/navigation/TopNav';
 import './App.css';

@@ -30,6 +32,8 @@
               <Switch>
                 <Route exact path="/" component={Home} />
                 <Route path="/experiments/" component={ExperimentsHome} />
+                <Route path="/organization/" component={OrganizationHome} />
+                <Route component={NotFound} />
               </Switch>
             </ContentContainer>
           </section>

<|original_code|>
import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom';

import ContentContainer from './patterns/containers/Content';
import ExperimentsHome from './experiments/Index';
import Home from './home/Index';
import HomeHeader from './home/Header';
import TopNav from './patterns/navigation/TopNav';
import './App.css';

class App extends Component {
  render() {
    return (
      <Router>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={HomeHeader} />
                <Route component={TopNav} />
              </Switch>
            </ContentContainer>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/experiments/" component={ExperimentsHome} />
              </Switch>
            </ContentContainer>

      </Router>
    );
  }
}

export default App;

<|edits_diff|>
--- src/App.js
+++ src/App.js
@@ -9,6 +9,8 @@
 import ExperimentsHome from './experiments/Index';
 import Home from './home/Index';
 import HomeHeader from './home/Header';
+import NotFound from './NotFound';
+import OrganizationHome from './organization/Index';
 import TopNav from './patterns/navigation/TopNav';
 import './App.css';

<|current_version|>
import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom';

import ContentContainer from './patterns/containers/Content';
import ExperimentsHome from './experiments/Index';
import Home from './home/Index';
import HomeHeader from './home/Header';
import NotFound from './NotFound';
import OrganizationHome from './organization/Index';
import TopNav from './patterns/navigation/TopNav';
import './App.css';

class App extends Component {
  render() {
    return (
      <Router>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={HomeHeader} />
                <Route component={TopNav} />
              </Switch>
            </ContentContainer>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/experiments/" component={ExperimentsHome} />
              </Switch>
            </ContentContainer>

      </Router>
    );
  }
}

export default App;

<|next_version|>
import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom';

import ContentContainer from './patterns/containers/Content';
import ExperimentsHome from './experiments/Index';
import Home from './home/Index';
import HomeHeader from './home/Header';
import NotFound from './NotFound';
import OrganizationHome from './organization/Index';
import TopNav from './patterns/navigation/TopNav';
import './App.css';

class App extends Component {
  render() {
    return (
      <Router>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={HomeHeader} />
                <Route component={TopNav} />
              </Switch>
            </ContentContainer>

            <ContentContainer>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path="/experiments/" component={ExperimentsHome} />
                <Route path="/organization/" component={OrganizationHome} />
                <Route component={NotFound} />
              </Switch>
            </ContentContainer>

      </Router>
    );
  }
}

export default App;


Add CSRF token to all requests

Add CSRF token to all requests

--- client/utils/fetcher.js
+++ client/utils/fetcher.js
@@ -1,6 +1,9 @@
 import fetch from 'isomorphic-fetch';
 import _ from 'lodash';
 import { push } from 'react-router-redux';
+
+// grab the CSRF token from the cookie
+const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=s*([^;]*).*$)|^.*$/, '$1');

 async function request(url, userOptions, dispatch) {
   const defaultOptions = {
@@ -11,6 +14,7 @@
       'Content-Security-Policy': 'default-src \'self\'',
       'X-Frame-Options': 'SAMEORIGIN',
       'X-XSS-Protection': 1,
+      'X-CSRFToken': csrfToken,
     },
   };

<|original_code|>
import fetch from 'isomorphic-fetch';
import _ from 'lodash';
import { push } from 'react-router-redux';

async function request(url, userOptions, dispatch) {
  const defaultOptions = {
    credentials: 'same-origin',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Content-Security-Policy': 'default-src \'self\'',
      'X-Frame-Options': 'SAMEORIGIN',
      'X-XSS-Protection': 1,
    },
  };

  if (userOptions.body && typeof userOptions.body === 'object') {
    userOptions.body = JSON.stringify(userOptions.body);
  }

  const options = { ...defaultOptions, ...userOptions };

  try {
    const response = await fetch(url, options);
    const body = await response.json();

    if (body.redirectTo) {
      if (!dispatch) {
        throw new Error('Dispatch not found!');
      }

      dispatch(push(body.redirectTo));
    }

    if (response.ok) {
      return body;
    }

    throw body;
  }
  catch (e) {
    const error = _.get(e, 'error', e) || 'An unexpected error has occurred!';
    return { error };
  }
}

const wrapper = {};
['get', 'post', 'put', 'delete'].forEach((method) => {
  wrapper[method] = (url, options = {}) => {
    options.method = method;
    return request(url, options);
  };
});

export default wrapper;

<|edits_diff|>
--- client/utils/fetcher.js
+++ client/utils/fetcher.js
@@ -1,6 +1,9 @@
 import fetch from 'isomorphic-fetch';
 import _ from 'lodash';
 import { push } from 'react-router-redux';
+
+// grab the CSRF token from the cookie
+const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=s*([^;]*).*$)|^.*$/, '$1');

 async function request(url, userOptions, dispatch) {
   const defaultOptions = {
<|current_version|>
import fetch from 'isomorphic-fetch';
import _ from 'lodash';
import { push } from 'react-router-redux';

// grab the CSRF token from the cookie
const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=s*([^;]*).*$)|^.*$/, '$1');

async function request(url, userOptions, dispatch) {
  const defaultOptions = {
    credentials: 'same-origin',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Content-Security-Policy': 'default-src \'self\'',
      'X-Frame-Options': 'SAMEORIGIN',
      'X-XSS-Protection': 1,
    },
  };

  if (userOptions.body && typeof userOptions.body === 'object') {
    userOptions.body = JSON.stringify(userOptions.body);
  }

  const options = { ...defaultOptions, ...userOptions };

  try {
    const response = await fetch(url, options);
    const body = await response.json();

    if (body.redirectTo) {
      if (!dispatch) {
        throw new Error('Dispatch not found!');
      }

      dispatch(push(body.redirectTo));
    }

    if (response.ok) {
      return body;
    }

    throw body;
  }
  catch (e) {
    const error = _.get(e, 'error', e) || 'An unexpected error has occurred!';
    return { error };
  }
}

const wrapper = {};
['get', 'post', 'put', 'delete'].forEach((method) => {
  wrapper[method] = (url, options = {}) => {
    options.method = method;
    return request(url, options);
  };
});

export default wrapper;

<|next_version|>
import fetch from 'isomorphic-fetch';
import _ from 'lodash';
import { push } from 'react-router-redux';

// grab the CSRF token from the cookie
const csrfToken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*=s*([^;]*).*$)|^.*$/, '$1');

async function request(url, userOptions, dispatch) {
  const defaultOptions = {
    credentials: 'same-origin',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Content-Security-Policy': 'default-src \'self\'',
      'X-Frame-Options': 'SAMEORIGIN',
      'X-XSS-Protection': 1,
      'X-CSRFToken': csrfToken,
    },
  };

  if (userOptions.body && typeof userOptions.body === 'object') {
    userOptions.body = JSON.stringify(userOptions.body);
  }

  const options = { ...defaultOptions, ...userOptions };

  try {
    const response = await fetch(url, options);
    const body = await response.json();

    if (body.redirectTo) {
      if (!dispatch) {
        throw new Error('Dispatch not found!');
      }

      dispatch(push(body.redirectTo));
    }

    if (response.ok) {
      return body;
    }

    throw body;
  }
  catch (e) {
    const error = _.get(e, 'error', e) || 'An unexpected error has occurred!';
    return { error };
  }
}

const wrapper = {};
['get', 'post', 'put', 'delete'].forEach((method) => {
  wrapper[method] = (url, options = {}) => {
    options.method = method;
    return request(url, options);
  };
});

export default wrapper;


Change cursor on rootToken as well

Change cursor on rootToken as well

--- app/js/arethusa.core/directives/root_token.js
+++ app/js/arethusa.core/directives/root_token.js
@@ -26,11 +26,15 @@
         element.bind('mouseenter', function () {
           apply(function() {
             element.addClass('hovered');
+            if (changeHeads && state.hasSelections()) {
+              element.addClass('copy-cursor');
+            }
           });
         });
         element.bind('mouseleave', function () {
           apply(function() {
             element.removeClass('hovered');
+            element.removeClass('copy-cursor');
           });
         });
       }

<|original_code|>
"use strict";

angular.module('arethusa.core').directive('rootToken', [
  'state',
  'depTree',
  function(state, depTree) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        function apply(fn) {
          scope.$apply(fn());
        }

        var changeHeads = depTree.mode === 'editor';

        element.bind('click', function() {
          apply(function() {
            if (changeHeads) {
              state.handleChangeHead('0000', 'click');
              state.deselectAll();
            }
          });
        });

        element.bind('mouseenter', function () {
          apply(function() {
            element.addClass('hovered');
          });
        });
        element.bind('mouseleave', function () {
          apply(function() {
            element.removeClass('hovered');
          });
        });
      }
    };
  }
]);

<|edits_diff|>
--- app/js/arethusa.core/directives/root_token.js
+++ app/js/arethusa.core/directives/root_token.js
@@ -26,6 +26,9 @@
         element.bind('mouseenter', function () {
           apply(function() {
             element.addClass('hovered');
+            if (changeHeads && state.hasSelections()) {
+              element.addClass('copy-cursor');
+            }
           });
         });
         element.bind('mouseleave', function () {
<|current_version|>
"use strict";

angular.module('arethusa.core').directive('rootToken', [
  'state',
  'depTree',
  function(state, depTree) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        function apply(fn) {
          scope.$apply(fn());
        }

        var changeHeads = depTree.mode === 'editor';

        element.bind('click', function() {
          apply(function() {
            if (changeHeads) {
              state.handleChangeHead('0000', 'click');
              state.deselectAll();
            }
          });
        });

        element.bind('mouseenter', function () {
          apply(function() {
            element.addClass('hovered');
            if (changeHeads && state.hasSelections()) {
              element.addClass('copy-cursor');
            }
          });
        });
        element.bind('mouseleave', function () {
          apply(function() {
            element.removeClass('hovered');
          });
        });
      }
    };
  }
]);

<|next_version|>
"use strict";

angular.module('arethusa.core').directive('rootToken', [
  'state',
  'depTree',
  function(state, depTree) {
    return {
      restrict: 'A',
      scope: {},
      link: function(scope, element, attrs) {
        function apply(fn) {
          scope.$apply(fn());
        }

        var changeHeads = depTree.mode === 'editor';

        element.bind('click', function() {
          apply(function() {
            if (changeHeads) {
              state.handleChangeHead('0000', 'click');
              state.deselectAll();
            }
          });
        });

        element.bind('mouseenter', function () {
          apply(function() {
            element.addClass('hovered');
            if (changeHeads && state.hasSelections()) {
              element.addClass('copy-cursor');
            }
          });
        });
        element.bind('mouseleave', function () {
          apply(function() {
            element.removeClass('hovered');
            element.removeClass('copy-cursor');
          });
        });
      }
    };
  }
]);


Update UI to represent that author is also followed when you send a friend request

--- project/static/js/authors/friend-requests.js
+++ project/static/js/authors/friend-requests.js
@@ -1,12 +1,16 @@
 $(function() {
     var $friend_request_button = $("#send-friend-request-button");
     var $friend_request_sent_message = $("#friend-request-sent-message");
+    var $follow_button = $("#follow-button");
+    var $unfollow_button = $("#unfollow-button");

     $friend_request_button.on('click', function () {
         var $that = $(this);
         $.post($that.data('url'), function () {
             $that.hide();
+            $follow_button.hide();
             $friend_request_sent_message.show()
+            $unfollow_button.show();
         });
     });
 });

<|original_code|>
$(function() {
    var $friend_request_button = $("#send-friend-request-button");
    var $friend_request_sent_message = $("#friend-request-sent-message");

    $friend_request_button.on('click', function () {
        var $that = $(this);
        $.post($that.data('url'), function () {
            $that.hide();
            $friend_request_sent_message.show()
        });
    });
});

<|edits_diff|>
--- project/static/js/authors/friend-requests.js
+++ project/static/js/authors/friend-requests.js
@@ -1,11 +1,14 @@
 $(function() {
     var $friend_request_button = $("#send-friend-request-button");
     var $friend_request_sent_message = $("#friend-request-sent-message");
+    var $follow_button = $("#follow-button");
+    var $unfollow_button = $("#unfollow-button");

     $friend_request_button.on('click', function () {
         var $that = $(this);
         $.post($that.data('url'), function () {
             $that.hide();
+            $follow_button.hide();
             $friend_request_sent_message.show()
         });
     });
<|current_version|>
$(function() {
    var $friend_request_button = $("#send-friend-request-button");
    var $friend_request_sent_message = $("#friend-request-sent-message");
    var $follow_button = $("#follow-button");
    var $unfollow_button = $("#unfollow-button");

    $friend_request_button.on('click', function () {
        var $that = $(this);
        $.post($that.data('url'), function () {
            $that.hide();
            $follow_button.hide();
            $friend_request_sent_message.show()
        });
    });
});

<|next_version|>
$(function() {
    var $friend_request_button = $("#send-friend-request-button");
    var $friend_request_sent_message = $("#friend-request-sent-message");
    var $follow_button = $("#follow-button");
    var $unfollow_button = $("#unfollow-button");

    $friend_request_button.on('click', function () {
        var $that = $(this);
        $.post($that.data('url'), function () {
            $that.hide();
            $follow_button.hide();
            $friend_request_sent_message.show()
            $unfollow_button.show();
        });
    });
});


Fix 129026 COG & SOG

Fix 129026 COG & SOG

--- test/129026_cog_sog_rapid_update.js
+++ test/129026_cog_sog_rapid_update.js
@@ -1,6 +1,8 @@
 var chai = require("chai");
 chai.Should();
 chai.use(require('chai-things'));
+chai.use(require('signalk-schema').chaiModule);
+

 describe('129026 COG & SOG, Rapid Update', function () {
@@ -11,6 +13,7 @@
     tree.should.have.deep.property('navigation.courseOverGroundTrue.value', 206.1);
     tree.should.have.deep.property('navigation.speedOverGround');
     tree.should.have.deep.property('navigation.speedOverGround.value', 3.65);
+    tree.should.be.validSignalK;
   });
 });

<|original_code|>
var chai = require("chai");
chai.Should();
chai.use(require('chai-things'));

describe('129026 COG & SOG, Rapid Update', function () {
  it('complete sentence converts', function () {
    var tree = require("../n2kMapper.js").toNested(
      JSON.parse('{"timestamp":"2014-08-15-18:00:10.005","prio":"2","src":"160","dst":"255","pgn":"129026","description":"COG & SOG, Rapid Update","fields":{"COG Reference":"True","COG":"206.1","SOG":"3.65"}}'));
    tree.should.have.deep.property('navigation.courseOverGroundTrue');
    tree.should.have.deep.property('navigation.courseOverGroundTrue.value', 206.1);
    tree.should.have.deep.property('navigation.speedOverGround');
    tree.should.have.deep.property('navigation.speedOverGround.value', 3.65);
  });
});

<|edits_diff|>
--- test/129026_cog_sog_rapid_update.js
+++ test/129026_cog_sog_rapid_update.js
@@ -1,6 +1,8 @@
 var chai = require("chai");
 chai.Should();
 chai.use(require('chai-things'));
+chai.use(require('signalk-schema').chaiModule);
+

 describe('129026 COG & SOG, Rapid Update', function () {
<|current_version|>
var chai = require("chai");
chai.Should();
chai.use(require('chai-things'));
chai.use(require('signalk-schema').chaiModule);

describe('129026 COG & SOG, Rapid Update', function () {
  it('complete sentence converts', function () {
    var tree = require("../n2kMapper.js").toNested(
      JSON.parse('{"timestamp":"2014-08-15-18:00:10.005","prio":"2","src":"160","dst":"255","pgn":"129026","description":"COG & SOG, Rapid Update","fields":{"COG Reference":"True","COG":"206.1","SOG":"3.65"}}'));
    tree.should.have.deep.property('navigation.courseOverGroundTrue');
    tree.should.have.deep.property('navigation.courseOverGroundTrue.value', 206.1);
    tree.should.have.deep.property('navigation.speedOverGround');
    tree.should.have.deep.property('navigation.speedOverGround.value', 3.65);
  });
});

<|next_version|>
var chai = require("chai");
chai.Should();
chai.use(require('chai-things'));
chai.use(require('signalk-schema').chaiModule);

describe('129026 COG & SOG, Rapid Update', function () {
  it('complete sentence converts', function () {
    var tree = require("../n2kMapper.js").toNested(
      JSON.parse('{"timestamp":"2014-08-15-18:00:10.005","prio":"2","src":"160","dst":"255","pgn":"129026","description":"COG & SOG, Rapid Update","fields":{"COG Reference":"True","COG":"206.1","SOG":"3.65"}}'));
    tree.should.have.deep.property('navigation.courseOverGroundTrue');
    tree.should.have.deep.property('navigation.courseOverGroundTrue.value', 206.1);
    tree.should.have.deep.property('navigation.speedOverGround');
    tree.should.have.deep.property('navigation.speedOverGround.value', 3.65);
    tree.should.be.validSignalK;
  });
});


Add Topic landing page route

[Add] Add Topic landing page route

--- src/routes/index.js
+++ src/routes/index.js
@@ -10,10 +10,12 @@
 import Search from '../containers/Search'
 import Tag from '../containers/Tag'
 import Topic from '../containers/Topic'
+import TopicLandingPage from '../containers/TopicLandingPage'

 export default function (history = browserHistory) {
   return (
     <Router history={history} onUpdate={() => window.scrollTo(0, 0)}>
+      <Route path="/topics/:slug" component={TopicLandingPage} />
       <Route path="/" component={App}>
         <IndexRoute component={Home} />
         <Route path="category/:category" component={Category}/>

<|original_code|>
import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'

import App from '../containers/App'
import Article from '../containers/Article'
import Blank from '../containers/Blank'
import Category from '../containers/Category'
import Home from '../containers/Home'
import Photography from '../containers/Photography'
import Search from '../containers/Search'
import Tag from '../containers/Tag'
import Topic from '../containers/Topic'

export default function (history = browserHistory) {
  return (
    <Router history={history} onUpdate={() => window.scrollTo(0, 0)}>
      <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="category/:category" component={Category}/>
        <Route path="topic/:topicId" component={Topic} />
        <Route path="tag/:tagId" component={Tag} />
        <Route path="photography" component={Photography}/>
        <Route path="search" component={Search}/>
        <Route path="check" component={Blank}/>
        <Route path="a/:slug" component={Article}/>
      </Route>
    </Router>
  )
}

<|edits_diff|>
--- src/routes/index.js
+++ src/routes/index.js
@@ -10,6 +10,7 @@
 import Search from '../containers/Search'
 import Tag from '../containers/Tag'
 import Topic from '../containers/Topic'
+import TopicLandingPage from '../containers/TopicLandingPage'

 export default function (history = browserHistory) {
   return (
<|current_version|>
import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'

import App from '../containers/App'
import Article from '../containers/Article'
import Blank from '../containers/Blank'
import Category from '../containers/Category'
import Home from '../containers/Home'
import Photography from '../containers/Photography'
import Search from '../containers/Search'
import Tag from '../containers/Tag'
import Topic from '../containers/Topic'
import TopicLandingPage from '../containers/TopicLandingPage'

export default function (history = browserHistory) {
  return (
    <Router history={history} onUpdate={() => window.scrollTo(0, 0)}>
      <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="category/:category" component={Category}/>
        <Route path="topic/:topicId" component={Topic} />
        <Route path="tag/:tagId" component={Tag} />
        <Route path="photography" component={Photography}/>
        <Route path="search" component={Search}/>
        <Route path="check" component={Blank}/>
        <Route path="a/:slug" component={Article}/>
      </Route>
    </Router>
  )
}

<|next_version|>
import React from 'react'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'

import App from '../containers/App'
import Article from '../containers/Article'
import Blank from '../containers/Blank'
import Category from '../containers/Category'
import Home from '../containers/Home'
import Photography from '../containers/Photography'
import Search from '../containers/Search'
import Tag from '../containers/Tag'
import Topic from '../containers/Topic'
import TopicLandingPage from '../containers/TopicLandingPage'

export default function (history = browserHistory) {
  return (
    <Router history={history} onUpdate={() => window.scrollTo(0, 0)}>
      <Route path="/topics/:slug" component={TopicLandingPage} />
      <Route path="/" component={App}>
        <IndexRoute component={Home} />
        <Route path="category/:category" component={Category}/>
        <Route path="topic/:topicId" component={Topic} />
        <Route path="tag/:tagId" component={Tag} />
        <Route path="photography" component={Photography}/>
        <Route path="search" component={Search}/>
        <Route path="check" component={Blank}/>
        <Route path="a/:slug" component={Article}/>
      </Route>
    </Router>
  )
}


Check if user is banned in /actions API

Check if user is banned in /actions API

--- src/http/action-http.js
+++ src/http/action-http.js
@@ -1,6 +1,7 @@
 import * as actionCore from '../core/action-core';
 import {createJsonRoute, throwStatus} from '../util/express';
 import {assert} from '../validation';
+import * as banCore from '../core/ban-core';
 import * as imageHttp from './image-http';
 import * as throttleCore from '../core/throttle-core';

@@ -9,6 +10,8 @@

   if (!throttleCore.canDoAction(action.user, action.type)) {
     throwStatus(429, `Too many actions of type ${ action.type }`);
+  } else if (banCore.isUserBanned(action.user)) {
+    banCore.throwBannedError();
   }

   let handleAction;

<|original_code|>
import * as actionCore from '../core/action-core';
import {createJsonRoute, throwStatus} from '../util/express';
import {assert} from '../validation';
import * as imageHttp from './image-http';
import * as throttleCore from '../core/throttle-core';

let postAction = createJsonRoute(function(req, res) {
  const action = assert(req.body, 'action');

  if (!throttleCore.canDoAction(action.user, action.type)) {
    throwStatus(429, `Too many actions of type ${ action.type }`);
  }

  let handleAction;
  if (action.type === 'IMAGE') {
    handleAction = imageHttp.postImage(req, res, action);
  } else {
    action.ip = req.ip;

    handleAction = actionCore.getActionType(action.type)
    .then(type => {
      if (type === null) {
        throwStatus(400, 'Action type ' + action.type + ' does not exist');
      }
    })
    .then(() => {
      return actionCore.createAction(action).then(rowsInserted => undefined);
    });
  }

  return handleAction
    .then(() => throttleCore.executeAction(action.user, action.type));
});

export {
  postAction
};

<|edits_diff|>
--- src/http/action-http.js
+++ src/http/action-http.js
@@ -1,6 +1,7 @@
 import * as actionCore from '../core/action-core';
 import {createJsonRoute, throwStatus} from '../util/express';
 import {assert} from '../validation';
+import * as banCore from '../core/ban-core';
 import * as imageHttp from './image-http';
 import * as throttleCore from '../core/throttle-core';

<|current_version|>
import * as actionCore from '../core/action-core';
import {createJsonRoute, throwStatus} from '../util/express';
import {assert} from '../validation';
import * as banCore from '../core/ban-core';
import * as imageHttp from './image-http';
import * as throttleCore from '../core/throttle-core';

let postAction = createJsonRoute(function(req, res) {
  const action = assert(req.body, 'action');

  if (!throttleCore.canDoAction(action.user, action.type)) {
    throwStatus(429, `Too many actions of type ${ action.type }`);
  }

  let handleAction;
  if (action.type === 'IMAGE') {
    handleAction = imageHttp.postImage(req, res, action);
  } else {
    action.ip = req.ip;

    handleAction = actionCore.getActionType(action.type)
    .then(type => {
      if (type === null) {
        throwStatus(400, 'Action type ' + action.type + ' does not exist');
      }
    })
    .then(() => {
      return actionCore.createAction(action).then(rowsInserted => undefined);
    });
  }

  return handleAction
    .then(() => throttleCore.executeAction(action.user, action.type));
});

export {
  postAction
};

<|next_version|>
import * as actionCore from '../core/action-core';
import {createJsonRoute, throwStatus} from '../util/express';
import {assert} from '../validation';
import * as banCore from '../core/ban-core';
import * as imageHttp from './image-http';
import * as throttleCore from '../core/throttle-core';

let postAction = createJsonRoute(function(req, res) {
  const action = assert(req.body, 'action');

  if (!throttleCore.canDoAction(action.user, action.type)) {
    throwStatus(429, `Too many actions of type ${ action.type }`);
  } else if (banCore.isUserBanned(action.user)) {
    banCore.throwBannedError();
  }

  let handleAction;
  if (action.type === 'IMAGE') {
    handleAction = imageHttp.postImage(req, res, action);
  } else {
    action.ip = req.ip;

    handleAction = actionCore.getActionType(action.type)
    .then(type => {
      if (type === null) {
        throwStatus(400, 'Action type ' + action.type + ' does not exist');
      }
    })
    .then(() => {
      return actionCore.createAction(action).then(rowsInserted => undefined);
    });
  }

  return handleAction
    .then(() => throttleCore.executeAction(action.user, action.type));
});

export {
  postAction
};


import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

let history = createHistory();

class TldrApp extends React.Component {

  render () {
    return (
      <div>
        <SearchBar history={history}/>
        <Results   history={history}/>
      </div>
    );
  }

}

export default TldrApp;

import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

import { Command } from '../actions/Command';

let history = createHistory();

class TldrApp extends React.Component {

  componentWillMount () {
    Command.getIndex().subscribe();
  }

  render () {
    return (
      <div>
        <SearchBar history={history}/>
        <Results   history={history}/>
      </div>
    );
  }

}

export default TldrApp;

Load index earlier because it's safe now

--- src/components/TldrApp.js
+++ src/components/TldrApp.js
@@ -6,9 +6,15 @@
 import SearchBar from './SearchBar';
 import Results   from './Results';

+import { Command } from '../actions/Command';
+
 let history = createHistory();

 class TldrApp extends React.Component {
+
+  componentWillMount () {
+    Command.getIndex().subscribe();
+  }

   render () {
     return (

import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

import { Command } from '../actions/Command';

let history = createHistory();

class TldrApp extends React.Component {

  render () {
    return (
      <div>
        <SearchBar history={history}/>
        <Results   history={history}/>
      </div>
    );
  }

}

export default TldrApp;

<|original_code|>
import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

let history = createHistory();

class TldrApp extends React.Component {

  render () {
    return (

        <SearchBar history={history}/>
        <Results   history={history}/>

    );
  }

}

export default TldrApp;

<|edits_diff|>
--- src/components/TldrApp.js
+++ src/components/TldrApp.js
@@ -5,6 +5,8 @@
 // Import components
 import SearchBar from './SearchBar';
 import Results   from './Results';
+
+import { Command } from '../actions/Command';

 let history = createHistory();

<|current_version|>
import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

import { Command } from '../actions/Command';

let history = createHistory();

class TldrApp extends React.Component {

  render () {
    return (

        <SearchBar history={history}/>
        <Results   history={history}/>

    );
  }

}

export default TldrApp;

<|next_version|>
import React from 'react';
//import { createHistory } from 'history';
import createHistory from 'history/lib/createHashHistory';

// Import components
import SearchBar from './SearchBar';
import Results   from './Results';

import { Command } from '../actions/Command';

let history = createHistory();

class TldrApp extends React.Component {

  componentWillMount () {
    Command.getIndex().subscribe();
  }

  render () {
    return (

        <SearchBar history={history}/>
        <Results   history={history}/>

    );
  }

}

export default TldrApp;


Call super-method from Mixin in sessionAuthenticated and sessionInvalidated

--- tests/dummy/app/routes/application.js
+++ tests/dummy/app/routes/application.js
@@ -7,8 +7,10 @@
   },
   sessionAuthenticated(){
     alert('Got authenticated!');
+    this._super.call(this);
   },
   sessionInvalidated(){
     alert('Got invalidated');
+    this._super.call(this);
   }
 } );

<|original_code|>
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'

export default Ember.Route.extend( ApplicationRouteMixin, {
  init() {
    this._super();
  },
  sessionAuthenticated(){
    alert('Got authenticated!');
  },
  sessionInvalidated(){
    alert('Got invalidated');
  }
} );

<|edits_diff|>
--- tests/dummy/app/routes/application.js
+++ tests/dummy/app/routes/application.js
@@ -7,6 +7,7 @@
   },
   sessionAuthenticated(){
     alert('Got authenticated!');
+    this._super.call(this);
   },
   sessionInvalidated(){
     alert('Got invalidated');
<|current_version|>
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'

export default Ember.Route.extend( ApplicationRouteMixin, {
  init() {
    this._super();
  },
  sessionAuthenticated(){
    alert('Got authenticated!');
    this._super.call(this);
  },
  sessionInvalidated(){
    alert('Got invalidated');
  }
} );

<|next_version|>
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'

export default Ember.Route.extend( ApplicationRouteMixin, {
  init() {
    this._super();
  },
  sessionAuthenticated(){
    alert('Got authenticated!');
    this._super.call(this);
  },
  sessionInvalidated(){
    alert('Got invalidated');
    this._super.call(this);
  }
} );


import React from 'react';

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <div className="about-page">
        <IntroBand />
        <AboutBand />
        <ProjectsBand />
      </div>
    );
  }
}

export default AboutPage;

import React from 'react';

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';
import ContactForm from '../components/Contact/ContactForm';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <div className="about-page">
        <IntroBand />
        <AboutBand />
        <ProjectsBand />
        <ContactForm />
      </div>
    );
  }
}

export default AboutPage;

Add contact form to about page

Add contact form to about page

--- imports/client/pages/AboutPage.js
+++ imports/client/pages/AboutPage.js
@@ -3,6 +3,7 @@
 import IntroBand from '../components/About/IntroBand';
 import AboutBand from '../components/About/AboutBand';
 import ProjectsBand from '../components/About/ProjectsBand';
+import ContactForm from '../components/Contact/ContactForm';

 class AboutPage extends React.Component {
   shouldComponentUpdate() {
@@ -15,6 +16,7 @@
         <IntroBand />
         <AboutBand />
         <ProjectsBand />
+        <ContactForm />
       </div>
     );
   }

import React from 'react';

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';
import ContactForm from '../components/Contact/ContactForm';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (
      <div className="about-page">
        <IntroBand />
        <AboutBand />
        <ProjectsBand />
      </div>
    );
  }
}

export default AboutPage;

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

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (

        <IntroBand />
        <AboutBand />
        <ProjectsBand />

    );
  }
}

export default AboutPage;

<|edits_diff|>
--- imports/client/pages/AboutPage.js
+++ imports/client/pages/AboutPage.js
@@ -3,6 +3,7 @@
 import IntroBand from '../components/About/IntroBand';
 import AboutBand from '../components/About/AboutBand';
 import ProjectsBand from '../components/About/ProjectsBand';
+import ContactForm from '../components/Contact/ContactForm';

 class AboutPage extends React.Component {
   shouldComponentUpdate() {
<|current_version|>
import React from 'react';

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';
import ContactForm from '../components/Contact/ContactForm';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (

        <IntroBand />
        <AboutBand />
        <ProjectsBand />

    );
  }
}

export default AboutPage;

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

import IntroBand from '../components/About/IntroBand';
import AboutBand from '../components/About/AboutBand';
import ProjectsBand from '../components/About/ProjectsBand';
import ContactForm from '../components/Contact/ContactForm';

class AboutPage extends React.Component {
  shouldComponentUpdate() {
    return false;
  }

  render() {
    return (

        <IntroBand />
        <AboutBand />
        <ProjectsBand />
        <ContactForm />

    );
  }
}

export default AboutPage;


Disable logging sql when creating tables

--- test/helpers/fixtures/index.js
+++ test/helpers/fixtures/index.js
@@ -5,6 +5,8 @@
     specificFixtures = [specificFixtures];
   }

+  var prevSetting = sequelize.options.logging;
+  sequelize.options.logging = false;
   try {
     yield exports.unload();

@@ -20,8 +22,12 @@
   } catch (e) {
     console.error(e);
   }
+  sequelize.options.logging = prevSetting;
 };

 exports.unload = function *() {
+  var prevSetting = sequelize.options.logging;
+  sequelize.options.logging = false;
   yield sequelize.sync({ force: true });
+  sequelize.options.logging = prevSetting;
 };

<|original_code|>
var fixtures = require('node-require-directory')(__dirname);

exports.load = function *(specificFixtures) {
  if (typeof specificFixtures === 'string') {
    specificFixtures = [specificFixtures];
  }

  try {
    yield exports.unload();

    var keys = Object.keys(fixtures).filter(function(key) {
      if (specificFixtures && specificFixtures.indexOf(key) === -1) {
        return false;
      }
      return true;
    });
    for (var i = 0; i < keys.length; ++i) {
      exports[keys[i]] = yield fixtures[keys[i]].load();
    }
  } catch (e) {
    console.error(e);
  }
};

exports.unload = function *() {
  yield sequelize.sync({ force: true });
};

<|edits_diff|>
--- test/helpers/fixtures/index.js
+++ test/helpers/fixtures/index.js
@@ -5,6 +5,8 @@
     specificFixtures = [specificFixtures];
   }

+  var prevSetting = sequelize.options.logging;
+  sequelize.options.logging = false;
   try {
     yield exports.unload();

@@ -20,8 +22,11 @@
   } catch (e) {
     console.error(e);
   }
+  sequelize.options.logging = prevSetting;
 };

 exports.unload = function *() {
+  var prevSetting = sequelize.options.logging;
+  sequelize.options.logging = false;
   yield sequelize.sync({ force: true });
 };
<|current_version|>
var fixtures = require('node-require-directory')(__dirname);

exports.load = function *(specificFixtures) {
  if (typeof specificFixtures === 'string') {
    specificFixtures = [specificFixtures];
  }

  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  try {
    yield exports.unload();

    var keys = Object.keys(fixtures).filter(function(key) {
      if (specificFixtures && specificFixtures.indexOf(key) === -1) {
        return false;
      }
      return true;
    });
    for (var i = 0; i < keys.length; ++i) {
      exports[keys[i]] = yield fixtures[keys[i]].load();
    }
  } catch (e) {
    console.error(e);
  }
  sequelize.options.logging = prevSetting;
};

exports.unload = function *() {
  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  yield sequelize.sync({ force: true });
};

<|next_version|>
var fixtures = require('node-require-directory')(__dirname);

exports.load = function *(specificFixtures) {
  if (typeof specificFixtures === 'string') {
    specificFixtures = [specificFixtures];
  }

  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  try {
    yield exports.unload();

    var keys = Object.keys(fixtures).filter(function(key) {
      if (specificFixtures && specificFixtures.indexOf(key) === -1) {
        return false;
      }
      return true;
    });
    for (var i = 0; i < keys.length; ++i) {
      exports[keys[i]] = yield fixtures[keys[i]].load();
    }
  } catch (e) {
    console.error(e);
  }
  sequelize.options.logging = prevSetting;
};

exports.unload = function *() {
  var prevSetting = sequelize.options.logging;
  sequelize.options.logging = false;
  yield sequelize.sync({ force: true });
  sequelize.options.logging = prevSetting;
};


Add computed property blockID to fix deprecated binding issues

--- app/models/comment.js
+++ app/models/comment.js
@@ -1,4 +1,5 @@
 import DS from 'ember-data';
+import Ember from 'ember';

 export default DS.Model.extend({
   blocks: DS.attr(),
@@ -7,4 +8,5 @@
   canvas: DS.belongsTo('canvas'),
   block: DS.belongsTo('block'),
   creator: DS.belongsTo('user'),
+  blockID: Ember.computed.readOnly('block.id')
 });

<|original_code|>
import DS from 'ember-data';

export default DS.Model.extend({
  blocks: DS.attr(),
  insertedAt: DS.attr('date', { defaultValue() { return new Date(); } }),
  updatedAt: DS.attr('date'),
  canvas: DS.belongsTo('canvas'),
  block: DS.belongsTo('block'),
  creator: DS.belongsTo('user'),
});

<|edits_diff|>
--- app/models/comment.js
+++ app/models/comment.js
@@ -1,4 +1,5 @@
 import DS from 'ember-data';
+import Ember from 'ember';

 export default DS.Model.extend({
   blocks: DS.attr(),
<|current_version|>
import DS from 'ember-data';
import Ember from 'ember';

export default DS.Model.extend({
  blocks: DS.attr(),
  insertedAt: DS.attr('date', { defaultValue() { return new Date(); } }),
  updatedAt: DS.attr('date'),
  canvas: DS.belongsTo('canvas'),
  block: DS.belongsTo('block'),
  creator: DS.belongsTo('user'),
});

<|next_version|>
import DS from 'ember-data';
import Ember from 'ember';

export default DS.Model.extend({
  blocks: DS.attr(),
  insertedAt: DS.attr('date', { defaultValue() { return new Date(); } }),
  updatedAt: DS.attr('date'),
  canvas: DS.belongsTo('canvas'),
  block: DS.belongsTo('block'),
  creator: DS.belongsTo('user'),
  blockID: Ember.computed.readOnly('block.id')
});


Fix lb group by issue

Fix lb group by issue

--- app/models/service.js
+++ app/models/service.js
@@ -1,10 +1,14 @@
 import Resource from 'ember-api-store/models/resource';
 import { get, computed } from '@ember/object';
+import { reference } from 'ember-api-store/utils/denormalize';
 import { inject as service } from '@ember/service';

 export default Resource.extend({
   intl: service(),
   scope: service(),
+  clusterStore: service(),
+
+  namespace: reference('namespaceId', 'namespace', 'clusterStore'),

   canEditYaml: true,

<|original_code|>
import Resource from 'ember-api-store/models/resource';
import { get, computed } from '@ember/object';
import { inject as service } from '@ember/service';

export default Resource.extend({
  intl: service(),
  scope: service(),

  canEditYaml: true,

  displayKind: computed('intl.locale', 'kind', function() {
    const intl = get(this, 'intl');

    if ( get(this, 'kind') === 'LoadBalancer' ) {
      return intl.t('model.service.displayKind.loadBalancer');
    } else {
      return intl.t('model.service.displayKind.generic');
    }
  }),

  proxyEndpoints: computed('labels', function(){
    const parts = []
    const labels = get(this, 'labels');
    const location = window.location;
    if ( labels && labels['kubernetes.io/cluster-service'] === 'true' ) {
      (get(this, 'ports') || []).forEach((port) => {
        const linkEndpoint = `${location.origin}/k8s/clusters/${get(this, 'scope.currentCluster.id')}/api/v1/namespaces/${get(this, 'namespaceId')}/services/${get(port, 'targetPort')}:${get(this, 'name')}:/proxy/`;
        parts.push({
          linkEndpoint: linkEndpoint,
          displayEndpoint: '/index.html',
          protocol: location.protocol.substr(0, location.protocol.length -1),
          isTcpish: true,
        });
      });
    }
    return parts;
  }),
});

<|edits_diff|>
--- app/models/service.js
+++ app/models/service.js
@@ -1,5 +1,6 @@
 import Resource from 'ember-api-store/models/resource';
 import { get, computed } from '@ember/object';
+import { reference } from 'ember-api-store/utils/denormalize';
 import { inject as service } from '@ember/service';

 export default Resource.extend({
<|current_version|>
import Resource from 'ember-api-store/models/resource';
import { get, computed } from '@ember/object';
import { reference } from 'ember-api-store/utils/denormalize';
import { inject as service } from '@ember/service';

export default Resource.extend({
  intl: service(),
  scope: service(),

  canEditYaml: true,

  displayKind: computed('intl.locale', 'kind', function() {
    const intl = get(this, 'intl');

    if ( get(this, 'kind') === 'LoadBalancer' ) {
      return intl.t('model.service.displayKind.loadBalancer');
    } else {
      return intl.t('model.service.displayKind.generic');
    }
  }),

  proxyEndpoints: computed('labels', function(){
    const parts = []
    const labels = get(this, 'labels');
    const location = window.location;
    if ( labels && labels['kubernetes.io/cluster-service'] === 'true' ) {
      (get(this, 'ports') || []).forEach((port) => {
        const linkEndpoint = `${location.origin}/k8s/clusters/${get(this, 'scope.currentCluster.id')}/api/v1/namespaces/${get(this, 'namespaceId')}/services/${get(port, 'targetPort')}:${get(this, 'name')}:/proxy/`;
        parts.push({
          linkEndpoint: linkEndpoint,
          displayEndpoint: '/index.html',
          protocol: location.protocol.substr(0, location.protocol.length -1),
          isTcpish: true,
        });
      });
    }
    return parts;
  }),
});

<|next_version|>
import Resource from 'ember-api-store/models/resource';
import { get, computed } from '@ember/object';
import { reference } from 'ember-api-store/utils/denormalize';
import { inject as service } from '@ember/service';

export default Resource.extend({
  intl: service(),
  scope: service(),
  clusterStore: service(),

  namespace: reference('namespaceId', 'namespace', 'clusterStore'),

  canEditYaml: true,

  displayKind: computed('intl.locale', 'kind', function() {
    const intl = get(this, 'intl');

    if ( get(this, 'kind') === 'LoadBalancer' ) {
      return intl.t('model.service.displayKind.loadBalancer');
    } else {
      return intl.t('model.service.displayKind.generic');
    }
  }),

  proxyEndpoints: computed('labels', function(){
    const parts = []
    const labels = get(this, 'labels');
    const location = window.location;
    if ( labels && labels['kubernetes.io/cluster-service'] === 'true' ) {
      (get(this, 'ports') || []).forEach((port) => {
        const linkEndpoint = `${location.origin}/k8s/clusters/${get(this, 'scope.currentCluster.id')}/api/v1/namespaces/${get(this, 'namespaceId')}/services/${get(port, 'targetPort')}:${get(this, 'name')}:/proxy/`;
        parts.push({
          linkEndpoint: linkEndpoint,
          displayEndpoint: '/index.html',
          protocol: location.protocol.substr(0, location.protocol.length -1),
          isTcpish: true,
        });
      });
    }
    return parts;
  }),
});


Add ability to close sidebar by clicking outside

--- src/files/navigation.js
+++ src/files/navigation.js
@@ -2,15 +2,18 @@
 var menuText = ["Menu", "Close"];
 var menuElement = document.querySelector(".menu-link");
 var sidebarElement = document.querySelector(".sidebar");
+var mainElement = document.querySelector("main");
 var headerElement = document.querySelector("header");
 var rAF;

+mainElement.addEventListener( "click", toggleSidebar, true );
 menuElement.addEventListener( "click", toggleSidebar, false );
 window.addEventListener( "hashchange", toggleSidebar, false );

 function toggleSidebar ( event ) {
     // Don't toggle when there's nothing to hide.
     if (event.type === "hashchange" && !sidebarOpen) return;
+    if (event.currentTarget === mainElement && !sidebarOpen) return;

     sidebarOpen = (!sidebarOpen);

<|original_code|>
var sidebarOpen = false;
var menuText = ["Menu", "Close"];
var menuElement = document.querySelector(".menu-link");
var sidebarElement = document.querySelector(".sidebar");
var headerElement = document.querySelector("header");
var rAF;

menuElement.addEventListener( "click", toggleSidebar, false );
window.addEventListener( "hashchange", toggleSidebar, false );

function toggleSidebar ( event ) {
    // Don't toggle when there's nothing to hide.
    if (event.type === "hashchange" && !sidebarOpen) return;

    sidebarOpen = (!sidebarOpen);

    cancelAnimationFrame(rAF);
    rAF = requestAnimationFrame(function () {
        sidebarElement.classList.toggle( "sidebar-open" );
        headerElement.classList.toggle( "hidden" );
        menuElement.textContent = menuText[(sidebarOpen ? 1 : 0)];
    });
}
<|edits_diff|>
--- src/files/navigation.js
+++ src/files/navigation.js
@@ -2,9 +2,11 @@
 var menuText = ["Menu", "Close"];
 var menuElement = document.querySelector(".menu-link");
 var sidebarElement = document.querySelector(".sidebar");
+var mainElement = document.querySelector("main");
 var headerElement = document.querySelector("header");
 var rAF;

+mainElement.addEventListener( "click", toggleSidebar, true );
 menuElement.addEventListener( "click", toggleSidebar, false );
 window.addEventListener( "hashchange", toggleSidebar, false );

<|current_version|>
var sidebarOpen = false;
var menuText = ["Menu", "Close"];
var menuElement = document.querySelector(".menu-link");
var sidebarElement = document.querySelector(".sidebar");
var mainElement = document.querySelector("main");
var headerElement = document.querySelector("header");
var rAF;

mainElement.addEventListener( "click", toggleSidebar, true );
menuElement.addEventListener( "click", toggleSidebar, false );
window.addEventListener( "hashchange", toggleSidebar, false );

function toggleSidebar ( event ) {
    // Don't toggle when there's nothing to hide.
    if (event.type === "hashchange" && !sidebarOpen) return;

    sidebarOpen = (!sidebarOpen);

    cancelAnimationFrame(rAF);
    rAF = requestAnimationFrame(function () {
        sidebarElement.classList.toggle( "sidebar-open" );
        headerElement.classList.toggle( "hidden" );
        menuElement.textContent = menuText[(sidebarOpen ? 1 : 0)];
    });
}
<|next_version|>
var sidebarOpen = false;
var menuText = ["Menu", "Close"];
var menuElement = document.querySelector(".menu-link");
var sidebarElement = document.querySelector(".sidebar");
var mainElement = document.querySelector("main");
var headerElement = document.querySelector("header");
var rAF;

mainElement.addEventListener( "click", toggleSidebar, true );
menuElement.addEventListener( "click", toggleSidebar, false );
window.addEventListener( "hashchange", toggleSidebar, false );

function toggleSidebar ( event ) {
    // Don't toggle when there's nothing to hide.
    if (event.type === "hashchange" && !sidebarOpen) return;
    if (event.currentTarget === mainElement && !sidebarOpen) return;

    sidebarOpen = (!sidebarOpen);

    cancelAnimationFrame(rAF);
    rAF = requestAnimationFrame(function () {
        sidebarElement.classList.toggle( "sidebar-open" );
        headerElement.classList.toggle( "hidden" );
        menuElement.textContent = menuText[(sidebarOpen ? 1 : 0)];
    });
}


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);


Make sure to trigger "change" event when filling in passwords, webpage logic might rely on it

--- data/fillIn.js
+++ data/fillIn.js
@@ -15,7 +15,10 @@
 if (fields.length > 0)
 {
   for (let field of fields)
+  {
     field.value = password;
+    field.dispatchEvent(new Event("change", {bubbles: true}));
+  }
   fields[0].focus();
   self.port.emit("success");
 }

<|original_code|>
/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

"use strict";

let {host, password} = self.options;
if (location.hostname != host)
  throw new Error("Password is meant for a different website, not filling in.");

let fields = document.querySelectorAll("input[type=password]");
fields = Array.filter(fields, element => element.offsetHeight && element.offsetWidth);
if (fields.length > 0)
{
  for (let field of fields)
    field.value = password;
  fields[0].focus();
  self.port.emit("success");
}
else
  self.port.emit("failure");

<|edits_diff|>
--- data/fillIn.js
+++ data/fillIn.js
@@ -15,6 +15,7 @@
 if (fields.length > 0)
 {
   for (let field of fields)
+  {
     field.value = password;
   fields[0].focus();
   self.port.emit("success");
<|current_version|>
/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

"use strict";

let {host, password} = self.options;
if (location.hostname != host)
  throw new Error("Password is meant for a different website, not filling in.");

let fields = document.querySelectorAll("input[type=password]");
fields = Array.filter(fields, element => element.offsetHeight && element.offsetWidth);
if (fields.length > 0)
{
  for (let field of fields)
  {
    field.value = password;
  fields[0].focus();
  self.port.emit("success");
}
else
  self.port.emit("failure");

<|next_version|>
/*
 * This Source Code is subject to the terms of the Mozilla Public License
 * version 2.0 (the "License"). You can obtain a copy of the License at
 * http://mozilla.org/MPL/2.0/.
 */

"use strict";

let {host, password} = self.options;
if (location.hostname != host)
  throw new Error("Password is meant for a different website, not filling in.");

let fields = document.querySelectorAll("input[type=password]");
fields = Array.filter(fields, element => element.offsetHeight && element.offsetWidth);
if (fields.length > 0)
{
  for (let field of fields)
  {
    field.value = password;
    field.dispatchEvent(new Event("change", {bubbles: true}));
  }
  fields[0].focus();
  self.port.emit("success");
}
else
  self.port.emit("failure");


Add openBasicModal reducer method to customer invoice

--- src/pages/dataTableUtilities/reducer/getReducer.js
+++ src/pages/dataTableUtilities/reducer/getReducer.js
@@ -25,6 +25,7 @@
   deselectAll,
   focusCell,
   sortData,
+  openBasicModal,
 } from './reducerMethods';

 /**
@@ -43,6 +44,7 @@
   selectRow,
   deselectRow,
   deselectAll,
+  openBasicModal,
 };

 const PAGE_REDUCERS = {

<|original_code|>
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/**
 * Method using a factory pattern variant to get a reducer
 * for a particular page or page style.
 *
 * For a new page - create a constant object with the
 * methods from reducerMethods which are composed to create
 * a reducer. Add to PAGE_REDUCERS.
 *
 * Each key value pair in a reducer object should be in the form
 * action type: reducer function, returning a new state object
 *
 */

import {
  filterData,
  editTotalQuantity,
  focusNextCell,
  selectRow,
  deselectRow,
  deselectAll,
  focusCell,
  sortData,
} from './reducerMethods';

/**
 * Used for actions that should be in all pages using a data table.
 */
const BASE_TABLE_PAGE_REDUCER = {
  focusNextCell,
  focusCell,
  sortData,
};

const customerInvoice = {
  ...BASE_TABLE_PAGE_REDUCER,
  filterData,
  editTotalQuantity,
  selectRow,
  deselectRow,
  deselectAll,
};

const PAGE_REDUCERS = {
  customerInvoice,
};

const getReducer = page => {
  const reducer = PAGE_REDUCERS[page];
  return (state, action) => {
    const { type } = action;
    if (!reducer[type]) return state;
    return reducer[type](state, action);
  };
};

export default getReducer;

<|edits_diff|>
--- src/pages/dataTableUtilities/reducer/getReducer.js
+++ src/pages/dataTableUtilities/reducer/getReducer.js
@@ -25,6 +25,7 @@
   deselectAll,
   focusCell,
   sortData,
+  openBasicModal,
 } from './reducerMethods';

 /**
<|current_version|>
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/**
 * Method using a factory pattern variant to get a reducer
 * for a particular page or page style.
 *
 * For a new page - create a constant object with the
 * methods from reducerMethods which are composed to create
 * a reducer. Add to PAGE_REDUCERS.
 *
 * Each key value pair in a reducer object should be in the form
 * action type: reducer function, returning a new state object
 *
 */

import {
  filterData,
  editTotalQuantity,
  focusNextCell,
  selectRow,
  deselectRow,
  deselectAll,
  focusCell,
  sortData,
  openBasicModal,
} from './reducerMethods';

/**
 * Used for actions that should be in all pages using a data table.
 */
const BASE_TABLE_PAGE_REDUCER = {
  focusNextCell,
  focusCell,
  sortData,
};

const customerInvoice = {
  ...BASE_TABLE_PAGE_REDUCER,
  filterData,
  editTotalQuantity,
  selectRow,
  deselectRow,
  deselectAll,
};

const PAGE_REDUCERS = {
  customerInvoice,
};

const getReducer = page => {
  const reducer = PAGE_REDUCERS[page];
  return (state, action) => {
    const { type } = action;
    if (!reducer[type]) return state;
    return reducer[type](state, action);
  };
};

export default getReducer;

<|next_version|>
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/**
 * Method using a factory pattern variant to get a reducer
 * for a particular page or page style.
 *
 * For a new page - create a constant object with the
 * methods from reducerMethods which are composed to create
 * a reducer. Add to PAGE_REDUCERS.
 *
 * Each key value pair in a reducer object should be in the form
 * action type: reducer function, returning a new state object
 *
 */

import {
  filterData,
  editTotalQuantity,
  focusNextCell,
  selectRow,
  deselectRow,
  deselectAll,
  focusCell,
  sortData,
  openBasicModal,
} from './reducerMethods';

/**
 * Used for actions that should be in all pages using a data table.
 */
const BASE_TABLE_PAGE_REDUCER = {
  focusNextCell,
  focusCell,
  sortData,
};

const customerInvoice = {
  ...BASE_TABLE_PAGE_REDUCER,
  filterData,
  editTotalQuantity,
  selectRow,
  deselectRow,
  deselectAll,
  openBasicModal,
};

const PAGE_REDUCERS = {
  customerInvoice,
};

const getReducer = page => {
  const reducer = PAGE_REDUCERS[page];
  return (state, action) => {
    const { type } = action;
    if (!reducer[type]) return state;
    return reducer[type](state, action);
  };
};

export default getReducer;


Add isLoggedIn knob to story for pollButtons

--- client/modules/core/components/.stories/poll_buttons.js
+++ client/modules/core/components/.stories/poll_buttons.js
@@ -26,11 +26,13 @@
         }
       ]
     });
+    const isLoggedIn = boolean('isLoggedIn', true);
     const colorScale = palette([ 'tol', 'tol-rainbow' ], poll.options.length);
     return (
       <PollButtons
         poll={poll}
         colorScale={colorScale}
+        isLoggedIn={isLoggedIn}
         vote={action('pollButtons-vote-clicked')}
       />
     );

<|original_code|>
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { withKnobs, text, boolean, number, object } from '@kadira/storybook-addon-knobs';
import { setComposerStub } from 'react-komposer';
import PollButtons from '../poll_buttons.jsx';
import palette from '../../libs/palette';

storiesOf('core.PollButtons', module)
  .addDecorator(withKnobs)
  .add('default view', () => {
    const poll = object('poll', {
      _id: 'abc123',
      question: 'Ice Cream Flavors',
      options: [
        {
          name: 'Chocolate',
          votes: 10
        },
        {
          name: 'Vanilla',
          votes: 5
        },
        {
          name: 'Rocky Road',
          votes: 20
        }
      ]
    });
    const colorScale = palette([ 'tol', 'tol-rainbow' ], poll.options.length);
    return (
      <PollButtons
        poll={poll}
        colorScale={colorScale}
        vote={action('pollButtons-vote-clicked')}
      />
    );
  });

<|edits_diff|>
--- client/modules/core/components/.stories/poll_buttons.js
+++ client/modules/core/components/.stories/poll_buttons.js
@@ -26,6 +26,7 @@
         }
       ]
     });
+    const isLoggedIn = boolean('isLoggedIn', true);
     const colorScale = palette([ 'tol', 'tol-rainbow' ], poll.options.length);
     return (
       <PollButtons
<|current_version|>
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { withKnobs, text, boolean, number, object } from '@kadira/storybook-addon-knobs';
import { setComposerStub } from 'react-komposer';
import PollButtons from '../poll_buttons.jsx';
import palette from '../../libs/palette';

storiesOf('core.PollButtons', module)
  .addDecorator(withKnobs)
  .add('default view', () => {
    const poll = object('poll', {
      _id: 'abc123',
      question: 'Ice Cream Flavors',
      options: [
        {
          name: 'Chocolate',
          votes: 10
        },
        {
          name: 'Vanilla',
          votes: 5
        },
        {
          name: 'Rocky Road',
          votes: 20
        }
      ]
    });
    const isLoggedIn = boolean('isLoggedIn', true);
    const colorScale = palette([ 'tol', 'tol-rainbow' ], poll.options.length);
    return (
      <PollButtons
        poll={poll}
        colorScale={colorScale}
        vote={action('pollButtons-vote-clicked')}
      />
    );
  });

<|next_version|>
import React from 'react';
import { storiesOf, action } from '@kadira/storybook';
import { withKnobs, text, boolean, number, object } from '@kadira/storybook-addon-knobs';
import { setComposerStub } from 'react-komposer';
import PollButtons from '../poll_buttons.jsx';
import palette from '../../libs/palette';

storiesOf('core.PollButtons', module)
  .addDecorator(withKnobs)
  .add('default view', () => {
    const poll = object('poll', {
      _id: 'abc123',
      question: 'Ice Cream Flavors',
      options: [
        {
          name: 'Chocolate',
          votes: 10
        },
        {
          name: 'Vanilla',
          votes: 5
        },
        {
          name: 'Rocky Road',
          votes: 20
        }
      ]
    });
    const isLoggedIn = boolean('isLoggedIn', true);
    const colorScale = palette([ 'tol', 'tol-rainbow' ], poll.options.length);
    return (
      <PollButtons
        poll={poll}
        colorScale={colorScale}
        isLoggedIn={isLoggedIn}
        vote={action('pollButtons-vote-clicked')}
      />
    );
  });


Add temporary console logs to help track down an auth bug sometimes found in mobile iOS Safari

--- src/middleware/auth/processJWTIfExists.js
+++ src/middleware/auth/processJWTIfExists.js
@@ -11,20 +11,29 @@

   let token = req.headers['authorization'];

+  console.log('line-1 ', token);
+
   if (token == null) {
     let cookies = req.cookies;
     token = cookies.idToken;
   }
+
+  console.log('line-2', token);

   if (token == null) {
     next();
     return;
   }

+  console.log('line-3 ', token);
+
   try {
+    console.log('config', config);
     const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
     req.feathers.userId = verifiedJwt.body.sub;
   } catch(e) {
+    console.log(token);
+    console.log(e);
     next();
     return;
   }

<|original_code|>
const
    nJwt = require('njwt'),
    config = require('config.js'),
    errors = require('feathers-errors');

function processJWTIfExists (req, res, next) {

  req.feathers = req.feathers || {};

  let token = req.headers['authorization'];

  if (token == null) {
    let cookies = req.cookies;
    token = cookies.idToken;
  }

  if (token == null) {
    next();
    return;
  }

  try {
    const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
    req.feathers.userId = verifiedJwt.body.sub;
  } catch(e) {
    next();
    return;
  }

  next();
}

module.exports = {
  processJWTIfExists
};

<|edits_diff|>
--- src/middleware/auth/processJWTIfExists.js
+++ src/middleware/auth/processJWTIfExists.js
@@ -11,17 +11,24 @@

   let token = req.headers['authorization'];

+  console.log('line-1 ', token);
+
   if (token == null) {
     let cookies = req.cookies;
     token = cookies.idToken;
   }
+
+  console.log('line-2', token);

   if (token == null) {
     next();
     return;
   }

+  console.log('line-3 ', token);
+
   try {
+    console.log('config', config);
     const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
     req.feathers.userId = verifiedJwt.body.sub;
   } catch(e) {
<|current_version|>
const
    nJwt = require('njwt'),
    config = require('config.js'),
    errors = require('feathers-errors');

function processJWTIfExists (req, res, next) {

  req.feathers = req.feathers || {};

  let token = req.headers['authorization'];

  console.log('line-1 ', token);

  if (token == null) {
    let cookies = req.cookies;
    token = cookies.idToken;
  }

  console.log('line-2', token);

  if (token == null) {
    next();
    return;
  }

  console.log('line-3 ', token);

  try {
    console.log('config', config);
    const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
    req.feathers.userId = verifiedJwt.body.sub;
  } catch(e) {
    next();
    return;
  }

  next();
}

module.exports = {
  processJWTIfExists
};

<|next_version|>
const
    nJwt = require('njwt'),
    config = require('config.js'),
    errors = require('feathers-errors');

function processJWTIfExists (req, res, next) {

  req.feathers = req.feathers || {};

  let token = req.headers['authorization'];

  console.log('line-1 ', token);

  if (token == null) {
    let cookies = req.cookies;
    token = cookies.idToken;
  }

  console.log('line-2', token);

  if (token == null) {
    next();
    return;
  }

  console.log('line-3 ', token);

  try {
    console.log('config', config);
    const verifiedJwt = nJwt.verify(token, config.jwtSigningKey);
    req.feathers.userId = verifiedJwt.body.sub;
  } catch(e) {
    console.log(token);
    console.log(e);
    next();
    return;
  }

  next();
}

module.exports = {
  processJWTIfExists
};


Improve login page when wrong password is entered

--- server/public/javascripts/frontpage.js
+++ server/public/javascripts/frontpage.js
@@ -22,6 +22,10 @@
     $('#login-form').submit(function() {
         $('.login-error').empty();

+        $('input').keypress(function() {
+            $('.login-error').empty();
+        });
+
         $.post('/login',
             $('#login-form').serialize(),
             function(data) {
@@ -34,6 +38,8 @@
                     window.location.pathname = '/app/';
                 } else {
                     $('.login-error').text(data.msg);
+                    $('input[name="username"]').val('').focus();
+                    $('input[name="password"]').val('');
                 }
             });

<|original_code|>
//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   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.
//

'use strict';

$(function() {
    $('input[name="username"]').focus();

    $('#login-form').submit(function() {
        $('.login-error').empty();

        $.post('/login',
            $('#login-form').serialize(),
            function(data) {
                if (data.success === true) {
                    $.cookie('ProjectEvergreen', data.userId + '-' + data.secret + '-n', {
                        path: '/',
                        expires: new Date(data.expires)
                    });

                    window.location.pathname = '/app/';
                } else {
                    $('.login-error').text(data.msg);
                }
            });

        return false;
    });
});

<|edits_diff|>
--- server/public/javascripts/frontpage.js
+++ server/public/javascripts/frontpage.js
@@ -22,6 +22,10 @@
     $('#login-form').submit(function() {
         $('.login-error').empty();

+        $('input').keypress(function() {
+            $('.login-error').empty();
+        });
+
         $.post('/login',
             $('#login-form').serialize(),
             function(data) {
<|current_version|>
//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   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.
//

'use strict';

$(function() {
    $('input[name="username"]').focus();

    $('#login-form').submit(function() {
        $('.login-error').empty();

        $('input').keypress(function() {
            $('.login-error').empty();
        });

        $.post('/login',
            $('#login-form').serialize(),
            function(data) {
                if (data.success === true) {
                    $.cookie('ProjectEvergreen', data.userId + '-' + data.secret + '-n', {
                        path: '/',
                        expires: new Date(data.expires)
                    });

                    window.location.pathname = '/app/';
                } else {
                    $('.login-error').text(data.msg);
                }
            });

        return false;
    });
});

<|next_version|>
//
//   Copyright 2014 Ilkka Oksanen <iao@iki.fi>
//
//   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.
//

'use strict';

$(function() {
    $('input[name="username"]').focus();

    $('#login-form').submit(function() {
        $('.login-error').empty();

        $('input').keypress(function() {
            $('.login-error').empty();
        });

        $.post('/login',
            $('#login-form').serialize(),
            function(data) {
                if (data.success === true) {
                    $.cookie('ProjectEvergreen', data.userId + '-' + data.secret + '-n', {
                        path: '/',
                        expires: new Date(data.expires)
                    });

                    window.location.pathname = '/app/';
                } else {
                    $('.login-error').text(data.msg);
                    $('input[name="username"]').val('').focus();
                    $('input[name="password"]').val('');
                }
            });

        return false;
    });
});


Add layers animation and fadeIn

Add layers animation and fadeIn

--- js/respuesta.js
+++ js/respuesta.js
@@ -11,6 +11,11 @@
             createMenuItem(menuListSelector,data[dataI]);
     }});
 }
+function initialAnimation() {
+    $("div>div").fadeTo(1,30);
+    $(".left , .right").animate({"margin-left":0},5000);
+}
 function init() {
     generateJSONMenu('js/menu.json',".main-list");
+    initialAnimation();
 }

<|original_code|>
function createMenuItem(parentSelector,menuData){
    var menuListItem=document.createElement('li');
    $(menuListItem).addClass(menuData.class).appendTo(parentSelector);
    var menuLink=document.createElement('a');
    $(menuLink).attr({"href":menuData.link}).html(menuData.name);
    $(menuLink).appendTo(menuListItem);
}
function generateJSONMenu(url,menuListSelector) {
    $.getJSON(url,function(data) {
    for(var dataI=0;dataI<data.length;dataI++) {
            createMenuItem(menuListSelector,data[dataI]);
    }});
}
function init() {
    generateJSONMenu('js/menu.json',".main-list");
}

<|edits_diff|>
--- js/respuesta.js
+++ js/respuesta.js
@@ -11,6 +11,10 @@
             createMenuItem(menuListSelector,data[dataI]);
     }});
 }
+function initialAnimation() {
+    $("div>div").fadeTo(1,30);
+    $(".left , .right").animate({"margin-left":0},5000);
+}
 function init() {
     generateJSONMenu('js/menu.json',".main-list");
 }
<|current_version|>
function createMenuItem(parentSelector,menuData){
    var menuListItem=document.createElement('li');
    $(menuListItem).addClass(menuData.class).appendTo(parentSelector);
    var menuLink=document.createElement('a');
    $(menuLink).attr({"href":menuData.link}).html(menuData.name);
    $(menuLink).appendTo(menuListItem);
}
function generateJSONMenu(url,menuListSelector) {
    $.getJSON(url,function(data) {
    for(var dataI=0;dataI<data.length;dataI++) {
            createMenuItem(menuListSelector,data[dataI]);
    }});
}
function initialAnimation() {
    $("div>div").fadeTo(1,30);
    $(".left , .right").animate({"margin-left":0},5000);
}
function init() {
    generateJSONMenu('js/menu.json',".main-list");
}

<|next_version|>
function createMenuItem(parentSelector,menuData){
    var menuListItem=document.createElement('li');
    $(menuListItem).addClass(menuData.class).appendTo(parentSelector);
    var menuLink=document.createElement('a');
    $(menuLink).attr({"href":menuData.link}).html(menuData.name);
    $(menuLink).appendTo(menuListItem);
}
function generateJSONMenu(url,menuListSelector) {
    $.getJSON(url,function(data) {
    for(var dataI=0;dataI<data.length;dataI++) {
            createMenuItem(menuListSelector,data[dataI]);
    }});
}
function initialAnimation() {
    $("div>div").fadeTo(1,30);
    $(".left , .right").animate({"margin-left":0},5000);
}
function init() {
    generateJSONMenu('js/menu.json',".main-list");
    initialAnimation();
}


Make timeline view a global for debugging.

--- src/index.js
+++ src/index.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+var g_timelineView;
 (function() {
   var include = function(path) {
     document.write('<script src="' + path + '"></script>');
@@ -17,6 +18,7 @@

   function onDOMContentLoaded() {
     timelineView = $('timeline-view');
+    g_timelineView = timelineView;
     cr.ui.decorate(timelineView, tracing.TimelineView);
     chrome.send('ready');
   }

<|original_code|>
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(function() {
  var include = function(path) {
    document.write('<script src="' + path + '"></script>');
  };
  include("/chrome/tracing/overlay.js");
  include("/chrome/tracing/timeline_model.js");
  include("/chrome/tracing/sorted_array_utils.js");
  include("/chrome/tracing/timeline.js");
  include("/chrome/tracing/timeline_track.js");
  include("/chrome/tracing/fast_rect_renderer.js");
  include("/chrome/tracing/timeline_view.js");

  var timelineView;

  function onDOMContentLoaded() {
    timelineView = $('timeline-view');
    cr.ui.decorate(timelineView, tracing.TimelineView);
    chrome.send('ready');
  }

  function loadTrace(trace) {
    if (timelineView == undefined)
      throw Error('timelineview is null');

    // some old traces were just arrays without an outer object
    if (!trace.traceEvents) {
      if (trace instanceof Array)
        timelineView.traceEvents = trace;
      else
        throw Error('trace does not have an events array');
    } else {
      timelineView.traceEvents = trace.traceEvents;
    }
    return true;
  }

  window.loadTrace = loadTrace;
  document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
})();

<|edits_diff|>
--- src/index.js
+++ src/index.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+var g_timelineView;
 (function() {
   var include = function(path) {
     document.write('<script src="' + path + '"></script>');
<|current_version|>
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var g_timelineView;
(function() {
  var include = function(path) {
    document.write('<script src="' + path + '"></script>');
  };
  include("/chrome/tracing/overlay.js");
  include("/chrome/tracing/timeline_model.js");
  include("/chrome/tracing/sorted_array_utils.js");
  include("/chrome/tracing/timeline.js");
  include("/chrome/tracing/timeline_track.js");
  include("/chrome/tracing/fast_rect_renderer.js");
  include("/chrome/tracing/timeline_view.js");

  var timelineView;

  function onDOMContentLoaded() {
    timelineView = $('timeline-view');
    cr.ui.decorate(timelineView, tracing.TimelineView);
    chrome.send('ready');
  }

  function loadTrace(trace) {
    if (timelineView == undefined)
      throw Error('timelineview is null');

    // some old traces were just arrays without an outer object
    if (!trace.traceEvents) {
      if (trace instanceof Array)
        timelineView.traceEvents = trace;
      else
        throw Error('trace does not have an events array');
    } else {
      timelineView.traceEvents = trace.traceEvents;
    }
    return true;
  }

  window.loadTrace = loadTrace;
  document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
})();

<|next_version|>
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var g_timelineView;
(function() {
  var include = function(path) {
    document.write('<script src="' + path + '"></script>');
  };
  include("/chrome/tracing/overlay.js");
  include("/chrome/tracing/timeline_model.js");
  include("/chrome/tracing/sorted_array_utils.js");
  include("/chrome/tracing/timeline.js");
  include("/chrome/tracing/timeline_track.js");
  include("/chrome/tracing/fast_rect_renderer.js");
  include("/chrome/tracing/timeline_view.js");

  var timelineView;

  function onDOMContentLoaded() {
    timelineView = $('timeline-view');
    g_timelineView = timelineView;
    cr.ui.decorate(timelineView, tracing.TimelineView);
    chrome.send('ready');
  }

  function loadTrace(trace) {
    if (timelineView == undefined)
      throw Error('timelineview is null');

    // some old traces were just arrays without an outer object
    if (!trace.traceEvents) {
      if (trace instanceof Array)
        timelineView.traceEvents = trace;
      else
        throw Error('trace does not have an events array');
    } else {
      timelineView.traceEvents = trace.traceEvents;
    }
    return true;
  }

  window.loadTrace = loadTrace;
  document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
})();


Update documentation for Route53 service

--- lib/services/route53.js
+++ lib/services/route53.js
@@ -16,15 +16,24 @@
 var AWS = require('../core');

 AWS.Route53 = AWS.Service.defineService('route53', ['2012-12-12'], {
+  /**
+   * @api private
+   */
   setupRequestListeners: function setupRequestListeners(request) {
     request.on('build', this.sanitizeUrl);
   },

+  /**
+   * @api private
+   */
   sanitizeUrl: function sanitizeUrl(request) {
     var path = request.httpRequest.path;
     request.httpRequest.path = path.replace(/\/%2F\w+%2F/, '/');
   },

+  /**
+   * @api private
+   */
   setEndpoint: function setEndpoint(endpoint) {
     if (endpoint) {
       AWS.Service.prototype.setEndpoint(endpoint);

<|original_code|>
/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 AWS = require('../core');

AWS.Route53 = AWS.Service.defineService('route53', ['2012-12-12'], {
  setupRequestListeners: function setupRequestListeners(request) {
    request.on('build', this.sanitizeUrl);
  },

  sanitizeUrl: function sanitizeUrl(request) {
    var path = request.httpRequest.path;
    request.httpRequest.path = path.replace(/\/%2F\w+%2F/, '/');
  },

  setEndpoint: function setEndpoint(endpoint) {
    if (endpoint) {
      AWS.Service.prototype.setEndpoint(endpoint);
    } else {
      var opts = {sslEnabled: true}; // SSL is always enabled for Route53
      this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, opts);
    }
  }
});

module.exports = AWS.Route53;

<|edits_diff|>
--- lib/services/route53.js
+++ lib/services/route53.js
@@ -16,10 +16,16 @@
 var AWS = require('../core');

 AWS.Route53 = AWS.Service.defineService('route53', ['2012-12-12'], {
+  /**
+   * @api private
+   */
   setupRequestListeners: function setupRequestListeners(request) {
     request.on('build', this.sanitizeUrl);
   },

+  /**
+   * @api private
+   */
   sanitizeUrl: function sanitizeUrl(request) {
     var path = request.httpRequest.path;
     request.httpRequest.path = path.replace(/\/%2F\w+%2F/, '/');
<|current_version|>
/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 AWS = require('../core');

AWS.Route53 = AWS.Service.defineService('route53', ['2012-12-12'], {
  /**
   * @api private
   */
  setupRequestListeners: function setupRequestListeners(request) {
    request.on('build', this.sanitizeUrl);
  },

  /**
   * @api private
   */
  sanitizeUrl: function sanitizeUrl(request) {
    var path = request.httpRequest.path;
    request.httpRequest.path = path.replace(/\/%2F\w+%2F/, '/');
  },

  setEndpoint: function setEndpoint(endpoint) {
    if (endpoint) {
      AWS.Service.prototype.setEndpoint(endpoint);
    } else {
      var opts = {sslEnabled: true}; // SSL is always enabled for Route53
      this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, opts);
    }
  }
});

module.exports = AWS.Route53;

<|next_version|>
/**
 * Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You
 * may not use this file except in compliance with the License. A copy of
 * the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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 AWS = require('../core');

AWS.Route53 = AWS.Service.defineService('route53', ['2012-12-12'], {
  /**
   * @api private
   */
  setupRequestListeners: function setupRequestListeners(request) {
    request.on('build', this.sanitizeUrl);
  },

  /**
   * @api private
   */
  sanitizeUrl: function sanitizeUrl(request) {
    var path = request.httpRequest.path;
    request.httpRequest.path = path.replace(/\/%2F\w+%2F/, '/');
  },

  /**
   * @api private
   */
  setEndpoint: function setEndpoint(endpoint) {
    if (endpoint) {
      AWS.Service.prototype.setEndpoint(endpoint);
    } else {
      var opts = {sslEnabled: true}; // SSL is always enabled for Route53
      this.endpoint = new AWS.Endpoint(this.api.globalEndpoint, opts);
    }
  }
});

module.exports = AWS.Route53;


Add service and description to detailed parsed by aliss

--- parser-aliss.js
+++ parser-aliss.js
@@ -5,7 +5,9 @@

     return json.results.map(item => {
         return {
+            _service: 'aliss',
             title: item.title,
+            description: item.description,
         }
     });
 };

<|original_code|>
const parse = json => {
    if(!json.results){
        return [];
    }

    return json.results.map(item => {
        return {
            title: item.title,
        }
    });
};

module.exports={
    parse: parse
};

<|edits_diff|>
--- parser-aliss.js
+++ parser-aliss.js
@@ -5,6 +5,7 @@

     return json.results.map(item => {
         return {
+            _service: 'aliss',
             title: item.title,
         }
     });
<|current_version|>
const parse = json => {
    if(!json.results){
        return [];
    }

    return json.results.map(item => {
        return {
            _service: 'aliss',
            title: item.title,
        }
    });
};

module.exports={
    parse: parse
};

<|next_version|>
const parse = json => {
    if(!json.results){
        return [];
    }

    return json.results.map(item => {
        return {
            _service: 'aliss',
            title: item.title,
            description: item.description,
        }
    });
};

module.exports={
    parse: parse
};